# ClawManager V2 E2E P0 Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build the first e2e development stage: the reusable Playwright/TypeScript harness plus the P0 pull-request gate.

**Architecture:** Add a root `e2e/` package that owns Playwright tests, shared fixtures, page objects, lifecycle scripts, Docker MySQL dependencies, logs, reports, and P0 specs. The harness starts isolated MySQL, resets the e2e database, runs the Go backend and Vite frontend, executes Playwright, writes logs/reports, and integrates `ci:p0` into GitHub Actions.

**Tech Stack:** TypeScript, Playwright Test, Node.js, Docker Compose, MySQL 8, Go backend, Vite frontend, GitHub Actions.

---

## Scope

This plan implements Batch 0 and Batch 1 from `docs/superpowers/specs/2026-06-03-clawmanager-v2-e2e-design.md`.

Included:
- Root `e2e/` package and `package-lock.json`.
- Reusable TypeScript fixture modules.
- Shared page objects.
- Lifecycle scripts with logs.
- P0 Playwright specs.
- GitHub Actions `e2e-p0` PR check.
- Verification by running `npm run ci:p0` locally when dependencies are available.

Excluded:
- P1 workspace file tests.
- P2 k8s browser tests.
- Any changes to production backend or frontend behavior unless a P0 test exposes a blocking issue.

## File Structure

- Create: `e2e/package.json` - e2e scripts and dependencies.
- Create: `e2e/package-lock.json` - generated by `npm install`.
- Create: `e2e/playwright.config.ts` - reporters, browser project, output dirs.
- Create: `e2e/tsconfig.json` - TypeScript config for Playwright and scripts.
- Create: `e2e/.gitignore` - generated report and state ignores.
- Create: `e2e/README.md` - local and CI usage.
- Create: `e2e/docker/docker-compose.e2e.yml` - isolated MySQL service.
- Create: `e2e/scripts/reset-db.ts` - drop/create the e2e database.
- Create: `e2e/scripts/wait-for.ts` - reusable TCP/HTTP readiness utilities.
- Create: `e2e/scripts/run-with-log.ts` - orchestrate dependencies, run Playwright, write `reports/run.log`.
- Create: `e2e/scripts/e2e-up.ps1` and `e2e/scripts/e2e-down.ps1` - Windows helper scripts.
- Create: `e2e/scripts/e2e-up.sh` and `e2e/scripts/e2e-down.sh` - Unix helper scripts.
- Create: `e2e/setup/auth.setup.ts` - write admin storage state.
- Create: `e2e/setup/db.setup.ts` - register one deterministic runtime pod through API.
- Create: `e2e/fixtures/env.ts` - typed environment config.
- Create: `e2e/fixtures/users.ts` - seeded user constants.
- Create: `e2e/fixtures/apiClient.ts` - shared API client helpers.
- Create: `e2e/fixtures/test.ts` - shared Playwright test export.
- Create: `e2e/pages/LoginPage.ts` - login page object.
- Create: `e2e/pages/AdminRuntimePodsPage.ts` - runtime Pods page object.
- Create: `e2e/tests/smoke/login.spec.ts` - P0 login UI tests.
- Create: `e2e/tests/smoke/navigation.spec.ts` - P0 route protection/navigation tests.
- Create: `e2e/tests/admin/access-control.spec.ts` - P0 admin route access tests.
- Create: `e2e/tests/admin/runtime-pods.spec.ts` - P0 runtime Pods UI tests.
- Create: `e2e/tests/api/auth.spec.ts` - P0 auth API tests.
- Create: `e2e/tests/api/runtime-agent.spec.ts` - P0 runtime-agent API tests.
- Create: `e2e/reports/.gitkeep` - keep reports directory tracked.
- Modify: `.github/workflows/build.yml` - add `workflow_dispatch` input and `e2e-p0` job.

## Shared Conventions

Environment values:

```text
E2E_FRONTEND_URL=http://127.0.0.1:9002
E2E_BACKEND_URL=http://127.0.0.1:9001/api/v1
E2E_DB_HOST=127.0.0.1
E2E_DB_PORT=13307
E2E_DB_USER=root
E2E_DB_PASSWORD=123456
E2E_DB_NAME=clawmanager_e2e
E2E_RUNTIME_AGENT_TOKEN=e2e-runtime-agent-token
E2E_WORKSPACE_ROOT=<repo>/.cache/e2e/workspaces
```

Backend runtime env overrides:

```text
SERVER_ADDRESS=:9001
DB_HOST=127.0.0.1
DB_PORT=13307
DB_USER=root
DB_PASSWORD=123456
DB_NAME=clawmanager_e2e
JWT_SECRET=clawmanager-e2e-secret
RUNTIME_SCHEDULER_ENABLED=false
RUNTIME_AGENT_REPORT_TOKEN=e2e-runtime-agent-token
RUNTIME_WORKSPACE_ROOT=<repo>/.cache/e2e/workspaces
OBJECT_STORAGE_LOCAL_FALLBACK=<repo>/.cache/e2e/object-storage
SKILL_SCANNER_ENABLED=false
```

## Task 1: Scaffold The E2E Package

**Files:**
- Create: `e2e/package.json`
- Create: `e2e/tsconfig.json`
- Create: `e2e/.gitignore`
- Create: `e2e/reports/.gitkeep`
- Create: `e2e/README.md`
- Generate: `e2e/package-lock.json`

- [ ] **Step 1: Create package metadata and scripts**

Create `e2e/package.json`:

```json
{
  "name": "clawmanager-e2e",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "test:p0": "tsx scripts/run-with-log.ts p0",
    "test:p1": "tsx scripts/run-with-log.ts p1",
    "test:p2": "tsx scripts/run-with-log.ts p2",
    "test:all": "tsx scripts/run-with-log.ts all",
    "ci:p0": "tsx scripts/run-with-log.ts p0 --ci",
    "pw:p0": "playwright test --grep @p0",
    "pw:p1": "playwright test --grep @p1",
    "pw:p2": "playwright test --grep @p2",
    "pw:all": "playwright test",
    "test:report": "playwright show-report playwright-report",
    "db:reset": "tsx scripts/reset-db.ts"
  },
  "devDependencies": {
    "@playwright/test": "^1.49.1",
    "@types/node": "^24.12.0",
    "mysql2": "^3.11.5",
    "tsx": "^4.19.2",
    "typescript": "^5.9.3"
  }
}
```

- [ ] **Step 2: Create TypeScript config**

Create `e2e/tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "types": ["node", "@playwright/test"],
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "noEmit": true
  },
  "include": ["**/*.ts"]
}
```

- [ ] **Step 3: Create ignores and report marker**

Create `e2e/.gitignore`:

```gitignore
node_modules/
playwright-report/
test-results/
reports/*
!reports/.gitkeep
.auth/
.cache/
```

Create empty file `e2e/reports/.gitkeep`.

- [ ] **Step 4: Create README**

Create `e2e/README.md` with:

```markdown
# ClawManager E2E

This directory contains Playwright-based TypeScript e2e tests.

## First-Time Setup

```bash
npm ci
npx playwright install chromium
```

## Run Locally

```bash
npm run test:p0
npm run test:report
```

The default local gate is P0. P1, P2, and all tests are explicit:

```bash
npm run test:p1
npm run test:p2
npm run test:all
```

## Reports

- Live progress appears in the terminal.
- Run log: `reports/run.log`
- JSON: `reports/results.json`
- JUnit: `reports/junit.xml`
- HTML: `playwright-report/`
- Raw artifacts: `test-results/`
```

- [ ] **Step 5: Install dependencies and generate lockfile**

Run:

```powershell
npm install
```

Working directory: `e2e`.

Expected: `package-lock.json` is generated and npm exits with code 0.

- [ ] **Step 6: Commit scaffold**

Run:

```powershell
git add e2e/package.json e2e/package-lock.json e2e/tsconfig.json e2e/.gitignore e2e/README.md e2e/reports/.gitkeep
git commit -m "test: scaffold e2e package"
```

## Task 2: Add Environment, API, And Page Object Modules

**Files:**
- Create: `e2e/fixtures/env.ts`
- Create: `e2e/fixtures/users.ts`
- Create: `e2e/fixtures/apiClient.ts`
- Create: `e2e/fixtures/test.ts`
- Create: `e2e/pages/LoginPage.ts`
- Create: `e2e/pages/AdminRuntimePodsPage.ts`

- [ ] **Step 1: Create environment helper**

Create `e2e/fixtures/env.ts` with typed parsing for URLs, database config, runtime token, workspace root, and CI flag. It must export `env`, `repoRoot`, `e2eRoot`, and `reportsDir`.

- [ ] **Step 2: Create user constants**

Create `e2e/fixtures/users.ts`:

```typescript
export const users = {
  admin: {
    username: "admin",
    password: "admin123",
    role: "admin",
    storageState: ".auth/admin.json"
  }
} as const;
```

- [ ] **Step 3: Create API client helper**

Create `e2e/fixtures/apiClient.ts` with helpers:

```typescript
export interface LoginTokens {
  access_token: string;
  refresh_token: string;
  expires_in: number;
}

export async function login(request: APIRequestContext, user = users.admin): Promise<LoginTokens>;
export async function getMe(request: APIRequestContext, accessToken: string): Promise<{ username: string; role: string }>;
export async function registerRuntimePod(request: APIRequestContext, overrides?: Partial<RuntimePodRegisterRequest>): Promise<RuntimePod>;
export async function sendRuntimeHeartbeat(request: APIRequestContext, identity: RuntimePodIdentity, overrides?: Partial<RuntimeHeartbeatRequest>): Promise<void>;
export async function listRuntimePods(request: APIRequestContext, accessToken: string): Promise<RuntimePod[]>;
```

The implementation must centralize the `X-ClawManager-Agent-Token` header and never log token values.

- [ ] **Step 4: Create shared Playwright export**

Create `e2e/fixtures/test.ts`:

```typescript
import { test as base, expect } from "@playwright/test";
import { env } from "./env";

export const test = base.extend({
  baseURL: async ({}, use) => {
    await use(env.frontendUrl);
  }
});

export { expect };
```

- [ ] **Step 5: Create page objects**

Create `e2e/pages/LoginPage.ts` with `goto()`, `login(username, password)`, and `expectVisible()`.

Create `e2e/pages/AdminRuntimePodsPage.ts` with `goto()`, `expectEmptyState()`, and `expectPodVisible(podName)`.

- [ ] **Step 6: Type-check helper modules**

Run:

```powershell
npx tsc --noEmit
```

Working directory: `e2e`.

Expected: TypeScript exits with code 0.

- [ ] **Step 7: Commit reusable modules**

Run:

```powershell
git add e2e/fixtures e2e/pages
git commit -m "test: add reusable e2e modules"
```

## Task 3: Add Docker And Lifecycle Scripts

**Files:**
- Create: `e2e/docker/docker-compose.e2e.yml`
- Create: `e2e/scripts/reset-db.ts`
- Create: `e2e/scripts/wait-for.ts`
- Create: `e2e/scripts/run-with-log.ts`
- Create: `e2e/scripts/e2e-up.ps1`
- Create: `e2e/scripts/e2e-down.ps1`
- Create: `e2e/scripts/e2e-up.sh`
- Create: `e2e/scripts/e2e-down.sh`

- [ ] **Step 1: Add MySQL compose file**

Create `e2e/docker/docker-compose.e2e.yml` with a MySQL 8 service named `mysql`, host port `13307`, root password `123456`, database `clawmanager_e2e`, and a healthcheck using `mysqladmin ping`.

- [ ] **Step 2: Add database reset script**

Create `e2e/scripts/reset-db.ts` that connects to MySQL without selecting a database, drops `clawmanager_e2e`, creates it with `utf8mb4_unicode_ci`, and exits with a non-zero code on failure.

- [ ] **Step 3: Add readiness helpers**

Create `e2e/scripts/wait-for.ts` exporting `waitForTcpPort`, `waitForHttpStatus`, and `sleep`.

- [ ] **Step 4: Add run-with-log orchestrator**

Create `e2e/scripts/run-with-log.ts` that:

1. Parses suite argument `p0`, `p1`, `p2`, or `all`.
2. Creates `reports/run.log`.
3. Starts Docker Compose MySQL.
4. Runs `reset-db.ts`.
5. Starts backend with e2e env.
6. Waits for backend port `9001`.
7. Starts frontend on `127.0.0.1:9002`.
8. Waits for frontend HTTP 200.
9. Runs `npx playwright test` with the requested suite.
10. Writes terminal output to both console and `reports/run.log`.
11. Stops backend, frontend, and Docker Compose in `finally`.

- [ ] **Step 5: Add platform helper scripts**

Create PowerShell and shell wrappers that run Docker Compose up/down for local manual control:

```powershell
docker compose -f docker/docker-compose.e2e.yml up -d mysql
```

```bash
docker compose -f docker/docker-compose.e2e.yml up -d mysql
```

The down scripts should run Docker Compose `down --remove-orphans`.

- [ ] **Step 6: Verify DB reset can run**

Run:

```powershell
docker compose -f docker/docker-compose.e2e.yml up -d mysql
npm run db:reset
docker compose -f docker/docker-compose.e2e.yml down --remove-orphans
```

Working directory: `e2e`.

Expected: database reset succeeds.

- [ ] **Step 7: Commit lifecycle scripts**

Run:

```powershell
git add e2e/docker e2e/scripts
git commit -m "test: add e2e lifecycle scripts"
```

## Task 4: Add Playwright Configuration And Setup Projects

**Files:**
- Create: `e2e/playwright.config.ts`
- Create: `e2e/setup/auth.setup.ts`
- Create: `e2e/setup/db.setup.ts`

- [ ] **Step 1: Add Playwright config**

Create `e2e/playwright.config.ts` with:

- `testDir: "./tests"`
- reporters: `list`, `html`, `json`, `junit`
- output dir `test-results`
- HTML report dir `playwright-report`
- retries `1` in CI, `0` locally
- Chromium project
- setup projects `auth setup` and `db setup`
- storage state path `.auth/admin.json` for UI projects that require admin auth

- [ ] **Step 2: Add auth setup**

Create `e2e/setup/auth.setup.ts`. It should login through the UI using `LoginPage`, verify localStorage has `access_token`, and write `.auth/admin.json`.

- [ ] **Step 3: Add runtime pod setup**

Create `e2e/setup/db.setup.ts`. It should call `registerRuntimePod` once with pod name `e2e-openclaw-runtime-p0`, namespace `clawmanager-e2e`, runtime type `openclaw`, state `ready`, capacity `100`, used slots `0`, and image `e2e/openclaw:latest`.

- [ ] **Step 4: Verify Playwright list command**

Run:

```powershell
npx playwright test --list
```

Working directory: `e2e`.

Expected: setup projects are listed and no config parse error occurs.

- [ ] **Step 5: Commit config and setup**

Run:

```powershell
git add e2e/playwright.config.ts e2e/setup
git commit -m "test: configure e2e playwright setup"
```

## Task 5: Add P0 API Tests

**Files:**
- Create: `e2e/tests/api/auth.spec.ts`
- Create: `e2e/tests/api/runtime-agent.spec.ts`

- [ ] **Step 1: Add auth API tests**

Create `auth.spec.ts` with P0 tests:

- `@p0 auth login returns tokens`
- `@p0 auth me returns current user with valid token`
- `@p0 auth me rejects unauthenticated requests`

Use `apiClient.login` and `apiClient.getMe`.

- [ ] **Step 2: Add runtime-agent API tests**

Create `runtime-agent.spec.ts` with P0 tests:

- `@p0 runtime-agent register persists an openclaw runtime pod`
- `@p0 runtime-agent heartbeat updates pod health state`
- `@p0 runtime-agent register rejects unsupported runtime type`

Use `apiClient.registerRuntimePod` and `apiClient.sendRuntimeHeartbeat`.

- [ ] **Step 3: Run P0 API tests**

Run:

```powershell
npm run test:p0 -- --grep "auth|runtime-agent"
```

Working directory: `e2e`.

Expected: API tests pass after services start.

- [ ] **Step 4: Commit API tests**

Run:

```powershell
git add e2e/tests/api
git commit -m "test: add p0 e2e api coverage"
```

## Task 6: Add P0 UI Tests

**Files:**
- Create: `e2e/tests/smoke/login.spec.ts`
- Create: `e2e/tests/smoke/navigation.spec.ts`
- Create: `e2e/tests/admin/access-control.spec.ts`
- Create: `e2e/tests/admin/runtime-pods.spec.ts`

- [ ] **Step 1: Add login smoke tests**

Create tests for:

- `@p0 login page renders`
- `@p0 admin can log in`
- `@p0 failed login shows an error`

Use `LoginPage`.

- [ ] **Step 2: Add navigation tests**

Create tests for:

- `@p0 unauthenticated users are redirected to login`
- `@p0 authenticated admin reaches admin dashboard`
- `@p0 authenticated admin can reach instances and runtime pods routes`

Use storage state from `.auth/admin.json`.

- [ ] **Step 3: Add access control tests**

Create tests for:

- `@p0 unauthenticated admin route redirects to login`
- `@p0 admin can open runtime pods route`

- [ ] **Step 4: Add runtime Pods UI tests**

Create tests for:

- `@p0 admin runtime pods page renders no runtime pods when unseeded`
- `@p0 admin runtime pods page renders seeded runtime pod`

If setup always seeds one pod, implement the empty-state test by directly clearing runtime Pods through `db.setup.ts` helper before visiting the page, then reseed for the seeded-state test.

- [ ] **Step 5: Run P0 UI tests**

Run:

```powershell
npm run test:p0 -- --grep "login|navigation|runtime pods|access"
```

Working directory: `e2e`.

Expected: UI tests pass after services start.

- [ ] **Step 6: Commit UI tests**

Run:

```powershell
git add e2e/tests/smoke e2e/tests/admin
git commit -m "test: add p0 e2e ui coverage"
```

## Task 7: Add GitHub Actions E2E Job

**Files:**
- Modify: `.github/workflows/build.yml`

- [ ] **Step 1: Add workflow dispatch input**

Modify `.github/workflows/build.yml` to add `workflow_dispatch` with `e2e_suite` choices `p0`, `p1`, `p2`, and `all`, defaulting to `p0`.

- [ ] **Step 2: Add e2e-p0 job**

Add job `e2e-p0`:

- `runs-on: ubuntu-latest`
- `needs: [frontend, backend]`
- checkout
- setup Node 20 with cache for `e2e/package-lock.json`
- setup Go using `backend/go.mod`
- install frontend deps with `npm ci` in `frontend`
- install e2e deps with `npm ci` in `e2e`
- install Chromium with `npx playwright install --with-deps chromium`
- run selected suite. For PRs use `npm run ci:p0`; for manual dispatch map `e2e_suite` to the requested command.
- upload `e2e/playwright-report`, `e2e/test-results`, and `e2e/reports` on failure.

- [ ] **Step 3: Validate workflow syntax**

Run:

```powershell
git diff -- .github/workflows/build.yml
```

Expected: e2e job is present, YAML indentation is valid on visual inspection, existing jobs are untouched except for workflow triggers.

- [ ] **Step 4: Commit GitHub Actions integration**

Run:

```powershell
git add .github/workflows/build.yml
git commit -m "ci: add p0 e2e pr check"
```

## Task 8: Full P0 Verification

**Files:**
- Verify all files changed in Tasks 1-7.

- [ ] **Step 1: Run TypeScript check**

Run:

```powershell
npx tsc --noEmit
```

Working directory: `e2e`.

Expected: exits with code 0.

- [ ] **Step 2: Run P0 suite**

Run:

```powershell
npm run ci:p0
```

Working directory: `e2e`.

Expected: P0 tests pass, `reports/run.log`, `reports/results.json`, `reports/junit.xml`, and `playwright-report/` are generated.

- [ ] **Step 3: Verify report opens**

Run:

```powershell
npm run test:report -- --host 127.0.0.1
```

Expected: Playwright report server starts. Stop it after confirming no report generation error.

- [ ] **Step 4: Run repository build checks affected by CI**

Run:

```powershell
npm run build
```

Working directory: `frontend`.

Run:

```powershell
go test ./...
```

Working directory: `backend`.

Expected: both commands exit with code 0, or any pre-existing failures are reported with exact output.

- [ ] **Step 5: Final status review**

Run:

```powershell
git status --short
git log --oneline -5
```

Expected: only intended files remain changed or all e2e work is committed. Existing unrelated dirty files from before this work remain untouched and unstaged.

## Self-Review Checklist

- Every P0 requirement in the spec maps to a test file.
- Repeated login and API logic is centralized.
- Runtime-agent payload construction exists in one helper.
- URLs and tokens come from `fixtures/env.ts`.
- Report artifacts match the spec paths.
- GitHub PR check uses `npm run ci:p0`.
- Local manual commands are documented.
- No production frontend/backend code is changed for test convenience.
