PRACTICAL GUIDE / Playwright tutorial

Playwright Tutorial: End-to-End Testing from Scratch

Playwright tutorial for beginners: install, write your first test, TypeScript setup, codegen, fixtures, API testing, debugging, and CI tips in one guide.

By The Testing AcademyUpdated July 9, 202616 min read
All field guides
In this guide27 sections
  1. Playwright Tutorial Roadmap
  2. What You Will Build
  3. Prerequisites
  4. Step 1: Install Playwright
  5. Verify Installation
  6. Step 2: Project Shape You Should Understand
  7. Key Config Ideas in playwright.config.ts
  8. Step 3: Write Your First Playwright Test
  9. First Test Design Rules
  10. Step 4: Locators That Stay Stable
  11. Preferred Locator Order
  12. Waiting the Playwright Way
  13. Step 5: A Realistic First Journey Test
  14. Negative Test Example
  15. Step 6: Playwright Codegen for Beginners
  16. Codegen Cleanup Checklist
  17. Step 7: Assertions Worth Writing
  18. Assertion Quality
  19. Step 8: Playwright Fixtures (and Fixtures vs Hooks)
  20. Built-in Fixtures
  21. Classic Hooks vs Fixtures
  22. Custom Fixture Example: Authenticated Page
  23. Storage State Pattern (Faster Auth)
  24. Step 9: Does Playwright Support API Testing? Yes
  25. Mixed API + UI Pattern
  26. Step 10: Page Object Style Without Overengineering
  27. Step 11: Debugging Like a Professional
  28. UI Mode
  29. Debug Flag
  30. Trace Viewer
  31. Screenshots and Video
  32. Local Troubleshooting Checklist
  33. Step 12: Organize Suites by Purpose
  34. Step 13: Test Data and Isolation
  35. Step 14: CI Basics with Playwright
  36. Step 15: Common Playwright Beginner Mistakes
  37. Mistake 1: Copying Codegen Blindly
  38. Mistake 2: Overusing CSS Selectors Tied to Layout
  39. Mistake 3: Giant End to End Tests That Do Everything
  40. Mistake 4: No Assertions
  41. Mistake 5: Sleeps Everywhere
  42. Mistake 6: Testing Against Production Only
  43. Mistake 7: Ignoring WebKit Until Users File Safari Bugs
  44. Mistake 8: Automating Unstable UX Still Under Heavy Design Churn
  45. Playwright TypeScript Getting Started Tips
  46. Practice Path for the Next Seven Days
  47. Sample Mini Suite You Can Imitate
  48. Handling Common UI Patterns in Playwright
  49. File Upload
  50. Download
  51. Dialogs
  52. iFrames
  53. Multiple Pages or Tabs
  54. Network and Timing Tools (Use Carefully)
  55. Environment Variables and Secrets
  56. Parallelism and Workers
  57. How Playwright Fits a Wider Quality System
  58. Final Practical Workflow
  59. Quick Command Cheat Sheet

What you will learn

  • Playwright Tutorial Roadmap
  • What You Will Build
  • Prerequisites
  • Step 1: Install Playwright

If you need a practical Playwright tutorial that takes you from zero to useful end-to-end tests, start with setup, one solid first test, locators, assertions, debugging, and then the features that make Playwright productive: codegen, fixtures, API setup, and CI. Playwright is a modern framework for reliable web automation across Chromium, Firefox, and WebKit, with strong first-party tooling for traces, reports, and parallel runs.

This guide is written for beginners and manual testers moving into automation. It uses TypeScript oriented examples, explains commands you will actually run, and shows patterns that keep suites maintainable. For tool choice context, see Selenium vs Playwright vs Cypress.

Playwright Tutorial Roadmap

This Playwright tutorial is ordered so each step unlocks the next. Do not skip installation and your first green test. Debugging, fixtures, and CI only make sense after you can run one intentional check locally.

What You Will Build

By the end of this tutorial path, you will know how to:

  1. Scaffold a Playwright project.
  2. Run tests on multiple browsers.
  3. Write a first UI test with stable locators.
  4. Use codegen without shipping messy scripts.
  5. Add assertions that fail for the right reasons.
  6. Use fixtures for authenticated state.
  7. Mix API setup with UI verification.
  8. Debug with UI mode, trace viewer, and screenshots.
  9. Organize a small smoke suite for CI.

If you are still choosing your first automation stack, compare the learning path in Playwright vs Selenium for beginners before you commit.

Prerequisites

  • Node.js LTS installed (18+ or current LTS recommended).
  • Basic command line comfort.
  • A target app: local dev app, staging, or a public demo site.
  • Optional: TypeScript basics help, but you can learn as you go.

If you still think in manual cases first, keep that strength. Automation should encode clear intent from good test cases, not random clicks.

Step 1: Install Playwright

Create a folder and scaffold:

Shell
mkdir playwright-demo
cd playwright-demo
npm init playwright@latest

The init wizard asks questions such as:

  • TypeScript or JavaScript
  • Tests folder name
  • GitHub Actions workflow
  • Install browsers

For most beginners, choose:

  • TypeScript: yes
  • Tests folder: tests
  • GitHub Actions: yes if you want CI early
  • Install browsers: yes

Manual equivalent pieces after scaffold:

Shell
npm i -D @playwright/test
npx playwright install

npx playwright install downloads browser binaries Playwright controls.

Verify Installation

Shell
npx playwright test

You should see example tests run and an HTML report option at the end.

Open the report:

Shell
npx playwright show-report

Step 2: Project Shape You Should Understand

A typical scaffold includes:

Example
playwright.config.ts
package.json
tests/example.spec.ts
tests-examples/   (sometimes)

Key Config Ideas in playwright.config.ts

You will commonly set:

  • testDir: where tests live
  • timeout: per test timeout
  • retries: CI retries policy
  • use.baseURL: app origin
  • use.trace: when to collect traces
  • projects: browser matrix

Example config sketch:

TypeScript
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 1 : 0,
  workers: process.env.CI ? 2 : undefined,
  reporter: [["list"], ["html", { open: "never" }]],
  use: {
    baseURL: "http://127.0.0.1:3000",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
    video: "retain-on-failure",
  },
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "webkit", use: { ...devices["Desktop Safari"] } },
  ],
});

Start with one browser locally if you want faster feedback, then enable the matrix in CI.

Step 3: Write Your First Playwright Test

Create tests/login.spec.ts:

TypeScript
import { test, expect } from "@playwright/test";

test("user can open login page and see form", async ({ page }) => {
  await page.goto("/login");
  await expect(page.getByRole("heading", { name: /sign in/i })).toBeVisible();
  await expect(page.getByLabel(/email/i)).toBeVisible();
  await expect(page.getByLabel(/password/i)).toBeVisible();
  await expect(page.getByRole("button", { name: /sign in|log in/i })).toBeEnabled();
});

Run one file:

Shell
npx playwright test tests/login.spec.ts

Run headed for visibility:

Shell
npx playwright test tests/login.spec.ts --headed

Run one browser project:

Shell
npx playwright test tests/login.spec.ts --project=chromium

First Test Design Rules

  • Assert something meaningful, not only that a page loaded.
  • Prefer role and label locators over brittle CSS when possible.
  • Keep one main behavior per test.
  • Name tests as behavior statements.

A weak test name:

TypeScript
test("test1", async ({ page }) => {});

A strong test name:

TypeScript
test("shows validation when email is empty", async ({ page }) => {});

Step 4: Locators That Stay Stable

Playwright locators are lazy and auto-waiting. You usually do not need manual sleeps.

Preferred Locator Order

  1. getByRole
  2. getByLabel
  3. getByPlaceholder when accessible names are weak
  4. getByText for unique visible text
  5. getByTestId when the app provides test ids
  6. CSS/XPath as last resort

Examples:

TypeScript
page.getByRole("button", { name: "Create project" });
page.getByLabel("Email");
page.getByTestId("checkout-pay");
page.locator("#legacy-id"); // avoid if you can

Waiting the Playwright Way

Prefer:

TypeScript
await expect(page.getByRole("alert")).toContainText("Saved");
await page.getByRole("button", { name: "Save" }).click();

Avoid:

TypeScript
await page.waitForTimeout(5000);

Sleeps hide race conditions and slow the suite.

Step 5: A Realistic First Journey Test

Assume a demo app with login and dashboard.

TypeScript
import { test, expect } from "@playwright/test";

test("registered user can log in and reach dashboard", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel(/email/i).fill("qa.user@example.com");
  await page.getByLabel(/password/i).fill("ValidPass#2026");
  await page.getByRole("button", { name: /log in|sign in/i }).click();

  await expect(page).toHaveURL(/dashboard/);
  await expect(page.getByRole("heading", { name: /dashboard/i })).toBeVisible();
});

Negative Test Example

TypeScript
test("login rejects invalid password with safe error", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel(/email/i).fill("qa.user@example.com");
  await page.getByLabel(/password/i).fill("WrongPass#1");
  await page.getByRole("button", { name: /log in|sign in/i }).click();

  await expect(page.getByRole("alert")).toContainText(/invalid|incorrect/i);
  await expect(page).toHaveURL(/login/);
});

Negative paths matter as much as happy paths. Automation should not only celebrate success screens.

Step 6: Playwright Codegen for Beginners

Codegen records actions and prints code.

Shell
npx playwright codegen http://127.0.0.1:3000/login

Use it to:

  • Discover locator candidates quickly.
  • Learn API surface by reading generated calls.
  • Draft long forms you do not want to type by hand.

Codegen Cleanup Checklist

Generated code is a draft, not a final suite.

  • Replace fragile selectors with roles/labels/test ids.
  • Remove unnecessary clicks and waits.
  • Add assertions after important steps.
  • Extract repeated login into fixtures or helpers.
  • Split giant recordings into focused tests.
  • Delete absolute timeouts codegen may encourage indirectly by structure.

Codegen is a bicycle with training wheels. Ride it, then graduate.

Step 7: Assertions Worth Writing

Playwright's expect is integrated and auto-retrying for many matchers.

Common assertions:

TypeScript
await expect(page).toHaveURL(/projects/);
await expect(page.getByRole("heading", { name: "Projects" })).toBeVisible();
await expect(page.getByRole("row")).toHaveCount(3);
await expect(page.getByLabel("Name")).toHaveValue("Alpha");
await expect(page.getByRole("button", { name: "Save" })).toBeDisabled();

Assertion Quality

Weak:

TypeScript
await expect(page.locator("body")).toBeVisible();

Strong:

TypeScript
await expect(page.getByRole("status")).toHaveText("Project created");
await expect(page.getByRole("link", { name: "Alpha" })).toBeVisible();

Strong assertions encode product oracles. Weak assertions only prove the browser did not crash immediately.

Step 8: Playwright Fixtures (and Fixtures vs Hooks)

Fixtures are one of Playwright's best productivity features.

Built-in Fixtures

  • page: a single tab
  • context: browser context
  • browser
  • request: API client
  • baseURL indirectly via config use

Classic Hooks vs Fixtures

ApproachStrengthWeakness
beforeEach hooksFamiliar, simpleCan become global spaghetti
FixturesComposable, typed, reusable, scopedSmall learning curve

Custom Fixture Example: Authenticated Page

tests/fixtures.ts:

TypeScript
import { test as base, expect } from "@playwright/test";

type MyFixtures = {
  loggedInPage: import("@playwright/test").Page;
};

export const test = base.extend<MyFixtures>({
  loggedInPage: async ({ page }, use) => {
    await page.goto("/login");
    await page.getByLabel(/email/i).fill(process.env.QA_EMAIL!);
    await page.getByLabel(/password/i).fill(process.env.QA_PASSWORD!);
    await page.getByRole("button", { name: /log in|sign in/i }).click();
    await expect(page).toHaveURL(/dashboard/);
    await use(page);
  },
});

export { expect };

Use it:

TypeScript
import { test, expect } from "./fixtures";

test("dashboard shows recent projects module", async ({ loggedInPage }) => {
  await expect(loggedInPage.getByRole("heading", { name: /recent projects/i })).toBeVisible();
});

Storage State Pattern (Faster Auth)

For larger suites, log in once, save storage state, reuse it.

Shell
# conceptual flow
# 1) global setup logs in
# 2) saves auth.json
# 3) config use.storageState = 'auth.json'

This reduces repetitive UI login cost. Refresh state when sessions expire.

Step 9: Does Playwright Support API Testing? Yes

Playwright can send HTTP requests with request.

TypeScript
import { test, expect } from "@playwright/test";

test("API health returns ok", async ({ request }) => {
  const response = await request.get("/api/health");
  expect(response.ok()).toBeTruthy();
  const body = await response.json();
  expect(body.status).toBe("ok");
});

Mixed API + UI Pattern

TypeScript
test("user sees project created via API", async ({ page, request }) => {
  const create = await request.post("/api/projects", {
    data: { name: `Proj ${Date.now()}` },
  });
  expect(create.ok()).toBeTruthy();
  const project = await create.json();

  await page.goto("/projects");
  await expect(page.getByRole("link", { name: project.name })).toBeVisible();
});

Why this matters:

  • Faster, more reliable setup than clicking through every precondition.
  • UI test focuses on user visible behavior.
  • API assertions catch backend contract issues earlier.

Do not replace all UI coverage with API checks. Use each layer for what it validates best.

Step 10: Page Object Style Without Overengineering

Small helper classes can reduce duplication.

TypeScript
import { Page, expect } from "@playwright/test";

export class LoginPage {
  constructor(private readonly page: Page) {}

  async open() {
    await this.page.goto("/login");
  }

  async login(email: string, password: string) {
    await this.page.getByLabel(/email/i).fill(email);
    await this.page.getByLabel(/password/i).fill(password);
    await this.page.getByRole("button", { name: /log in|sign in/i }).click();
  }

  async expectError(message: RegExp) {
    await expect(this.page.getByRole("alert")).toContainText(message);
  }
}

Guidelines:

  • Keep methods user oriented (login, createProject).
  • Do not hide assertions in every method blindly.
  • Avoid deep inheritance trees.
  • Prefer fixtures composing page objects for setup.

Step 11: Debugging Like a Professional

UI Mode

Shell
npx playwright test --ui

UI mode helps you watch steps, locate elements, and time travel through the run.

Debug Flag

Shell
npx playwright test tests/login.spec.ts --debug

Trace Viewer

When traces are captured:

Shell
npx playwright show-trace trace.zip

Traces show DOM snapshots, network, actions, and timing. They are often better than "works on my machine" debates.

Screenshots and Video

Configured failures can keep screenshots/videos. Use them in CI artifacts.

Local Troubleshooting Checklist

  1. Run headed and watch.
  2. Confirm baseURL and environment.
  3. Confirm test data still exists.
  4. Check if the locator matches multiple nodes.
  5. Open trace on failure.
  6. Verify the app bug is not a real regression.

When you find a product defect, file it with the same rigor as manual testing using a solid bug report habit.

Step 12: Organize Suites by Purpose

Not every test should run on every pull request.

SuitePurposeSize target
SmokeBuild confidenceTiny, stable
Critical journeyRevenue or mission pathsSmall
Full regressionBroad confidenceLarger, maybe nightly
API packContract and setup validationFast

Tagging example:

TypeScript
test("checkout paid path @smoke", async ({ page }) => {
  // ...
});

Run smoke:

Shell
npx playwright test --grep @smoke

This maps cleanly to smoke vs regression strategy.

Step 13: Test Data and Isolation

Flaky suites often share users, projects, or mutable global state.

Better patterns:

  • Create unique names with timestamps or UUIDs.
  • Prefer API seeding per test or per worker.
  • Clean up when cost is low, or use disposable tenants.
  • Never rely on "whatever is first in the list" without unique identifiers.
  • Keep secrets in env vars, not committed files.
TypeScript
const name = `billing-${crypto.randomUUID()}`;

Step 14: CI Basics with Playwright

If you accepted the GitHub Actions scaffold, you already have a starting workflow. Core CI needs:

  • Install dependencies
  • Install Playwright browsers with deps
  • Run tests
  • Upload report/trace artifacts on failure

Conceptual steps:

Shell
npm ci
npx playwright install --with-deps
npx playwright test

CI tips:

  • Use retries: 1 carefully; investigate flakes, do not normalize them.
  • Fail test.only in CI with forbidOnly.
  • Cache intelligently, but do not cache in ways that break browser installs.
  • Start with chromium on PR, broader matrix nightly if needed.
  • Publish HTML report artifacts.

Step 15: Common Playwright Beginner Mistakes

Mistake 1: Copying Codegen Blindly

Unedited recordings become unmaintainable.

Mistake 2: Overusing CSS Selectors Tied to Layout

Class names change with CSS refactors. Roles and test ids survive better.

Mistake 3: Giant End to End Tests That Do Everything

A 40 step test that signs up, updates profile, pays, and exports CSV fails opaquely. Split by risk and purpose.

Mistake 4: No Assertions

A test that only clicks can pass while the product is wrong.

Mistake 5: Sleeps Everywhere

If you need sleeps, prefer event based waits and fix app testability when needed.

Mistake 6: Testing Against Production Only

Use safe environments. If production checks exist, keep them read only and carefully governed.

Mistake 7: Ignoring WebKit Until Users File Safari Bugs

Run WebKit where Safari risk is real.

Mistake 8: Automating Unstable UX Still Under Heavy Design Churn

Wait for minimum stability or automate below the UI for changing rules.

Playwright TypeScript Getting Started Tips

TypeScript pays off quickly:

  • Config autocompletion
  • Fixture typing
  • Safer refactors

If TS errors intimidate you:

  1. Keep strict on if possible, but learn incrementally.
  2. Start from official scaffold types.
  3. Avoid any for page helpers if you can.
  4. Read the type error bottom line first.

JavaScript remains valid. Consistency matters more than purity.

Practice Path for the Next Seven Days

Day 1: Install, run examples, change baseURL.
Day 2: Write two smoke tests for your app.
Day 3: Add one negative validation test.
Day 4: Use codegen, then rewrite locators cleanly.
Day 5: Add a custom fixture for auth.
Day 6: Seed one entity via API, assert in UI.
Day 7: Run in CI and store artifacts.

For testing judgment practice under constraints, warm up in a QABattle battle, write the manual case, then automate only the highest value path in Playwright.

Sample Mini Suite You Can Imitate

TypeScript
import { test, expect } from "@playwright/test";

test.describe("Projects", () => {
  test("creates a project from empty state @smoke", async ({ page }) => {
    await page.goto("/projects");
    await page.getByRole("button", { name: /new project/i }).click();
    const name = `Demo ${Date.now()}`;
    await page.getByLabel(/name/i).fill(name);
    await page.getByRole("button", { name: /create/i }).click();
    await expect(page.getByRole("heading", { name })).toBeVisible();
  });

  test("shows validation when name is empty", async ({ page }) => {
    await page.goto("/projects");
    await page.getByRole("button", { name: /new project/i }).click();
    await page.getByRole("button", { name: /create/i }).click();
    await expect(page.getByText(/name is required/i)).toBeVisible();
  });
});

This suite demonstrates naming, smoke tagging, uniqueness, and a negative path.

Handling Common UI Patterns in Playwright

File Upload

TypeScript
await page.getByLabel(/upload/i).setInputFiles("tests/fixtures/sample.pdf");
await expect(page.getByText("sample.pdf")).toBeVisible();

Download

TypeScript
const [download] = await Promise.all([
  page.waitForEvent("download"),
  page.getByRole("button", { name: /export/i }).click(),
]);
const path = await download.path();
expect(path).toBeTruthy();

Dialogs

TypeScript
page.once("dialog", async (dialog) => {
  expect(dialog.message()).toMatch(/delete/i);
  await dialog.accept();
});
await page.getByRole("button", { name: /delete/i }).click();

iFrames

TypeScript
const frame = page.frameLocator('iframe[title="Billing"]');
await frame.getByLabel(/card number/i).fill("4242424242424242");

Multiple Pages or Tabs

TypeScript
const [newPage] = await Promise.all([
  page.context().waitForEvent("page"),
  page.getByRole("link", { name: /open statement/i }).click(),
]);
await newPage.waitForLoadState();
await expect(newPage.getByRole("heading", { name: /statement/i })).toBeVisible();

Learn these patterns only when your product needs them. Do not pad beginner suites with every API demo.

Network and Timing Tools (Use Carefully)

You can route network calls for deterministic tests:

TypeScript
await page.route("**/api/projects", async (route) => {
  await route.fulfill({
    status: 200,
    contentType: "application/json",
    body: JSON.stringify([{ id: "1", name: "Stubbed" }]),
  });
});

Use stubs for unit-like UI tests. Prefer real backend integration for true end-to-end confidence. Over-mocking creates green tests that miss contract breaks.

Environment Variables and Secrets

Shell
export QA_EMAIL="qa.user@example.com"
export QA_PASSWORD="ValidPass#2026"
export BASE_URL="https://staging.example.com"

In config:

TypeScript
use: {
  baseURL: process.env.BASE_URL || "http://127.0.0.1:3000",
}

Never commit real passwords. Use CI secrets and local .env files ignored by git.

Parallelism and Workers

Playwright runs tests in parallel by default in many setups. That is great until tests share one user account.

Rules for parallel safety:

  • Unique data per test
  • No shared mutable admin settings
  • Avoid depending on sort order of global lists
  • Use worker-scoped fixtures for expensive setup when needed

If everything is entangled, start with workers: 1, fix isolation, then scale up.

How Playwright Fits a Wider Quality System

Playwright is powerful, but it is not the whole strategy.

Pair it with:

  • Unit tests for pure logic
  • API tests for contracts
  • Accessibility checks
  • Exploratory sessions for unknown risks
  • Clear manual cases for judgment heavy areas

Automation should shorten feedback loops and protect known value, not replace thinking.

Final Practical Workflow

When automating a new feature:

  1. Write or refine the manual scenario and oracle.
  2. Decide if the check belongs in smoke, critical, or deep regression.
  3. Prefer API setup when UI setup is long and irrelevant.
  4. Choose stable locators with accessibility in mind.
  5. Write focused tests with strong assertions.
  6. Run locally headed once, then headless.
  7. Inspect failures with trace/UI mode.
  8. Add to CI with appropriate tags.
  9. Watch flake rate for a week.
  10. Refactor helpers only after duplication is real.

If you remember one lesson from this Playwright tutorial, remember this: reliable tests come from clear intent, stable locators, good data isolation, and honest assertions. Playwright gives you excellent tooling for those habits. The habits still have to be yours.

Install it today, write one meaningful test against a real flow, and make that test earn its place in smoke. That single green, trustworthy check is a better start than twenty fragile recordings.

Quick Command Cheat Sheet

Shell
npm init playwright@latest
npx playwright install
npx playwright test
npx playwright test tests/login.spec.ts --headed
npx playwright test --project=chromium
npx playwright test --ui
npx playwright test --debug
npx playwright test --grep @smoke
npx playwright codegen http://127.0.0.1:3000
npx playwright show-report
npx playwright show-trace trace.zip

Keep this list nearby for your first week. Muscle memory around these commands matters as much as memorizing API method names.

// FIELD DISPATCH

Get the QA Field Notes

Weekly QA battles, AI testing guides, and interview drills. Free on Substack.

// LIVE COURSE / THE TESTING ACADEMY

Playwright Automation Mastery

Go beyond Selenium. Master Playwright with JS/TS in 90 days.

From the instructor behind this guide.

Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 9, 2026 / Reviewed July 9, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Playwright documentation

    Microsoft

    Canonical API, locator, fixture, browser, and test-runner behavior.

  2. 02
    Playwright best practices

    Microsoft

    Official guidance for resilient tests, isolation, and user-facing locators.

  3. 03
    WebDriver standard

    W3C

    The browser automation protocol specification used by major automation stacks.

FAQ / QUICK ANSWERS

Questions testers ask

How do I get started with Playwright testing?

Install Node.js, scaffold a project with the Playwright init command, install browsers, then run the example tests. Next, replace examples with tests against your app using stable locators, assertions, and a local or staging base URL. Read failures with the HTML report and trace viewer.

How do you write your first Playwright test?

Create a test file, import test and expect from @playwright/test, open a page with page.goto, perform actions with locators, and assert outcomes with expect. Start with one user journey such as login or form submit. Run it with npx playwright test and iterate on locators until stable.

Does Playwright support API testing?

Yes. Playwright includes an APIRequestContext for HTTP requests, so you can set up data via API, assert responses, and mix API setup with UI checks in the same project. Many teams use API calls to create state quickly, then validate the UI path that users see.

What is Playwright codegen?

Codegen records your browser interactions and generates Playwright script stubs. It is excellent for learning locators and accelerating first drafts. Always clean up generated code: improve locator quality, add assertions, remove noise, and fit the test into your fixtures and folder structure.

What are Playwright fixtures?

Fixtures are Playwright's dependency injection system for tests. Built-in fixtures include page, context, and request. Custom fixtures can provide authenticated pages, test data, or API helpers. Compared with classic hooks, fixtures are more composable, scoped, and reusable across files.

Should beginners use TypeScript with Playwright?

Yes if you can. Playwright TypeScript getting started is smooth because the official scaffold supports it well, and types improve editor help for locators, assertions, and config. JavaScript is fine too. Pick one and stay consistent in the project.