Back to list
fcakyon

playwright-testing

by fcakyon

My personal Claude Code and OpenAI Codex setup with battle-tested skills, commands, hooks, agents and MCP servers that I use daily.

368🍴 40📅 Jan 23, 2026

SKILL.md


name: playwright-testing description: This skill should be used when user asks about "Playwright", "responsiveness test", "test with playwright", "test login flow", "file upload test", "handle authentication in tests", or "fix flaky tests".

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)

  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("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

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

Score

Total Score

85/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

+5
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

+5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon