Repository navigation
Expand file tree
/
Copy pathvitest.config.ts
More file actions
227 lines (222 loc) · 11.4 KB
/
Copy pathvitest.config.ts
File metadata and controls
227 lines (222 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import path from "node:path";
import { configDefaults, defineConfig } from "vitest/config";
/**
* The heavy-integration set (#253). Everything under `packages/browser-runner` drives
* something real — a full Chrome over CDP, a bound TCP socket, a spawned tmux pane, a
* `git clone` — so every one of these files has a wall-clock budget that a busy machine
* can blow. They are the only tests in the suite whose result depends on how much CPU is
* left over.
*
* A DIRECTORY, deliberately, not a list of file names. The first cut of this fix named
* `runner-browser.test.ts` because that is the file we had watched fail; the next
* full-suite run failed `test-job-server.test.ts` as well — a plain HTTP-server test with
* no browser in it, blowing vitest's default 5s budget. A list only ever contains the
* failures someone has already seen. A path covers the ones nobody has seen yet, and a new
* test added next to its subject is isolated by construction.
*/
const INTEGRATION_TESTS = ["packages/browser-runner/src/**/*.test.ts"];
const UNIT_TESTS = [
"packages/*/src/**/*.test.ts",
"workers/*/src/**/*.test.ts",
"agents/job-application-assistant/src/**/*.test.ts",
// The console had NO unit tests and was not even in this glob, so anything added
// there would have passed locally and never run in CI.
"store/*/src/**/*.test.ts",
// Same gap for the Coder web UI. Named explicitly rather than `agents/*` — most
// dirs under agents/ are inert vendored seed copies (see pags/CLAUDE.md) and must
// not be swept into the suite.
"agents/coder/web/src/**/*.test.ts",
// The CI guards under scripts/ had no tests at all: each is a regex over the tree
// that decides whether main is red, and nothing checked that any of them still
// matches the defect it was written for — or that it stays quiet on correct code,
// which is the half that decides whether people fix a finding or suppress it.
"scripts/**/*.test.mjs",
// The e2e HARNESS, not the e2e specs (`*.spec.ts`, which Playwright owns and vitest
// must never pick up). `e2e/console-server.mjs` decides what gets BUILT before the
// specs run, and for two commits it decided wrong — it could not see the workspace
// package that supplies the Coding tab, so the suite graded yesterday's bundle
// (#413). The only way to observe that was a full Playwright run, where it presents
// as a spec failing for a reason unrelated to the spec. `.test.mjs`, so the glob
// cannot widen to `.spec.ts` by accident.
"e2e/**/*.test.mjs",
];
// SDK source aliases — keeps vitest from resolving @proagentstore/sdk/* against
// the built dist/ output. Without this, a stale SDK build produces false test
// failures (e.g. "isClaudeEngine is not a function") because the package
// `exports` map points at ./dist, not ./src. The root tsconfig.json already
// carries the same paths for tsc; this makes vitest's Vite resolver agree.
// Mirrors the five subpaths declared in packages/sdk/package.json "exports". (#471)
//
// Array form (not object) so more-specific sub-paths are checked before the bare
// "@proagentstore/sdk" entry (Vite tries aliases in order for array form).
const SDK_ROOT = path.resolve(import.meta.dirname, "packages/sdk/src");
const SDK_ALIASES = [
{ find: "@proagentstore/sdk/ui-react", replacement: path.join(SDK_ROOT, "ui-react.ts") },
{ find: "@proagentstore/sdk/ui", replacement: path.join(SDK_ROOT, "ui.ts") },
{ find: "@proagentstore/sdk/client", replacement: path.join(SDK_ROOT, "client.ts") },
{ find: "@proagentstore/sdk/hooks", replacement: path.join(SDK_ROOT, "hooks.ts") },
{ find: "@proagentstore/sdk", replacement: path.join(SDK_ROOT, "index.ts") },
];
// The agent templates (`templates/*`) are standalone Workers a creator copies out, outside the
// workspace and never installed here. Their behaviour is still tested (#1086, `host-template-capture
// .test.ts`), so a template's own `hono` import resolves the way the API Worker's does. Scoped to
// importers under `templates/`: nothing else's resolution changes.
//
// The host Worker's `pages.js` is generated by its build and gitignored, so a test that drives the
// host's `fetch` reads a committed empty stand-in instead — on every machine, built or not.
const API_ENTRY = path.resolve(import.meta.dirname, "workers/api/src/index.ts");
const HOST_INDEX = path.resolve(import.meta.dirname, "workers/host/src/index.ts");
const HOST_PAGES_STUB = path.resolve(import.meta.dirname, "workers/host/src/pages.test-stub.ts");
const workerTestDeps = {
name: "worker-test-deps",
enforce: "pre" as const,
async resolveId(this: { resolve: (s: string, i: string, o: { skipSelf: boolean }) => Promise<unknown> }, source: string, importer?: string) {
if (source === "./pages.js" && importer === HOST_INDEX) return HOST_PAGES_STUB;
if (source !== "hono" || !importer?.includes(`${path.sep}templates${path.sep}`)) return null;
return this.resolve(source, API_ENTRY, { skipSelf: true });
},
};
export default defineConfig({
plugins: [workerTestDeps],
resolve: {
alias: SDK_ALIASES,
},
test: {
// One real event-loop turn before each test, so a worker can read the main process's RPC replies (#1093).
setupFiles: ["./vitest.setup-ipc-yield.mjs"],
/**
* Two projects (#253).
*
* ── The mechanism, stated plainly, because it keeps getting misdiagnosed ──
*
* These tests are sensitive to CPU AVAILABILITY, not to anything in the code
* under test. There is no race here, no port collision, no leaked browser
* between files. Every observed failure was `Test timed out in Nms` on code that
* passes every time the machine is quiet.
*
* Measured on a 10-core machine, per-test wall time for the same passing code:
*
* test isolated in-suite in-suite + a 2nd full suite
* heaviest browser test 11.4s 20.6s 39.3s
* captcha challenge 1.7s 1.7s 9.9s
* invisible reCAPTCHA 2.7s 2.7s 11.1s
*
* And the decisive one: when the box was fully saturated, the run that failed
* failed in `rate-limit.test.ts`, `publish.test.ts` and `storage-tools.test.ts`
* — three PURE UNIT files, no browser, no socket — all with `Test timed out in
* 5000ms`. Browser tests starve first because they are the heaviest, not because
* they are browser tests.
*
* So: if this flakes for you, do not weaken an assertion. Look at what else is
* running. (On a dev machine, also look for orphaned Chromes from killed runs:
* `ps ax | grep -c playwright_chromiumdev_profile`. Playwright's cleanup does not
* run when a runner is SIGKILLed, and they accumulate invisibly for days.)
*
* CI is NOT affected and never has been — it executes the suite alone on a
* dedicated runner, which is exactly why it has been green. This split is for
* the shared dev machine, where several agents run full suites at once.
*
* ── What the split actually buys ──
*
* 1. A budget sized for the work. Most files under `packages/browser-runner`
* never declared a timeout, so they inherited vitest's 5s default — a number
* chosen for pure functions, applied to binding a socket and cloning a repo.
* A per-directory project is how vitest lets you fix that in one place.
* 2. A fast loop: `pnpm test:unit` is the ~3100 quick tests without the ~10 that
* take longer than all of them combined.
* 3. `groupOrder` keeps the heavy set from competing with the unit fork pool
* within a single run. Note the limit honestly: this only controls contention
* INSIDE one vitest process. It does nothing about four other agents building
* on the same laptop, which is the larger effect.
*
* `pnpm test` still runs everything in one command, so nothing can be quietly
* skipped — but a failure now carries the `integration` project label.
*/
projects: [
{
extends: true,
test: {
name: "unit",
include: UNIT_TESTS,
exclude: [...configDefaults.exclude, ...INTEGRATION_TESTS],
sequence: { groupOrder: 0 },
},
},
{
extends: true,
test: {
name: "integration",
include: INTEGRATION_TESTS,
sequence: { groupOrder: 1 },
// One file at a time, one fork. Two real Chromes, or a Chrome next to a
// spawned CLI waiting on a 1.5s stdout-silence latch, is the same
// starvation at a smaller scale.
fileParallelism: false,
maxWorkers: 1,
minWorkers: 1,
// A floor, not the fix. Most files here never declared a budget, so they
// inherited vitest's 5s default — a number chosen for pure functions, not
// for binding a socket or cloning a repo.
//
// 120s is derived from a DELIBERATELY LOADED run, not a quiet one: with the
// machine saturated, `mcp-runtime.test.ts`'s navigate test went from 2.4s to
// 22.2s — 9x — and the heaviest browser test blew its own 120s budget. A
// threshold picked while the box is idle is optimistic by roughly that
// factor. This matches the number `runner-browser.test.ts` already chose by
// hand for its own tests, so nothing in this category silently keeps a
// budget meant for a pure function, and a real hang still fails in 2min.
testTimeout: 120_000,
// The same budget for HOOKS, and for the same reason — this was the gap
// that let the starvation this split exists to absorb land anyway.
//
// `testTimeout` does not cover `beforeAll`/`afterAll`, which stayed on
// vitest's 10s default. But a hook here does the heaviest work in the file:
// LAUNCHING the real Chrome, or tearing one down. On 2026-08-06 the #274
// reaper made `mcp-runtime.test.ts`'s teardown slower, all 3,711 tests
// passed, and the file failed on a 10s hook — which then gated CI *and the
// API deploy* for an unrelated commit that landed next.
//
// Found independently by two agents in one afternoon, which is the argument
// for fixing it in the config rather than per-file.
hookTimeout: 120_000,
},
},
],
coverage: {
provider: "v8",
reporter: ["text-summary", "json-summary"],
reportsDirectory: "coverage",
// Measure the unit-testable backend (Workers + packages), plus the front-end's
// PURE layer.
//
// This used to say the React UIs "are exercised by Playwright e2e, not vitest"
// and exclude `store/**` wholesale. That was only ever half-true, and #282
// re-checked it: `e2e/console.spec.ts` is the only spec in the repo, so
// `store/console` was e2e-covered and `store/admin` — which owns suspend,
// unpublish, delete, cancel, roles and key-revoke — was covered by neither.
//
// The honest split is by KIND, not by directory. Components stay excluded and
// stay e2e's job; the `lib/*.ts` modules the convention pushes decisions into
// (#280, #282 option b) are unit-tested and belong in the report — an untested
// one showing 0% is a true and actionable signal, which is the whole point of
// the convention. `.tsx` under lib/ is still a component and stays out.
all: true,
include: [
"workers/api/src/**/*.ts",
"workers/mcp/src/**/*.ts",
"packages/browser-runner/src/**/*.ts",
"packages/cli/src/**/*.ts",
"packages/sdk/src/**/*.ts",
"agents/job-application-assistant/src/**/*.ts",
"store/*/src/lib/**/*.ts",
],
exclude: [
"**/*.test.ts",
"**/*.d.ts",
"**/dist/**",
"**/node_modules/**",
"workers/host/src/pages.ts",
],
},
},
});