From harness-claude
Generates page objects and test infrastructure for Playwright, Cypress, or Selenium E2E tests. Covers critical-path test implementation and flakiness remediation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:harness-e2eThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> End-to-end browser testing with Playwright, Cypress, or Selenium. Covers page object scaffolding, critical-path test implementation, and systematic flakiness remediation.
End-to-end browser testing with Playwright, Cypress, or Selenium. Covers page object scaffolding, critical-path test implementation, and systematic flakiness remediation.
Scan for E2E configuration. Search for playwright.config.ts, playwright.config.js, cypress.config.ts, cypress.config.js, wdio.conf.js, or selenium directories. If multiple frameworks are present, prefer the one with the most existing tests.
Catalog existing E2E tests. Glob for *.spec.ts, *.e2e.ts, *.cy.ts, *.test.ts within E2E directories. Count tests per file and identify patterns: naming conventions, folder structure, shared utilities.
Map application entry points. Identify the base URL, authentication flow, and route structure. Check for:
.env.test, playwright.config.ts baseURL)Identify the test execution environment. Determine whether tests run against a dev server, a preview deployment, or a Docker Compose stack. Check package.json scripts for e2e, test:e2e, or playwright test commands.
Report findings. Summarize: framework detected, number of existing tests, coverage gaps relative to application routes, and any configuration issues (missing base URL, no auth setup).
Create the page object directory. Follow the project's existing conventions. If no convention exists, use e2e/pages/ for Playwright or cypress/pages/ for Cypress.
Generate page objects for target flows. Each page object encapsulates:
data-testid, role, aria-label) -- never CSS classes or XPath positional selectorsCreate shared fixtures and helpers. Generate:
Configure test parallelization. Set up:
Verify scaffold compiles. Run the test command with --list or --dry-run to confirm all imports resolve and page objects instantiate without errors.
Prioritize user flows by business impact. Order test implementation:
Write each test following the Arrange-Act-Assert pattern.
Use explicit waits, never arbitrary timeouts. Where the framework provides an auto-waiting mechanism (Playwright expect with auto-retry, Cypress implicit waits), rely on it. Where explicit waits are needed, wait for specific network responses, DOM mutations, or URL changes -- never page.waitForTimeout().
Isolate tests from each other. Each test must:
Tag tests by scope. Apply tags or annotations:
@smoke for tests that must pass on every deployment@critical-path for primary business flow coverage@slow for tests that exceed 30 secondsRun the full E2E suite. Verify all tests pass locally before proceeding to validation.
Run the suite 3 times consecutively. Track pass/fail per test across runs. Any test that fails in at least one run but passes in another is flagged as flaky.
Classify flaky tests by root cause. Common categories:
Apply remediation for each flaky test. Do not simply add retries -- fix the root cause. Retries mask problems. After remediation, rerun the previously-flaky test 5 times to confirm stability.
Run harness validate. Confirm the project passes all harness checks with the new E2E tests in place.
Generate a coverage summary. Report:
If a knowledge graph exists at .harness/graph/, refresh it after code changes to keep graph queries accurate:
harness scan [path]
harness validate -- Run in VALIDATE phase after all tests are implemented. Confirms project health including new E2E infrastructure.harness check-deps -- Run after SCAFFOLD phase to verify E2E test dependencies do not introduce forbidden imports into production code.emit_interaction -- Used at checkpoints to present flakiness findings and remediation options to the human for approval.data-testid, ARIA roles) -- no CSS class selectors or XPath positional selectorswaitForTimeout, cy.wait(N), Thread.sleep)harness validate passes after the full suite is in placeDETECT output:
Framework: Playwright 1.42 (playwright.config.ts found)
Existing tests: 12 specs in e2e/tests/
Base URL: http://localhost:3000
Auth: Cookie-based, no stored auth state found
Coverage gaps: settings page, billing flow, team invitation
SCAFFOLD -- Page object for dashboard:
// e2e/pages/dashboard.page.ts
import { type Page, type Locator, expect } from '@playwright/test';
export class DashboardPage {
readonly page: Page;
readonly heading: Locator;
readonly projectList: Locator;
readonly createButton: Locator;
constructor(page: Page) {
this.page = page;
this.heading = page.getByRole('heading', { name: 'Dashboard' });
this.projectList = page.getByTestId('project-list');
this.createButton = page.getByRole('button', { name: 'New Project' });
}
async goto() {
await this.page.goto('/dashboard');
await expect(this.heading).toBeVisible();
}
async createProject(name: string) {
await this.createButton.click();
await this.page.getByLabel('Project name').fill(name);
await this.page.getByRole('button', { name: 'Create' }).click();
await expect(this.page.getByText(name)).toBeVisible();
}
async expectProjectCount(count: number) {
await expect(this.projectList.getByRole('listitem')).toHaveCount(count);
}
}
IMPLEMENT -- Critical path test:
// e2e/tests/project-creation.spec.ts
import { test, expect } from '@playwright/test';
import { DashboardPage } from '../pages/dashboard.page';
import { LoginPage } from '../pages/login.page';
test.describe('Project creation', () => {
test('user can create a project from the dashboard', async ({ page }) => {
// Arrange: authenticate via stored state
const loginPage = new LoginPage(page);
await loginPage.loginAs('[email protected]');
// Act: create a new project
const dashboard = new DashboardPage(page);
await dashboard.goto();
await dashboard.createProject('My Test Project');
// Assert: project appears in the list
await expect(page.getByText('My Test Project')).toBeVisible();
await expect(page).toHaveURL(/\/projects\/[\w-]+/);
});
});
IMPLEMENT -- Checkout flow with network interception:
// cypress/e2e/checkout.cy.ts
describe('Checkout flow', () => {
beforeEach(() => {
cy.intercept('POST', '/api/orders', { fixture: 'order-success.json' }).as('createOrder');
cy.loginByApi('[email protected]', 'testpass123');
});
it('completes checkout with valid payment', () => {
cy.visit('/cart');
cy.findByTestId('cart-item').should('have.length.at.least', 1);
cy.findByRole('button', { name: 'Proceed to Checkout' }).click();
cy.url().should('include', '/checkout');
cy.findByLabelText('Card number').type('4242424242424242');
cy.findByLabelText('Expiry').type('12/28');
cy.findByLabelText('CVC').type('123');
cy.findByRole('button', { name: 'Place Order' }).click();
cy.wait('@createOrder');
cy.findByRole('heading', { name: 'Order Confirmed' }).should('be.visible');
});
});
| Rationalization | Why It Is Wrong |
|---|---|
| "Using CSS class selectors is faster than adding data-testid attributes" | No CSS class selectors in page objects. .btn-primary breaks when the design system updates class names. Use data-testid, ARIA roles, and accessible labels. |
| "Adding a short waitForTimeout is easier than figuring out the right wait condition" | No arbitrary waits is a hard gate. waitForTimeout is a flakiness timebomb. Wait for specific conditions: network responses, DOM mutations, or URL changes. |
| "This test creates data through the UI because the API setup is complex" | Test data must be created via API or fixtures, not through UI interactions. UI-based setup is slow, brittle, and conflates setup failures with assertion failures. |
| "The test only fails sometimes in CI -- adding a retry will fix it" | Flaky tests block merge. Diagnose the root cause. Retries mask problems. After remediation, rerun 5 times to confirm stability. |
.btn-primary or [class*="header"], the test is brittle. Use data-testid, ARIA roles, or accessible labels. Rewrite before merging.waitForTimeout, cy.wait(number), or Thread.sleep, it is not ready. Replace with explicit condition waits.npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeConfigures and writes end-to-end tests with Playwright or Cypress for validating user flows, browser integration, CI E2E tests, acceptance tests, and production smoke tests.
Build E2E test specs for critical user journeys — Playwright or Cypress, page objects, setup/teardown, CI config. Use when asked to "write E2E tests", "end-to-end testing", "browser tests", "UI tests", or "Playwright tests".