playwright-testing

Playwright Testing Best Practices

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "playwright-testing" with this command: npx skills add fcakyon/claude-codex-settings/fcakyon-claude-codex-settings-playwright-testing

Playwright Testing Best Practices

Test Organization

File Structure

tests/ ├── auth/ │ ├── login.spec.ts │ └── signup.spec.ts ├── dashboard/ │ └── dashboard.spec.ts ├── fixtures/ │ └── test-data.ts ├── pages/ │ └── login.page.ts └── playwright.config.ts

Naming Conventions

  • Files: feature-name.spec.ts

  • Tests: Describe user behavior, not implementation

  • Good: test('user can reset password via email')

  • Bad: test('test reset password')

Page Object Model

Basic Pattern

// pages/login.page.ts export class LoginPage { constructor(private page: Page) {}

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

async login(email: string, password: string) { await this.page.getByLabel("Email").fill(email); await this.page.getByLabel("Password").fill(password); await this.page.getByRole("button", { name: "Sign in" }).click(); } }

// tests/login.spec.ts test("successful login", async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login("user@example.com", "password"); await expect(page).toHaveURL("/dashboard"); });

Locator Strategies

Priority Order (Best to Worst)

  • getByRole

  • Accessible, resilient

  • getByLabel

  • Form inputs

  • getByPlaceholder

  • When no label

  • getByText

  • Visible text

  • getByTestId

  • When no better option

  • CSS/XPath - Last resort

Examples

// Preferred await page.getByRole("button", { name: "Submit" }).click(); await page.getByLabel("Email address").fill("user@example.com");

// Acceptable await page.getByTestId("submit-button").click();

// Avoid await page.locator("#submit-btn").click(); await page.locator('//button[@type="submit"]').click();

Authentication Handling

Storage State (Recommended)

Save logged-in state and reuse across tests:

// global-setup.ts async function globalSetup() { const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto("/login"); await page.getByLabel("Email").fill(process.env.TEST_USER_EMAIL); await page.getByLabel("Password").fill(process.env.TEST_USER_PASSWORD); await page.getByRole("button", { name: "Sign in" }).click(); await page.waitForURL("/dashboard"); await page.context().storageState({ path: "auth.json" }); await browser.close(); }

// playwright.config.ts export default defineConfig({ globalSetup: "./global-setup.ts", use: { storageState: "auth.json", }, });

Multi-User Scenarios

// Create different auth states const adminAuth = "admin-auth.json"; const userAuth = "user-auth.json";

test.describe("admin features", () => { test.use({ storageState: adminAuth }); // Admin tests });

test.describe("user features", () => { test.use({ storageState: userAuth }); // User tests });

File Upload Handling

Basic Upload

// Single file await page.getByLabel("Upload file").setInputFiles("path/to/file.pdf");

// Multiple files await page .getByLabel("Upload files") .setInputFiles(["path/to/file1.pdf", "path/to/file2.pdf"]);

// Clear file input await page.getByLabel("Upload file").setInputFiles([]);

Drag and Drop Upload

// Create file from buffer const buffer = Buffer.from("file content");

await page.getByTestId("dropzone").dispatchEvent("drop", { dataTransfer: { files: [{ name: "test.txt", mimeType: "text/plain", buffer }], }, });

File Download

const downloadPromise = page.waitForEvent("download"); await page.getByRole("button", { name: "Download" }).click(); const download = await downloadPromise; await download.saveAs("downloads/" + download.suggestedFilename());

Waiting Strategies

Auto-Wait (Preferred)

Playwright auto-waits for elements. Use assertions:

// Auto-waits for element to be visible and stable await page.getByRole("button", { name: "Submit" }).click();

// Auto-waits for condition await expect(page.getByText("Success")).toBeVisible();

Explicit Waits (When Needed)

// Wait for navigation await page.waitForURL("**/dashboard");

// Wait for network idle await page.waitForLoadState("networkidle");

// Wait for specific response await page.waitForResponse((resp) => resp.url().includes("/api/data"));

Network Mocking

Mock API Responses

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

// Mock error response await page.route("**/api/users", async (route) => { await route.fulfill({ status: 500 }); });

Intercept and Modify

await page.route("**/api/data", async (route) => { const response = await route.fetch(); const json = await response.json(); json.modified = true; await route.fulfill({ response, json }); });

CI/CD Integration

GitHub Actions Example

  • name: Run Playwright tests run: npx playwright test env: CI: true

  • name: Upload test results if: always() uses: actions/upload-artifact@v3 with: name: playwright-report path: playwright-report/

Parallel Execution

// playwright.config.ts export default defineConfig({ workers: process.env.CI ? 2 : undefined, fullyParallel: true, });

Debugging Failed Tests

Debug Tools

Run with UI mode

npx playwright test --ui

Run with inspector

npx playwright test --debug

Show browser

npx playwright test --headed

Trace Viewer

// playwright.config.ts use: { trace: 'on-first-retry', // Capture trace on failure }

Flaky Test Fixes

Common Causes and Solutions

Race conditions:

  • Use proper assertions instead of hard waits

  • Wait for network requests to complete

Animation issues:

  • Disable animations in test config

  • Wait for animation to complete

Dynamic content:

  • Use flexible locators (text content, not position)

  • Wait for loading states to resolve

Test isolation:

  • Each test should set up its own state

  • Don't depend on other tests' side effects

Anti-Patterns to Avoid

// Bad: Hard sleep await page.waitForTimeout(5000);

// Good: Wait for condition await expect(page.getByText("Loaded")).toBeVisible();

// Bad: Flaky selector await page.locator(".btn:nth-child(3)").click();

// Good: Semantic selector await page.getByRole("button", { name: "Submit" }).click();

Responsive Design Testing

For comprehensive responsive testing across viewport breakpoints, use the responsive-tester agent. It automatically:

  • Tests pages across 7 standard breakpoints (375px to 1536px)

  • Detects horizontal overflow issues

  • Verifies mobile-first design patterns

  • Checks touch target sizes (44x44px minimum)

  • Flags anti-patterns like fixed widths without mobile fallback

Trigger it by asking to "test responsiveness", "check breakpoints", or "test mobile/desktop layout".

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

paper-search-usage

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

gcloud-usage

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

mongodb-usage

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

pr-workflow

No summary provided by upstream source.

Repository SourceNeeds Review