From harness-claude
Writes maintainable Playwright E2E tests using page objects, accessible locators, fixtures, and parallel execution. Helps debug flaky tests and manage complex user flows.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:test-playwright-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Write maintainable Playwright tests using page objects, fixtures, and parallel execution
Write maintainable Playwright tests using page objects, fixtures, and parallel execution
// e2e/pages/login-page.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(private page: Page) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Log in' });
}
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();
}
}
// Best — accessible
page.getByRole('button', { name: 'Submit' });
page.getByLabel('Email address');
page.getByText('Welcome back');
page.getByPlaceholder('Search...');
// Acceptable — test IDs
page.getByTestId('user-avatar');
// Avoid — brittle
page.locator('.btn-primary');
page.locator('#submit-form');
page.locator('div > span:nth-child(2)');
import { test, expect } from '@playwright/test';
test('user can create and publish a post', async ({ page }) => {
await page.goto('/posts/new');
await page.getByLabel('Title').fill('My First Post');
await page.getByLabel('Content').fill('Hello, world!');
await page.getByRole('button', { name: 'Save draft' }).click();
await expect(page.getByText('Draft saved')).toBeVisible();
await page.getByRole('button', { name: 'Publish' }).click();
await expect(page).toHaveURL(/\/posts\/[\w-]+$/);
await expect(page.getByRole('heading', { name: 'My First Post' })).toBeVisible();
});
// Playwright auto-waits for elements to be actionable
await page.getByRole('button', { name: 'Submit' }).click(); // Waits for button to be enabled
// Wait for navigation
await Promise.all([
page.waitForURL('/dashboard'),
page.getByRole('button', { name: 'Log in' }).click(),
]);
// Wait for API response
const responsePromise = page.waitForResponse('/api/users');
await page.getByRole('button', { name: 'Load users' }).click();
const response = await responsePromise;
expect(response.status()).toBe(200);
page.on('dialog', (dialog) => dialog.accept());
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Item deleted')).toBeVisible();
test('homepage matches snapshot', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixels: 100,
});
});
// Each test gets its own browser context
test('user A sees their data', async ({ page }) => {
/* ... */
});
test('user B sees their data', async ({ page }) => {
/* ... */
});
// These run in parallel without interference
test('critical: checkout flow', { tag: '@critical' }, async ({ page }) => {
// ...
});
// Run only critical tests
// npx playwright test --grep @critical
Playwright tests run against real browsers with full DOM, network, and JavaScript execution. They are the closest thing to manual testing that can be automated.
Auto-waiting: Playwright automatically waits for elements to be visible, enabled, and stable before interacting. This eliminates most explicit waits and reduces flakiness compared to Selenium or Cypress.
Locator best practices:
getByRole — always first choice. Forces accessible markupgetByLabel — for form inputs, anchored to their labelgetByText — for visible text contentgetByTestId — when no accessible name exists. Add data-testid attributesDebugging tools:
npx playwright test --ui — interactive test runner with time-travel debuggingnpx playwright test --debug — step through with browser DevToolsnpx playwright show-trace trace.zip — replay recorded tracesHandling flakiness:
retries: 2 in CI as a safety net, but investigate repeated flakesTrade-offs:
https://playwright.dev/docs/test-patterns
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeWrites and debugs E2E tests with Playwright using Page Object Model, API mocking, and visual regression. Configures test infrastructure and CI integration.
Guides writing E2E tests with Playwright, configuring test infrastructure, debugging flaky browser tests, creating page objects, setting up fixtures, reporters, CI integration, API mocking, and visual regression testing.
Write Playwright E2E tests using fixtures and best practices. Use when creating E2E tests, writing browser automation tests, or testing user flows.