Files
2026-07-13 13:36:53 +08:00

160 lines
6.1 KiB
Markdown

# Dev scripts — CDP-based E2E harness
This directory hosts dev-only one-shot scripts that drive the running
dev Electron over the Chrome DevTools Protocol. Use it to reproduce
bugs, verify fixes, or probe runtime state — much faster and more
deterministic than screenshot-driven testing.
The harness is **opt-in**: nothing about it touches production builds
or normal `npm run dev` workflows.
## Quickstart
1. Start dev electron with CDP enabled:
```bash
ENABLE_CDP=1 npm run dev
```
(Or any other free port: `ENABLE_CDP=1 CDP_PORT=9223 npm run dev`.)
2. In a separate shell, run a script:
```bash
node scripts/e2e-attach.js
```
The shared `attach()` helper connects Playwright to the running
renderer over `http://127.0.0.1:9222` (or `$CDP_PORT`). You can
drive the UI with DOM-aware selectors, evaluate IPC calls in the
renderer, or read state from the running main process.
## Full live visual regression suite
For reconciliation work, use the reusable full-suite driver instead of
assembling one-off snippets:
```bash
npm run test:live-visual
```
It attaches to the running Electron app over CDP and drives the visible
Chat, Sessions, Models, model picker, and attachment controls for
local, remote HTTP, and SSH. It covers:
- valid prompt + restored session
- bad -> good -> bad -> good in one session + restored session
- add/remove model persistence + chat selector availability
- pasted image display live + restored
- generated image display live + restored
- duplicate/missing-message checks for every restored transcript
Reports and screenshots are written to
`.sandbox/live-visual-regression/<run-id>/`.
Useful flags:
```bash
node scripts/drive-live-regression-suite.js --modes=local,remote,ssh
node scripts/drive-live-regression-suite.js --skip-generated
node scripts/drive-live-regression-suite.js --paste-image=C:\path\to\image.png
node scripts/drive-live-regression-suite.js --remote-url=http://127.0.0.1:19080 --remote-token=<token>
```
## How the opt-in works
`src/main/index.ts` reads `process.env.ENABLE_CDP` at startup and, when
set to `"1"`, appends `--remote-debugging-port=<CDP_PORT|9222>` to the
Chromium command line. Without the env var the switch is never added,
so production builds (and normal dev) never expose the port.
```ts
if (process.env.ENABLE_CDP === "1") {
app.commandLine.appendSwitch(
"remote-debugging-port",
process.env.CDP_PORT || "9222",
);
}
```
Three properties this gives us:
- **Off by default.** A user running the shipped app sees no CDP
port. An attacker who sets the env var on a prod install still
hits the existing Electron security model (sandbox,
contextIsolation, preload allowlist) — they get whatever a regular
user would.
- **Per-developer.** Whoever wants the harness flips one env var;
everyone else has zero footprint.
- **Multi-window safe.** `CDP_PORT` lets you run multiple dev
electron instances side-by-side (a clean profile + a real profile,
for instance) without port collisions.
## Writing a repro script
The convention used by the existing scripts:
```js
// scripts/repro-my-bug.js
const { attach } = require("./e2e-attach");
(async () => {
const { browser, page } = await attach();
// …drive the app via page.click / page.fill / page.evaluate…
// …observe DOM, IPC return values, on-disk state…
const verdict = /* boolean check */;
console.log(`[VERDICT] ${verdict ? "✅" : "🔴"} <what was tested>`);
await browser.close();
})().catch((e) => {
console.error("FAILED:", e.stack || e.message || e);
process.exit(1);
});
```
Naming conventions:
| Prefix | Purpose | Lives long? |
|---|---|---|
| `repro-<short-name>.js` | Reproduce a specific bug. Pair with an issue number or commit. Print `[VERDICT] 🔴 REPRODUCED` (pre-fix) or `[VERDICT] ✅ FIXED` (post-fix). | Until the fix is shipped + a regression test exists; then it can be deleted or kept as a manual reference. |
| `drive-<flow>.js` | Walk through a user flow end-to-end (e.g. OAuth sign-in, model switch + chat). | Keep alongside the feature so future contributors can re-run. |
| `probe-<aspect>.js` | Read-only inspection. No state mutation. Useful for understanding a bug before writing a repro. | Useful long-term as documentation. |
| `verify-<feature>.js` | Live verifier paired with a PR. Asserts `[VERDICT A/B/C/D]` lines for each contract the PR claims. | Lives with the PR; can be repurposed as a manual smoke test. |
## Things to remember
- **The harness is a Node CommonJS script**, not part of the TS build.
Use `require()`. The project's ESLint config ignores
`scripts/e2e-attach.js`, `scripts/repro-*.js`, `scripts/probe-*.js`,
`scripts/drive-*.js`, and `scripts/verify-*.js` so the
`no-require-imports` rule doesn't fire here.
- **`page.evaluate(async () => window.hermesAPI.foo())` is your friend.**
The renderer's `hermesAPI` is exposed via contextBridge, so the
harness can call any IPC the UI can. This is often more reliable
than driving clicks, especially for tests of main-process state.
- **Don't close the dev electron from the script** —
`browser.close()` detaches Playwright but leaves the app running.
If you need the app gone, kill it separately.
- **Restart `npm run dev` after main-process changes.**
electron-vite hot-reloads renderer files, but main-process changes
don't always restart the bundled main binary. When in doubt, kill
the electron processes and restart dev.
- **Port 9222 can get stuck in a zombie LISTEN state** on Windows
after a force-kill. If `bind() returned an error` shows up in the
dev log, switch to `CDP_PORT=9223` (or any other free port).
## A real example
The patterns above came out of triaging the v0.5.1 bug reports
("Session continuation requires API key authentication", session
proliferation, Edit Model dialog API-key bug, Nous Portal silent
misconfiguration). Each reproducible bug got a `repro-*.js` that
flipped from 🔴 pre-fix to ✅ post-fix in under a minute — vs the
multi-minute screenshot loop the same flow used to require.
If you write a useful repro, add it to this directory and link it
from the related PR / issue. The next contributor will thank you.