| name | e2e-test-architect |
| description | End-to-end test automation expertise covering Playwright and Cypress patterns, page object model design, test selector strategies (data-testid), wait strategies for asynchronous content, screenshot comparison, parallel execution, flaky test management, CI integration, and comprehensive test reporting.
Use when the user asks about e2e test architect, e2e test architect best practices, or needs guidance on e2e test architect implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"testing best-practices guide","category":"testing-quality","subcategory":"test-automation","depends":"","disclaimer":"none","difficulty":"intermediate"} |
E2E Test Architect
Core Philosophy
End-to-end tests validate complete user workflows through the real application. They are the most expensive tests to write, maintain, and run, so be strategic: test critical paths, not every feature. A small number of well-designed E2E tests provides more value than hundreds of brittle ones. Follow the testing pyramid -- E2E tests sit at the top.
Framework Selection
| Feature | Playwright | Cypress |
|---|
| Language | JS/TS, Python, Java, .NET | JavaScript/TypeScript only |
| Browser support | Chromium, Firefox, WebKit | Chrome, Firefox, Edge, Electron |
| Multi-tab/window | Yes | No (workarounds exist) |
| iframes | Full support | Limited |
| Network interception | Yes | Yes |
| Parallel execution | Built-in | Via CI parallelization or Cypress Cloud |
| Auto-wait | Yes (built-in) | Yes (built-in) |
| Speed | Fast | Moderate |
| Best for | Cross-browser, complex apps | Simple to moderate web apps |
Playwright Patterns
Test Structure
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('successful login redirects to dashboard', async ({ page }) => {
await page.getByLabel('Email').fill('alice@example.com');
await page.getByLabel('Password').fill('secure-password');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Welcome, Alice' })).toBeVisible();
});
test('invalid credentials show error message', async ({ page }) => {
await page.getByLabel('Email').fill();
page.().();
page.(, { : }).();
(page.()).();
(page).();
});
(, ({ page }) => {
( i = ; i < ; i++) {
page.().();
page.().();
page.(, { : }).();
(i < ) {
page.().();
}
}
(page.()).();
});
});
Authentication State Reuse
import { test as setup, expect } from '@playwright/test';
setup('authenticate as admin', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('admin@example.com');
await page.getByLabel('Password').fill('admin-password');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL('/dashboard');
await page.context().storageState({ path: '.auth/admin.json' });
});
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'admin-tests',
dependencies: [],
: { : }
}
]
});
Page Object Model
Implementation
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly signInButton: Locator;
readonly errorAlert: Locator;
readonly forgotPasswordLink: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.signInButton = page.getByRole('button', { name: 'Sign In' });
this.errorAlert = page.getByRole('alert');
. = page.(, { : });
}
() {
..();
}
() {
..(email);
..(password);
..();
}
() {
(.).(message);
}
}
{
: ;
: ;
: ;
: ;
: ;
() {
. = page;
. = page.(, { : });
. = page.(, { : });
. = page.();
. = page.();
}
() {
(.).();
}
() {
..(query);
..();
..(
resp.().() && resp.() ===
);
}
(): <> {
..().();
}
}
(, ({ page }) => {
login = (page);
dashboard = (page);
login.();
login.(, );
dashboard.();
dashboard.();
rows = dashboard.();
(rows).();
});
Test Selectors Strategy
Priority Order (Best to Worst)
page.getByRole('button', { name: 'Submit' });
page.getByRole('heading', { name: 'Dashboard' });
page.getByRole('link', { name: 'Settings' });
page.getByRole('textbox', { name: 'Email' });
page.getByRole('checkbox', { name: 'Remember me' });
page.getByLabel('Email address');
page.getByPlaceholder('Search...');
page.getByText('Welcome to the app');
page.getByAltText('Company logo');
page.getByTitle('Close dialog');
page.getByTestId('submit-order-button');
page.getByTestId('user-avatar');
page.locator('.btn-primary');
page.locator('#submit-form');
page.locator('div > form > button');
Adding Test IDs in Your Application
<button data-testid="submit-order">Place Order</button> .// Vue
<button data-testid="submit-order">Place Order</button> .// Configure Playwright to use custom attribute
export default defineConfig({
use: {
testIdAttribute: 'data-testid',
}
});
Wait Strategies
Playwright Auto-Wait
Playwright automatically waits for elements to be actionable before performing actions. These are the built-in wait conditions:
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByText('Success')).toBeVisible({ timeout: 10000 });
await expect(page.getByTestId('loading')).toBeHidden();
await expect(page.getByRole('button')).toBeEnabled();
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveURL(/\/orders\/\d+/);
const responsePromise = page.waitForResponse(
resp => resp.url().includes('/api/orders') && resp.status() === 200
);
await page.getByRole('button', { : }).();
response = responsePromise;
data = response.();
page.(, { : }).();
page.().({ : });
(page.()).();
Anti-Patterns to Avoid
await page.waitForTimeout(3000);
while (!await page.getByText('Ready').isVisible()) {
await page.waitForTimeout(100);
}
await expect(page.getByText('Ready')).toBeVisible({ timeout: 15000 });
await page.waitForFunction(() => {
return document.querySelectorAll('table tbody tr').length > 0;
});
Screenshot Comparison (Visual Testing)
test('dashboard renders correctly', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixelRatio: 0.01,
animations: 'disabled',
});
});
test('navigation menu renders correctly', async ({ page }) => {
await page.goto('/dashboard');
const nav = page.getByRole('navigation');
await expect(nav).toHaveScreenshot('navigation.png');
});
export default defineConfig({
expect: {
toHaveScreenshot: {
maxDiffPixelRatio: 0.01,
animations: 'disabled',
caret: 'hide',
},
},
snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
});
Parallel Execution
export default defineConfig({
fullyParallel: true,
workers: ENV_CONFIG_VALUE ? 4 : undefined,
retries: ENV_CONFIG_VALUE ? 2 : 0,
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
],
});
Flaky Test Management
Detection
export default defineConfig({
retries: 2,
reporter: [
['html'],
['json', { outputFile: 'test-results.json' }]
],
});
Common Causes and Fixes
1. Race conditions with animations
FIX: Disable animations in test config
page.addStyleTag({ content: '*, *::before, *::after { transition: none !important; animation: none !important; }' });
2. Network timing
FIX: Wait for specific network responses, not arbitrary timeouts
3. Non-deterministic data
FIX: Use seeded test data, reset DB state between tests
4. Third-party dependencies (ads, analytics, chatbots)
FIX: Block these in tests via route interception
await page.route('**/*analytics*', route => route.abort());
5. Date/time dependencies
FIX: Mock the clock
await page.clock.install({ time: new Date('2025-03-15T10:00:00') });
CI Integration
GitHub Actions
name: E2E Tests
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
Test Reporting
export default defineConfig({
reporter: [
['list'],
['html', { open: 'never' }],
['junit', { outputFile: 'results.xml' }],
['json', { outputFile: 'results.json' }],
],
});
Best Practices Summary
- Test user workflows, not implementation: Click buttons, fill forms, verify outcomes
- Use page objects: Encapsulate page structure, keep tests readable
- Prefer role-based selectors: Accessible, resilient, framework-agnostic
- Never use hard-coded waits: Use Playwright's auto-wait or explicit conditions
- Keep tests independent: No shared state between tests
- Test the critical path first: Login, checkout, core features
- Run in CI on every PR: Catch regressions before merge
- Manage flaky tests proactively: Quarantine, fix, or delete them
When to Use
Use this skill when:
- Designing or implementing e2e test architect solutions
- Reviewing or improving existing e2e test architect approaches
- Making architectural or implementation decisions about e2e test architect
- Learning e2e test architect patterns and best practices
- Troubleshooting e2e test architect-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# E2e Test Architect Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement e2e test architect for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended e2e test architect approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When e2e test architect must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities