From asyrafhussin-agent-skills-1
Provides patterns and conventions for reliable end-to-end browser testing with Playwright, covering locators, auth, forms, assertions, and CI/CD.
How this skill is triggered — by the user, by Claude, or both
Slash command
/asyrafhussin-agent-skills-1:e2e-playwright-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Patterns and conventions for reliable end-to-end browser testing with Playwright.
Patterns and conventions for reliable end-to-end browser testing with Playwright.
Comprehensive 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.
Before writing or reviewing E2E tests, detect the project stack:
# Look in package.json devDependencies:
# "@playwright/test" → Playwright is installed
# Check for playwright.config.js or playwright.config.ts
@playwright/test is present → use Playwright patterns from this skillcypress is present → this skill does not apply# Check package.json dependencies:
# "react" + "@inertiajs/react" → React + Inertia.js (SPA with server routing)
# "react" + "react-router" → React SPA
# "vue" → Vue.js
# "next" → Next.js
Why this matters:
waitForURL not waitForLoadState('networkidle') — Inertia uses history.pushStatefill() works for text but keyboard.type() needed for date/time# Check for session-based (Laravel Sanctum) or token-based (JWT) auth
# Session: storageState saves cookies
# Token: may need to save tokens to localStorage
# Check routes for throttle middleware
# If present in dev, tests will hit 429 after ~5 login attempts
# Solution: production-only throttle or increase limits in test env
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Locators | CRITICAL | loc |
| 2 | Authentication | CRITICAL | auth |
| 3 | Assertions | HIGH | assert |
| 4 | Forms & Inputs | HIGH | form |
| 5 | Test Organization | MEDIUM | org |
| 6 | Reliability | MEDIUM | rel |
loc-prefer-role-locators - Use getByRole/getByLabel over CSS selectorsloc-strict-mode - Handle strict mode violations with exact/first/scopedauth-storage-state - Reuse login state via setup project patternassert-web-first - Use auto-retrying expect(locator) assertionsform-react-date-inputs - Use keyboard.type() for date/time in React appsform-custom-checkboxes - Handle sr-only checkbox componentsorg-mirror-routes - Directory structure mirrors route groupsrel-no-wait-for-timeout - Never use arbitrary waitForTimeout// 1st: Role (best — mirrors accessibility)
page.getByRole('button', { name: 'Submit' })
page.getByRole('tab', { name: 'Network' })
page.getByRole('heading', { name: 'Dashboard' })
// 2nd: Label (for form fields)
page.getByLabel('Email')
// 3rd: Text (for static content, use exact when ambiguous)
page.getByText('Welcome', { exact: true })
// 4th: ID (for inputs without proper labels)
page.locator('#password')
// Last resort: CSS selector
page.locator('button.submit-btn')
// Setup project logs in once per role, all tests reuse the state
// 3 logins for 90+ tests (not 90 logins)
// In test files:
test.use({ role: 'customer' });
test('shows dashboard', async ({ authedPage: page }) => {
await page.goto('/dashboard');
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
// fill() doesn't trigger React onChange for date/time inputs
// Use keyboard.type() instead:
await dateInput.click();
await page.keyboard.type('16042026'); // DDMMYYYY
await timeInput.click();
await page.keyboard.type('1000AM'); // 10:00 AM
// Bad: matches "Verified" AND "Unverified"
page.getByRole('button', { name: 'Verified' })
// Good: exact match
page.getByRole('button', { name: 'Verified', exact: true })
// Bad: matches heading AND breadcrumb
page.getByText('POS Demo')
// Good: use role to disambiguate
page.getByRole('heading', { name: 'POS Demo' })
// Default Playwright: fullyParallel: true
// Use this when: tests are independent, each test creates its own data
// Override to sequential: workers: 1
// Use this when: tests share a seeded database with mutable state
// Without this, one test's buy transaction changes the balance another test expects
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './specs',
fullyParallel: false, // true if tests are independent
workers: 1, // increase if no shared mutable state
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'auth-setup', testMatch: /auth\.setup\.js$/, testDir: './auth' },
{ name: 'chrome', use: { ...devices['Desktop Chrome'] }, dependencies: ['auth-setup'] },
{ name: 'mobile', use: { ...devices['iPhone 14'] }, dependencies: ['auth-setup'], testMatch: /responsive/ },
],
});
For the complete guide with all rules expanded: AGENTS.md
npx claudepluginhub asyrafhussin/agent-skillsProvides Playwright E2E testing patterns for full-stack Python/React apps, covering page object model, selectors, auth state reuse, and CI integration.
Writes, reviews, and debugs Playwright end-to-end tests using user-centric patterns, page objects, resilient selectors, and isolated tests.
Provides E2E testing setup and patterns with Playwright: page objects, cross-browser config, CI/CD integration, fixtures, and project structure.