| name | browser-testing |
| description | Playwright E2E testing and responsive screenshot testing across 12 viewports. Use when writing E2E tests, taking screenshots, or verifying layouts. Triggers: "playwright", "e2e", "responsive", "screenshot", "viewport", "mobile", "tablet", "desktop".
|
| category | web-ui |
Skill: browser-testing
Playwright Best Practices
Test User-Visible Behavior
Test what users see and interact with, not implementation details.
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
await expect(page.locator('.btn-primary')).toBeVisible();
Use Web-First Assertions
Playwright waits for conditions automatically.
await expect(page.getByText('Welcome')).toBeVisible();
expect(await page.getByText('Welcome').isVisible()).toBe(true);
Prefer User-Facing Locators
Use locators that are resilient to DOM changes.
page.getByRole('button', { name: 'submit' });
page.getByLabel('Username');
page.getByTestId('login-form');
page.locator('button.buttonIcon.episode-actions-later');
Use Chaining and Filtering
Narrow down locators for precision.
const product = page.getByRole('listitem').filter({ hasText: 'Product 2' });
await product.getByRole('button', { name: 'Add to cart' }).click();
Test Isolation
Each test should be independent with its own state.
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
Avoid Testing Third-Party Dependencies
Mock external APIs and services.
await page.route('**/api/external', route => route.fulfill({
status: 200,
body: JSON.stringify({ data: 'mocked' }),
}));
12-Viewport Matrix
| Viewport | Size | Name |
|---|
| Mobile narrow | 375x667 | mobile-narrow |
| Mobile standard | 390x844 | mobile-standard |
| Mobile landscape | 667x375 | mobile-landscape |
| Tablet portrait | 768x1024 | tablet-portrait |
| Tablet landscape | 1024x768 | tablet-landscape |
| Small desktop | 1280x800 | small-desktop |
| Desktop | 1440x900 | desktop |
| Wide desktop | 1536x864 | wide-desktop |
| Ultra-wide | 1920x1080 | ultra-wide |
| Ultra-wide plus | 2560x1440 | ultra-wide-plus |
| Foldable folded | 360x760 | foldable-folded |
| Foldable unfolded | 720x840 | foldable-unfolded |
Page Object Models
Encapsulate page interactions in reusable classes.
import { expect, type Locator, type Page } from '@playwright/test';
export class HomePage {
readonly page: Page;
readonly hero: Locator;
readonly features: Locator;
readonly ctaButton: Locator;
constructor(page: Page) {
this.page = page;
this.hero = page.locator('#main');
this.features = page.locator('#features');
this.ctaButton = page.getByRole('link', { name: 'Get Started' });
}
async goto() {
await this.page.goto('/');
}
async scrollToFeatures() {
await this.features.scrollIntoViewIfNeeded();
}
}
Accessibility Testing
Install @axe-core/playwright for WCAG testing.
npm install -D @axe-core/playwright
import AxeBuilder from '@axe-core/playwright';
test('should have no accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
expect(results.violations).toEqual([]);
});
Commands
npm run test:e2e
npm run test:e2e:ui
npm run test:e2e:report
npx playwright test --debug tests/e2e/react-app.spec.ts
npx playwright codegen http://localhost:5173
bash scripts/responsive-screenshot-matrix.sh http://localhost:5173
Configuration
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
});
What to Check
- Zero horizontal scroll at any viewport
- No layout drift or content overflow
- Touch targets >= 44px on mobile
- Bottom nav visible only on mobile (<768px)
- Sidebar collapses to drawer on tablet (<1024px)
Rules
- Use web-first assertions (don't check
.isVisible() manually)
- Prefer user-facing locators over CSS selectors
- Test user-visible behavior, not implementation details
- Each test should be isolated and independent
- Mock third-party APIs and services
- Run accessibility scans on every page
- Use Page Object Models for reusable interactions