[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6ENDMYRHCH4TPJMBPWC9Y98":3,"skill-same:01M6ENDMYRHCH4TPJMBPWC9Y98":121},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":66,"commitSha":67,"skillDirectory":68,"forksCount":69,"repositoryHtmlUrl":70,"repositoryHomepageUrl":71,"ownerHtmlUrl":72,"ownerBlogUrl":6,"ownerDescription":73,"updatedAt":74,"install":75,"resources":88},"01M6ENDMYRHCH4TPJMBPWC9Y98","playwright-testing",null,"Automatically activated when user works with Playwright tests, mentions Playwright configuration, asks about selectors\u002Flocators\u002Fpage objects, or has files matching *.spec.ts in e2e or tests directories. Provides Playwright-specific expertise for E2E and integration testing.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"playwright","playwright testing","testing","files","mocking","integration testing","tests","creation","fixture","objects","patterns","selection","debugging","read","page","Bash","api","e2e","network","browser","2026-11-10 10:12:30",false,"---\nname: playwright-testing\ndescription: Automatically activated when user works with Playwright tests, mentions Playwright configuration, asks about selectors\u002Flocators\u002Fpage objects, or has files matching *.spec.ts in e2e or tests directories. Provides Playwright-specific expertise for E2E and integration testing.\nversion: 1.1.0\nallowed-tools: Read, Grep, Glob, Bash\ncapabilities:\n  - playwright-configuration\n  - locator-selection\n  - page-object-patterns\n  - fixture-creation\n  - network-mocking\n  - accessibility-testing\n  - debugging-traces\n  - api-testing\n  - cross-browser-testing\n---\n\n# Playwright Testing Expertise\n\nYou are an expert in Playwright testing framework with deep knowledge of browser automation, selectors, page objects, and best practices for end-to-end testing.\n\n## Your Capabilities\n\n1. **Playwright Configuration**: Projects, browsers, reporters, and fixtures\n2. **Locators & Selectors**: Role-based, text, CSS, and chained locators\n3. **Page Object Model**: Organizing tests with page objects\n4. **Assertions**: Built-in assertions, custom matchers, auto-waiting\n5. **Test Fixtures**: Built-in and custom fixtures, test isolation\n6. **Debugging**: Traces, screenshots, videos, and Playwright Inspector\n7. **API Testing**: Request fixtures and API testing capabilities\n\n## When to Use This Skill\n\nClaude should automatically invoke this skill when:\n- The user mentions Playwright, playwright.config, or Playwright features\n- Files matching `*.spec.ts` in e2e, tests, or playwright directories are encountered\n- The user asks about locators, page objects, or browser automation\n- E2E or integration testing is discussed\n- Browser testing configuration is needed\n\n## How to Use This Skill\n\n### Accessing Resources\n\nUse `{baseDir}` to reference files in this skill directory:\n- Scripts: `{baseDir}\u002Fscripts\u002F`\n- Documentation: `{baseDir}\u002Freferences\u002F`\n- Templates: `{baseDir}\u002Fassets\u002F`\n\n## Available Resources\n\nThis skill includes ready-to-use resources in `{baseDir}`:\n\n- **references\u002Fplaywright-cheatsheet.md** - Quick reference for locators, assertions, actions, and CLI commands\n- **assets\u002Fpage-object.template.ts** - Complete Page Object Model template with base class and examples\n- **scripts\u002Fcheck-playwright-setup.sh** - Validates Playwright configuration and browser installation\n\n## Playwright Best Practices\n\n### Test Structure\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest.describe('Contact Form', () => {\n  test.beforeEach(async ({ page }) => {\n    await page.goto('\u002Fcontact');\n  });\n\n  test('should show success message after form submission', async ({ page }) => {\n    \u002F\u002F Arrange\n    await page.getByLabel('Name').fill('Test User');\n    await page.getByLabel('Email').fill('test@example.com');\n    await page.getByLabel('Message').fill('Hello, this is a test message.');\n\n    \u002F\u002F Act\n    await page.getByRole('button', { name: 'Submit' }).click();\n\n    \u002F\u002F Assert\n    await expect(page.getByText('Thank you for your message')).toBeVisible();\n    await expect(page.getByLabel('Name')).toBeEmpty();\n  });\n});\n```\n\n### Locator Best Practices\n\n#### Preferred Locators (Most Resilient)\n```typescript\n\u002F\u002F Role-based (best)\npage.getByRole('button', { name: 'Submit' });\npage.getByRole('textbox', { name: 'Email' });\npage.getByRole('heading', { level: 1 });\n\n\u002F\u002F Label-based\npage.getByLabel('Email address');\npage.getByPlaceholder('Enter your email');\n\n\u002F\u002F Text-based\npage.getByText('Welcome');\npage.getByTitle('Close');\n```\n\n#### Chaining Locators\n```typescript\npage.getByRole('listitem')\n  .filter({ hasText: 'Product 1' })\n  .getByRole('button', { name: 'Add' });\n```\n\n#### Test IDs (Last Resort)\n```typescript\npage.getByTestId('submit-button');\n```\n\n### Page Object Pattern\n```typescript\n\u002F\u002F pages\u002Flogin.page.ts\nimport { Page, Locator, expect } from '@playwright\u002Ftest';\n\nexport class LoginPage {\n  private readonly page: Page;\n  readonly emailInput: Locator;\n  readonly passwordInput: Locator;\n  readonly submitButton: 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.submitButton = page.getByRole('button', { name: 'Sign in' });\n    this.errorMessage = page.getByRole('alert');\n  }\n\n  async goto() {\n    await this.page.goto('\u002Flogin');\n    await expect(this.emailInput).toBeVisible();\n  }\n\n  async login(email: string, password: string) {\n    await this.emailInput.fill(email);\n    await this.passwordInput.fill(password);\n    await this.submitButton.click();\n  }\n\n  async getError(): Promise\u003Cstring | null> {\n    if (await this.errorMessage.isVisible()) {\n      return this.errorMessage.textContent();\n    }\n    return null;\n  }\n}\n\n\u002F\u002F Usage in test\nimport { test, expect } from '@playwright\u002Ftest';\nimport { LoginPage } from '.\u002Fpages\u002Flogin.page';\n\ntest('should login successfully', async ({ page }) => {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.login('user@test.com', 'password');\n  await expect(page).toHaveURL('\u002Fdashboard');\n});\n\ntest('should show error for invalid credentials', async ({ page }) => {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.login('invalid@test.com', 'wrongpassword');\n  const error = await loginPage.getError();\n  expect(error).toContain('Invalid credentials');\n});\n```\n\n### Auto-Waiting & Assertions\n```typescript\n\u002F\u002F Auto-waits for element\nawait expect(page.getByRole('alert')).toBeVisible();\nawait expect(page.getByRole('button')).toBeEnabled();\nawait expect(page.getByText('Count: 5')).toBeVisible();\n\n\u002F\u002F Negative assertions\nawait expect(page.getByRole('dialog')).toBeHidden();\nawait expect(page.getByText('Error')).not.toBeVisible();\n\n\u002F\u002F With custom timeout\nawait expect(page.getByText('Loaded')).toBeVisible({ timeout: 10000 });\n```\n\n### Fixtures\n```typescript\n\u002F\u002F fixtures.ts\nimport { test as base } from '@playwright\u002Ftest';\n\nexport const test = base.extend\u003C{\n  authenticatedPage: Page;\n}>({\n  authenticatedPage: async ({ page }, use) => {\n    await page.goto('\u002Flogin');\n    await page.getByLabel('Email').fill('test@test.com');\n    await page.getByLabel('Password').fill('password');\n    await page.getByRole('button', { name: 'Login' }).click();\n    await page.waitForURL('\u002Fdashboard');\n    await use(page);\n  },\n});\n```\n\n### Storage State Authentication\n\nFor efficient authentication without UI login each time:\n\n```typescript\n\u002F\u002F Setup: Save auth state after login (run once)\n\u002F\u002F auth.setup.ts\nimport { test as setup, expect } from '@playwright\u002Ftest';\n\nsetup('authenticate', async ({ page }) => {\n  await page.goto('\u002Flogin');\n  await page.getByLabel('Email').fill('test@example.com');\n  await page.getByLabel('Password').fill('password');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await expect(page).toHaveURL('\u002Fdashboard');\n\n  \u002F\u002F Save storage state (cookies, localStorage)\n  await page.context().storageState({ path: '.auth\u002Fuser.json' });\n});\n\n\u002F\u002F playwright.config.ts\nexport default defineConfig({\n  projects: [\n    { name: 'setup', testMatch: \u002F.*\\.setup\\.ts\u002F },\n    {\n      name: 'chromium',\n      use: { storageState: '.auth\u002Fuser.json' },\n      dependencies: ['setup'],\n    },\n  ],\n});\n\n\u002F\u002F Tests automatically have auth state\ntest('dashboard loads for authenticated user', async ({ page }) => {\n  await page.goto('\u002Fdashboard');\n  await expect(page.getByText('Welcome back')).toBeVisible();\n});\n```\n\n### Network Mocking & Interception\n\nMock API responses for reliable, fast tests:\n\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest('should display mocked user data', async ({ page }) => {\n  \u002F\u002F Mock API response\n  await page.route('**\u002Fapi\u002Fusers', route => {\n    route.fulfill({\n      status: 200,\n      contentType: 'application\u002Fjson',\n      body: JSON.stringify([\n        { id: 1, name: 'Test User', email: 'test@example.com' }\n      ]),\n    });\n  });\n\n  await page.goto('\u002Fusers');\n  await expect(page.getByText('Test User')).toBeVisible();\n});\n\ntest('should handle API errors gracefully', async ({ page }) => {\n  \u002F\u002F Mock error response\n  await page.route('**\u002Fapi\u002Fusers', route => {\n    route.fulfill({\n      status: 500,\n      body: JSON.stringify({ error: 'Internal Server Error' }),\n    });\n  });\n\n  await page.goto('\u002Fusers');\n  await expect(page.getByText('Failed to load users')).toBeVisible();\n});\n\ntest('should handle network failure', async ({ page }) => {\n  \u002F\u002F Abort network request\n  await page.route('**\u002Fapi\u002Fdata', route => route.abort());\n\n  await page.goto('\u002Fdata');\n  await expect(page.getByText('Network error')).toBeVisible();\n});\n\ntest('should handle slow responses', async ({ page }) => {\n  \u002F\u002F Simulate slow API\n  await page.route('**\u002Fapi\u002Fslow', async route => {\n    await new Promise(resolve => setTimeout(resolve, 3000));\n    await route.continue();\n  });\n\n  await page.goto('\u002Fslow-page');\n  await expect(page.getByText('Loading...')).toBeVisible();\n});\n\n\u002F\u002F Modify request\u002Fresponse\ntest('should modify request headers', async ({ page }) => {\n  await page.route('**\u002Fapi\u002F**', route => {\n    route.continue({\n      headers: {\n        ...route.request().headers(),\n        'X-Test-Header': 'test-value',\n      },\n    });\n  });\n});\n```\n\n### Accessibility Testing\n\nIntegrate accessibility audits with @axe-core\u002Fplaywright:\n\n```typescript\n\u002F\u002F Install: npm install @axe-core\u002Fplaywright\nimport { test, expect } from '@playwright\u002Ftest';\nimport AxeBuilder from '@axe-core\u002Fplaywright';\n\ntest('should pass accessibility audit', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const results = await new AxeBuilder({ page }).analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\ntest('should pass accessibility audit for specific section', async ({ page }) => {\n  await page.goto('\u002Fdashboard');\n\n  const results = await new AxeBuilder({ page })\n    .include('#main-content')\n    .exclude('#third-party-widget')\n    .withTags(['wcag2a', 'wcag2aa'])\n    .analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\n\u002F\u002F Check specific rules\ntest('should have proper color contrast', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const results = await new AxeBuilder({ page })\n    .withRules(['color-contrast'])\n    .analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\n\u002F\u002F Detailed violation reporting\ntest('accessibility check with detailed report', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const results = await new AxeBuilder({ page }).analyze();\n\n  if (results.violations.length > 0) {\n    console.log('Accessibility violations:');\n    results.violations.forEach(violation => {\n      console.log(`- ${violation.id}: ${violation.description}`);\n      violation.nodes.forEach(node => {\n        console.log(`  Element: ${node.html}`);\n        console.log(`  Fix: ${node.failureSummary}`);\n      });\n    });\n  }\n\n  expect(results.violations).toEqual([]);\n});\n```\n\n### Visual Regression Testing\n\nCompare screenshots to detect visual changes:\n\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest('homepage visual regression', async ({ page }) => {\n  await page.goto('\u002F');\n\n  \u002F\u002F Full page screenshot comparison\n  await expect(page).toHaveScreenshot('homepage.png');\n});\n\ntest('component visual regression', async ({ page }) => {\n  await page.goto('\u002Fcomponents');\n\n  \u002F\u002F Element-specific screenshot\n  const button = page.getByRole('button', { name: 'Submit' });\n  await expect(button).toHaveScreenshot('submit-button.png');\n});\n\ntest('visual with threshold', async ({ page }) => {\n  await page.goto('\u002F');\n\n  \u002F\u002F Allow small differences\n  await expect(page).toHaveScreenshot('homepage.png', {\n    maxDiffPixels: 100,\n    threshold: 0.2,\n  });\n});\n\n\u002F\u002F Update snapshots: npx playwright test --update-snapshots\n```\n\n## Playwright Configuration\n\n### Basic Configuration\n```typescript\n\u002F\u002F playwright.config.ts\nimport { defineConfig, devices } from '@playwright\u002Ftest';\n\nexport default defineConfig({\n  testDir: '.\u002Fe2e',\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',\n  use: {\n    baseURL: 'http:\u002F\u002Flocalhost:3000',\n    trace: 'on-first-retry',\n    screenshot: 'only-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  webServer: {\n    command: 'npm run start',\n    url: 'http:\u002F\u002Flocalhost:3000',\n    reuseExistingServer: !process.env.CI,\n  },\n});\n```\n\n## Debugging & Troubleshooting\n\n### Debug Mode\n```bash\nnpx playwright test --debug\nnpx playwright test --ui\n```\n\n### Traces\n```typescript\n\u002F\u002F Capture trace on failure\nuse: {\n  trace: 'on-first-retry',\n}\n\n\u002F\u002F View trace\nnpx playwright show-trace trace.zip\n```\n\n### Screenshots\n```typescript\nawait page.screenshot({ path: 'screenshot.png', fullPage: true });\n```\n\n## Common Issues & Solutions\n\n### Issue: Flaky tests\n- Use auto-waiting assertions instead of fixed waits\n- Wait for specific elements instead of `networkidle` (which fails with WebSockets, long-polling, analytics):\n  ```typescript\n  \u002F\u002F Bad: networkidle is unreliable\n  await page.waitForLoadState('networkidle');\n\n  \u002F\u002F Good: wait for specific content\n  await expect(page.getByRole('main')).toBeVisible();\n  await expect(page.getByTestId('data-loaded')).toBeAttached();\n  ```\n- Ensure proper test isolation\n\n### Issue: Elements not found\n- Use Playwright Inspector to find better locators\n- Prefer role-based selectors\n- Check for iframes or shadow DOM\n\n### Issue: Slow tests\n- Reuse authentication state\n- Use `test.describe.parallel()`\n- Mock slow API calls\n\n## Examples\n\n### Example 1: Form Testing\nWhen testing forms:\n1. Use getByLabel for inputs\n2. Use fill() instead of type() for speed\n3. Submit with button locator\n4. Assert on success message or navigation\n\n### Example 2: Table Testing\nWhen testing tables\u002Flists:\n1. Use getByRole('row') for table rows\n2. Filter by content with `.filter()`\n3. Chain to find actions within rows\n4. Assert on row count or content\n\n## Version Compatibility\n\nThe patterns in this skill require the following minimum versions:\n\n| Feature | Minimum Version | Notes |\n|---------|----------------|-------|\n| getByRole with name | 1.27+ | Role-based locators with accessible name |\n| toHaveScreenshot | 1.22+ | Visual regression testing |\n| storageState | 1.13+ | Authentication state persistence |\n| @axe-core\u002Fplaywright | 4.7+ | Accessibility testing integration |\n| route.fulfill | 1.0+ | Network mocking (stable) |\n| test.describe.configure | 1.24+ | Parallel\u002Fserial test configuration |\n\n### Feature Detection\n\nCheck your Playwright version:\n```bash\nnpx playwright --version\n```\n\n### Upgrading\n\n```bash\n# Update Playwright\nnpm install -D @playwright\u002Ftest@latest\n\n# Update browsers\nnpx playwright install\n```\n\n## Important Notes\n\n- Playwright is automatically invoked when relevant\n- Always check playwright.config.ts for project settings\n- Prefer role-based locators for resilient tests\n- Use auto-waiting assertions instead of explicit waits\n- Consider mobile and cross-browser testing in CI\n","# Playwright Testing Expertise\n\nYou are an expert in Playwright testing framework with deep knowledge of browser automation, selectors, page objects, and best practices for end-to-end testing.\n\n## Your Capabilities\n\n1. **Playwright Configuration**: Projects, browsers, reporters, and fixtures\n2. **Locators & Selectors**: Role-based, text, CSS, and chained locators\n3. **Page Object Model**: Organizing tests with page objects\n4. **Assertions**: Built-in assertions, custom matchers, auto-waiting\n5. **Test Fixtures**: Built-in and custom fixtures, test isolation\n6. **Debugging**: Traces, screenshots, videos, and Playwright Inspector\n7. **API Testing**: Request fixtures and API testing capabilities\n\n## When to Use This Skill\n\nClaude should automatically invoke this skill when:\n- The user mentions Playwright, playwright.config, or Playwright features\n- Files matching `*.spec.ts` in e2e, tests, or playwright directories are encountered\n- The user asks about locators, page objects, or browser automation\n- E2E or integration testing is discussed\n- Browser testing configuration is needed\n\n## How to Use This Skill\n\n### Accessing Resources\n\nUse `{baseDir}` to reference files in this skill directory:\n- Scripts: `{baseDir}\u002Fscripts\u002F`\n- Documentation: `{baseDir}\u002Freferences\u002F`\n- Templates: `{baseDir}\u002Fassets\u002F`\n\n## Available Resources\n\nThis skill includes ready-to-use resources in `{baseDir}`:\n\n- **references\u002Fplaywright-cheatsheet.md** - Quick reference for locators, assertions, actions, and CLI commands\n- **assets\u002Fpage-object.template.ts** - Complete Page Object Model template with base class and examples\n- **scripts\u002Fcheck-playwright-setup.sh** - Validates Playwright configuration and browser installation\n\n## Playwright Best Practices\n\n### Test Structure\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest.describe('Contact Form', () => {\n  test.beforeEach(async ({ page }) => {\n    await page.goto('\u002Fcontact');\n  });\n\n  test('should show success message after form submission', async ({ page }) => {\n    \u002F\u002F Arrange\n    await page.getByLabel('Name').fill('Test User');\n    await page.getByLabel('Email').fill('test@example.com');\n    await page.getByLabel('Message').fill('Hello, this is a test message.');\n\n    \u002F\u002F Act\n    await page.getByRole('button', { name: 'Submit' }).click();\n\n    \u002F\u002F Assert\n    await expect(page.getByText('Thank you for your message')).toBeVisible();\n    await expect(page.getByLabel('Name')).toBeEmpty();\n  });\n});\n```\n\n### Locator Best Practices\n\n#### Preferred Locators (Most Resilient)\n```typescript\n\u002F\u002F Role-based (best)\npage.getByRole('button', { name: 'Submit' });\npage.getByRole('textbox', { name: 'Email' });\npage.getByRole('heading', { level: 1 });\n\n\u002F\u002F Label-based\npage.getByLabel('Email address');\npage.getByPlaceholder('Enter your email');\n\n\u002F\u002F Text-based\npage.getByText('Welcome');\npage.getByTitle('Close');\n```\n\n#### Chaining Locators\n```typescript\npage.getByRole('listitem')\n  .filter({ hasText: 'Product 1' })\n  .getByRole('button', { name: 'Add' });\n```\n\n#### Test IDs (Last Resort)\n```typescript\npage.getByTestId('submit-button');\n```\n\n### Page Object Pattern\n```typescript\n\u002F\u002F pages\u002Flogin.page.ts\nimport { Page, Locator, expect } from '@playwright\u002Ftest';\n\nexport class LoginPage {\n  private readonly page: Page;\n  readonly emailInput: Locator;\n  readonly passwordInput: Locator;\n  readonly submitButton: 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.submitButton = page.getByRole('button', { name: 'Sign in' });\n    this.errorMessage = page.getByRole('alert');\n  }\n\n  async goto() {\n    await this.page.goto('\u002Flogin');\n    await expect(this.emailInput).toBeVisible();\n  }\n\n  async login(email: string, password: string) {\n    await this.emailInput.fill(email);\n    await this.passwordInput.fill(password);\n    await this.submitButton.click();\n  }\n\n  async getError(): Promise\u003Cstring | null> {\n    if (await this.errorMessage.isVisible()) {\n      return this.errorMessage.textContent();\n    }\n    return null;\n  }\n}\n\n\u002F\u002F Usage in test\nimport { test, expect } from '@playwright\u002Ftest';\nimport { LoginPage } from '.\u002Fpages\u002Flogin.page';\n\ntest('should login successfully', async ({ page }) => {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.login('user@test.com', 'password');\n  await expect(page).toHaveURL('\u002Fdashboard');\n});\n\ntest('should show error for invalid credentials', async ({ page }) => {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.login('invalid@test.com', 'wrongpassword');\n  const error = await loginPage.getError();\n  expect(error).toContain('Invalid credentials');\n});\n```\n\n### Auto-Waiting & Assertions\n```typescript\n\u002F\u002F Auto-waits for element\nawait expect(page.getByRole('alert')).toBeVisible();\nawait expect(page.getByRole('button')).toBeEnabled();\nawait expect(page.getByText('Count: 5')).toBeVisible();\n\n\u002F\u002F Negative assertions\nawait expect(page.getByRole('dialog')).toBeHidden();\nawait expect(page.getByText('Error')).not.toBeVisible();\n\n\u002F\u002F With custom timeout\nawait expect(page.getByText('Loaded')).toBeVisible({ timeout: 10000 });\n```\n\n### Fixtures\n```typescript\n\u002F\u002F fixtures.ts\nimport { test as base } from '@playwright\u002Ftest';\n\nexport const test = base.extend\u003C{\n  authenticatedPage: Page;\n}>({\n  authenticatedPage: async ({ page }, use) => {\n    await page.goto('\u002Flogin');\n    await page.getByLabel('Email').fill('test@test.com');\n    await page.getByLabel('Password').fill('password');\n    await page.getByRole('button', { name: 'Login' }).click();\n    await page.waitForURL('\u002Fdashboard');\n    await use(page);\n  },\n});\n```\n\n### Storage State Authentication\n\nFor efficient authentication without UI login each time:\n\n```typescript\n\u002F\u002F Setup: Save auth state after login (run once)\n\u002F\u002F auth.setup.ts\nimport { test as setup, expect } from '@playwright\u002Ftest';\n\nsetup('authenticate', async ({ page }) => {\n  await page.goto('\u002Flogin');\n  await page.getByLabel('Email').fill('test@example.com');\n  await page.getByLabel('Password').fill('password');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await expect(page).toHaveURL('\u002Fdashboard');\n\n  \u002F\u002F Save storage state (cookies, localStorage)\n  await page.context().storageState({ path: '.auth\u002Fuser.json' });\n});\n\n\u002F\u002F playwright.config.ts\nexport default defineConfig({\n  projects: [\n    { name: 'setup', testMatch: \u002F.*\\.setup\\.ts\u002F },\n    {\n      name: 'chromium',\n      use: { storageState: '.auth\u002Fuser.json' },\n      dependencies: ['setup'],\n    },\n  ],\n});\n\n\u002F\u002F Tests automatically have auth state\ntest('dashboard loads for authenticated user', async ({ page }) => {\n  await page.goto('\u002Fdashboard');\n  await expect(page.getByText('Welcome back')).toBeVisible();\n});\n```\n\n### Network Mocking & Interception\n\nMock API responses for reliable, fast tests:\n\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest('should display mocked user data', async ({ page }) => {\n  \u002F\u002F Mock API response\n  await page.route('**\u002Fapi\u002Fusers', route => {\n    route.fulfill({\n      status: 200,\n      contentType: 'application\u002Fjson',\n      body: JSON.stringify([\n        { id: 1, name: 'Test User', email: 'test@example.com' }\n      ]),\n    });\n  });\n\n  await page.goto('\u002Fusers');\n  await expect(page.getByText('Test User')).toBeVisible();\n});\n\ntest('should handle API errors gracefully', async ({ page }) => {\n  \u002F\u002F Mock error response\n  await page.route('**\u002Fapi\u002Fusers', route => {\n    route.fulfill({\n      status: 500,\n      body: JSON.stringify({ error: 'Internal Server Error' }),\n    });\n  });\n\n  await page.goto('\u002Fusers');\n  await expect(page.getByText('Failed to load users')).toBeVisible();\n});\n\ntest('should handle network failure', async ({ page }) => {\n  \u002F\u002F Abort network request\n  await page.route('**\u002Fapi\u002Fdata', route => route.abort());\n\n  await page.goto('\u002Fdata');\n  await expect(page.getByText('Network error')).toBeVisible();\n});\n\ntest('should handle slow responses', async ({ page }) => {\n  \u002F\u002F Simulate slow API\n  await page.route('**\u002Fapi\u002Fslow', async route => {\n    await new Promise(resolve => setTimeout(resolve, 3000));\n    await route.continue();\n  });\n\n  await page.goto('\u002Fslow-page');\n  await expect(page.getByText('Loading...')).toBeVisible();\n});\n\n\u002F\u002F Modify request\u002Fresponse\ntest('should modify request headers', async ({ page }) => {\n  await page.route('**\u002Fapi\u002F**', route => {\n    route.continue({\n      headers: {\n        ...route.request().headers(),\n        'X-Test-Header': 'test-value',\n      },\n    });\n  });\n});\n```\n\n### Accessibility Testing\n\nIntegrate accessibility audits with @axe-core\u002Fplaywright:\n\n```typescript\n\u002F\u002F Install: npm install @axe-core\u002Fplaywright\nimport { test, expect } from '@playwright\u002Ftest';\nimport AxeBuilder from '@axe-core\u002Fplaywright';\n\ntest('should pass accessibility audit', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const results = await new AxeBuilder({ page }).analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\ntest('should pass accessibility audit for specific section', async ({ page }) => {\n  await page.goto('\u002Fdashboard');\n\n  const results = await new AxeBuilder({ page })\n    .include('#main-content')\n    .exclude('#third-party-widget')\n    .withTags(['wcag2a', 'wcag2aa'])\n    .analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\n\u002F\u002F Check specific rules\ntest('should have proper color contrast', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const results = await new AxeBuilder({ page })\n    .withRules(['color-contrast'])\n    .analyze();\n\n  expect(results.violations).toEqual([]);\n});\n\n\u002F\u002F Detailed violation reporting\ntest('accessibility check with detailed report', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const results = await new AxeBuilder({ page }).analyze();\n\n  if (results.violations.length > 0) {\n    console.log('Accessibility violations:');\n    results.violations.forEach(violation => {\n      console.log(`- ${violation.id}: ${violation.description}`);\n      violation.nodes.forEach(node => {\n        console.log(`  Element: ${node.html}`);\n        console.log(`  Fix: ${node.failureSummary}`);\n      });\n    });\n  }\n\n  expect(results.violations).toEqual([]);\n});\n```\n\n### Visual Regression Testing\n\nCompare screenshots to detect visual changes:\n\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest('homepage visual regression', async ({ page }) => {\n  await page.goto('\u002F');\n\n  \u002F\u002F Full page screenshot comparison\n  await expect(page).toHaveScreenshot('homepage.png');\n});\n\ntest('component visual regression', async ({ page }) => {\n  await page.goto('\u002Fcomponents');\n\n  \u002F\u002F Element-specific screenshot\n  const button = page.getByRole('button', { name: 'Submit' });\n  await expect(button).toHaveScreenshot('submit-button.png');\n});\n\ntest('visual with threshold', async ({ page }) => {\n  await page.goto('\u002F');\n\n  \u002F\u002F Allow small differences\n  await expect(page).toHaveScreenshot('homepage.png', {\n    maxDiffPixels: 100,\n    threshold: 0.2,\n  });\n});\n\n\u002F\u002F Update snapshots: npx playwright test --update-snapshots\n```\n\n## Playwright Configuration\n\n### Basic Configuration\n```typescript\n\u002F\u002F playwright.config.ts\nimport { defineConfig, devices } from '@playwright\u002Ftest';\n\nexport default defineConfig({\n  testDir: '.\u002Fe2e',\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',\n  use: {\n    baseURL: 'http:\u002F\u002Flocalhost:3000',\n    trace: 'on-first-retry',\n    screenshot: 'only-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  webServer: {\n    command: 'npm run start',\n    url: 'http:\u002F\u002Flocalhost:3000',\n    reuseExistingServer: !process.env.CI,\n  },\n});\n```\n\n## Debugging & Troubleshooting\n\n### Debug Mode\n```bash\nnpx playwright test --debug\nnpx playwright test --ui\n```\n\n### Traces\n```typescript\n\u002F\u002F Capture trace on failure\nuse: {\n  trace: 'on-first-retry',\n}\n\n\u002F\u002F View trace\nnpx playwright show-trace trace.zip\n```\n\n### Screenshots\n```typescript\nawait page.screenshot({ path: 'screenshot.png', fullPage: true });\n```\n\n## Common Issues & Solutions\n\n### Issue: Flaky tests\n- Use auto-waiting assertions instead of fixed waits\n- Wait for specific elements instead of `networkidle` (which fails with WebSockets, long-polling, analytics):\n  ```typescript\n  \u002F\u002F Bad: networkidle is unreliable\n  await page.waitForLoadState('networkidle');\n\n  \u002F\u002F Good: wait for specific content\n  await expect(page.getByRole('main')).toBeVisible();\n  await expect(page.getByTestId('data-loaded')).toBeAttached();\n  ```\n- Ensure proper test isolation\n\n### Issue: Elements not found\n- Use Playwright Inspector to find better locators\n- Prefer role-based selectors\n- Check for iframes or shadow DOM\n\n### Issue: Slow tests\n- Reuse authentication state\n- Use `test.describe.parallel()`\n- Mock slow API calls\n\n## Examples\n\n### Example 1: Form Testing\nWhen testing forms:\n1. Use getByLabel for inputs\n2. Use fill() instead of type() for speed\n3. Submit with button locator\n4. Assert on success message or navigation\n\n### Example 2: Table Testing\nWhen testing tables\u002Flists:\n1. Use getByRole('row') for table rows\n2. Filter by content with `.filter()`\n3. Chain to find actions within rows\n4. Assert on row count or content\n\n## Version Compatibility\n\nThe patterns in this skill require the following minimum versions:\n\n| Feature | Minimum Version | Notes |\n|---------|----------------|-------|\n| getByRole with name | 1.27+ | Role-based locators with accessible name |\n| toHaveScreenshot | 1.22+ | Visual regression testing |\n| storageState | 1.13+ | Authentication state persistence |\n| @axe-core\u002Fplaywright | 4.7+ | Accessibility testing integration |\n| route.fulfill | 1.0+ | Network mocking (stable) |\n| test.describe.configure | 1.24+ | Parallel\u002Fserial test configuration |\n\n### Feature Detection\n\nCheck your Playwright version:\n```bash\nnpx playwright --version\n```\n\n### Upgrading\n\n```bash\n# Update Playwright\nnpm install -D @playwright\u002Ftest@latest\n\n# Update browsers\nnpx playwright install\n```\n\n## Important Notes\n\n- Playwright is automatically invoked when relevant\n- Always check playwright.config.ts for project settings\n- Prefer role-based locators for resilient tests\n- Use auto-waiting assertions instead of explicit waits\n- Consider mobile and cross-browser testing in CI\n",{"name":5,"version":54,"description":7,"capabilities":55,"allowed-tools":65},"1.1.0",[56,57,58,59,60,61,62,63,64],"playwright-configuration","locator-selection","page-object-patterns","fixture-creation","network-mocking","accessibility-testing","debugging-traces","api-testing","cross-browser-testing","Read, Grep, Glob, Bash","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":76,"prompt":86,"zipAvailable":87},[77,80,83],{"tool":78,"command":79},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill playwright-testing",{"tool":81,"command":82},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill playwright-testing",{"tool":84,"command":85},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill playwright-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002FSKILL.md",true,[89,95,101,105,109,113,117],{"path":90,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":93,"sourceHtmlUrl":94},"assets\u002Fpage-object.template.ts","SCRIPT","脚本",5496,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002Fassets\u002Fpage-object.template.ts",{"path":96,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":99,"sourceHtmlUrl":100},"references\u002Fax-profiles.md","REFERENCE","参考文档",4469,"https:\u002F\u002Fgithub.com\u002FTorchiaHub\u002FSkills\u002Fblob\u002Fmaster\u002F01-Inbox\u002Fdownloads\u002Fgithub-awesome-copilot\u002F20260429T123413Z\u002Fsource\u002Fskills\u002Farize-prompt-optimization\u002Freferences\u002Fax-profiles.md",{"path":102,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":103,"sourceHtmlUrl":104},"references\u002Fax-setup.md",1527,"https:\u002F\u002Fgithub.com\u002FTorchiaHub\u002FSkills\u002Fblob\u002Fmaster\u002F01-Inbox\u002Fdownloads\u002Fgithub-awesome-copilot\u002F20260429T123413Z\u002Fsource\u002Fskills\u002Farize-prompt-optimization\u002Freferences\u002Fax-setup.md",{"path":106,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":107,"sourceHtmlUrl":108},"references\u002Fplaywright-cheatsheet.md",5627,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002Freferences\u002Fplaywright-cheatsheet.md",{"path":110,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":111,"sourceHtmlUrl":112},"REVIEW.md",9332,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002FREVIEW.md",{"path":114,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":115,"sourceHtmlUrl":116},"scripts\u002Fcheck-playwright-setup.sh",5021,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002Fscripts\u002Fcheck-playwright-setup.sh",{"path":118,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":119,"sourceHtmlUrl":120},"skill-report.json",11821,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fc0ntr0lledcha0s\u002Fplaywright-testing\u002Fskill-report.json",[122,134,144,153,159,166],{"skillNo":123,"name":124,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":130,"publishedAt":49},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[127],{"categoryNo":128,"categoryName":129,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[131,132,133],"model","model hierarchy","model hierarchy skill",{"skillNo":135,"name":136,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":137,"categories":138,"keywords":140,"publishedAt":49},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[139],{"categoryNo":128,"categoryName":129,"taxonomyType":21,"taxonomyTypeDesc":22},[141,142,143],"MCP","zoom","zoom mcp",{"skillNo":145,"name":146,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":149,"publishedAt":49},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[150,151,152,142],"sdk","zoom apps sdk","apps",{"skillNo":154,"name":155,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":158,"publishedAt":49},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[155],{"skillNo":160,"name":161,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":162,"categories":163,"keywords":165,"publishedAt":49},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[164],{"categoryNo":128,"categoryName":129,"taxonomyType":21,"taxonomyTypeDesc":22},[161],{"skillNo":167,"name":168,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":49},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[168]]