| name | playwright-testing |
| description | Expert guidance for writing end-to-end tests with Playwright Test framework. Use this skill when writing browser automation tests, creating test suites, working with locators and assertions, mocking network requests, handling authentication, or configuring the Playwright test runner. Trigger keywords include "playwright", "e2e test", "end-to-end", "browser test", "getByRole", "locator", "toBeVisible", "page.goto", "test runner". |
Playwright Testing
End-to-end testing framework with auto-waiting, web-first assertions, and multi-browser support.
Quick Start
import { test, expect } from "@playwright/test";
test("user can log in", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Password").fill("secret");
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByText("Welcome")).toBeVisible();
});
Core Concepts
Locators (Priority Order)
page.getByRole('button', { name: 'Submit' }) — ARIA roles (most resilient)
page.getByLabel('Email') — Form labels
page.getByText('Welcome') — Visible text
page.getByTestId('user-menu') — Test IDs (explicit contracts)
page.getByPlaceholder('Search') — Placeholder text
Avoid CSS selectors and XPath—they break with DOM changes.
Chaining and filtering:
page.locator(".modal").getByRole("button", { name: "Save" });
page.getByRole("listitem").filter({ hasText: "Product" });
page.getByRole("listitem").filter({ has: page.getByRole("button") });
See references/locators.md for complete locator API.
Assertions
Auto-retrying (use these for web elements):
await expect(locator).toBeVisible();
await expect(locator).toHaveText("Hello");
await expect(locator).toHaveValue("input text");
await expect(locator).toBeChecked();
await expect(page).toHaveURL(/dashboard/);
Non-retrying (for static values):
expect(value).toBe(5);
expect(array).toContain("item");
expect(obj).toEqual({ key: "value" });
See references/assertions.md for all assertion types.
Actions
await locator.click();
await locator.fill("text");
await locator.pressSequentially("t");
await locator.selectOption("value");
await locator.check();
await locator.setInputFiles("file.pdf");
await page.keyboard.press("Enter");
All actions auto-wait for elements to be visible, stable, and enabled.
See references/actions.md for complete action reference.
Test Structure
import { test, expect } from "@playwright/test";
test.describe("Feature", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
test("scenario one", async ({ page }) => {
});
test("scenario two", async ({ page }) => {
});
});
Hooks
test.beforeEach() / test.afterEach() — Run before/after each test
test.beforeAll() / test.afterAll() — Run once per worker
Configuration
Minimal playwright.config.ts:
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: "html",
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
See references/configuration.md for all options.
CLI Commands
npx playwright test
npx playwright test --ui
npx playwright test --headed
npx playwright test --debug
npx playwright test -g "login"
npx playwright test --project=chromium
npx playwright test --last-failed
npx playwright codegen
npx playwright show-report
Advanced Features
Network Mocking
await page.route("**/api/users", (route) =>
route.fulfill({ json: [{ id: 1, name: "Mock User" }] }),
);
await page.route("**/api/error", (route) => route.fulfill({ status: 500 }));
Authentication State
await page.context().storageState({ path: "auth.json" });
use: {
storageState: "auth.json";
}
Fixtures
const test = base.extend<{ userPage: Page }>({
userPage: async ({ browser }, use) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("/login");
await use(page);
await context.close();
},
});
See references/advanced.md for network mocking, auth patterns, fixtures, and Page Object Model.
Best Practices
- Use role-based locators —
getByRole() is most resilient to changes
- Prefer auto-retrying assertions —
await expect(locator).toBeVisible() not locator.isVisible()
- Keep tests isolated — Each test gets fresh browser context
- Avoid hardcoded waits — Playwright auto-waits; use explicit waits only for custom conditions
- Test user behaviour — Focus on what users see, not implementation details
- Use soft assertions sparingly —
expect.soft() continues after failure for comprehensive reports