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
This skill provides guidance for writing and debugging Playwright E2E tests for Landbruget.dk.
This skill activates when:
cd frontend
# Run all tests
npm test
# Run specific test file
npm test -- e2e/feature.spec.ts
# Run tests matching pattern
npm test -- --grep "search"
# Run in UI mode (interactive)
npm run test:ui
# Run smoke tests only (fast feedback)
npm run test:smoke
# Run with browser visible
npm run test:headed
# Debug mode
npm test -- --debug
// e2e/feature-name.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Feature Name', () => {
test.beforeEach(async ({ page }) => {
// Setup before each test
await page.goto('/');
});
test('should [describe expected behavior]', async ({ page }) => {
// Arrange - Setup test state
const input = page.locator('[data-testid="search-input"]');
// Act - Perform actions
await input.fill('test query');
await page.click('[data-testid="search-button"]');
// Assert - Verify results
await expect(page.locator('[data-testid="results"]')).toBeVisible();
await expect(page.locator('.result-item')).toHaveCount(5);
});
});
Always use data-testid attributes for reliable test selectors:
// In component
<button data-testid="submit-button">Submit</button>
// In test
await page.click('[data-testid="submit-button"]');
Naming Convention:
data-testid="field-search-input"data-testid="map-zoom-in" not data-testid="button"data-testid="farm-details-close" not data-testid="close"// Wait for element to be visible
await expect(page.locator('[data-testid="loading"]')).toBeHidden();
await expect(page.locator('[data-testid="content"]')).toBeVisible();
// Wait for network request
await page.waitForResponse('**/api/fields');
// Wait for navigation
await Promise.all([
page.waitForNavigation(),
page.click('[data-testid="nav-link"]'),
]);
test('should submit form successfully', async ({ page }) => {
// Fill form
await page.fill('[data-testid="name-input"]', 'Test Farm');
await page.selectOption('[data-testid="type-select"]', 'agriculture');
await page.check('[data-testid="terms-checkbox"]');
// Submit
await page.click('[data-testid="submit-button"]');
// Verify success
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});
test('should zoom map on scroll', async ({ page }) => {
const map = page.locator('[data-testid="map-container"]');
// Get initial zoom level
const initialZoom = await map.getAttribute('data-zoom');
// Perform zoom action
await map.hover();
await page.mouse.wheel(0, -100);
// Wait for animation
await page.waitForTimeout(500);
// Verify zoom changed
const newZoom = await map.getAttribute('data-zoom');
expect(Number(newZoom)).toBeGreaterThan(Number(initialZoom));
});
test('should display data from API', async ({ page }) => {
// Mock the API response
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');
// Verify mocked data is displayed
await expect(page.locator('[data-testid="field-name"]')).toHaveText('Test Field');
});
test('should show error message on API failure', async ({ page }) => {
// Mock API error
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();
});
// Use semantic locators when possible
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('test@example.com');
await page.getByText('Welcome').isVisible();
// Take screenshot
await page.screenshot({ path: 'debug.png', fullPage: true });
// Slow down execution
test.use({ actionTimeout: 5000 });
// Pause execution (in debug mode)
await page.pause();
// Capture console messages
page.on('console', (msg) => console_log('Browser:', msg.text()));
// Capture network errors
page.on('requestfailed', (request) =>
console_log('Failed:', request.url(), request.failure()?.errorText)
);
Create fast smoke tests for critical paths:
// e2e/smoke/critical-paths.spec.ts
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();
});
});
Before marking test work complete:
npx skills add Klimabevaegelsen/playwright-testing下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer