Control UI E2E
Control UI design feedback + real browser flows with deterministic Gateway data.
UI Stress Test
- Substantial UI changes: build local HTML stress-test gallery early; let user compare meaningful states and give feedback on concrete examples.
- Use for layouts, interactions, components with multiple states. Small copy/icon edits may skip when gallery adds no useful comparison.
- Derive examples from affected components + data contracts. Cover relevant normal, loading, empty, error, unavailable, permission, selected, expanded states; long text/dense content where they stress layout. Show mutually exclusive states separately; label proposed states unsupported by current implementation.
- One browser-openable HTML overview: stable example IDs, short state labels, enough context to understand each example. Prefer real components + deterministic mock fixtures. Label static/approximate renderings; link running UI for interactions they cannot reproduce. Generated galleries: task-owned artifact storage; no default commit.
- Every example: labeled feedback input. Persist locally across refreshes with gallery-specific storage key + stable example IDs. Copy feedback action exports example IDs, state labels, comments as Markdown/plain text for user to return to conversation.
- Open gallery in available preview browser; share URL/file path. Preserve same gallery, example IDs, existing comments through iteration. Apply user feedback to both gallery + implementation; keep comparable.
- Before requesting feedback: inspect rendered examples at relevant viewport sizes; verify feedback survives refresh + exports correct example labels. Report unsupported states/preview limitations.
Gallery supports design review; never replaces focused behavior tests or inspected before/after proof from running UI. Preserve final proof in fresh capture directories below, separate from evolving gallery.
Test Shape
- Full GUI flows:
ui/src/**/*.e2e.test.ts. ui/src/test-helpers/control-ui-e2e.ts: start Vite Control UI + install mocked Gateway WebSocket.- Deterministic scenarios. No live provider keys, real channel credentials, real Gateway unless user explicitly asks for live proof.
- Narrow rendering logic: prefer existing
.browser.test.ts/unit tests. E2E lane: routing, app boot, Gateway handshake, requests, visible UI behavior together.
Commands
- Target one E2E test in Codex worktree:
node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/e2e/chat-flow.messaging.e2e.test.ts
- Whole local lane in normal checkout:
pnpm test:ui:e2e
- Use ready dependency installation or prepared normal checkout; never reconcile shared install while other jobs use it.
- Follow
$openclaw-testing: trusted development proof may run locally; remote proof needs browser/platform, clean-environment, or source-isolation reason.
Visual Proof Default
- Appearance changes: inspected before/after visual evidence.
- Other behavior: clearest appropriate boundary proof; video + screenshot set not mandatory when assertions already demonstrate change.
- Deterministic Vitest E2E assertions; never commit generated screenshots/videos.
- Shared suite disables Chromium partial rasterization + GPU rasterization to avoid observed paint-history-dependent edge pixels. Exact repeat comparisons still required before claiming reproducibility.
- Transient states such as Saved: install
page.clockbefore fixture; pause withpauseVirtualClock; advance only fixture work needed to enter state.setFixedTimealone does not pause timers. Capture readiness uses native layout delivery without advancing fixture clock. - Stills:
takeControlUiScreenshotFramefromui/src/test-helpers/control-ui-e2e-screenshot.ts, explicit semantic content +animations: "disabled". Pass viewport changes/intended scroll target throughviewport/scrollTo; verifies retained centering, viewport/scroll-clip intersection, settled layout/fonts/visible images. - Pass all related element locators in
elements; save returned page PNG + crops from that single frame. Crops enclose fractional bounds in measured PNG pixels; dimensions + unchanged bounds asserted. Never combine separate page/locator screenshots as same-frame evidence. - Default current-frame mode + existing viewport/element helpers preserve recording + sampled animation behavior. Static preparation stays active through bounds measurement + unclipped capture; full-page proofs must fit viewport-frame dimensions.
- After/alongside focused E2E test: run mocked Control UI app when available, e.g.
pnpm dev:ui:mock -- --port <port>. - Drive Chromium with Playwright against local mock URL. Capture needed states with screenshots/short video.
- Use
browser.newContext({ recordVideo: { dir, size }, viewport }),page.screenshot({ path }); close context before reporting video path. - Session-host command-state proof: viewport-only captures, verified with Playwright 1.62.1 + Chrome 151.0.7922.34 (Linux real Gateway; macOS arm64 synthetic reproduction). Other recording owners not migrated/certified by this fix; verify their required screenshot content + finalized video separately. See the verified capture path and upstream limitation.
- Allocate retained proof with
createControlUiE2eArtifactDir(scope, parentDir?)fromui/src/test-helpers/control-ui-e2e-artifacts.ts. Each call atomically creates fresh directory + logs actual path. Parent precedence: explicit parent → trimmed existingOPENCLAW_UI_E2E_ARTIFACT_DIR→ repository's.artifacts/control-ui-e2eparent. Existing custom output controls select parents; never add/rewrite env vars to enable capture. - Allocate during test/scenario or
beforeEach, once per attempt; standalone scripts once per invocation. Pass owner explicitly to shared capture helpers. Keep original gates, feature/stage names, viewports, waits, recording options. Distinct stages: distinct filenames. Keep screenshots/reports/video together. - Retain successful + failed evidence. Report actual allocated paths, including relocated filename overrides. Manually delete only exact owned directories after review; never clear shared parents before replay. Disposable build/media fixtures + owned temporary raw video may keep cleanup. New synthetic captures cannot recover overwritten evidence.
- Timeout diagnostics: fresh children beneath existing diagnostic parent. Mantis retains every capture attempt under invocation-owned directory; refuses report overwrite. Real-Gateway suites,
chat-outbox-*,chat-attachment-read-lifecycle: separate owners; coordinate before claiming replay-safe retention there. - Recording = validation, not only demo capture. Recorder fails/shows surprising behavior: stop, fix behavior, add/update regression test, rerecord.
- Visual proof blocked: state exact blocker; still report textual E2E evidence.
Mock Pattern
Start app server; install mock before page.goto; assert Gateway traffic + visible UI:
const server = await startControlUiE2eServer();
const page = await context.newPage();
const gateway = await installMockGateway(page, {
historyMessages: [{ role: "assistant", content: [{ type: "text", text: "Ready." }] }],
});
await page.goto(`${server.baseUrl}chat`);
await page.locator(".agent-chat__composer-combobox textarea").fill("hello");
await page.getByRole("button", { name: "Send message" }).click();
const request = await gateway.waitForRequest("chat.send");
await gateway.emitChatFinal({ runId: String(request.params.idempotencyKey), text: "Done." });
await page.getByText("Done.").waitFor();
New flow needs more Gateway surface: extend installMockGateway with typed
scenario options/method responses.
Run Inspector evidence
- Collect with
withControlUiRunInspectorfromui/src/test-helpers/control-ui-run-inspector.ts. Owns separate page; closes on success/failure; leaves caller's Chat page + unsent draft in place. preparePage: mock Gateway or campaign's existing per-tab authentication setup. Shared browser context does not copy another tab's session-storage token.- Helper uses rendered component model's
RunInspectorSelector+activityRunInspectorSelectorHref, including selected receipt's decision cursor. - Rendered panel's
data-run-id+data-execution-id: returned present identity, not just requested URL. Selected detail'sdata-receipt-selector-id=DecisionReceiptDisplayV1.selectorId. - Missing/ambiguous identity or missing selected receipts must not count as matches.
- Decision selectors separate from optional terminal transcript key
agent.wait.terminalReceipt.assistantTranscriptIdempotencyKey. Never manufacture key from DOM selector/history row or claim Inspector evidence repairs its absence. - Sidebar run state's current accessible label:
Active run, notRunning.
Standalone Recording
- Narrated captions, eased target zooms, fast-forwarded pauses: use proof-video dev skill. Standalone template records raw evidence + cue sidecar, renders captioned MP4 with system ffmpeg. Keep raw capture; attach inspected polished video.
- Already-running mocked Control UI URL: temporary Playwright script or
playwright testspec; keep recording flow focused:- Open mock URL; interact through stable
data-*/user-facing role selectors; wait on asserted states, not fixed sleeps. - Request-driven flows: assert visible UI + mocked Gateway traffic. Example:
expected count/row visible +
sessions.listcalled with expectedsearch,offset,limit. - Short sleeps only after assertions for readable captured video.
- Generated video: invocation's fresh allocated directory. Never commit or remove older captures.
- Open mock URL; interact through stable