Playwright E2E Testing (TypeScript)
Comprehensive toolkit for end-to-end testing of web applications using Playwright with TypeScript. Enables robust UI testing, UI-dependent API setup, and responsive design verification following best practices.
Activation: This skill is triggered when authoring or maintaining versioned Playwright UI specs and their test infrastructure.
When to Use This Skill
- Write E2E tests for user flows, forms, navigation, and authentication
- UI-dependent API setup via the
request fixture or network interception
- Responsive testing across mobile, tablet, and desktop viewports
- Debug flaky tests using traces, screenshots, videos, and Playwright Inspector
- Setup test infrastructure with Page Object Model and fixtures
- Mock/intercept APIs for isolated, deterministic testing
- Visual regression testing with screenshot comparisons
Do NOT Use For
- Standalone API/contract testing with no browser (use
api-testing).
- Driving a live browser interactively for exploration or debugging (use
playwright-cli).
- Governing a large regression suite, tiers, or CI sharding strategy (use
playwright-regression-testing).
- Selenium/Java browser automation (use
webapp-selenium-testing).
Prerequisites
| Requirement |
Details |
| Node.js |
v18+ recommended |
| Package Manager |
npm, yarn, or pnpm |
| Playwright |
@playwright/test package |
| TypeScript |
typescript + ts-node (optional but recommended) |
| Browsers |
Installed via npx playwright install |
Quick Setup
# Initialize new project
npm init playwright@latest
# Or add to existing project
npm install -D @playwright/test
npx playwright install
First Questions to Ask
Before writing tests, clarify:
- App URL: Local dev server command + port, or staging URL?
- Critical flows: Which user journeys must be covered (happy path + error states)?
- Browsers/devices: Chrome, Firefox, Safari? Mobile viewports?
- API strategy: Real backend, mocked responses, or hybrid?
- Test data: Seed data available? Reset/cleanup strategy?
Core Principles
1. Test Runner & TypeScript
Always use @playwright/test with TypeScript for type safety and better IDE support.
import { test, expect } from "@playwright/test";
test("user can login", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@test.com");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page).toHaveURL(/.*dashboard/);
});
2. Locator Strategy (Priority Order)
Prefer role-based locators (getByRole) with accessible names, then label → placeholder → text → test ID → CSS (last resort). XPath is never used.
➡️ Full priority hierarchy, role reference, and examples: Locator Strategies: Priority — the single source of truth.
3. Auto-Waiting & Web-First Assertions
Playwright auto-waits for elements. Never use sleep() or arbitrary timeouts.
// [ok] Web-first assertions (auto-retry)
await expect(page.getByRole("alert")).toBeVisible();
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByTestId("status")).toHaveText("Success!");
// [no] Avoid manual waits
await page.waitForTimeout(2000); // Bad practice
4. Test Structure with Steps
Use test.step() for readable reports and failure localization:
test("checkout flow", async ({ page }) => {
await test.step("Add item to cart", async () => {
await page.goto("/products/1");
await page.getByRole("button", { name: "Add to Cart" }).click();
});
await test.step("Complete checkout", async () => {
await page.goto("/checkout");
await page.getByRole("button", { name: "Pay Now" }).click();
});
await test.step("Verify confirmation", async () => {
await expect(page.getByRole("heading")).toContainText("Order Confirmed");
});
});
Key Workflows
Forms & Navigation
// Form submit and wait for navigation (auto-waiting)
await page.getByRole("button", { name: "Login" }).click();
await expect(page).toHaveURL(/.*dashboard/);
// Form with API response validation
const responsePromise = page.waitForResponse(
(r) => r.url().includes("/api/login") && r.status() === 200,
);
await page.getByRole("button", { name: "Login" }).click();
const response = await responsePromise;
API Testing (Request Fixture)
test("API health check", async ({ request }) => {
const response = await request.get("/api/health");
expect(response.ok()).toBeTruthy();
expect(await response.json()).toMatchObject({ status: "ok" });
});
API Mocking & Interception
test("handles API error", async ({ page }) => {
await page.route("**/api/users", (route) =>
route.fulfill({
status: 500,
body: JSON.stringify({ error: "Server error" }),
}),
);
await page.goto("/users");
await expect(page.getByRole("alert")).toContainText("Something went wrong");
});
Responsive Testing
const viewports = [
{ width: 375, height: 667, name: "mobile" },
{ width: 768, height: 1024, name: "tablet" },
{ width: 1280, height: 720, name: "desktop" },
];
for (const vp of viewports) {
test(`navigation works on ${vp.name}`, async ({ page }) => {
await page.setViewportSize(vp);
await page.goto("/");
// Mobile: hamburger menu
if (vp.width < 768) {
await page.getByRole("button", { name: /menu/i }).click();
}
await page.getByRole("link", { name: "About" }).click();
await expect(page).toHaveURL(/about/);
});
}
Configuration
Use playwright.config.ts for project-wide settings:
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
retries: process.env.CI ? 2 : 0,
reporter: [["html"], ["junit", { outputFile: "results.xml" }]],
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{ name: "chromium", use: devices["Desktop Chrome"] },
{ name: "mobile", use: devices["Pixel 5"] },
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
Troubleshooting
| Problem |
Cause |
Solution |
| Element not found |
Wrong locator or not rendered |
Use PWDEBUG=1 to inspect, verify with getByRole |
| Timeout waiting |
Element hidden or slow load |
Check for overlays, increase timeout, use waitFor() |
| Flaky tests |
Race conditions, animations |
Add test.step(), use proper waits, disable animations |
| Strict mode violation |
Multiple elements match |
Use .first(), .filter(), or more specific locator |
| Screenshots differ |
Dynamic content |
Mask dynamic areas, use deterministic data |
| CI fails, local passes |
Environment differences |
Check baseURL, timeouts, webServer config |
| API mock not working |
Route pattern mismatch |
Use **/api/... glob, verify with page.on('request') |
CLI Quick Reference
| Command |
Description |
npx playwright test |
Run all tests headless |
npx playwright test --ui |
Open UI mode (interactive) |
npx playwright test --headed |
Run with visible browser |
npx playwright test --debug |
Run with Playwright Inspector |
npx playwright test -g "login" |
Run tests matching pattern |
npx playwright test --project=chromium |
Run specific project |
npx playwright show-report |
Open HTML report |
npx playwright codegen |
Generate tests by recording |
PWDEBUG=1 npx playwright test |
Debug with Inspector |
DEBUG=pw:api npx playwright test |
Verbose API logging |
Red Flags
- CSS/XPath locators when a role/label/testId is available — brittle and breaks on refactor.
waitForTimeout / manual sleeps instead of web-first auto-retrying assertions.
- Tests sharing state and depending on execution order — flaky and order-coupled.
- Assertions only on status/URL with no visible-state check — hides render regressions.
- Inline page setup repeated across tests instead of fixtures — duplication and drift.
References
| Document |
Content |
| Snippets: Setup |
Config, auth setup, custom fixtures & logging |
| Snippets: Interactions |
Form interactions, API testing & network interception |
| Snippets: Viewports & Auth |
Responsive viewports & authentication patterns |
| Snippets: Assertions & Debug |
Assertions, debug commands & utility helpers |
| Locator Strategies: Priority |
Locator priority hierarchy & role-based locators |
| Locator Strategies: Text |
Label, text, placeholder, alt-text & test-ID locators |
| Locator Strategies: Filtering |
Filtering, chaining & complex locator patterns |
| Locator Strategies: Anti & Debug |
Anti-patterns, CSS last-resort, debugging & quick reference |
| POM: Basics |
POM concepts, directory structure, base page & fluent API |
| POM: Components |
Page object & reusable component object implementation |
| POM: Fixtures |
Custom & authenticated page-object fixtures |
| POM: Practices |
Best practices, anti-patterns & a complete worked example |
| Debugging: Tools & UI |
Debugging tools, UI mode, Inspector & headed mode |
| Debugging: Tracing & Logs |
Trace viewer, verbose logging, screenshots & videos |
| Debugging: Errors & Network |
Console/page errors & network debugging |
| Debugging: Flaky & Locators |
Flaky-test fixes, locator debugging & quick commands |
Verification
1---2name: playwright-e2e-testing3description: Author and maintain versioned Playwright (@playwright/test) TypeScript UI specs for browser user flows. Use when asked to create, run, debug, or refactor E2E tests, form/navigation/auth flows, responsive checks, UI mocking, fixtures, Page Objects, or visual comparisons. Use api-testing for standalone REST/GraphQL contracts and playwright-cli for live browser sessions. Keywords: E2E spec, Playwright test, POM, fixtures, UI regression.4license: Complete terms in LICENSE.txt5---67# Playwright E2E Testing (TypeScript)89Comprehensive toolkit for end-to-end testing of web applications using Playwright with TypeScript. Enables robust UI testing, UI-dependent API setup, and responsive design verification following best practices.1011> **Activation:** This skill is triggered when authoring or maintaining versioned Playwright UI specs and their test infrastructure.1213## When to Use This Skill1415- **Write E2E tests** for user flows, forms, navigation, and authentication16- **UI-dependent API setup** via the `request` fixture or network interception17- **Responsive testing** across mobile, tablet, and desktop viewports18- **Debug flaky tests** using traces, screenshots, videos, and Playwright Inspector19- **Setup test infrastructure** with Page Object Model and fixtures20- **Mock/intercept APIs** for isolated, deterministic testing21- **Visual regression testing** with screenshot comparisons2223### Do NOT Use For2425- Standalone API/contract testing with no browser (use `api-testing`).26- Driving a live browser interactively for exploration or debugging (use `playwright-cli`).27- Governing a large regression suite, tiers, or CI sharding strategy (use `playwright-regression-testing`).28- Selenium/Java browser automation (use `webapp-selenium-testing`).2930## Prerequisites3132| Requirement | Details |33| --------------- | --------------------------------------------------- |34| Node.js | v18+ recommended |35| Package Manager | npm, yarn, or pnpm |36| Playwright | `@playwright/test` package |37| TypeScript | `typescript` + `ts-node` (optional but recommended) |38| Browsers | Installed via `npx playwright install` |3940### Quick Setup4142```bash43# Initialize new project44npm init playwright@latest4546# Or add to existing project47npm install -D @playwright/test48npx playwright install49```5051## First Questions to Ask5253Before writing tests, clarify:54551. **App URL**: Local dev server command + port, or staging URL?562. **Critical flows**: Which user journeys must be covered (happy path + error states)?573. **Browsers/devices**: Chrome, Firefox, Safari? Mobile viewports?584. **API strategy**: Real backend, mocked responses, or hybrid?595. **Test data**: Seed data available? Reset/cleanup strategy?6061---6263## Core Principles6465### 1. Test Runner & TypeScript6667Always use `@playwright/test` with TypeScript for type safety and better IDE support.6869```typescript70import { test, expect } from "@playwright/test";7172test("user can login", async ({ page }) => {73 await page.goto("/login");74 await page.getByLabel("Email").fill("user@test.com");75 await page.getByLabel("Password").fill("password123");76 await page.getByRole("button", { name: "Sign in" }).click();77 await expect(page).toHaveURL(/.*dashboard/);78});79```8081### 2. Locator Strategy (Priority Order)8283Prefer role-based locators (`getByRole`) with accessible names, then label → placeholder → text → test ID → CSS (last resort). XPath is never used.8485➡️ **Full priority hierarchy, role reference, and examples:** [Locator Strategies: Priority](./references/locator-strategies-priority.md) — the single source of truth.8687### 3. Auto-Waiting & Web-First Assertions8889Playwright auto-waits for elements. Never use `sleep()` or arbitrary timeouts.9091```typescript92// [ok] Web-first assertions (auto-retry)93await expect(page.getByRole("alert")).toBeVisible();94await expect(page).toHaveURL(/dashboard/);95await expect(page.getByTestId("status")).toHaveText("Success!");9697// [no] Avoid manual waits98await page.waitForTimeout(2000); // Bad practice99```100101### 4. Test Structure with Steps102103Use `test.step()` for readable reports and failure localization:104105```typescript106test("checkout flow", async ({ page }) => {107 await test.step("Add item to cart", async () => {108 await page.goto("/products/1");109 await page.getByRole("button", { name: "Add to Cart" }).click();110 });111112 await test.step("Complete checkout", async () => {113 await page.goto("/checkout");114 await page.getByRole("button", { name: "Pay Now" }).click();115 });116117 await test.step("Verify confirmation", async () => {118 await expect(page.getByRole("heading")).toContainText("Order Confirmed");119 });120});121```122123---124125## Key Workflows126127### Forms & Navigation128129```typescript130// Form submit and wait for navigation (auto-waiting)131await page.getByRole("button", { name: "Login" }).click();132await expect(page).toHaveURL(/.*dashboard/);133134// Form with API response validation135const responsePromise = page.waitForResponse(136 (r) => r.url().includes("/api/login") && r.status() === 200,137);138await page.getByRole("button", { name: "Login" }).click();139const response = await responsePromise;140```141142### API Testing (Request Fixture)143144```typescript145test("API health check", async ({ request }) => {146 const response = await request.get("/api/health");147 expect(response.ok()).toBeTruthy();148 expect(await response.json()).toMatchObject({ status: "ok" });149});150```151152### API Mocking & Interception153154```typescript155test("handles API error", async ({ page }) => {156 await page.route("**/api/users", (route) =>157 route.fulfill({158 status: 500,159 body: JSON.stringify({ error: "Server error" }),160 }),161 );162 await page.goto("/users");163 await expect(page.getByRole("alert")).toContainText("Something went wrong");164});165```166167### Responsive Testing168169```typescript170const viewports = [171 { width: 375, height: 667, name: "mobile" },172 { width: 768, height: 1024, name: "tablet" },173 { width: 1280, height: 720, name: "desktop" },174];175176for (const vp of viewports) {177 test(`navigation works on ${vp.name}`, async ({ page }) => {178 await page.setViewportSize(vp);179 await page.goto("/");180 // Mobile: hamburger menu181 if (vp.width < 768) {182 await page.getByRole("button", { name: /menu/i }).click();183 }184 await page.getByRole("link", { name: "About" }).click();185 await expect(page).toHaveURL(/about/);186 });187}188```189190---191192## Configuration193194Use `playwright.config.ts` for project-wide settings:195196```typescript197import { defineConfig, devices } from "@playwright/test";198199export default defineConfig({200 testDir: "./tests",201 retries: process.env.CI ? 2 : 0,202 reporter: [["html"], ["junit", { outputFile: "results.xml" }]],203 use: {204 baseURL: "http://localhost:3000",205 trace: "on-first-retry",206 screenshot: "only-on-failure",207 video: "retain-on-failure",208 },209 projects: [210 { name: "chromium", use: devices["Desktop Chrome"] },211 { name: "mobile", use: devices["Pixel 5"] },212 ],213 webServer: {214 command: "npm run dev",215 url: "http://localhost:3000",216 reuseExistingServer: !process.env.CI,217 },218});219```220221---222223## Troubleshooting224225| Problem | Cause | Solution |226| ---------------------- | ----------------------------- | ------------------------------------------------------- |227| Element not found | Wrong locator or not rendered | Use `PWDEBUG=1` to inspect, verify with `getByRole` |228| Timeout waiting | Element hidden or slow load | Check for overlays, increase timeout, use `waitFor()` |229| Flaky tests | Race conditions, animations | Add `test.step()`, use proper waits, disable animations |230| Strict mode violation | Multiple elements match | Use `.first()`, `.filter()`, or more specific locator |231| Screenshots differ | Dynamic content | Mask dynamic areas, use deterministic data |232| CI fails, local passes | Environment differences | Check `baseURL`, timeouts, `webServer` config |233| API mock not working | Route pattern mismatch | Use `**/api/...` glob, verify with `page.on('request')` |234235---236237## CLI Quick Reference238239| Command | Description |240| ---------------------------------------- | ----------------------------- |241| `npx playwright test` | Run all tests headless |242| `npx playwright test --ui` | Open UI mode (interactive) |243| `npx playwright test --headed` | Run with visible browser |244| `npx playwright test --debug` | Run with Playwright Inspector |245| `npx playwright test -g "login"` | Run tests matching pattern |246| `npx playwright test --project=chromium` | Run specific project |247| `npx playwright show-report` | Open HTML report |248| `npx playwright codegen` | Generate tests by recording |249| `PWDEBUG=1 npx playwright test` | Debug with Inspector |250| `DEBUG=pw:api npx playwright test` | Verbose API logging |251252---253254## Red Flags255256- CSS/XPath locators when a role/label/testId is available — brittle and breaks on refactor.257- `waitForTimeout` / manual sleeps instead of web-first auto-retrying assertions.258- Tests sharing state and depending on execution order — flaky and order-coupled.259- Assertions only on status/URL with no visible-state check — hides render regressions.260- Inline page setup repeated across tests instead of fixtures — duplication and drift.261262---263264## References265266| Document | Content |267| -------------------------------------------------------------------------------- | ---------------------------------------------------------- |268| [Snippets: Setup](./references/snippets-setup.md) | Config, auth setup, custom fixtures & logging |269| [Snippets: Interactions](./references/snippets-interactions.md) | Form interactions, API testing & network interception |270| [Snippets: Viewports & Auth](./references/snippets-viewports-auth.md) | Responsive viewports & authentication patterns |271| [Snippets: Assertions & Debug](./references/snippets-assertions-debugging.md) | Assertions, debug commands & utility helpers |272| [Locator Strategies: Priority](./references/locator-strategies-priority.md) | Locator priority hierarchy & role-based locators |273| [Locator Strategies: Text](./references/locator-strategies-text.md) | Label, text, placeholder, alt-text & test-ID locators |274| [Locator Strategies: Filtering](./references/locator-strategies-filtering.md) | Filtering, chaining & complex locator patterns |275| [Locator Strategies: Anti & Debug](./references/locator-strategies-anti-debug.md)| Anti-patterns, CSS last-resort, debugging & quick reference|276| [POM: Basics](./references/page-object-model-basics.md) | POM concepts, directory structure, base page & fluent API |277| [POM: Components](./references/page-object-model-components.md) | Page object & reusable component object implementation |278| [POM: Fixtures](./references/page-object-model-fixtures.md) | Custom & authenticated page-object fixtures |279| [POM: Practices](./references/page-object-model-practices.md) | Best practices, anti-patterns & a complete worked example |280| [Debugging: Tools & UI](./references/debugging-tools-ui.md) | Debugging tools, UI mode, Inspector & headed mode |281| [Debugging: Tracing & Logs](./references/debugging-tracing-logging.md) | Trace viewer, verbose logging, screenshots & videos |282| [Debugging: Errors & Network](./references/debugging-errors-network.md) | Console/page errors & network debugging |283| [Debugging: Flaky & Locators](./references/debugging-flaky-locators.md) | Flaky-test fixes, locator debugging & quick commands |284285---286287## Verification288289- [ ] **Uses custom fixture injection** — No `new PageObject()` calls in spec files; all POMs injected via fixtures290- [ ] **Locators use recommended strategies** — All locators use `getByRole()`, `getByTestId()`, or `getByText()`; no CSS selectors for interactive elements291- [ ] **Tests are independent** — Each test sets up and tears down its own state; no `beforeAll` with shared mutable state292- [ ] **Error states covered** — At least one test verifies error/empty/loading states alongside happy path