{
  "schemaVersion": "1.0",
  "item": {
    "slug": "e2e-testing-patterns",
    "name": "E2E Testing Patterns",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/wpank/e2e-testing-patterns",
    "canonicalUrl": "https://clawhub.ai/wpank/e2e-testing-patterns",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/e2e-testing-patterns",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=e2e-testing-patterns",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "slug": "e2e-testing-patterns",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-02T18:08:14.132Z",
      "expiresAt": "2026-05-09T18:08:14.132Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=e2e-testing-patterns",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=e2e-testing-patterns",
        "contentDisposition": "attachment; filename=\"e2e-testing-patterns-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "e2e-testing-patterns"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/e2e-testing-patterns"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/e2e-testing-patterns",
    "agentPageUrl": "https://openagent3.xyz/skills/e2e-testing-patterns/agent",
    "manifestUrl": "https://openagent3.xyz/skills/e2e-testing-patterns/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/e2e-testing-patterns/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "E2E Testing Patterns",
        "body": "Test what users do, not how code works. E2E tests prove the system works as a whole — they're your confidence to ship."
      },
      {
        "title": "OpenClaw / Moltbot / Clawbot",
        "body": "npx clawhub@latest install e2e-testing-patterns"
      },
      {
        "title": "WHAT This Skill Does",
        "body": "Provides patterns for building end-to-end test suites that:\n\nCatch regressions before users do\nRun fast enough for CI/CD\nRemain stable (no flaky failures)\nCover critical user journeys without over-testing"
      },
      {
        "title": "WHEN To Use",
        "body": "Implementing E2E test automation for a web application\nDebugging flaky tests that fail intermittently\nSetting up CI/CD test pipelines with browser tests\nTesting critical user workflows (auth, checkout, signup)\nChoosing what to test with E2E vs unit/integration tests"
      },
      {
        "title": "Test Pyramid — Know Your Layer",
        "body": "/\\\n       /E2E\\         ← FEW: Critical paths only (this skill)\n      /─────\\\n     /Integr\\        ← MORE: Component interactions, API contracts\n    /────────\\\n   /Unit Tests\\      ← MANY: Fast, isolated, cover edge cases\n  /────────────\\"
      },
      {
        "title": "What E2E Tests Are For",
        "body": "E2E Tests ✓NOT E2E Tests ✗Critical user journeys (login → dashboard → action → logout)Unit-level logic (use unit tests)Multi-step flows (checkout, onboarding wizard)API contracts (use integration tests)Cross-browser compatibilityEdge cases (too slow, use unit tests)Real API integrationInternal implementation detailsAuthentication flowsComponent visual states (use Storybook)\n\nRule of thumb: If it would devastate your business to break, E2E test it. If it's just inconvenient, test it faster with unit/integration tests."
      },
      {
        "title": "Core Principles",
        "body": "PrincipleWhyHowTest behavior, not implementationSurvives refactorsAssert on user-visible outcomes, not DOM structureIndependent testsParallelizable, debuggableEach test creates its own data, cleans up afterDeterministic waitsNo flakinessWait for conditions, not fixed timeoutsStable selectorsSurvives UI changesUse data-testid, roles, labels — never CSS classesFast feedbackDevelopers run themMock external services, parallelize, shard"
      },
      {
        "title": "Configuration",
        "body": "// playwright.config.ts\nimport { defineConfig, devices } from \"@playwright/test\";\n\nexport default defineConfig({\n  testDir: \"./e2e\",\n  timeout: 30000,\n  expect: { timeout: 5000 },\n  fullyParallel: true,\n  forbidOnly: !!process.env.CI,\n  retries: process.env.CI ? 2 : 0,\n  workers: process.env.CI ? 1 : undefined,\n  reporter: [[\"html\"], [\"junit\", { outputFile: \"results.xml\" }]],\n  use: {\n    baseURL: \"http://localhost:3000\",\n    trace: \"on-first-retry\",\n    screenshot: \"only-on-failure\",\n    video: \"retain-on-failure\",\n  },\n  projects: [\n    { name: \"chromium\", use: { ...devices[\"Desktop Chrome\"] } },\n    { name: \"firefox\", use: { ...devices[\"Desktop Firefox\"] } },\n    { name: \"webkit\", use: { ...devices[\"Desktop Safari\"] } },\n    { name: \"mobile\", use: { ...devices[\"iPhone 13\"] } },\n  ],\n});"
      },
      {
        "title": "Pattern: Page Object Model",
        "body": "Encapsulate page logic. Tests read like user stories.\n\n// pages/LoginPage.ts\nimport { Page, Locator } from \"@playwright/test\";\n\nexport class LoginPage {\n  readonly page: Page;\n  readonly emailInput: Locator;\n  readonly passwordInput: Locator;\n  readonly loginButton: Locator;\n  readonly errorMessage: Locator;\n\n  constructor(page: Page) {\n    this.page = page;\n    this.emailInput = page.getByLabel(\"Email\");\n    this.passwordInput = page.getByLabel(\"Password\");\n    this.loginButton = page.getByRole(\"button\", { name: \"Login\" });\n    this.errorMessage = page.getByRole(\"alert\");\n  }\n\n  async goto() {\n    await this.page.goto(\"/login\");\n  }\n\n  async login(email: string, password: string) {\n    await this.emailInput.fill(email);\n    await this.passwordInput.fill(password);\n    await this.loginButton.click();\n  }\n}\n\n// tests/login.spec.ts\nimport { test, expect } from \"@playwright/test\";\nimport { LoginPage } from \"../pages/LoginPage\";\n\ntest(\"successful login redirects to dashboard\", async ({ page }) => {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.login(\"user@example.com\", \"password123\");\n\n  await expect(page).toHaveURL(\"/dashboard\");\n  await expect(page.getByRole(\"heading\", { name: \"Dashboard\" })).toBeVisible();\n});"
      },
      {
        "title": "Pattern: Fixtures for Test Data",
        "body": "Create and clean up test data automatically.\n\n// fixtures/test-data.ts\nimport { test as base } from \"@playwright/test\";\n\nexport const test = base.extend<{ testUser: TestUser }>({\n  testUser: async ({}, use) => {\n    // Setup: Create user\n    const user = await createTestUser({\n      email: `test-${Date.now()}@example.com`,\n      password: \"Test123!@#\",\n    });\n\n    await use(user);\n\n    // Teardown: Clean up\n    await deleteTestUser(user.id);\n  },\n});\n\n// Usage — testUser is created before, deleted after\ntest(\"user can update profile\", async ({ page, testUser }) => {\n  await page.goto(\"/login\");\n  await page.getByLabel(\"Email\").fill(testUser.email);\n  // ...\n});"
      },
      {
        "title": "Pattern: Smart Waiting",
        "body": "Never use fixed timeouts. Wait for specific conditions.\n\n// ❌ FLAKY: Fixed timeout\nawait page.waitForTimeout(3000);\n\n// ✅ STABLE: Wait for conditions\nawait page.waitForLoadState(\"networkidle\");\nawait page.waitForURL(\"/dashboard\");\n\n// ✅ BEST: Auto-waiting assertions\nawait expect(page.getByText(\"Welcome\")).toBeVisible();\nawait expect(page.getByRole(\"button\", { name: \"Submit\" })).toBeEnabled();\n\n// Wait for API response\nconst responsePromise = page.waitForResponse(\n  (r) => r.url().includes(\"/api/users\") && r.status() === 200\n);\nawait page.getByRole(\"button\", { name: \"Load\" }).click();\nawait responsePromise;"
      },
      {
        "title": "Pattern: Network Mocking",
        "body": "Isolate tests from real external services.\n\ntest(\"shows error when API fails\", async ({ page }) => {\n  // Mock the API response\n  await page.route(\"**/api/users\", (route) => {\n    route.fulfill({\n      status: 500,\n      body: JSON.stringify({ error: \"Server Error\" }),\n    });\n  });\n\n  await page.goto(\"/users\");\n  await expect(page.getByText(\"Failed to load users\")).toBeVisible();\n});\n\ntest(\"handles slow network gracefully\", async ({ page }) => {\n  await page.route(\"**/api/data\", async (route) => {\n    await new Promise((r) => setTimeout(r, 3000)); // Simulate delay\n    await route.continue();\n  });\n\n  await page.goto(\"/dashboard\");\n  await expect(page.getByText(\"Loading...\")).toBeVisible();\n});"
      },
      {
        "title": "Custom Commands",
        "body": "// cypress/support/commands.ts\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      login(email: string, password: string): Chainable<void>;\n      dataCy(value: string): Chainable<JQuery<HTMLElement>>;\n    }\n  }\n}\n\nCypress.Commands.add(\"login\", (email, password) => {\n  cy.visit(\"/login\");\n  cy.get('[data-testid=\"email\"]').type(email);\n  cy.get('[data-testid=\"password\"]').type(password);\n  cy.get('[data-testid=\"login-button\"]').click();\n  cy.url().should(\"include\", \"/dashboard\");\n});\n\nCypress.Commands.add(\"dataCy\", (value) => {\n  return cy.get(`[data-cy=\"${value}\"]`);\n});\n\n// Usage\ncy.login(\"user@example.com\", \"password\");\ncy.dataCy(\"submit-button\").click();"
      },
      {
        "title": "Network Intercepts",
        "body": "// Mock API\ncy.intercept(\"GET\", \"/api/users\", {\n  statusCode: 200,\n  body: [{ id: 1, name: \"John\" }],\n}).as(\"getUsers\");\n\ncy.visit(\"/users\");\ncy.wait(\"@getUsers\");\ncy.get('[data-testid=\"user-list\"]').children().should(\"have.length\", 1);"
      },
      {
        "title": "Selector Strategy",
        "body": "PrioritySelector TypeExampleWhy1Role + namegetByRole(\"button\", { name: \"Submit\" })Accessible, user-facing2LabelgetByLabel(\"Email address\")Accessible, semantic3data-testidgetByTestId(\"checkout-form\")Stable, explicit for testing4Text contentgetByText(\"Welcome back\")User-facing❌CSS classes.btn-primaryBreaks on styling changes❌DOM structurediv > form > input:nth-child(2)Breaks on any restructure\n\n// ❌ BAD: Brittle selectors\ncy.get(\".btn.btn-primary.submit-button\").click();\ncy.get(\"div > form > div:nth-child(2) > input\").type(\"text\");\n\n// ✅ GOOD: Stable selectors\npage.getByRole(\"button\", { name: \"Submit\" }).click();\npage.getByLabel(\"Email address\").fill(\"user@example.com\");\npage.getByTestId(\"email-input\").fill(\"user@example.com\");"
      },
      {
        "title": "Visual Regression Testing",
        "body": "// Playwright visual comparisons\ntest(\"homepage looks correct\", async ({ page }) => {\n  await page.goto(\"/\");\n  await expect(page).toHaveScreenshot(\"homepage.png\", {\n    fullPage: true,\n    maxDiffPixels: 100,\n  });\n});\n\ntest(\"button states\", async ({ page }) => {\n  const button = page.getByRole(\"button\", { name: \"Submit\" });\n\n  await expect(button).toHaveScreenshot(\"button-default.png\");\n\n  await button.hover();\n  await expect(button).toHaveScreenshot(\"button-hover.png\");\n});"
      },
      {
        "title": "Accessibility Testing",
        "body": "// npm install @axe-core/playwright\nimport AxeBuilder from \"@axe-core/playwright\";\n\ntest(\"page has no accessibility violations\", async ({ page }) => {\n  await page.goto(\"/\");\n\n  const results = await new AxeBuilder({ page })\n    .exclude(\"#third-party-widget\")  // Exclude things you can't control\n    .analyze();\n\n  expect(results.violations).toEqual([]);\n});"
      },
      {
        "title": "Debugging Failed Tests",
        "body": "# Run in headed mode (see the browser)\nnpx playwright test --headed\n\n# Debug mode (step through)\nnpx playwright test --debug\n\n# Show trace viewer for failed tests\nnpx playwright show-report\n\n// Add test steps for better failure reports\ntest(\"checkout flow\", async ({ page }) => {\n  await test.step(\"Add item to cart\", async () => {\n    await page.goto(\"/products\");\n    await page.getByRole(\"button\", { name: \"Add to Cart\" }).click();\n  });\n\n  await test.step(\"Complete checkout\", async () => {\n    await page.goto(\"/checkout\");\n    // ... if this fails, you know which step\n  });\n});\n\n// Pause for manual inspection\nawait page.pause();"
      },
      {
        "title": "Flaky Test Checklist",
        "body": "When a test fails intermittently, check:\n\nIssueFixFixed waitForTimeout() callsReplace with waitForSelector() or expect assertionsRace conditions on page loadWait for networkidle or specific elementsTest data pollutionEnsure tests create/clean their own dataAnimation timingWait for animations to complete or disable themViewport inconsistencySet explicit viewport in configRandom test order issuesTests must be independentThird-party service flakinessMock external APIs"
      },
      {
        "title": "CI/CD Integration",
        "body": "# GitHub Actions example\nname: E2E Tests\non: [push, pull_request]\n\njobs:\n  e2e:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm ci\n      - run: npx playwright install --with-deps\n      - run: npm run build\n      - run: npm run start & npx wait-on http://localhost:3000\n      - run: npx playwright test\n      - uses: actions/upload-artifact@v4\n        if: failure()\n        with:\n          name: playwright-report\n          path: playwright-report/"
      },
      {
        "title": "NEVER Do",
        "body": "NEVER use fixed waitForTimeout() or cy.wait(ms) — they cause flaky tests and slow down suites\nNEVER rely on CSS classes or DOM structure for selectors — use roles, labels, or data-testid\nNEVER share state between tests — each test must be completely independent\nNEVER test implementation details — test what users see and do, not internal structure\nNEVER skip cleanup — always delete test data you created, even on failure\nNEVER test everything with E2E — reserve for critical paths; use faster tests for edge cases\nNEVER ignore flaky tests — fix them immediately or delete them; a flaky test is worse than no test\nNEVER hardcode test data in selectors — use dynamic waits for content that varies"
      },
      {
        "title": "Playwright Commands",
        "body": "// Navigation\nawait page.goto(\"/path\");\nawait page.goBack();\nawait page.reload();\n\n// Interactions\nawait page.click(\"selector\");\nawait page.fill(\"selector\", \"text\");\nawait page.type(\"selector\", \"text\");  // Types character by character\nawait page.selectOption(\"select\", \"value\");\nawait page.check(\"checkbox\");\n\n// Assertions\nawait expect(page).toHaveURL(\"/expected\");\nawait expect(locator).toBeVisible();\nawait expect(locator).toHaveText(\"expected\");\nawait expect(locator).toBeEnabled();\nawait expect(locator).toHaveCount(3);"
      },
      {
        "title": "Cypress Commands",
        "body": "// Navigation\ncy.visit(\"/path\");\ncy.go(\"back\");\ncy.reload();\n\n// Interactions\ncy.get(\"selector\").click();\ncy.get(\"selector\").type(\"text\");\ncy.get(\"selector\").clear().type(\"text\");\ncy.get(\"select\").select(\"value\");\ncy.get(\"checkbox\").check();\n\n// Assertions\ncy.url().should(\"include\", \"/expected\");\ncy.get(\"selector\").should(\"be.visible\");\ncy.get(\"selector\").should(\"have.text\", \"expected\");\ncy.get(\"selector\").should(\"have.length\", 3);"
      }
    ],
    "body": "E2E Testing Patterns\n\nTest what users do, not how code works. E2E tests prove the system works as a whole — they're your confidence to ship.\n\nInstallation\nOpenClaw / Moltbot / Clawbot\nnpx clawhub@latest install e2e-testing-patterns\n\nWHAT This Skill Does\n\nProvides patterns for building end-to-end test suites that:\n\nCatch regressions before users do\nRun fast enough for CI/CD\nRemain stable (no flaky failures)\nCover critical user journeys without over-testing\nWHEN To Use\nImplementing E2E test automation for a web application\nDebugging flaky tests that fail intermittently\nSetting up CI/CD test pipelines with browser tests\nTesting critical user workflows (auth, checkout, signup)\nChoosing what to test with E2E vs unit/integration tests\nTest Pyramid — Know Your Layer\n        /\\\n       /E2E\\         ← FEW: Critical paths only (this skill)\n      /─────\\\n     /Integr\\        ← MORE: Component interactions, API contracts\n    /────────\\\n   /Unit Tests\\      ← MANY: Fast, isolated, cover edge cases\n  /────────────\\\n\nWhat E2E Tests Are For\nE2E Tests ✓\tNOT E2E Tests ✗\nCritical user journeys (login → dashboard → action → logout)\tUnit-level logic (use unit tests)\nMulti-step flows (checkout, onboarding wizard)\tAPI contracts (use integration tests)\nCross-browser compatibility\tEdge cases (too slow, use unit tests)\nReal API integration\tInternal implementation details\nAuthentication flows\tComponent visual states (use Storybook)\n\nRule of thumb: If it would devastate your business to break, E2E test it. If it's just inconvenient, test it faster with unit/integration tests.\n\nCore Principles\nPrinciple\tWhy\tHow\nTest behavior, not implementation\tSurvives refactors\tAssert on user-visible outcomes, not DOM structure\nIndependent tests\tParallelizable, debuggable\tEach test creates its own data, cleans up after\nDeterministic waits\tNo flakiness\tWait for conditions, not fixed timeouts\nStable selectors\tSurvives UI changes\tUse data-testid, roles, labels — never CSS classes\nFast feedback\tDevelopers run them\tMock external services, parallelize, shard\nPlaywright Patterns\nConfiguration\n// playwright.config.ts\nimport { defineConfig, devices } from \"@playwright/test\";\n\nexport default defineConfig({\n  testDir: \"./e2e\",\n  timeout: 30000,\n  expect: { timeout: 5000 },\n  fullyParallel: true,\n  forbidOnly: !!process.env.CI,\n  retries: process.env.CI ? 2 : 0,\n  workers: process.env.CI ? 1 : undefined,\n  reporter: [[\"html\"], [\"junit\", { outputFile: \"results.xml\" }]],\n  use: {\n    baseURL: \"http://localhost:3000\",\n    trace: \"on-first-retry\",\n    screenshot: \"only-on-failure\",\n    video: \"retain-on-failure\",\n  },\n  projects: [\n    { name: \"chromium\", use: { ...devices[\"Desktop Chrome\"] } },\n    { name: \"firefox\", use: { ...devices[\"Desktop Firefox\"] } },\n    { name: \"webkit\", use: { ...devices[\"Desktop Safari\"] } },\n    { name: \"mobile\", use: { ...devices[\"iPhone 13\"] } },\n  ],\n});\n\nPattern: Page Object Model\n\nEncapsulate page logic. Tests read like user stories.\n\n// pages/LoginPage.ts\nimport { Page, Locator } from \"@playwright/test\";\n\nexport class LoginPage {\n  readonly page: Page;\n  readonly emailInput: Locator;\n  readonly passwordInput: Locator;\n  readonly loginButton: Locator;\n  readonly errorMessage: Locator;\n\n  constructor(page: Page) {\n    this.page = page;\n    this.emailInput = page.getByLabel(\"Email\");\n    this.passwordInput = page.getByLabel(\"Password\");\n    this.loginButton = page.getByRole(\"button\", { name: \"Login\" });\n    this.errorMessage = page.getByRole(\"alert\");\n  }\n\n  async goto() {\n    await this.page.goto(\"/login\");\n  }\n\n  async login(email: string, password: string) {\n    await this.emailInput.fill(email);\n    await this.passwordInput.fill(password);\n    await this.loginButton.click();\n  }\n}\n\n// tests/login.spec.ts\nimport { test, expect } from \"@playwright/test\";\nimport { LoginPage } from \"../pages/LoginPage\";\n\ntest(\"successful login redirects to dashboard\", async ({ page }) => {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.login(\"user@example.com\", \"password123\");\n\n  await expect(page).toHaveURL(\"/dashboard\");\n  await expect(page.getByRole(\"heading\", { name: \"Dashboard\" })).toBeVisible();\n});\n\nPattern: Fixtures for Test Data\n\nCreate and clean up test data automatically.\n\n// fixtures/test-data.ts\nimport { test as base } from \"@playwright/test\";\n\nexport const test = base.extend<{ testUser: TestUser }>({\n  testUser: async ({}, use) => {\n    // Setup: Create user\n    const user = await createTestUser({\n      email: `test-${Date.now()}@example.com`,\n      password: \"Test123!@#\",\n    });\n\n    await use(user);\n\n    // Teardown: Clean up\n    await deleteTestUser(user.id);\n  },\n});\n\n// Usage — testUser is created before, deleted after\ntest(\"user can update profile\", async ({ page, testUser }) => {\n  await page.goto(\"/login\");\n  await page.getByLabel(\"Email\").fill(testUser.email);\n  // ...\n});\n\nPattern: Smart Waiting\n\nNever use fixed timeouts. Wait for specific conditions.\n\n// ❌ FLAKY: Fixed timeout\nawait page.waitForTimeout(3000);\n\n// ✅ STABLE: Wait for conditions\nawait page.waitForLoadState(\"networkidle\");\nawait page.waitForURL(\"/dashboard\");\n\n// ✅ BEST: Auto-waiting assertions\nawait expect(page.getByText(\"Welcome\")).toBeVisible();\nawait expect(page.getByRole(\"button\", { name: \"Submit\" })).toBeEnabled();\n\n// Wait for API response\nconst responsePromise = page.waitForResponse(\n  (r) => r.url().includes(\"/api/users\") && r.status() === 200\n);\nawait page.getByRole(\"button\", { name: \"Load\" }).click();\nawait responsePromise;\n\nPattern: Network Mocking\n\nIsolate tests from real external services.\n\ntest(\"shows error when API fails\", async ({ page }) => {\n  // Mock the API response\n  await page.route(\"**/api/users\", (route) => {\n    route.fulfill({\n      status: 500,\n      body: JSON.stringify({ error: \"Server Error\" }),\n    });\n  });\n\n  await page.goto(\"/users\");\n  await expect(page.getByText(\"Failed to load users\")).toBeVisible();\n});\n\ntest(\"handles slow network gracefully\", async ({ page }) => {\n  await page.route(\"**/api/data\", async (route) => {\n    await new Promise((r) => setTimeout(r, 3000)); // Simulate delay\n    await route.continue();\n  });\n\n  await page.goto(\"/dashboard\");\n  await expect(page.getByText(\"Loading...\")).toBeVisible();\n});\n\nCypress Patterns\nCustom Commands\n// cypress/support/commands.ts\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      login(email: string, password: string): Chainable<void>;\n      dataCy(value: string): Chainable<JQuery<HTMLElement>>;\n    }\n  }\n}\n\nCypress.Commands.add(\"login\", (email, password) => {\n  cy.visit(\"/login\");\n  cy.get('[data-testid=\"email\"]').type(email);\n  cy.get('[data-testid=\"password\"]').type(password);\n  cy.get('[data-testid=\"login-button\"]').click();\n  cy.url().should(\"include\", \"/dashboard\");\n});\n\nCypress.Commands.add(\"dataCy\", (value) => {\n  return cy.get(`[data-cy=\"${value}\"]`);\n});\n\n// Usage\ncy.login(\"user@example.com\", \"password\");\ncy.dataCy(\"submit-button\").click();\n\nNetwork Intercepts\n// Mock API\ncy.intercept(\"GET\", \"/api/users\", {\n  statusCode: 200,\n  body: [{ id: 1, name: \"John\" }],\n}).as(\"getUsers\");\n\ncy.visit(\"/users\");\ncy.wait(\"@getUsers\");\ncy.get('[data-testid=\"user-list\"]').children().should(\"have.length\", 1);\n\nSelector Strategy\nPriority\tSelector Type\tExample\tWhy\n1\tRole + name\tgetByRole(\"button\", { name: \"Submit\" })\tAccessible, user-facing\n2\tLabel\tgetByLabel(\"Email address\")\tAccessible, semantic\n3\tdata-testid\tgetByTestId(\"checkout-form\")\tStable, explicit for testing\n4\tText content\tgetByText(\"Welcome back\")\tUser-facing\n❌\tCSS classes\t.btn-primary\tBreaks on styling changes\n❌\tDOM structure\tdiv > form > input:nth-child(2)\tBreaks on any restructure\n// ❌ BAD: Brittle selectors\ncy.get(\".btn.btn-primary.submit-button\").click();\ncy.get(\"div > form > div:nth-child(2) > input\").type(\"text\");\n\n// ✅ GOOD: Stable selectors\npage.getByRole(\"button\", { name: \"Submit\" }).click();\npage.getByLabel(\"Email address\").fill(\"user@example.com\");\npage.getByTestId(\"email-input\").fill(\"user@example.com\");\n\nVisual Regression Testing\n// Playwright visual comparisons\ntest(\"homepage looks correct\", async ({ page }) => {\n  await page.goto(\"/\");\n  await expect(page).toHaveScreenshot(\"homepage.png\", {\n    fullPage: true,\n    maxDiffPixels: 100,\n  });\n});\n\ntest(\"button states\", async ({ page }) => {\n  const button = page.getByRole(\"button\", { name: \"Submit\" });\n\n  await expect(button).toHaveScreenshot(\"button-default.png\");\n\n  await button.hover();\n  await expect(button).toHaveScreenshot(\"button-hover.png\");\n});\n\nAccessibility Testing\n// npm install @axe-core/playwright\nimport AxeBuilder from \"@axe-core/playwright\";\n\ntest(\"page has no accessibility violations\", async ({ page }) => {\n  await page.goto(\"/\");\n\n  const results = await new AxeBuilder({ page })\n    .exclude(\"#third-party-widget\")  // Exclude things you can't control\n    .analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\nDebugging Failed Tests\n# Run in headed mode (see the browser)\nnpx playwright test --headed\n\n# Debug mode (step through)\nnpx playwright test --debug\n\n# Show trace viewer for failed tests\nnpx playwright show-report\n\n// Add test steps for better failure reports\ntest(\"checkout flow\", async ({ page }) => {\n  await test.step(\"Add item to cart\", async () => {\n    await page.goto(\"/products\");\n    await page.getByRole(\"button\", { name: \"Add to Cart\" }).click();\n  });\n\n  await test.step(\"Complete checkout\", async () => {\n    await page.goto(\"/checkout\");\n    // ... if this fails, you know which step\n  });\n});\n\n// Pause for manual inspection\nawait page.pause();\n\nFlaky Test Checklist\n\nWhen a test fails intermittently, check:\n\nIssue\tFix\nFixed waitForTimeout() calls\tReplace with waitForSelector() or expect assertions\nRace conditions on page load\tWait for networkidle or specific elements\nTest data pollution\tEnsure tests create/clean their own data\nAnimation timing\tWait for animations to complete or disable them\nViewport inconsistency\tSet explicit viewport in config\nRandom test order issues\tTests must be independent\nThird-party service flakiness\tMock external APIs\nCI/CD Integration\n# GitHub Actions example\nname: E2E Tests\non: [push, pull_request]\n\njobs:\n  e2e:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm ci\n      - run: npx playwright install --with-deps\n      - run: npm run build\n      - run: npm run start & npx wait-on http://localhost:3000\n      - run: npx playwright test\n      - uses: actions/upload-artifact@v4\n        if: failure()\n        with:\n          name: playwright-report\n          path: playwright-report/\n\nNEVER Do\nNEVER use fixed waitForTimeout() or cy.wait(ms) — they cause flaky tests and slow down suites\nNEVER rely on CSS classes or DOM structure for selectors — use roles, labels, or data-testid\nNEVER share state between tests — each test must be completely independent\nNEVER test implementation details — test what users see and do, not internal structure\nNEVER skip cleanup — always delete test data you created, even on failure\nNEVER test everything with E2E — reserve for critical paths; use faster tests for edge cases\nNEVER ignore flaky tests — fix them immediately or delete them; a flaky test is worse than no test\nNEVER hardcode test data in selectors — use dynamic waits for content that varies\nQuick Reference\nPlaywright Commands\n// Navigation\nawait page.goto(\"/path\");\nawait page.goBack();\nawait page.reload();\n\n// Interactions\nawait page.click(\"selector\");\nawait page.fill(\"selector\", \"text\");\nawait page.type(\"selector\", \"text\");  // Types character by character\nawait page.selectOption(\"select\", \"value\");\nawait page.check(\"checkbox\");\n\n// Assertions\nawait expect(page).toHaveURL(\"/expected\");\nawait expect(locator).toBeVisible();\nawait expect(locator).toHaveText(\"expected\");\nawait expect(locator).toBeEnabled();\nawait expect(locator).toHaveCount(3);\n\nCypress Commands\n// Navigation\ncy.visit(\"/path\");\ncy.go(\"back\");\ncy.reload();\n\n// Interactions\ncy.get(\"selector\").click();\ncy.get(\"selector\").type(\"text\");\ncy.get(\"selector\").clear().type(\"text\");\ncy.get(\"select\").select(\"value\");\ncy.get(\"checkbox\").check();\n\n// Assertions\ncy.url().should(\"include\", \"/expected\");\ncy.get(\"selector\").should(\"be.visible\");\ncy.get(\"selector\").should(\"have.text\", \"expected\");\ncy.get(\"selector\").should(\"have.length\", 3);"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/wpank/e2e-testing-patterns",
    "publisherUrl": "https://clawhub.ai/wpank/e2e-testing-patterns",
    "owner": "wpank",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/e2e-testing-patterns",
    "downloadUrl": "https://openagent3.xyz/downloads/e2e-testing-patterns",
    "agentUrl": "https://openagent3.xyz/skills/e2e-testing-patterns/agent",
    "manifestUrl": "https://openagent3.xyz/skills/e2e-testing-patterns/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/e2e-testing-patterns/agent.md"
  }
}