e2e-testing-frontend
End-to-end frontend testing with Playwright. Page Object Model, selectors, fixtures,accessibility audits. Limited E2E coverage currently - area for improvement.Keywords: Playwright, E2E, Page Object Model, POM, data-testid, getByRole, getByLabel,getByText, fixtures, axe-playwright, frontend testing
When & Why to Use This Skill
This Claude skill facilitates comprehensive end-to-end (E2E) frontend testing using the Playwright framework. It streamlines the creation of robust test suites by implementing the Page Object Model (POM), utilizing resilient selector strategies, and integrating automated accessibility audits with axe-playwright to ensure high-quality, inclusive web applications.
Use Cases
- Automating critical user journeys such as multi-step authentication and dashboard navigation using maintainable Page Object Models.
- Enhancing test stability and reliability by adopting a prioritized selector strategy that favors semantic roles and dedicated test IDs over fragile CSS selectors.
- Integrating automated accessibility (A11y) compliance checks into the testing pipeline to detect WCAG violations early in the development cycle.
- Managing complex test environments by orchestrating backend data setup and teardown via API requests and service fixtures for isolated test execution.
| name | E2E Testing (Frontend) |
|---|---|
| description | | |
| Keywords | Playwright, E2E, Page Object Model, POM, data-testid, getByRole, getByLabel, |
E2E Testing (Frontend)
Note: E2E test coverage is currently limited. This is an area for improvement.
Running Tests
npx playwright install # First time only
npm run test:e2e
Page Object Model
Create page objects for reusable page interactions:
// e2e/pages/login-page.ts
import { type Page, type Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: /log in/i });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
}
Using Page Objects in Tests
// e2e/auth/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login-page';
test.describe('Login', () => {
test('successful login redirects to dashboard', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/');
});
test('invalid credentials shows error', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('wrong@example.com', 'wrongpassword');
await loginPage.expectError('Invalid email or password');
});
});
Selector Priority
Semantic selectors first:
page.getByRole('button', { name: /submit/i }); page.getByLabel('Email address'); page.getByText('Welcome back');Fallback to test IDs:
page.getByTestId('stack-trace');Avoid implementation details:
// ❌ Avoid CSS classes and IDs page.locator('.btn-primary');
Backend Data Setup
E2E tests run against the full Aspire stack. The backend uses the same AppWebHostFactory infrastructure from backend-testing.
For tests requiring specific data, consider:
- API calls in beforeEach — Use Playwright's request context to set up data
- Test-specific endpoints — Create
/api/test/*endpoints for test data management - Database seeding — Seed required data before test runs
- Aspire orchestration — Tests start with Elasticsearch and Redis pre-configured
test.beforeEach(async ({ request }) => {
// Set up test data via API
await request.post('/api/test/seed', {
data: { scenario: 'events-with-errors' }
});
});
test.afterEach(async ({ request }) => {
await request.delete('/api/test/cleanup');
});
Note: Backend services use in-memory implementations during tests. See AppWebHostFactory for how test infrastructure is configured.
Accessibility Audits
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('login page has no accessibility violations', async ({ page }) => {
await page.goto('/login');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
See accessibility for WCAG guidelines.