mcpField
P
playwright-testing

playwright-testing

github.com/fcakyonTesting & quality1.2k39 installsUpdated 1d ago
Repository

About playwright-testing

A collection of practices for developers writing Playwright tests, covering test organization, locators, authentication, file handling, network mocking, and debugging. It also describes ways to reduce flaky tests and points to responsive-tester for checking responsive layouts.

Key features

  1. 01Organizes test files and naming by feature
  2. 02Uses semantic locators and Page Objects
  3. 03Reuses authentication state across users
  4. 04Handles file uploads and downloads
  5. 05Mocks network requests and debugs failures
  6. 06Checks responsive breakpoints and layouts

Use cases

  1. 01Structure end-to-end tests for login and dashboard features
  2. 02Reuse login state to test users with different roles
  3. 03Mock API responses to verify interface behavior
  4. 04Investigate race conditions and flaky tests
  5. 05Check layouts on mobile and desktop viewports

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/fcakyon/claude-codex-settings --skill playwright-testing

After installing, type/playwright-testingin Claude Code to confirm it loaded.

Details

From the project README

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("[email protected]", "password");
  await expect(page).toHaveURL("/dashboard");
});

Locator Strategies

Priority Order (Best to Worst)
  1. getByRole - Accessible, resilient
  2. getByLabel - Form inputs
  3. getByPlaceholder - When no label
  4. getByText - Visible text
  5. getByTestId - When no better option
  6. CSS/XPath - Last resort
Examples
// Preferred
await page.getByRole("button", { name: "Submit" }).click();
await page.getByLabel("Email address").fill("[email protected]");

// 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".

Comments

Markdown supported