ccqa 1.51.1 → 1.53.0

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.
@@ -0,0 +1,39 @@
1
+ //#region src/runtime/test-helpers.d.ts
2
+ declare function __setCurrentStep(stepId: string, source: string): void;
3
+ declare function ab(...args: string[]): void;
4
+ /** Wait for element/text with an explicit timeout so long-running async ops don't hang. */
5
+ declare function abWait(selector: string, timeoutMs?: number): void;
6
+ /**
7
+ * Upload one or more files to a file input via `agent-browser upload`.
8
+ * Relative paths resolve against the test's cwd. We pre-check existence
9
+ * locally so a typo in a fixture path surfaces as a clear error before
10
+ * agent-browser exits with an opaque non-zero status.
11
+ */
12
+ declare function abUpload(selector: string, ...files: string[]): void;
13
+ /** Assert stable text is visible on page (via wait --text). */
14
+ declare function abAssertTextVisible(text: string, timeoutMs?: number): void;
15
+ /** Assert element is visible (polls `get count`; never uses the blocking `wait <selector>`). */
16
+ declare function abAssertVisible(selector: string, timeoutMs?: number): void;
17
+ /** Assert element is NOT visible (polls `get count` for absence; --fn for text). */
18
+ declare function abAssertNotVisible(selector: string, timeoutMs?: number): void;
19
+ /** Assert URL contains a pattern (via get url). */
20
+ declare function abAssertUrl(pattern: string): void;
21
+ /** Assert element is enabled (via is enabled). */
22
+ declare function abAssertEnabled(selector: string): void;
23
+ /** Assert element is disabled (via is enabled). */
24
+ declare function abAssertDisabled(selector: string): void;
25
+ /** Assert checkbox is checked (via is checked). */
26
+ declare function abAssertChecked(selector: string): void;
27
+ /** Assert checkbox is unchecked (via is checked). */
28
+ declare function abAssertUnchecked(selector: string): void;
29
+ /**
30
+ * Capture a step-boundary evidence pair (PNG + JSON metadata) so a reviewer
31
+ * can confirm at a glance that a passing spec actually drove the app to the
32
+ * state its `expected` describes. Opt-in at runtime via `CCQA_EVIDENCE_DIR` so
33
+ * generated scripts hand-run outside `ccqa run` don't write stray files. All
34
+ * errors are swallowed with a stderr warning — evidence capture must never
35
+ * flip a passing spec to red.
36
+ */
37
+ declare function abStepEvidence(stepId: string, source: string): void;
38
+ //#endregion
39
+ export { __setCurrentStep, ab, abAssertChecked, abAssertDisabled, abAssertEnabled, abAssertNotVisible, abAssertTextVisible, abAssertUnchecked, abAssertUrl, abAssertVisible, abStepEvidence, abUpload, abWait };
@@ -1,7 +1,138 @@
1
- import { i as sanitizeStepId, n as FAILURE_SOURCE, r as FAILURE_STEP_ID, t as EVIDENCE_DIR_ENV } from "../evidence-constants-Cm_S_5od.mjs";
2
- import { n as spawnAB, t as sleepSync } from "../spawn-ab-CR_Sr7wh.mjs";
3
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { i as sanitizeStepId, n as FAILURE_SOURCE, r as FAILURE_STEP_ID, t as EVIDENCE_DIR_ENV } from "../evidence-constants-BLXD0LRY.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ //#region src/runtime/agent-browser-bin.ts
7
+ const require = createRequire(import.meta.url);
8
+ function hasAgentBrowserShim(dir) {
9
+ try {
10
+ statSync(join(dir, "agent-browser"));
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+ /**
17
+ * Walks up from `start` looking for a `node_modules/.bin/agent-browser` shim.
18
+ * Returns the .bin directory containing the shim, or null if none is found.
19
+ */
20
+ function findNodeModulesBin(start) {
21
+ let cur = start;
22
+ while (true) {
23
+ const candidate = join(cur, "node_modules", ".bin");
24
+ if (hasAgentBrowserShim(candidate)) return candidate;
25
+ const parent = dirname(cur);
26
+ if (parent === cur) return null;
27
+ cur = parent;
28
+ }
29
+ }
30
+ /** The shim-directory walk shared by every resolution below (no env override). */
31
+ function resolveShimDir() {
32
+ const fromCwd = findNodeModulesBin(process.cwd());
33
+ if (fromCwd) return fromCwd;
34
+ const fromSelf = findNodeModulesBin(dirname(require.resolve("agent-browser/package.json")));
35
+ if (fromSelf) return fromSelf;
36
+ try {
37
+ const candidate = join(dirname(require.resolve("agent-browser/package.json")), "node_modules", ".bin");
38
+ if (hasAgentBrowserShim(candidate)) return candidate;
39
+ } catch {}
40
+ return null;
41
+ }
42
+ /**
43
+ * INVARIANT: every agent-browser invocation in one ccqa process — the host
44
+ * side (`spawnAB`: state load, replay probes, …) and the Claude subprocess
45
+ * (via the PATH prepended by `pathWithAgentBrowserShim`) — must resolve to
46
+ * the SAME binary. agent-browser runs one daemon per binary, and a state
47
+ * loaded into one daemon's session is invisible to a same-named session on
48
+ * another daemon, so a split resolution silently loses session restores.
49
+ * Both entry points below therefore share one resolution order:
50
+ * `CCQA_AB_BIN` (explicit override, e.g. the e2e harness or a dev build
51
+ * driving a consumer project's agent-browser) → the peer-installed shim
52
+ * (consumer project first, then ccqa's own tree).
53
+ */
54
+ /**
55
+ * Resolves the executable `spawnAB` should invoke. Falls back to the package
56
+ * JS entry (resolvable whenever the peer dependency is installed) when no
57
+ * shim directory exists; throws only if agent-browser is missing entirely.
58
+ */
59
+ function resolveAgentBrowserBin() {
60
+ const override = process.env["CCQA_AB_BIN"];
61
+ if (override) return override;
62
+ const shimDir = resolveShimDir();
63
+ if (shimDir) return join(shimDir, "agent-browser");
64
+ return require.resolve("agent-browser/bin/agent-browser.js");
65
+ }
66
+ //#endregion
67
+ //#region src/runtime/spawn-ab.ts
68
+ const AB = resolveAgentBrowserBin();
69
+ const EAGAIN_PATTERN = /Resource temporarily unavailable|os error 35/i;
70
+ const EAGAIN_TOTAL_BUDGET_MS = 3e4;
71
+ const EAGAIN_BACKOFF_MS = [
72
+ 100,
73
+ 200,
74
+ 300,
75
+ 500,
76
+ 700,
77
+ 1e3,
78
+ 1500,
79
+ 2e3,
80
+ 2500,
81
+ 3e3,
82
+ 3e3,
83
+ 3e3,
84
+ 3e3,
85
+ 3e3,
86
+ 3e3
87
+ ];
88
+ const PROCESS_HARD_TIMEOUT_MS = 35e3;
89
+ function sleepSync(ms) {
90
+ const buf = new SharedArrayBuffer(4);
91
+ Atomics.wait(new Int32Array(buf), 0, 0, ms);
92
+ }
93
+ function spawnABOnce(args, timeoutMs) {
94
+ const result = spawnSync(AB, args, {
95
+ stdio: "pipe",
96
+ timeout: timeoutMs
97
+ });
98
+ const wedged = result.error?.code === "ETIMEDOUT";
99
+ return {
100
+ status: result.status,
101
+ stdout: result.stdout?.toString() ?? "",
102
+ stderr: (result.stderr?.toString() ?? "") + (wedged ? `\n[ccqa] agent-browser ${subcommand(args)} did not answer in ${timeoutMs}ms — killed after hard timeout` : ""),
103
+ wedged
104
+ };
105
+ }
106
+ /** The verb a reader recognises, past the one prefix every caller writes. */
107
+ function subcommand(args) {
108
+ return (args[0] === "--session" ? args[2] : args[0]) ?? "(no command)";
109
+ }
110
+ /**
111
+ * Invoke `agent-browser` once and return its exit status/stdout/stderr,
112
+ * retrying internally up to ~30s while the daemon's state file is in the
113
+ * "Resource temporarily unavailable" race window. Used by both the test
114
+ * runtime (`test-helpers.ts`) and the post-trace replay validation
115
+ * (`replay-validate.ts`). Kept out of `test-helpers.ts` because that
116
+ * module is also the public surface for generated test scripts — exposing
117
+ * the raw spawner there would widen the contract for end users.
118
+ */
119
+ function spawnAB(args, opts) {
120
+ const timeoutMs = opts?.timeoutMs ?? PROCESS_HARD_TIMEOUT_MS;
121
+ let result = spawnABOnce(args, timeoutMs);
122
+ let elapsed = 0;
123
+ let attempt = 0;
124
+ while (result.status !== 0 && elapsed < EAGAIN_TOTAL_BUDGET_MS) {
125
+ const combined = `${result.stdout}\n${result.stderr}`;
126
+ if (!EAGAIN_PATTERN.test(combined)) return result;
127
+ const wait = EAGAIN_BACKOFF_MS[attempt] ?? 3e3;
128
+ sleepSync(wait);
129
+ elapsed += wait;
130
+ attempt++;
131
+ result = spawnABOnce(args, timeoutMs);
132
+ }
133
+ return result;
134
+ }
135
+ //#endregion
5
136
  //#region src/runtime/test-helpers.ts
6
137
  const POST_OPEN_SETTLE_MS = 600;
7
138
  function logStep(action, args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.51.1",
3
+ "version": "1.53.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {
@@ -16,20 +16,60 @@
16
16
  },
17
17
  "exports": {
18
18
  "./test-helpers": {
19
- "types": "./dist/runtime/test-helpers.d.mts",
20
- "import": "./dist/runtime/test-helpers.mjs"
19
+ "import": {
20
+ "types": "./dist/runtime/test-helpers.d.mts",
21
+ "default": "./dist/runtime/test-helpers.mjs"
22
+ },
23
+ "require": {
24
+ "types": "./dist/runtime/test-helpers.d.cts",
25
+ "default": "./dist/runtime/test-helpers.cjs"
26
+ }
21
27
  },
22
28
  "./step-evidence": {
23
- "types": "./dist/runtime/step-evidence.d.mts",
24
- "import": "./dist/runtime/step-evidence.mjs"
29
+ "import": {
30
+ "types": "./dist/runtime/step-evidence.d.mts",
31
+ "default": "./dist/runtime/step-evidence.mjs"
32
+ },
33
+ "require": {
34
+ "types": "./dist/runtime/step-evidence.d.cts",
35
+ "default": "./dist/runtime/step-evidence.cjs"
36
+ }
25
37
  },
26
38
  "./hub-client": {
27
- "types": "./dist/hub-client/index.d.mts",
28
- "import": "./dist/hub-client/index.mjs"
39
+ "import": {
40
+ "types": "./dist/hub-client/index.d.mts",
41
+ "default": "./dist/hub-client/index.mjs"
42
+ },
43
+ "require": {
44
+ "types": "./dist/hub-client/index.d.cts",
45
+ "default": "./dist/hub-client/index.cjs"
46
+ }
29
47
  },
30
48
  "./judge": {
31
- "types": "./dist/runtime/judge.d.mts",
32
- "import": "./dist/runtime/judge.mjs"
49
+ "import": {
50
+ "types": "./dist/runtime/judge.d.mts",
51
+ "default": "./dist/runtime/judge.mjs"
52
+ },
53
+ "require": {
54
+ "types": "./dist/runtime/judge.d.cts",
55
+ "default": "./dist/runtime/judge.cjs"
56
+ }
57
+ }
58
+ },
59
+ "typesVersions": {
60
+ "*": {
61
+ "test-helpers": [
62
+ "dist/runtime/test-helpers.d.mts"
63
+ ],
64
+ "step-evidence": [
65
+ "dist/runtime/step-evidence.d.mts"
66
+ ],
67
+ "hub-client": [
68
+ "dist/hub-client/index.d.mts"
69
+ ],
70
+ "judge": [
71
+ "dist/runtime/judge.d.mts"
72
+ ]
33
73
  }
34
74
  },
35
75
  "files": [