skills/playwright-e2e-tester/SKILL.md
--- license: Apache-2.0 name: playwright-e2e-tester version: 1.0.0 category: Code Quality & Testing tags: - e2e - playwright - testing - automation - ci-cd - cross-browser --- # Playwright E2E Tester ## Overview Expert in end-to-end testing with Playwright, the modern cross-browser testing framework. Specializes in test generation, page object patterns, visual regression testing, and CI/CD integration. Handles complex testing scenarios including authentication flows, API mocking,
npx skillsauth add curiositech/windags-skills playwright-e2e-testerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in end-to-end testing with Playwright, the modern cross-browser testing framework. Specializes in test generation, page object patterns, visual regression testing, and CI/CD integration. Handles complex testing scenarios including authentication flows, API mocking, and mobile emulation.
Is element semantic (button, heading, form control)?
├─ YES → Use role-based locators (getByRole, getByLabel)
│ └─ Expected to change frequently?
│ ├─ NO → Stop here (most stable)
│ └─ YES → Add getByTestId as backup
└─ NO → Is element purely presentational?
├─ YES → Use getByTestId (add data-testid attribute)
│ └─ Can't modify markup?
│ └─ Use CSS selector (last resort, document fragility)
└─ NO → Use getByText for content-based selection
Action type:
├─ Navigation (page.goto, click link) → Use waitForURL()
├─ Element appears/disappears → Use waitForSelector() or visibility assertions
├─ API response affects UI → Use waitForResponse() then element assertion
├─ Animation/transition → Use waitForFunction() with custom condition
└─ Network request completion → Use page.route() with route.fulfill()
Test complexity:
├─ Single page interaction → Inline test with direct selectors
├─ Multi-step flow → Use Page Object Model pattern
├─ Cross-page workflow → Use fixtures for shared state
└─ Multi-app integration → Use projects with different configs
Symptoms: Error: Element is not attached to the DOM
Detection Rule: If you see detachment errors during dynamic content updates
Fix: Replace element variables with fresh locator calls:
// BAD: Storing element reference
const button = page.locator('button');
await button.click(); // May fail if DOM updated
// GOOD: Fresh locator each time
await page.locator('button').click();
Symptoms: Tests fail with "Timeout exceeded" in CI but pass locally Detection Rule: If tests have inconsistent CI failures with 30s+ timeouts Fix: Implement explicit waits with proper conditions:
// BAD: Blind timeout increase
await page.waitForTimeout(5000);
// GOOD: Wait for specific condition
await page.waitForSelector('[data-testid="loading"]', { state: 'hidden' });
await expect(page.locator('[data-testid="results"]')).toBeVisible();
Symptoms: Intermittent failures where elements "aren't ready yet" Detection Rule: If test flakiness correlates with slow network/CPU Fix: Chain waits to ensure proper sequencing:
// BAD: Assuming immediate availability
await page.click('#submit');
await page.fill('#new-field', 'value'); // May fail
// GOOD: Wait for UI state transition
await page.click('#submit');
await page.waitForSelector('#new-field:not([disabled])');
await page.fill('#new-field', 'value');
Symptoms: Tests break when CSS classes or DOM structure changes Detection Rule: If tests fail after frontend refactoring without feature changes Fix: Migrate to semantic locators:
// BAD: Structural dependency
await page.click('.header > .nav > .item:nth-child(3)');
// GOOD: Semantic meaning
await page.getByRole('navigation').getByRole('link', { name: 'Products' }).click();
Symptoms: Visual regression tests fail due to minor rendering differences Detection Rule: If screenshot tests fail in CI with <5% pixel differences Fix: Configure appropriate tolerances and masks:
await expect(page).toHaveScreenshot('page.png', {
maxDiffPixelRatio: 0.01,
mask: [page.locator('[data-testid="dynamic-timestamp"]')],
animations: 'disabled'
});
Scenario: Test user login with error handling and success validation
Expert Approach:
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test('should handle complete login flow', async ({ page }) => {
// Decision: Use Page Object for multi-step flow
const loginPage = new LoginPage(page);
await loginPage.goto();
// Decision: Test error state first (negative case)
await loginPage.signIn('[email protected]', 'wrongpass');
// Wait strategy: Error message should appear
await expect(page.getByRole('alert')).toContainText('Invalid credentials');
// Decision: Clear state before positive test
await loginPage.clearForm();
// Decision: Use valid test data
await loginPage.signIn('[email protected]', 'validpass');
// Wait strategy: Navigation indicates success
await page.waitForURL('/dashboard');
// Verification: Check authenticated state
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByText('Welcome back, John')).toBeVisible();
});
});
class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
// Wait for form to be interactive
await this.page.waitForSelector('form[data-testid="login-form"]');
}
async signIn(email: string, password: string) {
// Decision: Use semantic locators
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Sign In' }).click();
}
async clearForm() {
await this.page.getByLabel('Email').clear();
await this.page.getByLabel('Password').clear();
}
}
Novice would miss: Error state testing, proper wait strategies, form state management Expert catches: Complete flow coverage, semantic locators, defensive waits
Test implementation checklist:
Don't use this skill for:
vitest-testing-patterns insteadapi-architect for endpoint validationDelegate to other skills:
github-actions-pipeline-builderaccessibility-auditorvitest-testing-patternstools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.