| name | playwright-testing |
| description | Activates when writing E2E tests, debugging test failures, or working with Playwright.
Use this skill for: writing test cases, using data-testid selectors, handling async operations,
mocking API responses, running tests in different modes, and debugging flaky tests.
Keywords: test, playwright, e2e, spec, expect, locator, data-testid, assertion
|
Playwright E2E Testing Skill
This skill provides guidance for writing and debugging Playwright E2E tests for Landbruget.dk.
Activation Context
This skill activates when:
- Writing new E2E tests
- Debugging test failures
- Working with Playwright configuration
- Handling async test operations
- Mocking API responses
Quick Reference
Running Tests
cd frontend
npm test
npm test -- e2e/feature.spec.ts
npm test -- --grep "search"
npm run test:ui
npm run test:smoke
npm run test:headed
npm test -- --debug
Test Structure
Standard Test File
import { test, expect } from '@playwright/test';
test.describe('Feature Name', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('should [describe expected behavior]', async ({ page }) => {
const input = page.locator('[data-testid="search-input"]');
await input.fill('test query');
await page.click('[data-testid="search-button"]');
await expect(page.locator('[data-testid="results"]')).toBeVisible();
await expect(page.locator('.result-item')).toHaveCount(5);
});
});
Data-testid Convention
Always use data-testid attributes for reliable test selectors:
<button data-testid="submit-button">Submit</button>
await page.click('[data-testid="submit-button"]');
Naming Convention:
- Use kebab-case:
data-testid="field-search-input"
- Be specific:
data-testid="map-zoom-in" not data-testid="button"
- Include context:
data-testid="farm-details-close" not data-testid="close"
Common Patterns
Waiting for Elements
await expect(page.locator('[data-testid="loading"]')).toBeHidden();
await expect(page.locator('[data-testid="content"]')).toBeVisible();
await page.waitForResponse('**/api/fields');
await Promise.all([
page.waitForNavigation(),
page.click('[data-testid="nav-link"]'),
]);
Form Testing
test('should submit form successfully', async ({ page }) => {
await page.fill('[data-testid="name-input"]', 'Test Farm');
await page.selectOption('[data-testid="type-select"]', 'agriculture');
await page.check('[data-testid="terms-checkbox"]');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});
Map Testing
test('should zoom map on scroll', async ({ page }) => {
const map = page.locator('[data-testid="map-container"]');
const initialZoom = await map.getAttribute('data-zoom');
await map.hover();
await page.mouse.wheel(0, -100);
await page.waitForTimeout(500);
const newZoom = await map.getAttribute('data-zoom');
expect(Number(newZoom)).toBeGreaterThan(Number(initialZoom));
});
Mocking API Responses
test('should display data from API', async ({ page }) => {
await page.route('**/api/fields', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Test Field', area: 100 },
]),
});
});
await page.goto('/fields');
await expect(page.locator('[data-testid="field-name"]')).toHaveText('Test Field');
});
Error State Testing
test('should show error message on API failure', async ({ page }) => {
await page.route('**/api/fields', async (route) => {
await route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server error' }),
});
});
await page.goto('/fields');
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
await expect(page.locator('[data-testid="retry-button"]')).toBeVisible();
});
Accessibility Testing
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('test@example.com');
await page.getByText('Welcome').isVisible();
Debugging Techniques
Visual Debugging
await page.screenshot({ path: 'debug.png', fullPage: true });
test.use({ actionTimeout: 5000 });
await page.pause();
Console Logging
page.on('console', (msg) => console_log('Browser:', msg.text()));
page.on('requestfailed', (request) =>
console_log('Failed:', request.url(), request.failure()?.errorText)
);
Smoke Tests
Create fast smoke tests for critical paths:
import { test, expect } from '@playwright/test';
test.describe('Smoke Tests', () => {
test('homepage loads', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Landbruget/);
});
test('map displays', async ({ page }) => {
await page.goto('/');
await expect(page.locator('[data-testid="map-container"]')).toBeVisible();
});
test('search works', async ({ page }) => {
await page.goto('/');
await page.fill('[data-testid="search-input"]', '12345678');
await expect(page.locator('[data-testid="search-results"]')).toBeVisible();
});
});
Test Quality Checklist
Before marking test work complete: