import { defineConfig } from "vitest/config"; // One project per target. Same scenario files, different running instance:// `vitest run --project cloud` / `--project selfhost` (or both, the default).// Each project's globalsetup boots that app's OWN dev server (or attaches to// E2E_<TARGET>_URL). Scenarios are isolated by fresh identities, not resets.const project = (name: string, overrides: Record<string, unknown> = {}) => ({ test: { name, include: ["scenarios/**/*.test.ts", `${name}/**/*.test.ts`], env: { E2E_TARGET: name }, globalSetup: [`./setup/${name}.globalsetup.ts`], testTimeout: 180_000, hookTimeout: 120_000, ...overrides, },}); export default defineConfig({ test: { projects: [ // PGlite's socket server is effectively single-connection; parallel test // files (each fanning out per-request postgres sockets) crash it. Run // files serially — swap PGlite for real Postgres if wall-clock matters. project("cloud", { fileParallelism: false }), // selfhost identities are the shared bootstrap admin for now — run files // serially until per-test invite-signup isolation lands. project("selfhost", { fileParallelism: false }), // The same app as the PRODUCTION Docker artifact (the image users // deploy: production build, bun serve.ts, /data volume) instead of the // dev server. Runs the cross-target scenarios AND the selfhost/** // scenarios — it is the same single-tenant app, so they all apply. // Needs a docker daemon with host-networking support (Engine ≥ 26 on // Docker Desktop); not part of the default `npm run test` chain — run // with `npm run test:selfhost-docker` (release gate + CI for the // publish workflow). project("selfhost-docker", { include: ["scenarios/**/*.test.ts", "selfhost/**/*.test.ts"], fileParallelism: false, }), // The Cloudflare self-host worker (workerd via wrangler dev, dev-auth). // Scoped to the cross-target scenarios wired for this host; the rest of // scenarios/** is not yet validated against the worker. The full-graph // scenario is included on purpose: workerd's 128MB isolate is the exact // limit the streaming compile + content-addressed serve path defends, so // it is the one host where that regression must be proven. Shares // self-host's single-admin model. project("cloudflare", { include: [ "scenarios/browser-approval.test.ts", "scenarios/microsoft-graph-full.test.ts", "scenarios/toolkits-mcp.test.ts", "cloudflare/**/*.test.ts", ], fileParallelism: false, }), // The Electron desktop app. Only desktop/** scenarios — the desktop // target provides none of the standard surfaces (each scenario // launches its own app via Playwright's electron driver), so running // the cross-target suite here would just emit a page of skips. Needs // a display; not part of the default `npm run test` chain. project("desktop", { include: ["desktop/**/*.test.ts"], fileParallelism: false, testTimeout: 300_000, }), // The PACKAGED desktop app: the real electron-builder bundle, where // app.isPackaged is true — the ONLY target that exercises the supervised- // daemon attach path (ensureSupervisedConnection) and the bundled executor. // Its globalsetup builds the bundle (slow), so it's separate from // `desktop` to keep the fast dev-electron suite off the package build. // Needs a display; not part of the default `npm run test` chain — run with // `vitest run --project desktop-packaged`. project("desktop-packaged", { include: ["desktop-packaged/**/*.test.ts"], fileParallelism: false, testTimeout: 360_000, hookTimeout: 600_000, }), // The packaged desktop app inside a GUI guest, driven over CDP from the // host and filmed (the cross-OS counterpart of desktop-packaged) — one // shared scenario (desktop-vm/), one project per guest OS. The globalsetup // provisions the guest, launches the bundle with --remote-debugging-port, // and forwards it; the scenario connects, drives, and records the console. // Each lands in runs/<target>/. Not in the default `npm run test` chain — // run with `vitest run --project desktop-macos` (or desktop-linux). The VM // is provisioned automatically; set E2E_DESKTOP_VM_IP to attach to an // already-running guest instead. // macos/linux provision a tart guest and build+push the ~450MB bundle; // windows ATTACHES to a long-lived dockur guest over an SSH jump (no // provision), so it needs no build but the same generous hooks. ...(["macos", "linux", "windows"] as const).map((os) => project(`desktop-${os}`, { include: ["desktop-vm/**/*.test.ts"], fileParallelism: false, testTimeout: 300_000, hookTimeout: 900_000, }), ), // The single-user local app. Each scenario launches its OWN `executor // web` via the CLI on a throwaway data dir + an OS-assigned port, so // there is no shared instance and scenarios are independent. Files run // SERIALLY (like every other server-booting project): a cold `executor // web` boot runs vite's optimizeDeps, and several booting at once on a // CI runner thrash hard enough to blow the token-URL wait. Serial lets // the first boot warm the shared vite cache so the rest come up fast. No // globalSetup (nothing shared to boot). Only local/** scenarios. Not part // of the default `npm run test` chain; run with `vitest run --project local`. project("local", { include: ["local/**/*.test.ts"], globalSetup: [], fileParallelism: false, testTimeout: 300_000, }), // The supervised CLI daemon inside a guest VM, one project per OS. The // globalsetup provisions a VM, `executor service install`s the daemon, and // tunnels it; restart() reboots the guest for REAL, so restart-persistence // proves the boot-time auto-start path. Needs tart (macOS/Linux) or an EC2 // credential (Windows); not part of the default `npm run test` chain — run // with `vitest run --project cli-macos` (etc.) on the Mini. ...(["macos", "linux", "windows"] as const).map((os) => project(`cli-${os}`, { include: ["scenarios/restart-persistence.test.ts", "cli/**/*.test.ts"], env: { E2E_TARGET: `cli-${os}`, E2E_VM_OS: os }, fileParallelism: false, testTimeout: 300_000, hookTimeout: 900_000, }), ), ], },});
import { defineConfig } from "vitest/config";
// One project per target. Same scenario files, different running instance:
// `vitest run --project cloud` / `--project selfhost` (or both, the default).
// Each project's globalsetup boots that app's OWN dev server (or attaches to
// E2E_<TARGET>_URL). Scenarios are isolated by fresh identities, not resets.
const project = (name: string, overrides: Record<string, unknown> = {}) => ({
test: {
name,
include: ["scenarios/**/*.test.ts", `${name}/**/*.test.ts`],
env: { E2E_TARGET: name },
globalSetup: [`./setup/${name}.globalsetup.ts`],
testTimeout: 180_000,
hookTimeout: 120_000,
...overrides,
},
});
export default defineConfig({
test: {
projects: [
// PGlite's socket server is effectively single-connection; parallel test
// files (each fanning out per-request postgres sockets) crash it. Run
// files serially — swap PGlite for real Postgres if wall-clock matters.
project("cloud", { fileParallelism: false }),
// selfhost identities are the shared bootstrap admin for now — run files
// serially until per-test invite-signup isolation lands.
project("selfhost", { fileParallelism: false }),
// The same app as the PRODUCTION Docker artifact (the image users
// deploy: production build, bun serve.ts, /data volume) instead of the
// dev server. Runs the cross-target scenarios AND the selfhost/**
// scenarios — it is the same single-tenant app, so they all apply.
// Needs a docker daemon with host-networking support (Engine ≥ 26 on
// Docker Desktop); not part of the default `npm run test` chain — run
// with `npm run test:selfhost-docker` (release gate + CI for the
// publish workflow).
project("selfhost-docker", {
include: ["scenarios/**/*.test.ts", "selfhost/**/*.test.ts"],
fileParallelism: false,
}),
// The Cloudflare self-host worker (workerd via wrangler dev, dev-auth).
// Scoped to the cross-target scenarios wired for this host; the rest of
// scenarios/** is not yet validated against the worker. The full-graph
// scenario is included on purpose: workerd's 128MB isolate is the exact
// limit the streaming compile + content-addressed serve path defends, so
// it is the one host where that regression must be proven. Shares
// self-host's single-admin model.
project("cloudflare", {
include: [
"scenarios/browser-approval.test.ts",
"scenarios/microsoft-graph-full.test.ts",
"scenarios/toolkits-mcp.test.ts",
"cloudflare/**/*.test.ts",
],
fileParallelism: false,
}),
// The Electron desktop app. Only desktop/** scenarios — the desktop
// target provides none of the standard surfaces (each scenario
// launches its own app via Playwright's electron driver), so running
// the cross-target suite here would just emit a page of skips. Needs
// a display; not part of the default `npm run test` chain.
project("desktop", {
include: ["desktop/**/*.test.ts"],
fileParallelism: false,
testTimeout: 300_000,
}),
// The PACKAGED desktop app: the real electron-builder bundle, where
// app.isPackaged is true — the ONLY target that exercises the supervised-
// daemon attach path (ensureSupervisedConnection) and the bundled executor.
// Its globalsetup builds the bundle (slow), so it's separate from
// `desktop` to keep the fast dev-electron suite off the package build.
// Needs a display; not part of the default `npm run test` chain — run with
// `vitest run --project desktop-packaged`.
project("desktop-packaged", {
include: ["desktop-packaged/**/*.test.ts"],
fileParallelism: false,
testTimeout: 360_000,
hookTimeout: 600_000,
}),
// The packaged desktop app inside a GUI guest, driven over CDP from the
// host and filmed (the cross-OS counterpart of desktop-packaged) — one
// shared scenario (desktop-vm/), one project per guest OS. The globalsetup
// provisions the guest, launches the bundle with --remote-debugging-port,
// and forwards it; the scenario connects, drives, and records the console.
// Each lands in runs/<target>/. Not in the default `npm run test` chain —
// run with `vitest run --project desktop-macos` (or desktop-linux). The VM
// is provisioned automatically; set E2E_DESKTOP_VM_IP to attach to an
// already-running guest instead.
// macos/linux provision a tart guest and build+push the ~450MB bundle;
// windows ATTACHES to a long-lived dockur guest over an SSH jump (no
// provision), so it needs no build but the same generous hooks.
...(["macos", "linux", "windows"] as const).map((os) =>
project(`desktop-${os}`, {
include: ["desktop-vm/**/*.test.ts"],
fileParallelism: false,
testTimeout: 300_000,
hookTimeout: 900_000,
}),
),
// The single-user local app. Each scenario launches its OWN `executor
// web` via the CLI on a throwaway data dir + an OS-assigned port, so
// there is no shared instance and scenarios are independent. Files run
// SERIALLY (like every other server-booting project): a cold `executor
// web` boot runs vite's optimizeDeps, and several booting at once on a
// CI runner thrash hard enough to blow the token-URL wait. Serial lets
// the first boot warm the shared vite cache so the rest come up fast. No
// globalSetup (nothing shared to boot). Only local/** scenarios. Not part
// of the default `npm run test` chain; run with `vitest run --project local`.
project("local", {
include: ["local/**/*.test.ts"],
globalSetup: [],
fileParallelism: false,
testTimeout: 300_000,
}),
// The supervised CLI daemon inside a guest VM, one project per OS. The
// globalsetup provisions a VM, `executor service install`s the daemon, and
// tunnels it; restart() reboots the guest for REAL, so restart-persistence
// proves the boot-time auto-start path. Needs tart (macOS/Linux) or an EC2
// credential (Windows); not part of the default `npm run test` chain — run
// with `vitest run --project cli-macos` (etc.) on the Mini.
...(["macos", "linux", "windows"] as const).map((os) =>
project(`cli-${os}`, {
include: ["scenarios/restart-persistence.test.ts", "cli/**/*.test.ts"],
env: { E2E_TARGET: `cli-${os}`, E2E_VM_OS: os },
fileParallelism: false,
testTimeout: 300_000,
hookTimeout: 900_000,
}),
),
],
},
});