From harness-claude
Implements E2E browser tests using Playwright, Cypress, or Selenium: scaffolds page objects, critical-path tests for UI flows, and remediates flakiness.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> End-to-end browser testing with Playwright, Cypress, or Selenium. Covers page object scaffolding, critical-path test implementation, and systematic flakiness remediation.
Executes end-to-end tests for full user workflows across frontend and backend using Playwright, Cypress, or Selenium. Use for specialized testing of user journeys.
Builds E2E test suites for critical user journeys using Playwright or Cypress, with page objects, setup/teardown, and CI config.
Share bugs, ideas, or general feedback.
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('test-user@example.com');
// 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('customer@shop.com', '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.