{"id":"a87f2864-64cd-4345-99dd-28a3095f3661","shortId":"pPHmF8","kind":"skill","title":"e2e-playwright-testing","tagline":"End-to-end testing with Playwright for web applications. Use when writing E2E tests, browser automation, form submission testing, or user flow testing. Triggers on \"playwright\", \"e2e test\", \"browser test\", \"end-to-end\", \"form flow testing\", or test files in tests/e2e/.","description":"# E2E Playwright Testing\n\n> Patterns and conventions for reliable end-to-end browser testing with Playwright.\n\nComprehensive E2E testing guide for web applications. Contains 8 rules across 6 categories covering locator strategies, authentication reuse, form testing (including React/SPA-specific gotchas), assertions, test organization, reliability, and CI/CD configuration.\n\n## Stack Detection\n\n**Before writing or reviewing E2E tests, detect the project stack:**\n\n### Step 1 — Check for Playwright\n\n```bash\n# Look in package.json devDependencies:\n# \"@playwright/test\" → Playwright is installed\n# Check for playwright.config.js or playwright.config.ts\n```\n\n- If `@playwright/test` is present → **use Playwright patterns from this skill**\n- If `cypress` is present → this skill does not apply\n\n### Step 2 — Detect Frontend Framework\n\n```bash\n# Check package.json dependencies:\n# \"react\" + \"@inertiajs/react\" → React + Inertia.js (SPA with server routing)\n# \"react\" + \"react-router\" → React SPA\n# \"vue\" → Vue.js\n# \"next\" → Next.js\n```\n\n**Why this matters:**\n- **React + Inertia.js**: Use `waitForURL` not `waitForLoadState('networkidle')` — Inertia uses `history.pushState`\n- **React controlled inputs**: `fill()` works for text but `keyboard.type()` needed for date/time\n- **SPA navigation**: Page doesn't do full reload — assertions must wait for content, not network idle\n\n### Step 3 — Detect Auth Pattern\n\n```bash\n# Check for session-based (Laravel Sanctum) or token-based (JWT) auth\n# Session: storageState saves cookies\n# Token: may need to save tokens to localStorage\n```\n\n### Step 4 — Detect Rate Limiting\n\n```bash\n# Check routes for throttle middleware\n# If present in dev, tests will hit 429 after ~5 login attempts\n# Solution: production-only throttle or increase limits in test env\n```\n\n---\n\n## Metadata\n\n- **Version:** 1.0.0\n- **Rule Count:** 8 rules across 6 categories\n- **License:** MIT\n\n## When to Apply\n\n- Writing or reviewing Playwright E2E tests\n- Setting up E2E testing for a new project\n- Debugging flaky browser tests\n- Testing form submissions, authentication flows, or user interactions\n- Choosing locator strategies for elements\n- Configuring Playwright for CI/CD\n\n## Rule Categories by Priority\n\n| Priority | Category | Impact | Prefix |\n|----------|----------|--------|--------|\n| 1 | Locators | CRITICAL | `loc` |\n| 2 | Authentication | CRITICAL | `auth` |\n| 3 | Assertions | HIGH | `assert` |\n| 4 | Forms & Inputs | HIGH | `form` |\n| 5 | Test Organization | MEDIUM | `org` |\n| 6 | Reliability | MEDIUM | `rel` |\n\n## Quick Reference\n\n### 1. Locators (CRITICAL)\n\n- `loc-prefer-role-locators` - Use getByRole/getByLabel over CSS selectors\n- `loc-strict-mode` - Handle strict mode violations with exact/first/scoped\n\n### 2. Authentication (CRITICAL)\n\n- `auth-storage-state` - Reuse login state via setup project pattern\n\n### 3. Assertions (HIGH)\n\n- `assert-web-first` - Use auto-retrying expect(locator) assertions\n\n### 4. Forms & Inputs (HIGH)\n\n- `form-react-date-inputs` - Use keyboard.type() for date/time in React apps\n- `form-custom-checkboxes` - Handle sr-only checkbox components\n\n### 5. Test Organization (MEDIUM)\n\n- `org-mirror-routes` - Directory structure mirrors route groups\n\n### 6. Reliability (MEDIUM)\n\n- `rel-no-wait-for-timeout` - Never use arbitrary waitForTimeout\n\n## Essential Patterns\n\n### Locator Priority\n\n```javascript\n// 1st: Role (best — mirrors accessibility)\npage.getByRole('button', { name: 'Submit' })\npage.getByRole('tab', { name: 'Network' })\npage.getByRole('heading', { name: 'Dashboard' })\n\n// 2nd: Label (for form fields)\npage.getByLabel('Email')\n\n// 3rd: Text (for static content, use exact when ambiguous)\npage.getByText('Welcome', { exact: true })\n\n// 4th: ID (for inputs without proper labels)\npage.locator('#password')\n\n// Last resort: CSS selector\npage.locator('button.submit-btn')\n```\n\n### Auth Setup Pattern\n\n```javascript\n// Setup project logs in once per role, all tests reuse the state\n// 3 logins for 90+ tests (not 90 logins)\n\n// In test files:\ntest.use({ role: 'customer' });\ntest('shows dashboard', async ({ authedPage: page }) => {\n    await page.goto('/dashboard');\n    await expect(page.getByRole('heading', { level: 1 })).toBeVisible();\n});\n```\n\n### React Date Input Gotcha\n\n```javascript\n// fill() doesn't trigger React onChange for date/time inputs\n// Use keyboard.type() instead:\nawait dateInput.click();\nawait page.keyboard.type('16042026');  // DDMMYYYY\n\nawait timeInput.click();\nawait page.keyboard.type('1000AM');    // 10:00 AM\n```\n\n### Strict Mode Fix\n\n```javascript\n// Bad: matches \"Verified\" AND \"Unverified\"\npage.getByRole('button', { name: 'Verified' })\n\n// Good: exact match\npage.getByRole('button', { name: 'Verified', exact: true })\n\n// Bad: matches heading AND breadcrumb\npage.getByText('POS Demo')\n\n// Good: use role to disambiguate\npage.getByRole('heading', { name: 'POS Demo' })\n```\n\n### Parallelism Decision\n\n```javascript\n// Default Playwright: fullyParallel: true\n// Use this when: tests are independent, each test creates its own data\n\n// Override to sequential: workers: 1\n// Use this when: tests share a seeded database with mutable state\n// Without this, one test's buy transaction changes the balance another test expects\n```\n\n## Configuration Template\n\n```javascript\nimport { defineConfig, devices } from '@playwright/test';\n\nexport default defineConfig({\n    testDir: './specs',\n    fullyParallel: false,              // true if tests are independent\n    workers: 1,                        // increase if no shared mutable state\n    forbidOnly: !!process.env.CI,\n    retries: process.env.CI ? 2 : 0,\n    reporter: [['html', { open: 'never' }], ['list']],\n    use: {\n        baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8000',\n        trace: 'on-first-retry',\n        screenshot: 'only-on-failure',\n        video: 'retain-on-failure',\n    },\n    projects: [\n        { name: 'auth-setup', testMatch: /auth\\.setup\\.js$/, testDir: './auth' },\n        { name: 'chrome', use: { ...devices['Desktop Chrome'] }, dependencies: ['auth-setup'] },\n        { name: 'mobile', use: { ...devices['iPhone 14'] }, dependencies: ['auth-setup'], testMatch: /responsive/ },\n    ],\n});\n```\n\n## References\n\n- [Playwright Documentation](https://playwright.dev)\n- [Best Practices](https://playwright.dev/docs/best-practices)\n- [Locators Guide](https://playwright.dev/docs/locators)\n- [Authentication Guide](https://playwright.dev/docs/auth)\n- [Test Assertions](https://playwright.dev/docs/test-assertions)\n- [Auto-waiting](https://playwright.dev/docs/actionability)\n\n## Full Compiled Document\n\nFor the complete guide with all rules expanded: `AGENTS.md`","tags":["e2e","playwright","testing","agent","skills","asyrafhussin","agent-rules","agent-skills","ai-agents","ai-slop","claude-code","code-quality"],"capabilities":["skill","source-asyrafhussin","skill-e2e-playwright-testing","topic-agent-rules","topic-agent-skills","topic-ai-agents","topic-ai-slop","topic-claude-code","topic-code-quality","topic-code-review","topic-codex","topic-cursor","topic-laravel","topic-nodejs","topic-react"],"categories":["agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/AsyrafHussin/agent-skills/e2e-playwright-testing","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add AsyrafHussin/agent-skills","source_repo":"https://github.com/AsyrafHussin/agent-skills","install_from":"skills.sh"}},"qualityScore":"0.469","qualityRationale":"deterministic score 0.47 from registry signals: · indexed on github topic:agent-skills · 39 github stars · SKILL.md body (6,654 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:58:24.154Z","embedding":null,"createdAt":"2026-05-16T18:57:13.953Z","updatedAt":"2026-05-18T18:58:24.154Z","lastSeenAt":"2026-05-18T18:58:24.154Z","tsv":"'/auth':756,760 '/dashboard':562 '/docs/actionability)':812 '/docs/auth)':801 '/docs/best-practices)':791 '/docs/locators)':796 '/docs/test-assertions)':806 '/responsive':782 '/specs':701 '0':722 '00':599 '1':107,335,363,568,664,710 '1.0.0':279 '10':598 '1000am':597 '14':776 '16042026':591 '1st':471 '2':145,339,386,721 '2nd':488 '3':213,343,400,540 '3rd':495 '4':244,347,414 '429':261 '4th':508 '5':263,352,440 '6':75,285,357,453 '8':72,282 '8000':734 '90':543,546 'access':475 'across':74,284 'agents.md':824 'ambigu':503 'anoth':686 'app':429 'appli':143,291 'applic':14,70 'arbitrari':464 'assert':87,204,344,346,401,404,413,803 'assert-web-first':403 'async':557 'attempt':265 'auth':215,230,342,390,524,753,769,779 'auth-setup':752,768,778 'auth-storage-st':389 'authedpag':558 'authent':80,313,340,387,797 'auto':409,808 'auto-retri':408 'auto-wait':807 'autom':21 'await':560,563,587,589,593,595 'bad':605,623 'balanc':685 'base':222,228,731 'baseurl':729 'bash':111,149,217,248 'best':473,787 'breadcrumb':627 'browser':20,34,60,308 'btn':523 'button':477,611,618 'button.submit':522 'buy':681 'categori':76,286,328,332 'chang':683 'check':108,120,150,218,249 'checkbox':433,438 'choos':318 'chrome':762,766 'ci/cd':92,326 'compil':814 'complet':818 'compon':439 'comprehens':64 'configur':93,323,689 'contain':71 'content':208,499 'control':185 'convent':53 'cooki':234 'count':281 'cover':77 'creat':656 'critic':337,341,365,388 'css':374,519 'custom':432,553 'cypress':136 'dashboard':487,556 'data':659 'databas':672 'date':421,571 'date/time':195,426,582 'dateinput.click':588 'ddmmyyyy':592 'debug':306 'decis':642 'default':644,698 'defineconfig':693,699 'demo':630,640 'depend':152,767,777 'desktop':765 'detect':95,102,146,214,245 'dev':257 'devdepend':115 'devic':694,764,774 'directori':448 'disambigu':635 'document':785,815 'doesn':199,576 'e2e':2,18,32,48,65,100,296,300 'e2e-playwright-testing':1 'element':322 'email':494 'end':6,8,37,39,57,59 'end-to-end':5,36,56 'env':276 'essenti':466 'exact':501,506,615,621 'exact/first/scoped':385 'expand':823 'expect':411,564,688 'export':697 'failur':744,749 'fals':703 'field':492 'file':45,550 'fill':187,575 'first':406,738 'fix':603 'flaki':307 'flow':27,41,314 'forbidon':717 'form':22,40,82,311,348,351,415,419,431,491 'form-custom-checkbox':430 'form-react-date-input':418 'framework':148 'frontend':147 'full':202,813 'fullyparallel':646,702 'getbyrole/getbylabel':372 'good':614,631 'gotcha':86,573 'group':452 'guid':67,793,798,819 'handl':380,434 'head':485,566,625,637 'high':345,350,402,417 'history.pushstate':183 'hit':260 'html':724 'id':509 'idl':211 'impact':333 'import':692 'includ':84 'increas':272,711 'independ':653,708 'inertia':181 'inertia.js':156,175 'inertiajs/react':154 'input':186,349,416,422,511,572,583 'instal':119 'instead':586 'interact':317 'iphon':775 'javascript':470,527,574,604,643,691 'js':758 'jwt':229 'keyboard.type':192,424,585 'label':489,514 'laravel':223 'last':517 'level':567 'licens':287 'limit':247,273 'list':727 'loc':338,367,377 'loc-prefer-role-loc':366 'loc-strict-mod':376 'localhost':733 'localstorag':242 'locat':78,319,336,364,370,412,468,792 'log':530 'login':264,394,541,547 'look':112 'match':606,616,624 'matter':173 'may':236 'medium':355,359,443,455 'metadata':277 'middlewar':253 'mirror':446,450,474 'mit':288 'mobil':772 'mode':379,382,602 'must':205 'mutabl':674,715 'name':478,482,486,612,619,638,751,761,771 'navig':197 'need':193,237 'network':210,483 'networkidl':180 'never':462,726 'new':304 'next':169 'next.js':170 'on-first-retri':736 'onchang':580 'one':678 'only-on-failur':741 'open':725 'org':356,445 'org-mirror-rout':444 'organ':89,354,442 'overrid':660 'package.json':114,151 'page':198,559 'page.getbylabel':493 'page.getbyrole':476,480,484,565,610,617,636 'page.getbytext':504,628 'page.goto':561 'page.keyboard.type':590,596 'page.locator':515,521 'parallel':641 'password':516 'pattern':51,131,216,399,467,526 'per':533 'playwright':3,11,31,49,63,110,117,130,295,324,645,784 'playwright.config.js':122 'playwright.config.ts':124 'playwright.dev':786,790,795,800,805,811 'playwright.dev/docs/actionability)':810 'playwright.dev/docs/auth)':799 'playwright.dev/docs/best-practices)':789 'playwright.dev/docs/locators)':794 'playwright.dev/docs/test-assertions)':804 'playwright/test':116,126,696 'pos':629,639 'practic':788 'prefer':368 'prefix':334 'present':128,138,255 'prioriti':330,331,469 'process.env.ci':718,720 'process.env.playwright':730 'product':268 'production-on':267 'project':104,305,398,529,750 'proper':513 'quick':361 'rate':246 'react':153,155,161,163,165,174,184,420,428,570,579 'react-rout':162 'react/spa-specific':85 'refer':362,783 'rel':360,457 'rel-no-wait-for-timeout':456 'reliabl':55,90,358,454 'reload':203 'report':723 'resort':518 'retain':747 'retain-on-failur':746 'retri':410,719,739 'reus':81,393,537 'review':99,294 'role':369,472,534,552,633 'rout':160,250,447,451 'router':164 'rule':73,280,283,327,822 'sanctum':224 'save':233,239 'screenshot':740 'seed':671 'selector':375,520 'sequenti':662 'server':159 'session':221,231 'session-bas':220 'set':298 'setup':397,525,528,754,757,770,780 'share':669,714 'show':555 'skill':134,140 'skill-e2e-playwright-testing' 'solut':266 'source-asyrafhussin' 'spa':157,166,196 'sr':436 'sr-on':435 'stack':94,105 'state':392,395,539,675,716 'static':498 'step':106,144,212,243 'storag':391 'storagest':232 'strategi':79,320 'strict':378,381,601 'structur':449 'submiss':23,312 'submit':479 'tab':481 'templat':690 'test':4,9,19,24,28,33,35,42,44,50,61,66,83,88,101,258,275,297,301,309,310,353,441,536,544,549,554,651,655,668,679,687,706,802 'test.use':551 'testdir':700,759 'testmatch':755,781 'tests/e2e':47 'text':190,496 'throttl':252,270 'timeinput.click':594 'timeout':461 'tobevis':569 'token':227,235,240 'token-bas':226 'topic-agent-rules' 'topic-agent-skills' 'topic-ai-agents' 'topic-ai-slop' 'topic-claude-code' 'topic-code-quality' 'topic-code-review' 'topic-codex' 'topic-cursor' 'topic-laravel' 'topic-nodejs' 'topic-react' 'trace':735 'transact':682 'trigger':29,578 'true':507,622,647,704 'unverifi':609 'url':732 'use':15,129,176,182,371,407,423,463,500,584,632,648,665,728,763,773 'user':26,316 'verifi':607,613,620 'version':278 'via':396 'video':745 'violat':383 'vue':167 'vue.js':168 'wait':206,459,809 'waitforloadst':179 'waitfortimeout':465 'waitforurl':177 'web':13,69,405 'welcom':505 'without':512,676 'work':188 'worker':663,709 'write':17,97,292","prices":[{"id":"10c6d8b3-6037-4081-8128-83526e056d2e","listingId":"a87f2864-64cd-4345-99dd-28a3095f3661","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"AsyrafHussin","category":"agent-skills","install_from":"skills.sh"},"createdAt":"2026-05-16T18:57:13.953Z"}],"sources":[{"listingId":"a87f2864-64cd-4345-99dd-28a3095f3661","source":"github","sourceId":"AsyrafHussin/agent-skills/e2e-playwright-testing","sourceUrl":"https://github.com/AsyrafHussin/agent-skills/tree/main/skills/e2e-playwright-testing","isPrimary":false,"firstSeenAt":"2026-05-16T18:57:13.953Z","lastSeenAt":"2026-05-18T18:58:24.154Z"}],"details":{"listingId":"a87f2864-64cd-4345-99dd-28a3095f3661","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"AsyrafHussin","slug":"e2e-playwright-testing","github":{"repo":"AsyrafHussin/agent-skills","stars":39,"topics":["agent-rules","agent-skills","ai-agents","ai-slop","claude-code","code-quality","code-review","codex","cursor","laravel","nodejs","react","technical-debt","typescript","windsurf"],"license":"mit","html_url":"https://github.com/AsyrafHussin/agent-skills","pushed_at":"2026-05-16T19:24:02Z","description":"Agent skills for AI coding agents (Claude Code, Cursor, Codex, Windsurf) — Laravel, React, TypeScript, MySQL, code quality, technical debt, documentation, and security.","skill_md_sha":"255b59a78ef197d6aff9564b7a00e8fc264fac42","skill_md_path":"skills/e2e-playwright-testing/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/AsyrafHussin/agent-skills/tree/main/skills/e2e-playwright-testing"},"layout":"multi","source":"github","category":"agent-skills","frontmatter":{"name":"e2e-playwright-testing","license":"MIT","description":"End-to-end testing with Playwright for web applications. Use when writing E2E tests, browser automation, form submission testing, or user flow testing. Triggers on \"playwright\", \"e2e test\", \"browser test\", \"end-to-end\", \"form flow testing\", or test files in tests/e2e/."},"skills_sh_url":"https://skills.sh/AsyrafHussin/agent-skills/e2e-playwright-testing"},"updatedAt":"2026-05-18T18:58:24.154Z"}}