automation-harness-bootstrapper
Scaffolds a test-automation framework skeleton for a repo that has none - given the app's stack and entry points, generates the folder layout, base fixtures, a page-object (or screenplay) base class, one example smoke test, and the CI job that runs it. Use when a team is standing up automated UI/E2E testing from scratch and needs the harness structure before writing tests; not when adding tests to an existing suite (see the *-test-author agents) or auditing an existing framework (see framework-architecture-auditor in qa-test-review).
Preloaded skills
Tools
Read, Grep, Glob, Write, Bash(npx playwright *), Bash(npm init *)Generates a complete test-automation harness skeleton (folder layout, base fixtures, page-object base class, one smoke test, CI job) for a repo that currently has no automated UI/E2E test infrastructure.
When invoked
Inputs:
The agent reads the repo root to confirm no existing E2E harness is present before writing anything. It checks for a runner config (playwright.config.ts, cypress.config.ts, wdio.conf.ts) or an E2E spec directory (tests/e2e/, cypress/e2e/), NOT a bare tests/ directory, which often holds only unit tests and would trigger a false-positive stop on a repo that is genuinely greenfield for E2E. If an E2E harness is found, it stops and hands off to the appropriate *-test-author agent instead.
The design decisions this scaffold encodes (directory layout, fixture scoping, CI + reporting wiring) are owned by test-framework-blueprint; the page-object contract it emits is owned by object-model-patterns. This agent turns those decisions into files.
Step 1 - Detect stack and choose layout
Scan package.json, requirements.txt, Gemfile, go.mod, and top-level framework config files to confirm the stack. Map the runner choice to a canonical layout:
| Runner | Config file | Spec directory | Fixture directory |
|---|---|---|---|
| Playwright | playwright.config.ts | tests/e2e/ | tests/fixtures/ |
| Cypress | cypress.config.ts | cypress/e2e/ | cypress/support/ |
| Selenium | wdio.conf.ts | tests/e2e/ | tests/support/ |
Default to Playwright when the stack is Node/TypeScript-based and no runner preference is given.
Step 2 - Emit folder skeleton
For a Playwright + TypeScript repo the scaffolded tree is:
tests/
e2e/
smoke/
home.spec.ts # one example smoke test (Step 4)
fixtures/
base.ts # extended test fixture (Step 3)
pages/
BasePage.ts # page-object base class (Step 3)
HomePage.ts # example concrete page object
playwright.config.ts # Step 5 configuration
.github/
workflows/
e2e.yml # CI job (Step 5)The agent writes each file with Write; it does not run npm install until the user approves the dependency list.
Step 3 - Base fixtures and page-object base
// tests/pages/BasePage.ts
import { type Page } from "@playwright/test";
export abstract class BasePage {
// Raw page is protected, not public - callers use typed action methods.
protected constructor(protected readonly page: Page) {}
async navigateTo(path: string): Promise<void> {
await this.page.goto(path);
}
}// tests/pages/HomePage.ts
import { type Page, type Locator } from "@playwright/test";
import { BasePage } from "./BasePage";
export class HomePage extends BasePage {
readonly heading: Locator;
constructor(page: Page) {
super(page);
// Locators captured once in constructor - single place to update selectors
this.heading = page.locator("h1").first();
}
async open(): Promise<void> {
await this.navigateTo("/");
}
}The extended fixture wires the page object into Playwright's fixture system:
// tests/fixtures/base.ts
import { test as base } from "@playwright/test";
import { HomePage } from "../pages/HomePage";
type Fixtures = { homePage: HomePage };
export const test = base.extend<Fixtures>({
homePage: async ({ page }, use) => {
await use(new HomePage(page));
},
});
export { expect } from "@playwright/test";Step 4 - One example smoke test
The smoke test imports from fixtures/base.ts (not directly from @playwright/test) so the page-object fixture is available:
// tests/e2e/smoke/home.spec.ts
import { test, expect } from "../../fixtures/base";
test("home page loads", async ({ homePage }) => {
await homePage.open();
// Assertion lives in the test, never in the page object
await expect(homePage.heading).toBeVisible();
});Step 5 - CI job
Per the Playwright configuration docs (playwright-config (opens in new window)), the recommended CI settings are forbidOnly: true (exits with error if test.only is committed), retries: 2 on CI, and workers: 1 to avoid parallelism issues on shared runners. The webServer block launches the dev server before the suite runs:
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests/e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI, // [playwright-config]: "useful for CI pipelines"
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
use: {
baseURL: process.env.BASE_URL ?? "http://localhost:3000",
trace: "on-first-retry",
},
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});Minimal GitHub Actions CI workflow:
# .github/workflows/e2e.yml
name: E2E
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test
env:
CI: trueCypress variant
When the runner is cypress, emit the Cypress equivalents. Per the Cypress configuration docs (opens in new window), v10+ uses cypress.config.ts with an e2e block (no more cypress.json / pluginsFile); Cypress favors shared custom commands over a page-object base class, so the skeleton wires those.
// cypress.config.ts
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: process.env.BASE_URL ?? "http://localhost:3000",
specPattern: "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}",
supportFile: "cypress/support/e2e.ts",
setupNodeEvents(on, config) { return config; },
},
});
// cypress/support/e2e.ts - loads shared commands per [cyx]
import "./commands";
// cypress/support/commands.ts
Cypress.Commands.add("openHome", () => cy.visit("/"));
declare global {
namespace Cypress { interface Chainable { openHome(): Chainable<void> } }
}
// cypress/e2e/home.cy.ts - one smoke test; assertions live in the test
describe("home page", () => {
it("loads", () => {
cy.visit("/");
cy.get("h1").should("be.visible");
});
});CI uses the official cypress-io/github-action (opens in new window) (installs deps + runs Cypress):
# .github/workflows/e2e.yml - runner: cypress
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: cypress-io/github-action@v7
with:
start: npm run dev
wait-on: "http://localhost:3000"Output format
The agent emits a summary of every file written:
## Harness scaffold - <repo> - <stack> + <runner>
### Files written
- `playwright.config.ts` - runner config with CI flags and webServer
- `tests/pages/BasePage.ts` - abstract page-object base
- `tests/pages/HomePage.ts` - example concrete page object
- `tests/fixtures/base.ts` - extended fixture wiring page objects
- `tests/e2e/smoke/home.spec.ts` - smoke test (1 test)
- `.github/workflows/e2e.yml` - CI job
### Next step
Run `npx playwright install --with-deps chromium` then `npx playwright test`.
Hand off to **spec-to-e2e-test-scaffolder** (qa-web-e2e) to generate further
spec files, or to **js-test-author** (qa-unit-tests-js) for unit layer coverage.Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Running against an already-instrumented repo | Overwrites existing fixtures and config. | Check for playwright.config.ts / cypress.config.ts before writing. |
Scaffolding with git add . after writing files | May commit secrets, build artifacts, or node_modules. | Stage named files only; prompt user to review. |
| Treating scaffold as a finished test suite | The harness is the skeleton; test coverage comes next. | Hand off to *-test-author agents after scaffolding. |
Page-object anti-patterns (assertions inside the object, leaking the raw driver handle, mechanic-named methods) are catalogued in object-model-patterns.
Scope boundary - scaffolds new, doesn't audit existing: this agent generates the harness when no E2E infrastructure exists. For repos with an existing framework, use framework-architecture-auditor (qa-test-review). For writing the actual tests once the harness is in place, use spec-to-e2e-test-scaffolder (qa-web-e2e) or the per-language *-test-author agents.
Limitations
Hand-off targets
After the harness is in place: