[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EFVRSN3QWN7ZWX88XSP7WX":3,"skill-same:01M6EFVRSN3QWN7ZWX88XSP7WX":53},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":26,"linkOnly":27,"markdownContent":28,"markdownBody":28,"frontMatter":29,"refName":30,"commitSha":31,"skillDirectory":32,"forksCount":33,"repositoryHtmlUrl":34,"repositoryHomepageUrl":35,"ownerHtmlUrl":36,"ownerBlogUrl":6,"ownerDescription":37,"updatedAt":38,"install":39,"resources":52},"01M6EFVRSN3QWN7ZWX88XSP7WX","playwright-testing",null,"ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FTerminalSkills\u002Fskills\u002Fplaywright-testing\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[23,24,25],"testing","playwright","playwright testing","2026-11-10 10:12:30",false,"# Playwright Testing\n\n## Overview\n\nThis skill helps AI agents write reliable end-to-end tests using Playwright. It covers project setup, writing tests with auto-waiting locators, page object patterns, authentication handling, API mocking, visual regression, accessibility testing, and CI\u002FCD integration.\n\n## Instructions\n\n### Step 1: Project Setup\n\n```bash\nnpm init playwright@latest\n# Or add to existing project:\nnpm install -D @playwright\u002Ftest && npx playwright install\n```\n\n```typescript\n\u002F\u002F playwright.config.ts\nimport { defineConfig, devices } from '@playwright\u002Ftest';\n\nexport default defineConfig({\n  testDir: '.\u002Ftests',\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', { open: 'never' }], ['junit', { outputFile: 'test-results\u002Fjunit.xml' }]],\n  use: {\n    baseURL: process.env.BASE_URL || '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-chrome', use: { ...devices['Pixel 5'] } },\n  ],\n  webServer: {\n    command: 'npm run dev',\n    url: 'http:\u002F\u002Flocalhost:3000',\n    reuseExistingServer: !process.env.CI,\n  },\n});\n```\n\n### Step 2: Write Tests\n\n```typescript\nimport { test, expect } from '@playwright\u002Ftest';\n\ntest.describe('Homepage', () => {\n  test('displays hero and navigates to features', async ({ page }) => {\n    await page.goto('\u002F');\n    await expect(page.getByRole('heading', { name: \u002Fwelcome\u002Fi })).toBeVisible();\n    await page.getByRole('link', { name: 'View Features' }).click();\n    await expect(page).toHaveURL(\u002F.*features\u002F);\n  });\n\n  test('shows search results', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.getByPlaceholder('Search...').fill('playwright');\n    await page.getByPlaceholder('Search...').press('Enter');\n    await expect(page.getByTestId('search-results')).toBeVisible();\n    await expect(page.getByTestId('search-result-item')).toHaveCount(10);\n  });\n});\n```\n\n### Step 3: Authentication Pattern\n\n```typescript\n\u002F\u002F tests\u002Fauth.setup.ts — authenticate once, reuse across tests\nimport { test as setup } from '@playwright\u002Ftest';\nimport path from 'path';\n\nconst authFile = path.join(__dirname, '.auth\u002Fuser.json');\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('password123');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await expect(page).toHaveURL('\u002Fdashboard');\n  await page.context().storageState({ path: authFile });\n});\n\n\u002F\u002F In config: add setup dependency\n\u002F\u002F projects: [\n\u002F\u002F   { name: 'setup', testMatch: \u002F.*\\.setup\\.ts\u002F },\n\u002F\u002F   { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'tests\u002F.auth\u002Fuser.json' }, dependencies: ['setup'] },\n\u002F\u002F ]\n```\n\n### Step 4: Page Object Pattern\n\n```typescript\n\u002F\u002F tests\u002Fpages\u002Flogin.page.ts\nimport { Page, Locator, expect } from '@playwright\u002Ftest';\n\nexport class LoginPage {\n  readonly emailInput: Locator;\n  readonly passwordInput: Locator;\n  readonly submitButton: Locator;\n  readonly errorMessage: Locator;\n\n  constructor(private 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() { await this.page.goto('\u002Flogin'); }\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  async expectError(msg: string) { await expect(this.errorMessage).toContainText(msg); }\n}\n\n\u002F\u002F tests\u002Flogin.spec.ts\ntest.describe('Login', () => {\n  let loginPage: LoginPage;\n  test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); await loginPage.goto(); });\n\n  test('successful login redirects to dashboard', async ({ page }) => {\n    await loginPage.login('test@example.com', 'password123');\n    await expect(page).toHaveURL('\u002Fdashboard');\n  });\n  test('invalid credentials show error', async () => {\n    await loginPage.login('wrong@example.com', 'wrong');\n    await loginPage.expectError('Invalid email or password');\n  });\n});\n```\n\n### Step 5: API Mocking\n\n```typescript\ntest('shows error state when API fails', async ({ page }) => {\n  await page.route('**\u002Fapi\u002Fprojects', (route) => route.fulfill({\n    status: 500, contentType: 'application\u002Fjson',\n    body: JSON.stringify({ error: 'Internal server error' }),\n  }));\n  await page.goto('\u002Fdashboard');\n  await expect(page.getByText('Failed to load projects')).toBeVisible();\n});\n\ntest('modify API response for premium features', async ({ page }) => {\n  await page.route('**\u002Fapi\u002Fuser', async (route) => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.plan = 'enterprise';\n    await route.fulfill({ response, json });\n  });\n  await page.goto('\u002Fsettings');\n  await expect(page.getByText('Enterprise Plan')).toBeVisible();\n});\n```\n\n### Step 6: Visual & Accessibility Testing\n\n```typescript\ntest('homepage visual regression', async ({ page }) => {\n  await page.goto('\u002F');\n  await expect(page).toHaveScreenshot('homepage.png', { fullPage: true, maxDiffPixelRatio: 0.01 });\n});\n\u002F\u002F Update snapshots: npx playwright test --update-snapshots\n\nimport AxeBuilder from '@axe-core\u002Fplaywright';\ntest('no accessibility violations', async ({ page }) => {\n  await page.goto('\u002F');\n  const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();\n  expect(results.violations).toEqual([]);\n});\n```\n\n### Step 7: CI Integration\n\n```yaml\n# .github\u002Fworkflows\u002Fe2e.yml\nname: E2E Tests\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 20, cache: npm }\n      - run: npm ci\n      - run: npx playwright install --with-deps\n      - run: npx playwright test\n      - uses: actions\u002Fupload-artifact@v4\n        if: always()\n        with: { name: playwright-report, path: playwright-report\u002F, retention-days: 30 }\n```\n\n### Useful Commands\n\n```bash\nnpx playwright test                          # Run all tests\nnpx playwright test tests\u002Flogin.spec.ts      # Specific file\nnpx playwright test --headed                 # See browser\nnpx playwright test --project=chromium       # Specific browser\nnpx playwright test --debug                  # Step-through debugger\nnpx playwright codegen http:\u002F\u002Flocalhost:3000 # Record actions\nnpx playwright show-report                   # View HTML report\n```\n\n## Examples\n\n### Example 1: Add e2e tests for a login flow with page objects\n**User prompt:** \"Write Playwright tests for our login page that test successful login, invalid credentials, and form validation, using the page object pattern.\"\n\nThe agent will:\n1. Create `tests\u002Fpages\u002Flogin.page.ts` with locators for email, password, submit button, and error message using role-based selectors (`getByLabel`, `getByRole`)\n2. Add `goto()`, `login()`, and `expectError()` methods to the page object\n3. Write three tests: successful login verifying redirect to `\u002Fdashboard`, invalid credentials checking the error alert, and empty form submission checking validation messages\n4. Set up `beforeEach` to instantiate the page object and navigate to `\u002Flogin`\n\n### Example 2: Mock API responses to test error and empty states\n**User prompt:** \"Add tests for our dashboard that verify it handles API errors gracefully and shows an empty state when there are no projects.\"\n\nThe agent will:\n1. Use `page.route('**\u002Fapi\u002Fprojects', ...)` to intercept the API call\n2. For the error test: fulfill with status 500 and verify the \"Failed to load projects\" message and Retry button appear\n3. For the empty test: fulfill with status 200 and an empty projects array, then verify the \"No projects yet\" empty state is shown\n4. Both tests run in isolation without needing a real backend, making them fast and reliable in CI\n\n## Guidelines\n\n- Use role-based locators (`getByRole`, `getByLabel`, `getByText`) over CSS selectors — more resilient to DOM changes\n- Add `data-testid` attributes only when no semantic locator works\n- Never use `page.waitForTimeout()` — use auto-waiting locators or `expect` with timeout\n- Run auth setup once and share state across tests via `storageState`\n- Use page objects for complex pages to keep tests readable\n- Mock external APIs in tests — test UI behavior, not third-party services\n- Run tests in parallel (`fullyParallel: true`) for speed\n- Capture traces on first retry — invaluable for debugging flaky tests in CI\n- Use `webServer` config to auto-start your dev server during tests\n- Keep visual snapshots in version control and review changes in PRs\n",{},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FTerminalSkills\u002Fskills\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":7,"skillName":5,"commands":40,"prompt":50,"zipAvailable":51},[41,44,47],{"tool":42,"command":43},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill playwright-testing",{"tool":45,"command":46},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill playwright-testing",{"tool":48,"command":49},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill playwright-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FTerminalSkills\u002Fskills\u002Fplaywright-testing\u002FSKILL.md",true,[],[54,66,76,85,91,98],{"skillNo":55,"name":56,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":57,"categories":58,"keywords":62,"publishedAt":26},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[59],{"categoryNo":60,"categoryName":61,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[63,64,65],"model","model hierarchy","model hierarchy skill",{"skillNo":67,"name":68,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":72,"publishedAt":26},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[71],{"categoryNo":60,"categoryName":61,"taxonomyType":20,"taxonomyTypeDesc":21},[73,74,75],"MCP","zoom","zoom mcp",{"skillNo":77,"name":78,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":81,"publishedAt":26},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[82,83,84,74],"sdk","zoom apps sdk","apps",{"skillNo":86,"name":87,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":90,"publishedAt":26},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[87],{"skillNo":92,"name":93,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":97,"publishedAt":26},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[96],{"categoryNo":60,"categoryName":61,"taxonomyType":20,"taxonomyTypeDesc":21},[93],{"skillNo":99,"name":100,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":26},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[100]]