A dependency-free end-to-end harness that launches the built OpenWork Electron app with the Chrome DevTools Protocol enabled, drives the real UI over CDP, and asserts on what the user would actually see.
It exists so an automated feature loop (and humans) can answer one question per change: does this feature actually work in the running app?
bun run e2eThis will:
- Build the Electron bundles if
apps/electron/dist/{main.cjs,renderer/index.html}are missing (setE2E_BUILD=1to force a rebuild). - For each
e2e/assertions/*.assert.ts, launch a fresh, isolated app instance with--remote-debugging-port, run the assertion, then shut it down. - Print a pass/fail summary and exit non-zero if anything failed (so it can gate a PR).
Failure screenshots land in .e2e/screenshots/ (gitignored). The app runs
against an isolated profile under .e2e/ so runs never touch your real OpenWork
data — Electron userData, app config (~/.craft-agent), and the default
conversation workspace (~/Documents/...) are all redirected under .e2e/.
On a headless Linux host (no DISPLAY), the launcher automatically runs Electron
under xvfb-run with --no-sandbox. The host must have xvfb and Electron's
runtime libraries (libgtk-3, libnss3, …) installed.
| Var | Effect |
|---|---|
E2E_BUILD=1 |
Force electron:build before launching. |
E2E_PORT=<n> |
Pin the remote-debugging port instead of using a free one. |
Drop a *.assert.ts file into e2e/assertions/. It must default-export an
Assertion — { name, run(app) }. The runner discovers it automatically.
import type { Assertion } from '../runner';
const assertion: Assertion = {
name: 'settings dialog opens from the sidebar',
async run(app) {
const { session } = app;
await session.click('[aria-label="Settings"]');
await session.waitForSelector('[role="dialog"]');
const heading = await session.getText('[role="dialog"] h2');
if (heading?.trim() !== 'Settings') {
throw new Error(`expected Settings dialog, saw heading: ${heading}`);
}
},
};
export default assertion;Guidelines:
- Assert on visible result/state, not "it booted." A green check should mean the feature's key path works.
- Prefer stable selectors (
role,aria-label, visible text) over brittle class chains. If the UI lacks a hook, add adata-testidin the same PR. - Each assertion gets a clean app instance, so they're independent and order doesn't matter.
cdp.ts— minimal CDP client over Bun's globalWebSocket+fetch(evaluate,waitForFunction,waitForSelector,click,getText,screenshot). Zero external deps.app.ts— launcheselectron apps/electron --remote-debugging-port=<port>, finds the main renderer target (titleQwen Code Desktop), attaches a session, and owns teardown.runner.ts— discovers assertions, runs each against a fresh app, reports.assertions/*.assert.ts— the checks themselves.