@ait-co/devtools 0.1.107 → 0.1.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle-KFs4t-wc.d.ts +96 -0
- package/dist/bundle-KFs4t-wc.d.ts.map +1 -0
- package/dist/cdp-connection-C0AP0tH2.d.ts +277 -0
- package/dist/cdp-connection-C0AP0tH2.d.ts.map +1 -0
- package/dist/in-app/auto.d.ts +17 -0
- package/dist/in-app/auto.d.ts.map +1 -1
- package/dist/in-app/auto.js +76 -0
- package/dist/in-app/auto.js.map +1 -1
- package/dist/in-app/index.d.ts +48 -1
- package/dist/in-app/index.d.ts.map +1 -1
- package/dist/in-app/index.js +60 -1
- package/dist/in-app/index.js.map +1 -1
- package/dist/mcp/cli.js +651 -9
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +59 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/panel/index.js +1 -1
- package/dist/pool-CuVMzWGB.d.ts +14577 -0
- package/dist/pool-CuVMzWGB.d.ts.map +1 -0
- package/dist/relay-worker-xxanNQGs.d.ts +74 -0
- package/dist/relay-worker-xxanNQGs.d.ts.map +1 -0
- package/dist/runtime-Wi5d6Ywz.d.ts +50 -0
- package/dist/runtime-Wi5d6Ywz.d.ts.map +1 -0
- package/dist/test-runner/bundle.d.ts +2 -0
- package/dist/test-runner/bundle.js +232 -0
- package/dist/test-runner/bundle.js.map +1 -0
- package/dist/test-runner/cli.d.ts +462 -0
- package/dist/test-runner/cli.d.ts.map +1 -0
- package/dist/test-runner/cli.js +516 -0
- package/dist/test-runner/cli.js.map +1 -0
- package/dist/test-runner/config.d.ts +80 -0
- package/dist/test-runner/config.d.ts.map +1 -0
- package/dist/test-runner/config.js +54 -0
- package/dist/test-runner/config.js.map +1 -0
- package/dist/test-runner/pool.d.ts +2 -0
- package/dist/test-runner/pool.js +136 -0
- package/dist/test-runner/pool.js.map +1 -0
- package/dist/test-runner/relay-worker.d.ts +2 -0
- package/dist/test-runner/relay-worker.js +96 -0
- package/dist/test-runner/relay-worker.js.map +1 -0
- package/dist/test-runner/rpc.d.ts +53 -0
- package/dist/test-runner/rpc.d.ts.map +1 -0
- package/dist/test-runner/rpc.js +78 -0
- package/dist/test-runner/rpc.js.map +1 -0
- package/dist/test-runner/task-graph.d.ts +38 -0
- package/dist/test-runner/task-graph.d.ts.map +1 -0
- package/dist/test-runner/task-graph.js +182 -0
- package/dist/test-runner/task-graph.js.map +1 -0
- package/dist/unplugin/index.d.cts +13 -32
- package/dist/unplugin/index.d.cts.map +1 -1
- package/dist/unplugin/index.d.ts +13 -32
- package/dist/unplugin/index.d.ts.map +1 -1
- package/package.json +13 -3
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { i as createRelayPool, n as RelayConnectionFactory, t as RELAY_POOL_NAME } from "../pool-CuVMzWGB.js";
|
|
2
|
+
|
|
3
|
+
//#region src/test-runner/config.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolved phone-test configuration returned by `definePhoneTestConfig`.
|
|
6
|
+
*/
|
|
7
|
+
interface PhoneTestConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Glob patterns (relative to cwd) for test files to run on the device.
|
|
10
|
+
* Defaults to `['**\/*.phone.test.ts']`.
|
|
11
|
+
*/
|
|
12
|
+
include: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Per-file evaluate timeout in milliseconds. Defaults to 30 000.
|
|
15
|
+
*/
|
|
16
|
+
timeoutMs: number;
|
|
17
|
+
/**
|
|
18
|
+
* Additional esbuild `external` patterns for bundling.
|
|
19
|
+
* The SDK package is always external; add more here if needed.
|
|
20
|
+
*/
|
|
21
|
+
extraExternals: string[];
|
|
22
|
+
}
|
|
23
|
+
/** User-facing config shape accepted by `definePhoneTestConfig`. */
|
|
24
|
+
interface PhoneTestUserConfig {
|
|
25
|
+
include?: string[];
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
extraExternals?: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Define a phone-relay test configuration.
|
|
31
|
+
*
|
|
32
|
+
* Merges user overrides with sensible defaults and returns a resolved
|
|
33
|
+
* `PhoneTestConfig`. This object can be passed to `runTestFilesOverRelay`
|
|
34
|
+
* (via the CLI or custom scripts) to run tests on a real device WebView.
|
|
35
|
+
*/
|
|
36
|
+
declare function definePhoneTestConfig(userConfig?: PhoneTestUserConfig): PhoneTestConfig;
|
|
37
|
+
/**
|
|
38
|
+
* The slice of a Vitest `test` config this helper produces — `pool`,
|
|
39
|
+
* `include`, and `testTimeout`. Typed structurally (not against Vitest's
|
|
40
|
+
* `InlineConfig`) so consumers can spread it into their own `defineConfig`
|
|
41
|
+
* without this module taking a value dependency on `vitest`.
|
|
42
|
+
*/
|
|
43
|
+
interface PhoneVitestTestConfig {
|
|
44
|
+
/** The relay `PoolRunnerInitializer`, matched by `getFilePoolName`. */
|
|
45
|
+
pool: ReturnType<typeof createRelayPool>;
|
|
46
|
+
/** Glob patterns for device test files. */
|
|
47
|
+
include: string[];
|
|
48
|
+
/** Per-test timeout in ms (mirrors the relay per-file evaluate timeout). */
|
|
49
|
+
testTimeout: number;
|
|
50
|
+
}
|
|
51
|
+
/** User config accepted by {@link definePhoneVitestConfig}. */
|
|
52
|
+
interface PhoneVitestUserConfig extends PhoneTestUserConfig {
|
|
53
|
+
/**
|
|
54
|
+
* Opens/closes the CDP relay connection that tests run over. Required —
|
|
55
|
+
* without it there is no device to dispatch files to.
|
|
56
|
+
*
|
|
57
|
+
* SECRET-HANDLING: the factory owns the relay wss/TOTP; this config object
|
|
58
|
+
* never stores or logs those values.
|
|
59
|
+
*/
|
|
60
|
+
connection: RelayConnectionFactory;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the Vitest `test` config slice for running tests on a device over the
|
|
64
|
+
* relay. Spread the result into your Vitest config:
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // vitest.config.ts
|
|
68
|
+
* import { defineConfig } from 'vitest/config';
|
|
69
|
+
* import { definePhoneVitestConfig } from '@ait-co/devtools/test-runner';
|
|
70
|
+
* export default defineConfig({
|
|
71
|
+
* test: definePhoneVitestConfig({ connection: myRelayFactory }),
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* Files matching `include` are dispatched to the relay pool (named
|
|
75
|
+
* {@link RELAY_POOL_NAME}); everything else runs in Vitest's default pool.
|
|
76
|
+
*/
|
|
77
|
+
declare function definePhoneVitestConfig(userConfig: PhoneVitestUserConfig): PhoneVitestTestConfig;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { PhoneTestConfig, PhoneTestUserConfig, PhoneVitestTestConfig, PhoneVitestUserConfig, RELAY_POOL_NAME, type RelayConnectionFactory, createRelayPool, definePhoneTestConfig, definePhoneVitestConfig };
|
|
80
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/test-runner/config.ts"],"mappings":";;;;;AA6DA;UArCiB,eAAA;;;;;EAKf,OAAA;EAgCsF;;AAcxF;EA1CE,SAAA;;;;;EAKA,cAAA;AAAA;;UAIe,mBAAA;EACf,OAAA;EACA,SAAA;EACA,cAAA;AAAA;;;;;;;AAkEF;iBAlDgB,qBAAA,CAAsB,UAAA,GAAa,mBAAA,GAAsB,eAAA;;;;;;;UAcxD,qBAAA;;EAEf,IAAA,EAAM,UAAA,QAAkB,eAAA;;EAExB,OAAA;;EAEA,WAAA;AAAA;;UAIe,qBAAA,SAA8B,mBAAA;;;;;;;;EAQ7C,UAAA,EAAY,sBAAA;AAAA;;;;;;;;;;;;;;;;iBAkBE,uBAAA,CAAwB,UAAA,EAAY,qBAAA,GAAwB,qBAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { RELAY_POOL_NAME, createRelayPool } from "./pool.js";
|
|
2
|
+
//#region src/test-runner/config.ts
|
|
3
|
+
const DEFAULT_CONFIG = {
|
|
4
|
+
include: ["**/*.phone.test.ts"],
|
|
5
|
+
timeoutMs: 3e4,
|
|
6
|
+
extraExternals: []
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Define a phone-relay test configuration.
|
|
10
|
+
*
|
|
11
|
+
* Merges user overrides with sensible defaults and returns a resolved
|
|
12
|
+
* `PhoneTestConfig`. This object can be passed to `runTestFilesOverRelay`
|
|
13
|
+
* (via the CLI or custom scripts) to run tests on a real device WebView.
|
|
14
|
+
*/
|
|
15
|
+
function definePhoneTestConfig(userConfig) {
|
|
16
|
+
return {
|
|
17
|
+
include: userConfig?.include ?? DEFAULT_CONFIG.include,
|
|
18
|
+
timeoutMs: userConfig?.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
|
|
19
|
+
extraExternals: userConfig?.extraExternals ?? DEFAULT_CONFIG.extraExternals
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the Vitest `test` config slice for running tests on a device over the
|
|
24
|
+
* relay. Spread the result into your Vitest config:
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // vitest.config.ts
|
|
28
|
+
* import { defineConfig } from 'vitest/config';
|
|
29
|
+
* import { definePhoneVitestConfig } from '@ait-co/devtools/test-runner';
|
|
30
|
+
* export default defineConfig({
|
|
31
|
+
* test: definePhoneVitestConfig({ connection: myRelayFactory }),
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* Files matching `include` are dispatched to the relay pool (named
|
|
35
|
+
* {@link RELAY_POOL_NAME}); everything else runs in Vitest's default pool.
|
|
36
|
+
*/
|
|
37
|
+
function definePhoneVitestConfig(userConfig) {
|
|
38
|
+
const resolved = definePhoneTestConfig(userConfig);
|
|
39
|
+
return {
|
|
40
|
+
pool: createRelayPool({
|
|
41
|
+
connection: userConfig.connection,
|
|
42
|
+
run: {
|
|
43
|
+
timeoutMs: resolved.timeoutMs,
|
|
44
|
+
bundleOptions: { extraExternals: resolved.extraExternals }
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
include: resolved.include,
|
|
48
|
+
testTimeout: resolved.timeoutMs
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { RELAY_POOL_NAME, createRelayPool, definePhoneTestConfig, definePhoneVitestConfig };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../../src/test-runner/config.ts"],"sourcesContent":["/**\n * Configuration helper for phone-relay test runs.\n *\n * `definePhoneTestConfig` is the user-facing entry point for configuring the\n * relay test runner. It mirrors the pattern of Vitest's `defineConfig` so\n * users can write:\n *\n * // phone-test.config.ts\n * import { definePhoneTestConfig } from '@ait-co/devtools/test-runner';\n * export default definePhoneTestConfig({ ... });\n *\n * The helper resolves user config and, when given a relay connection factory,\n * builds the Vitest `pool` (`createRelayPool`) so Vitest's own config\n * resolution routes matching files to the device over the relay (#645). Without\n * a connection factory it still returns the resolved config for use with the\n * lower-level `runTestFilesOverRelay` transport (#644).\n */\n\nimport type { RelayConnectionFactory } from './pool.js';\nimport { createRelayPool, RELAY_POOL_NAME } from './pool.js';\n\n/**\n * Resolved phone-test configuration returned by `definePhoneTestConfig`.\n */\nexport interface PhoneTestConfig {\n /**\n * Glob patterns (relative to cwd) for test files to run on the device.\n * Defaults to `['**\\/*.phone.test.ts']`.\n */\n include: string[];\n /**\n * Per-file evaluate timeout in milliseconds. Defaults to 30 000.\n */\n timeoutMs: number;\n /**\n * Additional esbuild `external` patterns for bundling.\n * The SDK package is always external; add more here if needed.\n */\n extraExternals: string[];\n}\n\n/** User-facing config shape accepted by `definePhoneTestConfig`. */\nexport interface PhoneTestUserConfig {\n include?: string[];\n timeoutMs?: number;\n extraExternals?: string[];\n}\n\nconst DEFAULT_CONFIG: PhoneTestConfig = {\n include: ['**/*.phone.test.ts'],\n timeoutMs: 30_000,\n extraExternals: [],\n};\n\n/**\n * Define a phone-relay test configuration.\n *\n * Merges user overrides with sensible defaults and returns a resolved\n * `PhoneTestConfig`. This object can be passed to `runTestFilesOverRelay`\n * (via the CLI or custom scripts) to run tests on a real device WebView.\n */\nexport function definePhoneTestConfig(userConfig?: PhoneTestUserConfig): PhoneTestConfig {\n return {\n include: userConfig?.include ?? DEFAULT_CONFIG.include,\n timeoutMs: userConfig?.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,\n extraExternals: userConfig?.extraExternals ?? DEFAULT_CONFIG.extraExternals,\n };\n}\n\n/**\n * The slice of a Vitest `test` config this helper produces — `pool`,\n * `include`, and `testTimeout`. Typed structurally (not against Vitest's\n * `InlineConfig`) so consumers can spread it into their own `defineConfig`\n * without this module taking a value dependency on `vitest`.\n */\nexport interface PhoneVitestTestConfig {\n /** The relay `PoolRunnerInitializer`, matched by `getFilePoolName`. */\n pool: ReturnType<typeof createRelayPool>;\n /** Glob patterns for device test files. */\n include: string[];\n /** Per-test timeout in ms (mirrors the relay per-file evaluate timeout). */\n testTimeout: number;\n}\n\n/** User config accepted by {@link definePhoneVitestConfig}. */\nexport interface PhoneVitestUserConfig extends PhoneTestUserConfig {\n /**\n * Opens/closes the CDP relay connection that tests run over. Required —\n * without it there is no device to dispatch files to.\n *\n * SECRET-HANDLING: the factory owns the relay wss/TOTP; this config object\n * never stores or logs those values.\n */\n connection: RelayConnectionFactory;\n}\n\n/**\n * Build the Vitest `test` config slice for running tests on a device over the\n * relay. Spread the result into your Vitest config:\n *\n * @example\n * // vitest.config.ts\n * import { defineConfig } from 'vitest/config';\n * import { definePhoneVitestConfig } from '@ait-co/devtools/test-runner';\n * export default defineConfig({\n * test: definePhoneVitestConfig({ connection: myRelayFactory }),\n * });\n *\n * Files matching `include` are dispatched to the relay pool (named\n * {@link RELAY_POOL_NAME}); everything else runs in Vitest's default pool.\n */\nexport function definePhoneVitestConfig(userConfig: PhoneVitestUserConfig): PhoneVitestTestConfig {\n const resolved = definePhoneTestConfig(userConfig);\n return {\n pool: createRelayPool({\n connection: userConfig.connection,\n run: {\n timeoutMs: resolved.timeoutMs,\n bundleOptions: { extraExternals: resolved.extraExternals },\n },\n }),\n include: resolved.include,\n testTimeout: resolved.timeoutMs,\n };\n}\n\nexport type { RelayConnectionFactory };\nexport { createRelayPool, RELAY_POOL_NAME };\n"],"mappings":";;AAgDA,MAAM,iBAAkC;CACtC,SAAS,CAAC,qBAAqB;CAC/B,WAAW;CACX,gBAAgB,EAAE;CACnB;;;;;;;;AASD,SAAgB,sBAAsB,YAAmD;AACvF,QAAO;EACL,SAAS,YAAY,WAAW,eAAe;EAC/C,WAAW,YAAY,aAAa,eAAe;EACnD,gBAAgB,YAAY,kBAAkB,eAAe;EAC9D;;;;;;;;;;;;;;;;;AA6CH,SAAgB,wBAAwB,YAA0D;CAChG,MAAM,WAAW,sBAAsB,WAAW;AAClD,QAAO;EACL,MAAM,gBAAgB;GACpB,YAAY,WAAW;GACvB,KAAK;IACH,WAAW,SAAS;IACpB,eAAe,EAAE,gBAAgB,SAAS,gBAAgB;IAC3D;GACF,CAAC;EACF,SAAS,SAAS;EAClB,aAAa,SAAS;EACvB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { runTestFilesOverRelay } from "./relay-worker.js";
|
|
2
|
+
import { synthesizeFileTask, toTaskEventPacks, toTaskResultPacks } from "./task-graph.js";
|
|
3
|
+
//#region src/test-runner/pool.ts
|
|
4
|
+
/** The pool name Vitest matches against `getFilePoolName(project)`. */
|
|
5
|
+
const RELAY_POOL_NAME = "ait-relay";
|
|
6
|
+
/**
|
|
7
|
+
* In-process `PoolWorker` backed by a CDP relay. Mirrors `TypecheckPoolWorker`:
|
|
8
|
+
* `send` handles the message in-process and emits a `WorkerResponse` via the
|
|
9
|
+
* `'message'` listener registered by the core's pool driver.
|
|
10
|
+
*/
|
|
11
|
+
var RelayPoolWorker = class {
|
|
12
|
+
name = RELAY_POOL_NAME;
|
|
13
|
+
cacheFs = false;
|
|
14
|
+
project;
|
|
15
|
+
factory;
|
|
16
|
+
runOpts;
|
|
17
|
+
listeners = /* @__PURE__ */ new Map();
|
|
18
|
+
connection;
|
|
19
|
+
constructor(project, opts) {
|
|
20
|
+
this.project = project;
|
|
21
|
+
this.factory = opts.connection;
|
|
22
|
+
this.runOpts = opts.run;
|
|
23
|
+
}
|
|
24
|
+
on(event, callback) {
|
|
25
|
+
let set = this.listeners.get(event);
|
|
26
|
+
if (!set) {
|
|
27
|
+
set = /* @__PURE__ */ new Set();
|
|
28
|
+
this.listeners.set(event, set);
|
|
29
|
+
}
|
|
30
|
+
set.add(callback);
|
|
31
|
+
}
|
|
32
|
+
off(event, callback) {
|
|
33
|
+
this.listeners.get(event)?.delete(callback);
|
|
34
|
+
}
|
|
35
|
+
emit(event, arg) {
|
|
36
|
+
const set = this.listeners.get(event);
|
|
37
|
+
if (!set) return;
|
|
38
|
+
for (const cb of set) cb(arg);
|
|
39
|
+
}
|
|
40
|
+
deserialize(data) {
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
async start() {}
|
|
44
|
+
async stop() {
|
|
45
|
+
if (this.connection) {
|
|
46
|
+
const conn = this.connection;
|
|
47
|
+
this.connection = void 0;
|
|
48
|
+
await this.factory.close(conn);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
send(message) {
|
|
52
|
+
this.handle(message).then((response) => {
|
|
53
|
+
if (response) this.emit("message", response);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async handle(message) {
|
|
57
|
+
switch (message.type) {
|
|
58
|
+
case "start": return {
|
|
59
|
+
__vitest_worker_response__: true,
|
|
60
|
+
type: "started"
|
|
61
|
+
};
|
|
62
|
+
case "run":
|
|
63
|
+
case "collect": {
|
|
64
|
+
const files = message.context.files.map((s) => s.filepath);
|
|
65
|
+
return {
|
|
66
|
+
__vitest_worker_response__: true,
|
|
67
|
+
type: "testfileFinished",
|
|
68
|
+
error: await this.runFiles(files).catch((e) => e)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
case "stop": return {
|
|
72
|
+
__vitest_worker_response__: true,
|
|
73
|
+
type: "stopped"
|
|
74
|
+
};
|
|
75
|
+
case "cancel": return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Bundles + injects + collects the given files over the relay, synthesises a
|
|
80
|
+
* Vitest task graph per file, and reports it through `vitest.state` so the
|
|
81
|
+
* reporter/watch/UI ecosystem sees real results.
|
|
82
|
+
*
|
|
83
|
+
* Returns nothing on success; a thrown error is surfaced as the
|
|
84
|
+
* `testfileFinished` error (a whole-pool failure, e.g. relay unreachable).
|
|
85
|
+
*/
|
|
86
|
+
async runFiles(files) {
|
|
87
|
+
const report = await runTestFilesOverRelay(await this.ensureConnection(), files, this.runOpts);
|
|
88
|
+
const root = this.project.config.root;
|
|
89
|
+
const projectName = this.project.name || void 0;
|
|
90
|
+
const state = this.project.vitest.state;
|
|
91
|
+
for (const fileResult of report.files) {
|
|
92
|
+
const perFile = "error" in fileResult.result ? {
|
|
93
|
+
startedAt: report.startedAt,
|
|
94
|
+
duration: 0,
|
|
95
|
+
passed: 0,
|
|
96
|
+
failed: 1,
|
|
97
|
+
skipped: 0,
|
|
98
|
+
tests: []
|
|
99
|
+
} : fileResult.result;
|
|
100
|
+
const fileTask = synthesizeFileTask(fileResult.file, root, projectName, RELAY_POOL_NAME, perFile);
|
|
101
|
+
state.collectFiles(this.project, [fileTask]);
|
|
102
|
+
state.updateTasks(toTaskResultPacks(fileTask));
|
|
103
|
+
toTaskEventPacks(fileTask);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async ensureConnection() {
|
|
107
|
+
if (!this.connection) this.connection = await this.factory.open();
|
|
108
|
+
return this.connection;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Creates the Vitest `PoolRunnerInitializer` for the relay pool.
|
|
113
|
+
*
|
|
114
|
+
* Wire it into a Vitest config as `pool: createRelayPool({ connection })`.
|
|
115
|
+
* Vitest moves an object `pool` into `config.poolRunner` and dispatches files
|
|
116
|
+
* whose pool name equals `RELAY_POOL_NAME` to our `createPoolWorker`.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* // vitest.config.ts
|
|
120
|
+
* import { createRelayPool } from '@ait-co/devtools/test-runner';
|
|
121
|
+
* export default defineConfig({
|
|
122
|
+
* test: { pool: createRelayPool({ connection: myFactory }) },
|
|
123
|
+
* });
|
|
124
|
+
*/
|
|
125
|
+
function createRelayPool(opts) {
|
|
126
|
+
return {
|
|
127
|
+
name: RELAY_POOL_NAME,
|
|
128
|
+
createPoolWorker(options) {
|
|
129
|
+
return new RelayPoolWorker(options.project, opts);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { RELAY_POOL_NAME, createRelayPool };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","names":[],"sources":["../../src/test-runner/pool.ts"],"sourcesContent":["/**\n * Vitest 4.x custom pool: runs mini-app tests on a real device WebView over a\n * CDP relay instead of in a Node child process.\n *\n * Vitest's pool abstraction lets a `PoolRunnerInitializer` create a `PoolWorker`\n * that the core drives via a `WorkerRequest`/`WorkerResponse` message protocol.\n * Built-in pools (`forks`/`threads`) fork a child process; the `typecheck` pool\n * runs in-process and handles messages synchronously. We follow the in-process\n * `TypecheckPoolWorker` shape: a single long-lived \"worker\" that, on a `run`\n * request, bundles + injects + collects each file over the relay (#644's\n * `runTestFilesOverRelay`), synthesises a Vitest task graph (`task-graph.ts`),\n * and reports it through `vitest.state` so reporters/watch/UI/snapshot all work.\n *\n * Single-attach constraint: the relay supports one active page, so we keep one\n * worker for the whole run (`isolate: false`) rather than starting/stopping per\n * file — per-file start/stop would re-attach the relay on every file.\n *\n * SECRET-HANDLING: the relay connection (wss/TOTP) is supplied by the caller's\n * factory and never logged here; bundle code and result values are not logged.\n */\n\nimport type { PoolOptions, PoolWorker, TestProject, WorkerRequest } from 'vitest/node';\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\nimport type { RelayRunOptions } from './relay-worker.js';\nimport { runTestFilesOverRelay } from './relay-worker.js';\nimport { synthesizeFileTask, toTaskEventPacks, toTaskResultPacks } from './task-graph.js';\n\n/** The pool name Vitest matches against `getFilePoolName(project)`. */\nexport const RELAY_POOL_NAME = 'ait-relay';\n\n/**\n * Factory the caller provides to open (and later close) the CDP relay\n * connection. Called once per worker lifecycle (`start`). The returned\n * `close` runs on `stop`.\n *\n * Kept as a factory (not a live connection) so the pool can be constructed in\n * config before any device is attached, and so `start`/`stop` own the lifecycle.\n */\nexport interface RelayConnectionFactory {\n /** Opens the relay connection. Resolves once the page is attached & ready. */\n open(): Promise<CdpConnection>;\n /** Closes the relay connection opened by {@link open}. */\n close(connection: CdpConnection): Promise<void>;\n}\n\n/** Options for {@link createRelayPool}. */\nexport interface RelayPoolOptions {\n /** Opens/closes the relay connection. */\n connection: RelayConnectionFactory;\n /** Forwarded to `runTestFilesOverRelay` (bundle options, per-file timeout). */\n run?: RelayRunOptions;\n}\n\n/* -------------------------------------------------------------------------- */\n/* WorkerResponse helpers */\n/* -------------------------------------------------------------------------- */\n\n/** Minimal `WorkerResponse` shape we emit. Matches vitest/node's union. */\ntype EmittedResponse =\n | { __vitest_worker_response__: true; type: 'started'; error?: unknown }\n | { __vitest_worker_response__: true; type: 'stopped'; error?: unknown }\n | { __vitest_worker_response__: true; type: 'testfileFinished'; error?: unknown };\n\n/* -------------------------------------------------------------------------- */\n/* The in-process relay PoolWorker */\n/* -------------------------------------------------------------------------- */\n\n/**\n * In-process `PoolWorker` backed by a CDP relay. Mirrors `TypecheckPoolWorker`:\n * `send` handles the message in-process and emits a `WorkerResponse` via the\n * `'message'` listener registered by the core's pool driver.\n */\nclass RelayPoolWorker implements PoolWorker {\n readonly name = RELAY_POOL_NAME;\n readonly cacheFs = false;\n\n private readonly project: TestProject;\n private readonly factory: RelayConnectionFactory;\n private readonly runOpts: RelayRunOptions | undefined;\n private readonly listeners = new Map<string, Set<(arg: unknown) => void>>();\n private connection: CdpConnection | undefined;\n\n constructor(project: TestProject, opts: RelayPoolOptions) {\n this.project = project;\n this.factory = opts.connection;\n this.runOpts = opts.run;\n }\n\n on(event: string, callback: (arg: unknown) => void): void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(callback);\n }\n\n off(event: string, callback: (arg: unknown) => void): void {\n this.listeners.get(event)?.delete(callback);\n }\n\n private emit(event: string, arg: unknown): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const cb of set) cb(arg);\n }\n\n deserialize(data: unknown): unknown {\n // Relay results arrive as plain JSON objects already — identity is correct.\n return data;\n }\n\n async start(): Promise<void> {\n // Lifecycle ownership lives here, but the connection is opened lazily on the\n // first `run`/`collect` so a never-run worker never touches the device.\n }\n\n async stop(): Promise<void> {\n if (this.connection) {\n const conn = this.connection;\n this.connection = undefined;\n await this.factory.close(conn);\n }\n }\n\n send(message: WorkerRequest): void {\n // Fire-and-forget like TypecheckPoolWorker: handle async, emit on settle.\n void this.handle(message).then((response) => {\n if (response) this.emit('message', response);\n });\n }\n\n private async handle(message: WorkerRequest): Promise<EmittedResponse | undefined> {\n switch (message.type) {\n case 'start':\n return { __vitest_worker_response__: true, type: 'started' };\n case 'run':\n case 'collect': {\n const files = message.context.files.map((s) => s.filepath);\n const error = await this.runFiles(files).catch((e) => e);\n return { __vitest_worker_response__: true, type: 'testfileFinished', error };\n }\n case 'stop':\n return { __vitest_worker_response__: true, type: 'stopped' };\n case 'cancel':\n return undefined;\n }\n }\n\n /**\n * Bundles + injects + collects the given files over the relay, synthesises a\n * Vitest task graph per file, and reports it through `vitest.state` so the\n * reporter/watch/UI ecosystem sees real results.\n *\n * Returns nothing on success; a thrown error is surfaced as the\n * `testfileFinished` error (a whole-pool failure, e.g. relay unreachable).\n */\n private async runFiles(files: string[]): Promise<void> {\n const conn = await this.ensureConnection();\n const report = await runTestFilesOverRelay(conn, files, this.runOpts);\n\n const root = this.project.config.root;\n const projectName = this.project.name || undefined;\n const state = this.project.vitest.state;\n\n for (const fileResult of report.files) {\n const perFile =\n 'error' in fileResult.result\n ? // Whole-file bundle/inject error → synthesise a failed file with no tests.\n {\n startedAt: report.startedAt,\n duration: 0,\n passed: 0,\n failed: 1,\n skipped: 0,\n tests: [],\n }\n : fileResult.result;\n\n const fileTask = synthesizeFileTask(\n fileResult.file,\n root,\n projectName,\n RELAY_POOL_NAME,\n perFile,\n );\n\n state.collectFiles(this.project, [fileTask]);\n state.updateTasks(toTaskResultPacks(fileTask));\n // Event packs drive reporter progress; updateTasks already recorded\n // results, so events are advisory but keep watch/UI output coherent.\n void toTaskEventPacks(fileTask);\n }\n }\n\n private async ensureConnection(): Promise<CdpConnection> {\n if (!this.connection) {\n this.connection = await this.factory.open();\n }\n return this.connection;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* PoolRunnerInitializer factory */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Creates the Vitest `PoolRunnerInitializer` for the relay pool.\n *\n * Wire it into a Vitest config as `pool: createRelayPool({ connection })`.\n * Vitest moves an object `pool` into `config.poolRunner` and dispatches files\n * whose pool name equals `RELAY_POOL_NAME` to our `createPoolWorker`.\n *\n * @example\n * // vitest.config.ts\n * import { createRelayPool } from '@ait-co/devtools/test-runner';\n * export default defineConfig({\n * test: { pool: createRelayPool({ connection: myFactory }) },\n * });\n */\nexport function createRelayPool(opts: RelayPoolOptions): {\n readonly name: string;\n createPoolWorker: (options: PoolOptions) => PoolWorker;\n} {\n return {\n name: RELAY_POOL_NAME,\n createPoolWorker(options: PoolOptions): PoolWorker {\n return new RelayPoolWorker(options.project, opts);\n },\n };\n}\n"],"mappings":";;;;AA4BA,MAAa,kBAAkB;;;;;;AA4C/B,IAAM,kBAAN,MAA4C;CAC1C,OAAgB;CAChB,UAAmB;CAEnB;CACA;CACA;CACA,4BAA6B,IAAI,KAA0C;CAC3E;CAEA,YAAY,SAAsB,MAAwB;AACxD,OAAK,UAAU;AACf,OAAK,UAAU,KAAK;AACpB,OAAK,UAAU,KAAK;;CAGtB,GAAG,OAAe,UAAwC;EACxD,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM;AACnC,MAAI,CAAC,KAAK;AACR,yBAAM,IAAI,KAAK;AACf,QAAK,UAAU,IAAI,OAAO,IAAI;;AAEhC,MAAI,IAAI,SAAS;;CAGnB,IAAI,OAAe,UAAwC;AACzD,OAAK,UAAU,IAAI,MAAM,EAAE,OAAO,SAAS;;CAG7C,KAAa,OAAe,KAAoB;EAC9C,MAAM,MAAM,KAAK,UAAU,IAAI,MAAM;AACrC,MAAI,CAAC,IAAK;AACV,OAAK,MAAM,MAAM,IAAK,IAAG,IAAI;;CAG/B,YAAY,MAAwB;AAElC,SAAO;;CAGT,MAAM,QAAuB;CAK7B,MAAM,OAAsB;AAC1B,MAAI,KAAK,YAAY;GACnB,MAAM,OAAO,KAAK;AAClB,QAAK,aAAa,KAAA;AAClB,SAAM,KAAK,QAAQ,MAAM,KAAK;;;CAIlC,KAAK,SAA8B;AAE5B,OAAK,OAAO,QAAQ,CAAC,MAAM,aAAa;AAC3C,OAAI,SAAU,MAAK,KAAK,WAAW,SAAS;IAC5C;;CAGJ,MAAc,OAAO,SAA8D;AACjF,UAAQ,QAAQ,MAAhB;GACE,KAAK,QACH,QAAO;IAAE,4BAA4B;IAAM,MAAM;IAAW;GAC9D,KAAK;GACL,KAAK,WAAW;IACd,MAAM,QAAQ,QAAQ,QAAQ,MAAM,KAAK,MAAM,EAAE,SAAS;AAE1D,WAAO;KAAE,4BAA4B;KAAM,MAAM;KAAoB,OADvD,MAAM,KAAK,SAAS,MAAM,CAAC,OAAO,MAAM,EAAE;KACoB;;GAE9E,KAAK,OACH,QAAO;IAAE,4BAA4B;IAAM,MAAM;IAAW;GAC9D,KAAK,SACH;;;;;;;;;;;CAYN,MAAc,SAAS,OAAgC;EAErD,MAAM,SAAS,MAAM,sBADR,MAAM,KAAK,kBAAkB,EACO,OAAO,KAAK,QAAQ;EAErE,MAAM,OAAO,KAAK,QAAQ,OAAO;EACjC,MAAM,cAAc,KAAK,QAAQ,QAAQ,KAAA;EACzC,MAAM,QAAQ,KAAK,QAAQ,OAAO;AAElC,OAAK,MAAM,cAAc,OAAO,OAAO;GACrC,MAAM,UACJ,WAAW,WAAW,SAElB;IACE,WAAW,OAAO;IAClB,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO,EAAE;IACV,GACD,WAAW;GAEjB,MAAM,WAAW,mBACf,WAAW,MACX,MACA,aACA,iBACA,QACD;AAED,SAAM,aAAa,KAAK,SAAS,CAAC,SAAS,CAAC;AAC5C,SAAM,YAAY,kBAAkB,SAAS,CAAC;AAGzC,oBAAiB,SAAS;;;CAInC,MAAc,mBAA2C;AACvD,MAAI,CAAC,KAAK,WACR,MAAK,aAAa,MAAM,KAAK,QAAQ,MAAM;AAE7C,SAAO,KAAK;;;;;;;;;;;;;;;;;AAsBhB,SAAgB,gBAAgB,MAG9B;AACA,QAAO;EACL,MAAM;EACN,iBAAiB,SAAkC;AACjD,UAAO,IAAI,gBAAgB,QAAQ,SAAS,KAAK;;EAEpD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { bundleTestFile } from "./bundle.js";
|
|
2
|
+
import { injectAndRunBundle } from "./rpc.js";
|
|
3
|
+
//#region src/test-runner/relay-worker.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runs all `files` sequentially over the given CDP `connection`.
|
|
6
|
+
*
|
|
7
|
+
* For each file:
|
|
8
|
+
* 1. Bundle with esbuild (includes SDK shim + runtime).
|
|
9
|
+
* 2. Inject into the attached page via `Runtime.evaluate`.
|
|
10
|
+
* 3. Await the `RunReport` JSON response.
|
|
11
|
+
* 4. Accumulate results.
|
|
12
|
+
*
|
|
13
|
+
* Returns a `RelayRunReport` with per-file results and flattened totals.
|
|
14
|
+
*
|
|
15
|
+
* This function does NOT open or manage the relay connection — the caller
|
|
16
|
+
* is responsible for attaching and closing it.
|
|
17
|
+
*
|
|
18
|
+
* TODO (#645): implement the Vitest `PoolRunnerInitializer` interface here
|
|
19
|
+
* so that `runTestFilesOverRelay` can be used as a Vitest pool entry.
|
|
20
|
+
*
|
|
21
|
+
* @param connection - Active CDP connection (relay or local kind).
|
|
22
|
+
* @param files - Absolute paths to test files, run in order.
|
|
23
|
+
* @param opts - Optional per-run overrides.
|
|
24
|
+
*/
|
|
25
|
+
async function runTestFilesOverRelay(connection, files, opts) {
|
|
26
|
+
const wallStart = Date.now();
|
|
27
|
+
const startedAt = new Date(wallStart).toISOString();
|
|
28
|
+
const fileResults = [];
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
let fileEntry;
|
|
31
|
+
try {
|
|
32
|
+
const { code } = await bundleTestFile(file, opts?.bundleOptions);
|
|
33
|
+
const rpcResult = await injectAndRunBundle(connection, code, opts?.timeoutMs);
|
|
34
|
+
if (rpcResult.ok) fileEntry = {
|
|
35
|
+
file,
|
|
36
|
+
result: rpcResult.report
|
|
37
|
+
};
|
|
38
|
+
else fileEntry = {
|
|
39
|
+
file,
|
|
40
|
+
result: { error: rpcResult.error }
|
|
41
|
+
};
|
|
42
|
+
} catch (e) {
|
|
43
|
+
fileEntry = {
|
|
44
|
+
file,
|
|
45
|
+
result: { error: e instanceof Error ? e.message : String(e) }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
fileResults.push(fileEntry);
|
|
49
|
+
}
|
|
50
|
+
const totals = fileResults.reduce((acc, { result }) => {
|
|
51
|
+
if ("error" in result) {
|
|
52
|
+
acc.failed += 1;
|
|
53
|
+
acc.total += 1;
|
|
54
|
+
} else {
|
|
55
|
+
acc.passed += result.passed;
|
|
56
|
+
acc.failed += result.failed;
|
|
57
|
+
acc.skipped += result.skipped;
|
|
58
|
+
acc.total += result.passed + result.failed + result.skipped;
|
|
59
|
+
}
|
|
60
|
+
return acc;
|
|
61
|
+
}, {
|
|
62
|
+
passed: 0,
|
|
63
|
+
failed: 0,
|
|
64
|
+
skipped: 0,
|
|
65
|
+
total: 0
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
startedAt,
|
|
69
|
+
duration: Date.now() - wallStart,
|
|
70
|
+
files: fileResults,
|
|
71
|
+
totals
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Flattens all test results from a `RelayRunReport` into a single array.
|
|
76
|
+
* Files that errored during bundle/inject produce a synthetic failed entry.
|
|
77
|
+
*/
|
|
78
|
+
function flattenResults(report) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const { file, result } of report.files) if ("error" in result) out.push({
|
|
81
|
+
file,
|
|
82
|
+
name: `<bundle/inject error>`,
|
|
83
|
+
status: "fail",
|
|
84
|
+
duration: 0,
|
|
85
|
+
error: result.error
|
|
86
|
+
});
|
|
87
|
+
else for (const t of result.tests) out.push({
|
|
88
|
+
...t,
|
|
89
|
+
file
|
|
90
|
+
});
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { flattenResults, runTestFilesOverRelay };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=relay-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-worker.js","names":[],"sources":["../../src/test-runner/relay-worker.ts"],"sourcesContent":["/**\n * Orchestrator: runs a list of test files sequentially over a CDP relay.\n *\n * Each file goes through: bundle → inject → run → collect.\n * This is the transport layer: it does NOT integrate with Vitest's pool or the\n * MCP surface. The Vitest custom pool (`pool.ts`) and the `run_tests` MCP tool\n * are separate callers that build on this orchestrator.\n *\n * Single-attach constraint: only one page is active at a time. Files run\n * sequentially; parallel execution across targets is out of scope.\n *\n * The 30-second per-file timeout is inherited from `injectAndRunBundle`.\n * For suites that exceed it, split the file into smaller pieces.\n *\n * SECRET-HANDLING: file paths are surfaced in reports; relay URLs are not.\n */\n\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\nimport { type BundleOptions, bundleTestFile } from './bundle.js';\nimport { injectAndRunBundle } from './rpc.js';\nimport type { RunReport, TestResult } from './runtime.js';\n\n/** Per-file result in the aggregate `RunReport`. */\nexport interface FileResult {\n /** Absolute or relative path to the test file. */\n file: string;\n /** Full run report for this file, or an error if bundling/injection failed. */\n result: RunReport | { error: string };\n}\n\n/** Aggregate report returned by `runTestFilesOverRelay`. */\nexport interface RelayRunReport {\n /** ISO timestamp of when the run started. */\n startedAt: string;\n /** Total elapsed wall-clock milliseconds. */\n duration: number;\n /** Per-file results in execution order. */\n files: FileResult[];\n /** Flattened totals across all files. */\n totals: {\n passed: number;\n failed: number;\n skipped: number;\n total: number;\n };\n}\n\n/** Options for `runTestFilesOverRelay`. */\nexport interface RelayRunOptions {\n /**\n * Options forwarded to `bundleTestFile` for each file.\n */\n bundleOptions?: BundleOptions;\n /**\n * Per-file evaluate timeout in milliseconds. Defaults to 30 000.\n * Increase for long-running suites or split the file.\n */\n timeoutMs?: number;\n}\n\n/**\n * Runs all `files` sequentially over the given CDP `connection`.\n *\n * For each file:\n * 1. Bundle with esbuild (includes SDK shim + runtime).\n * 2. Inject into the attached page via `Runtime.evaluate`.\n * 3. Await the `RunReport` JSON response.\n * 4. Accumulate results.\n *\n * Returns a `RelayRunReport` with per-file results and flattened totals.\n *\n * This function does NOT open or manage the relay connection — the caller\n * is responsible for attaching and closing it.\n *\n * TODO (#645): implement the Vitest `PoolRunnerInitializer` interface here\n * so that `runTestFilesOverRelay` can be used as a Vitest pool entry.\n *\n * @param connection - Active CDP connection (relay or local kind).\n * @param files - Absolute paths to test files, run in order.\n * @param opts - Optional per-run overrides.\n */\nexport async function runTestFilesOverRelay(\n connection: CdpConnection,\n files: string[],\n opts?: RelayRunOptions,\n): Promise<RelayRunReport> {\n const wallStart = Date.now();\n const startedAt = new Date(wallStart).toISOString();\n const fileResults: FileResult[] = [];\n\n for (const file of files) {\n let fileEntry: FileResult;\n try {\n const { code } = await bundleTestFile(file, opts?.bundleOptions);\n const rpcResult = await injectAndRunBundle(connection, code, opts?.timeoutMs);\n if (rpcResult.ok) {\n fileEntry = { file, result: rpcResult.report };\n } else {\n fileEntry = { file, result: { error: rpcResult.error } };\n }\n } catch (e) {\n // Capture bundle/inject errors per-file so subsequent files still run.\n fileEntry = {\n file,\n result: {\n error: e instanceof Error ? e.message : String(e),\n },\n };\n }\n fileResults.push(fileEntry);\n }\n\n const totals = fileResults.reduce(\n (acc, { result }) => {\n if ('error' in result) {\n // Treat whole-file errors as a single failure.\n acc.failed += 1;\n acc.total += 1;\n } else {\n acc.passed += result.passed;\n acc.failed += result.failed;\n acc.skipped += result.skipped;\n acc.total += result.passed + result.failed + result.skipped;\n }\n return acc;\n },\n { passed: 0, failed: 0, skipped: 0, total: 0 },\n );\n\n return {\n startedAt,\n duration: Date.now() - wallStart,\n files: fileResults,\n totals,\n };\n}\n\n/**\n * Flattens all test results from a `RelayRunReport` into a single array.\n * Files that errored during bundle/inject produce a synthetic failed entry.\n */\nexport function flattenResults(report: RelayRunReport): Array<TestResult & { file: string }> {\n const out: Array<TestResult & { file: string }> = [];\n for (const { file, result } of report.files) {\n if ('error' in result) {\n out.push({\n file,\n name: `<bundle/inject error>`,\n status: 'fail',\n duration: 0,\n error: result.error,\n });\n } else {\n for (const t of result.tests) {\n out.push({ ...t, file });\n }\n }\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAiFA,eAAsB,sBACpB,YACA,OACA,MACyB;CACzB,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,YAAY,IAAI,KAAK,UAAU,CAAC,aAAa;CACnD,MAAM,cAA4B,EAAE;AAEpC,MAAK,MAAM,QAAQ,OAAO;EACxB,IAAI;AACJ,MAAI;GACF,MAAM,EAAE,SAAS,MAAM,eAAe,MAAM,MAAM,cAAc;GAChE,MAAM,YAAY,MAAM,mBAAmB,YAAY,MAAM,MAAM,UAAU;AAC7E,OAAI,UAAU,GACZ,aAAY;IAAE;IAAM,QAAQ,UAAU;IAAQ;OAE9C,aAAY;IAAE;IAAM,QAAQ,EAAE,OAAO,UAAU,OAAO;IAAE;WAEnD,GAAG;AAEV,eAAY;IACV;IACA,QAAQ,EACN,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAClD;IACF;;AAEH,cAAY,KAAK,UAAU;;CAG7B,MAAM,SAAS,YAAY,QACxB,KAAK,EAAE,aAAa;AACnB,MAAI,WAAW,QAAQ;AAErB,OAAI,UAAU;AACd,OAAI,SAAS;SACR;AACL,OAAI,UAAU,OAAO;AACrB,OAAI,UAAU,OAAO;AACrB,OAAI,WAAW,OAAO;AACtB,OAAI,SAAS,OAAO,SAAS,OAAO,SAAS,OAAO;;AAEtD,SAAO;IAET;EAAE,QAAQ;EAAG,QAAQ;EAAG,SAAS;EAAG,OAAO;EAAG,CAC/C;AAED,QAAO;EACL;EACA,UAAU,KAAK,KAAK,GAAG;EACvB,OAAO;EACP;EACD;;;;;;AAOH,SAAgB,eAAe,QAA8D;CAC3F,MAAM,MAA4C,EAAE;AACpD,MAAK,MAAM,EAAE,MAAM,YAAY,OAAO,MACpC,KAAI,WAAW,OACb,KAAI,KAAK;EACP;EACA,MAAM;EACN,QAAQ;EACR,UAAU;EACV,OAAO,OAAO;EACf,CAAC;KAEF,MAAK,MAAM,KAAK,OAAO,MACrB,KAAI,KAAK;EAAE,GAAG;EAAG;EAAM,CAAC;AAI9B,QAAO"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { t as CdpConnection } from "../cdp-connection-C0AP0tH2.js";
|
|
2
|
+
import { t as RunReport } from "../runtime-Wi5d6Ywz.js";
|
|
3
|
+
|
|
4
|
+
//#region src/test-runner/rpc.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wraps bundle code in a self-executing IIFE that:
|
|
7
|
+
* 1. Evaluates the bundle (registering describe/it/test).
|
|
8
|
+
* 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.
|
|
9
|
+
* 3. Returns a JSON-serialised `RunReport` string.
|
|
10
|
+
*
|
|
11
|
+
* The double-serialisation (RunReport → JSON string → returnByValue string)
|
|
12
|
+
* is intentional: CDP `returnByValue` reliably transports strings; deeply
|
|
13
|
+
* nested objects can lose fidelity across the Chii relay.
|
|
14
|
+
*
|
|
15
|
+
* SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.
|
|
16
|
+
*/
|
|
17
|
+
declare function buildRunTestsExpression(bundleCode: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Result of `injectAndRunBundle`.
|
|
20
|
+
*/
|
|
21
|
+
type RpcRunResult = {
|
|
22
|
+
ok: true;
|
|
23
|
+
report: RunReport;
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
error: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`
|
|
30
|
+
* evaluate call into a typed `RpcRunResult`.
|
|
31
|
+
*
|
|
32
|
+
* Throws only on parse failure — an `ok:false` envelope is a normal result.
|
|
33
|
+
*
|
|
34
|
+
* SECRET-HANDLING: `rawValue` is not included in error messages.
|
|
35
|
+
*/
|
|
36
|
+
declare function parseRunTestsResult(rawValue: unknown): RpcRunResult;
|
|
37
|
+
/**
|
|
38
|
+
* Injects `bundleCode` into the attached page and awaits test execution.
|
|
39
|
+
*
|
|
40
|
+
* Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the
|
|
41
|
+
* async IIFE to settle. The 30-second CDP command timeout covers even
|
|
42
|
+
* long-running test suites; split into smaller files if you hit it.
|
|
43
|
+
*
|
|
44
|
+
* @param connection - Active CDP connection (relay or local).
|
|
45
|
+
* @param bundleCode - IIFE bundle string from `bundleTestFile`.
|
|
46
|
+
* @param timeoutMs - Override the default 30 s timeout.
|
|
47
|
+
*
|
|
48
|
+
* SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.
|
|
49
|
+
*/
|
|
50
|
+
declare function injectAndRunBundle(connection: CdpConnection, bundleCode: string, timeoutMs?: number): Promise<RpcRunResult>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { RpcRunResult, buildRunTestsExpression, injectAndRunBundle, parseRunTestsResult };
|
|
53
|
+
//# sourceMappingURL=rpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","names":[],"sources":["../../src/test-runner/rpc.ts"],"mappings":";;;;;;;;;;;;;AAmEA;;;iBAvCgB,uBAAA,CAAwB,UAAA;;AAiFxC;;KApDY,YAAA;EAAiB,EAAA;EAAU,MAAA,EAAQ,SAAA;AAAA;EAAgB,EAAA;EAAW,KAAA;AAAA;;;;;;;;;iBAU1D,mBAAA,CAAoB,QAAA,YAAoB,YAAA;;;;;;;;;;;;;;iBA0ClC,kBAAA,CACpB,UAAA,EAAY,aAAA,EACZ,UAAA,UACA,SAAA,YACC,OAAA,CAAQ,YAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/test-runner/rpc.ts
|
|
2
|
+
/** Maximum milliseconds to wait for a single evaluate round-trip. */
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
4
|
+
/**
|
|
5
|
+
* Wraps bundle code in a self-executing IIFE that:
|
|
6
|
+
* 1. Evaluates the bundle (registering describe/it/test).
|
|
7
|
+
* 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.
|
|
8
|
+
* 3. Returns a JSON-serialised `RunReport` string.
|
|
9
|
+
*
|
|
10
|
+
* The double-serialisation (RunReport → JSON string → returnByValue string)
|
|
11
|
+
* is intentional: CDP `returnByValue` reliably transports strings; deeply
|
|
12
|
+
* nested objects can lose fidelity across the Chii relay.
|
|
13
|
+
*
|
|
14
|
+
* SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.
|
|
15
|
+
*/
|
|
16
|
+
function buildRunTestsExpression(bundleCode) {
|
|
17
|
+
return `(async () => { try { ${bundleCode} } catch(e) { return JSON.stringify({ok:false,error:'bundle-eval: ' + String(e && e.message || e)}); } if (typeof globalThis.__testBundle !== 'object' || typeof globalThis.__testBundle.runTestModule !== 'function' || typeof globalThis.__testBundle.__userFactory !== 'function') { return JSON.stringify({ok:false,error:'bundle-missing-export: __testBundle.runTestModule or __userFactory is not a function'}); } try { const report = await globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory); return JSON.stringify({ok:true,value:report}); } catch(e) { return JSON.stringify({ok:false,error:'test-run: ' + String(e && e.message || e)}); }})()`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`
|
|
21
|
+
* evaluate call into a typed `RpcRunResult`.
|
|
22
|
+
*
|
|
23
|
+
* Throws only on parse failure — an `ok:false` envelope is a normal result.
|
|
24
|
+
*
|
|
25
|
+
* SECRET-HANDLING: `rawValue` is not included in error messages.
|
|
26
|
+
*/
|
|
27
|
+
function parseRunTestsResult(rawValue) {
|
|
28
|
+
if (typeof rawValue !== "string") throw new Error(`rpc.parseRunTestsResult: unexpected return type "${typeof rawValue}" — expected JSON string`);
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(rawValue);
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error("rpc.parseRunTestsResult: bridge returned non-JSON string");
|
|
34
|
+
}
|
|
35
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("rpc.parseRunTestsResult: parsed result is not an object");
|
|
36
|
+
const obj = parsed;
|
|
37
|
+
if (obj.ok === true) return {
|
|
38
|
+
ok: true,
|
|
39
|
+
report: obj.value
|
|
40
|
+
};
|
|
41
|
+
if (obj.ok === false) return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: typeof obj.error === "string" ? obj.error : String(obj.error)
|
|
44
|
+
};
|
|
45
|
+
throw new Error("rpc.parseRunTestsResult: result missing \"ok\" field");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Injects `bundleCode` into the attached page and awaits test execution.
|
|
49
|
+
*
|
|
50
|
+
* Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the
|
|
51
|
+
* async IIFE to settle. The 30-second CDP command timeout covers even
|
|
52
|
+
* long-running test suites; split into smaller files if you hit it.
|
|
53
|
+
*
|
|
54
|
+
* @param connection - Active CDP connection (relay or local).
|
|
55
|
+
* @param bundleCode - IIFE bundle string from `bundleTestFile`.
|
|
56
|
+
* @param timeoutMs - Override the default 30 s timeout.
|
|
57
|
+
*
|
|
58
|
+
* SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.
|
|
59
|
+
*/
|
|
60
|
+
async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
61
|
+
const expression = buildRunTestsExpression(bundleCode);
|
|
62
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`rpc: evaluate timed out after ${timeoutMs}ms`)), timeoutMs));
|
|
63
|
+
const evalPromise = connection.send("Runtime.evaluate", {
|
|
64
|
+
expression,
|
|
65
|
+
returnByValue: true,
|
|
66
|
+
awaitPromise: true
|
|
67
|
+
});
|
|
68
|
+
const cdpResult = await Promise.race([evalPromise, timeoutPromise]);
|
|
69
|
+
if (cdpResult.exceptionDetails) {
|
|
70
|
+
const msg = cdpResult.exceptionDetails.exception?.description ?? cdpResult.exceptionDetails.text ?? "Runtime.evaluate threw an exception";
|
|
71
|
+
throw new Error(`rpc.injectAndRunBundle: ${msg}`);
|
|
72
|
+
}
|
|
73
|
+
return parseRunTestsResult(cdpResult.result.value);
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { buildRunTestsExpression, injectAndRunBundle, parseRunTestsResult };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","names":[],"sources":["../../src/test-runner/rpc.ts"],"sourcesContent":["/**\n * Node-side RPC helpers for injecting and collecting test execution over CDP.\n *\n * Uses the same IIFE + JSON.stringify envelope pattern as `buildCallSdkExpression`\n * in `src/mcp/tools.ts` to reliably shuttle structured results through\n * `Runtime.evaluate`'s `returnByValue: true` boundary.\n *\n * SECRET-HANDLING: bundle code, relay URLs, and result values are NOT logged.\n */\n\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\nimport type { RunReport } from './runtime.js';\n\n/** Maximum milliseconds to wait for a single evaluate round-trip. */\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/**\n * Wraps bundle code in a self-executing IIFE that:\n * 1. Evaluates the bundle (registering describe/it/test).\n * 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.\n * 3. Returns a JSON-serialised `RunReport` string.\n *\n * The double-serialisation (RunReport → JSON string → returnByValue string)\n * is intentional: CDP `returnByValue` reliably transports strings; deeply\n * nested objects can lose fidelity across the Chii relay.\n *\n * SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.\n */\nexport function buildRunTestsExpression(bundleCode: string): string {\n // We trust bundleCode is already a self-contained IIFE that installs\n // `window.__testBundle` (or `globalThis.__testBundle`).\n // We then call `__testBundle.runTestModule()` and return a JSON string.\n return (\n `(async () => {` +\n // Step 1: evaluate the bundle to register tests\n ` try { ${bundleCode} } catch(e) {` +\n ` return JSON.stringify({ok:false,error:'bundle-eval: ' + String(e && e.message || e)});` +\n ` }` +\n // Step 2: check that the expected exports are present\n ` if (typeof globalThis.__testBundle !== 'object' || typeof globalThis.__testBundle.runTestModule !== 'function' || typeof globalThis.__testBundle.__userFactory !== 'function') {` +\n ` return JSON.stringify({ok:false,error:'bundle-missing-export: __testBundle.runTestModule or __userFactory is not a function'});` +\n ` }` +\n // Step 3: run tests — pass the factory so runTestModule installs globals\n // first, then invokes the factory to register describe/it/test blocks.\n ` try {` +\n ` const report = await globalThis.__testBundle.runTestModule(globalThis.__testBundle.__userFactory);` +\n ` return JSON.stringify({ok:true,value:report});` +\n ` } catch(e) {` +\n ` return JSON.stringify({ok:false,error:'test-run: ' + String(e && e.message || e)});` +\n ` }` +\n `})()`\n );\n}\n\n/**\n * Result of `injectAndRunBundle`.\n */\nexport type RpcRunResult = { ok: true; report: RunReport } | { ok: false; error: string };\n\n/**\n * Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`\n * evaluate call into a typed `RpcRunResult`.\n *\n * Throws only on parse failure — an `ok:false` envelope is a normal result.\n *\n * SECRET-HANDLING: `rawValue` is not included in error messages.\n */\nexport function parseRunTestsResult(rawValue: unknown): RpcRunResult {\n if (typeof rawValue !== 'string') {\n throw new Error(\n `rpc.parseRunTestsResult: unexpected return type \"${typeof rawValue}\" — expected JSON string`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawValue);\n } catch {\n // Do NOT include rawValue — could contain secrets.\n throw new Error('rpc.parseRunTestsResult: bridge returned non-JSON string');\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('rpc.parseRunTestsResult: parsed result is not an object');\n }\n const obj = parsed as Record<string, unknown>;\n if (obj.ok === true) {\n return { ok: true, report: obj.value as RunReport };\n }\n if (obj.ok === false) {\n return {\n ok: false,\n error: typeof obj.error === 'string' ? obj.error : String(obj.error),\n };\n }\n throw new Error('rpc.parseRunTestsResult: result missing \"ok\" field');\n}\n\n/**\n * Injects `bundleCode` into the attached page and awaits test execution.\n *\n * Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the\n * async IIFE to settle. The 30-second CDP command timeout covers even\n * long-running test suites; split into smaller files if you hit it.\n *\n * @param connection - Active CDP connection (relay or local).\n * @param bundleCode - IIFE bundle string from `bundleTestFile`.\n * @param timeoutMs - Override the default 30 s timeout.\n *\n * SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.\n */\nexport async function injectAndRunBundle(\n connection: CdpConnection,\n bundleCode: string,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n): Promise<RpcRunResult> {\n const expression = buildRunTestsExpression(bundleCode);\n\n // Use AbortSignal-style timeout via Promise.race so we surface a clear\n // message rather than hanging indefinitely.\n const timeoutPromise = new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(`rpc: evaluate timed out after ${timeoutMs}ms`)), timeoutMs),\n );\n\n const evalPromise = connection.send('Runtime.evaluate', {\n expression,\n returnByValue: true,\n awaitPromise: true,\n });\n\n const cdpResult = await Promise.race([evalPromise, timeoutPromise]);\n\n if (cdpResult.exceptionDetails) {\n // Surface only the engine error string — not the expression or value.\n const msg =\n cdpResult.exceptionDetails.exception?.description ??\n cdpResult.exceptionDetails.text ??\n 'Runtime.evaluate threw an exception';\n throw new Error(`rpc.injectAndRunBundle: ${msg}`);\n }\n\n return parseRunTestsResult(cdpResult.result.value);\n}\n"],"mappings":";;AAcA,MAAM,qBAAqB;;;;;;;;;;;;;AAc3B,SAAgB,wBAAwB,YAA4B;AAIlE,QACE,yBAEW,WAAW;;;;;;;;;;AAgC1B,SAAgB,oBAAoB,UAAiC;AACnE,KAAI,OAAO,aAAa,SACtB,OAAM,IAAI,MACR,oDAAoD,OAAO,SAAS,0BACrE;CAEH,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,SAAS;SACvB;AAEN,QAAM,IAAI,MAAM,2DAA2D;;AAE7E,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CACxE,OAAM,IAAI,MAAM,0DAA0D;CAE5E,MAAM,MAAM;AACZ,KAAI,IAAI,OAAO,KACb,QAAO;EAAE,IAAI;EAAM,QAAQ,IAAI;EAAoB;AAErD,KAAI,IAAI,OAAO,MACb,QAAO;EACL,IAAI;EACJ,OAAO,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,OAAO,IAAI,MAAM;EACrE;AAEH,OAAM,IAAI,MAAM,uDAAqD;;;;;;;;;;;;;;;AAgBvE,eAAsB,mBACpB,YACA,YACA,YAAY,oBACW;CACvB,MAAM,aAAa,wBAAwB,WAAW;CAItD,MAAM,iBAAiB,IAAI,SAAgB,GAAG,WAC5C,iBAAiB,uBAAO,IAAI,MAAM,iCAAiC,UAAU,IAAI,CAAC,EAAE,UAAU,CAC/F;CAED,MAAM,cAAc,WAAW,KAAK,oBAAoB;EACtD;EACA,eAAe;EACf,cAAc;EACf,CAAC;CAEF,MAAM,YAAY,MAAM,QAAQ,KAAK,CAAC,aAAa,eAAe,CAAC;AAEnE,KAAI,UAAU,kBAAkB;EAE9B,MAAM,MACJ,UAAU,iBAAiB,WAAW,eACtC,UAAU,iBAAiB,QAC3B;AACF,QAAM,IAAI,MAAM,2BAA2B,MAAM;;AAGnD,QAAO,oBAAoB,UAAU,OAAO,MAAM"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { t as RunReport } from "../runtime-Wi5d6Ywz.js";
|
|
2
|
+
import { File, TaskEventPack, TaskResultPack } from "@vitest/runner";
|
|
3
|
+
|
|
4
|
+
//#region src/test-runner/task-graph.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Rebuilds a single `File` task graph from one file's flat `RunReport`.
|
|
7
|
+
*
|
|
8
|
+
* @param filepath Absolute path to the test file.
|
|
9
|
+
* @param root Project root (used for the file id hash).
|
|
10
|
+
* @param projectName Vitest project name (or undefined for the default project).
|
|
11
|
+
* @param pool Pool name to stamp on the file task (our relay pool name).
|
|
12
|
+
* @param report The page-runtime report for this file.
|
|
13
|
+
*/
|
|
14
|
+
declare function synthesizeFileTask(filepath: string, root: string, projectName: string | undefined, pool: string, report: RunReport): File;
|
|
15
|
+
/**
|
|
16
|
+
* Flattens a File task subtree into the {@link TaskResultPack} tuples Vitest's
|
|
17
|
+
* `state.updateTasks` consumes: `[id, result, meta]`.
|
|
18
|
+
*
|
|
19
|
+
* Includes every task (file, suites, tests). Suites/files without a result
|
|
20
|
+
* report `undefined` in the result slot — Vitest tolerates that.
|
|
21
|
+
*/
|
|
22
|
+
declare function toTaskResultPacks(file: File): TaskResultPack[];
|
|
23
|
+
/**
|
|
24
|
+
* Builds the {@link TaskEventPack} sequence reporters expect: a `suite-prepare`
|
|
25
|
+
* before a suite's children, `test-prepare`/`test-finished` per test, and a
|
|
26
|
+
* `suite-finished` after. This drives reporter progress output.
|
|
27
|
+
*/
|
|
28
|
+
declare function toTaskEventPacks(file: File): TaskEventPack[];
|
|
29
|
+
/**
|
|
30
|
+
* Deterministic synthetic id for a file that failed before any task graph could
|
|
31
|
+
* be built (bundle/inject error). Uses the same `generateHash` Vitest uses, on
|
|
32
|
+
* the root-relative path + projectName, matching `generateFileHash` semantics so
|
|
33
|
+
* the id is stable across runs and won't collide with real file ids.
|
|
34
|
+
*/
|
|
35
|
+
declare function syntheticFileId(relativePath: string, projectName: string | undefined): string;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { synthesizeFileTask, syntheticFileId, toTaskEventPacks, toTaskResultPacks };
|
|
38
|
+
//# sourceMappingURL=task-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-graph.d.ts","names":[],"sources":["../../src/test-runner/task-graph.ts"],"mappings":";;;;AA6MA;;;;;;;;;AAAA,iBA5DgB,kBAAA,CACd,QAAA,UACA,IAAA,UACA,WAAA,sBACA,IAAA,UACA,MAAA,EAAQ,SAAA,GACP,IAAA;;;;;;;;iBAmCa,iBAAA,CAAkB,IAAA,EAAM,IAAA,GAAO,cAAA;;;;;;iBAmB/B,gBAAA,CAAiB,IAAA,EAAM,IAAA,GAAO,aAAA;;;;;;;iBAgC9B,eAAA,CAAgB,YAAA,UAAsB,WAAA"}
|