E2E Runner Skill
End-to-end testing specialist using Playwright. Creates reliable, maintainable test journeys.
Test Journey Creation
- Define user flow: Login → action → assertion
- Use semantic locators: role, label, placeholder over CSS/XPath
- One journey per file: Logical grouping (e.g.,
auth.spec.ts,checkout.spec.ts) - Arrange-Act-Assert: Clear structure in each test
Page Object Model (POM)
// pages/LoginPage.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.page.getByRole('textbox', { name: /email/i }).fill(email);
await this.page.getByRole('textbox', { name: /password/i }).fill(password);
await this.page.getByRole('button', { name: /sign in/i }).click();
}
async expectLoggedIn() {
await expect(this.page.getByRole('link', { name: /dashboard/i })).toBeVisible();
}
}
Semantic Locators (Preferred Order)
getByRole('button', { name: 'Submit' })— accessibility-friendlygetByLabel('Email')— form fieldsgetByPlaceholder('Enter email')— when no labelgetByText('Welcome')— unique textgetByTestId('submit-btn')— last resort; requires data-testid
Avoid: getByCss, getByXPath for dynamic selectors; brittle and slow.
Wait for Conditions, Not Time
// BAD - flaky
await page.waitForTimeout(2000);
// GOOD - wait for condition
await expect(page.getByRole('heading', { name: 'Success' })).toBeVisible();
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByText('Saved')).toBeVisible({ timeout: 5000 });
- Use
expect(...).toBeVisible(),toBeEnabled(), etc. - Playwright auto-waits; explicit waits rarely needed
- For network:
page.waitForResponse()orpage.waitForRequest()
Flaky Test Management
| Cause | Mitigation |
|---|---|
| Timing | Replace waitForTimeout with condition waits |
| Race conditions | Use Promise.all for parallel; sequential for dependent |
| Shared state | Isolate: fresh page, clean DB, or unique test data |
| Animations | Wait for final state; disable animations in test config |
| External deps | Mock APIs; use test fixtures |
| Order dependency | Tests should be independent; no shared order |
Artifact Management
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
- Trace: Inspect with
npx playwright show-trace trace.zip - Screenshots: On failure for debugging
- Video: Retain only on failure to save space
Running Tests
# Headed (see browser)
npx playwright test --headed
# Specific file
npx playwright test auth.spec.ts
# Debug mode
npx playwright test --debug
# UI mode (interactive)
npx playwright test --ui
Best Practices
- Keep tests independent; no shared mutable state
- Use data-testid sparingly; prefer semantic queries
- One assertion focus per test when possible
- Use fixtures for setup (auth, test data)
- Run in CI with
--retries=2for transient failures