@monoes/monobrowse 1.0.12 → 1.0.14

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.
Files changed (271) hide show
  1. package/dist/src/__tests__/bridge-instruments.test.d.ts +2 -0
  2. package/dist/src/__tests__/bridge-instruments.test.d.ts.map +1 -0
  3. package/dist/src/__tests__/bridge-instruments.test.js +132 -0
  4. package/dist/src/__tests__/bridge-instruments.test.js.map +1 -0
  5. package/dist/src/__tests__/bridge-transport.test.d.ts +2 -0
  6. package/dist/src/__tests__/bridge-transport.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/bridge-transport.test.js +287 -0
  8. package/dist/src/__tests__/bridge-transport.test.js.map +1 -0
  9. package/dist/src/__tests__/cdp-transport.test.d.ts +2 -0
  10. package/dist/src/__tests__/cdp-transport.test.d.ts.map +1 -0
  11. package/dist/src/__tests__/cdp-transport.test.js +167 -0
  12. package/dist/src/__tests__/cdp-transport.test.js.map +1 -0
  13. package/dist/src/__tests__/profiler-heap.test.d.ts +2 -0
  14. package/dist/src/__tests__/profiler-heap.test.d.ts.map +1 -0
  15. package/dist/src/__tests__/profiler-heap.test.js +154 -0
  16. package/dist/src/__tests__/profiler-heap.test.js.map +1 -0
  17. package/dist/src/__tests__/report-a11y.test.d.ts +2 -0
  18. package/dist/src/__tests__/report-a11y.test.d.ts.map +1 -0
  19. package/dist/src/__tests__/report-a11y.test.js +147 -0
  20. package/dist/src/__tests__/report-a11y.test.js.map +1 -0
  21. package/dist/src/__tests__/report-analyze.test.d.ts +2 -0
  22. package/dist/src/__tests__/report-analyze.test.d.ts.map +1 -0
  23. package/dist/src/__tests__/report-analyze.test.js +157 -0
  24. package/dist/src/__tests__/report-analyze.test.js.map +1 -0
  25. package/dist/src/__tests__/report-budget.test.d.ts +2 -0
  26. package/dist/src/__tests__/report-budget.test.d.ts.map +1 -0
  27. package/dist/src/__tests__/report-budget.test.js +80 -0
  28. package/dist/src/__tests__/report-budget.test.js.map +1 -0
  29. package/dist/src/__tests__/report-diff.test.d.ts +2 -0
  30. package/dist/src/__tests__/report-diff.test.d.ts.map +1 -0
  31. package/dist/src/__tests__/report-diff.test.js +231 -0
  32. package/dist/src/__tests__/report-diff.test.js.map +1 -0
  33. package/dist/src/__tests__/report-evidence.test.d.ts +2 -0
  34. package/dist/src/__tests__/report-evidence.test.d.ts.map +1 -0
  35. package/dist/src/__tests__/report-evidence.test.js +214 -0
  36. package/dist/src/__tests__/report-evidence.test.js.map +1 -0
  37. package/dist/src/__tests__/report-flake.test.d.ts +2 -0
  38. package/dist/src/__tests__/report-flake.test.d.ts.map +1 -0
  39. package/dist/src/__tests__/report-flake.test.js +182 -0
  40. package/dist/src/__tests__/report-flake.test.js.map +1 -0
  41. package/dist/src/__tests__/report-history.test.d.ts +2 -0
  42. package/dist/src/__tests__/report-history.test.d.ts.map +1 -0
  43. package/dist/src/__tests__/report-history.test.js +229 -0
  44. package/dist/src/__tests__/report-history.test.js.map +1 -0
  45. package/dist/src/__tests__/report-output.test.d.ts +2 -0
  46. package/dist/src/__tests__/report-output.test.d.ts.map +1 -0
  47. package/dist/src/__tests__/report-output.test.js +169 -0
  48. package/dist/src/__tests__/report-output.test.js.map +1 -0
  49. package/dist/src/__tests__/report-png.test.d.ts +2 -0
  50. package/dist/src/__tests__/report-png.test.d.ts.map +1 -0
  51. package/dist/src/__tests__/report-png.test.js +84 -0
  52. package/dist/src/__tests__/report-png.test.js.map +1 -0
  53. package/dist/src/__tests__/report-render-extras.test.d.ts +2 -0
  54. package/dist/src/__tests__/report-render-extras.test.d.ts.map +1 -0
  55. package/dist/src/__tests__/report-render-extras.test.js +285 -0
  56. package/dist/src/__tests__/report-render-extras.test.js.map +1 -0
  57. package/dist/src/__tests__/report-render.test.d.ts +2 -0
  58. package/dist/src/__tests__/report-render.test.d.ts.map +1 -0
  59. package/dist/src/__tests__/report-render.test.js +143 -0
  60. package/dist/src/__tests__/report-render.test.js.map +1 -0
  61. package/dist/src/__tests__/report-run.test.d.ts +2 -0
  62. package/dist/src/__tests__/report-run.test.d.ts.map +1 -0
  63. package/dist/src/__tests__/report-run.test.js +234 -0
  64. package/dist/src/__tests__/report-run.test.js.map +1 -0
  65. package/dist/src/browser/bridge.d.ts +119 -0
  66. package/dist/src/browser/bridge.d.ts.map +1 -0
  67. package/dist/src/browser/bridge.js +264 -0
  68. package/dist/src/browser/bridge.js.map +1 -0
  69. package/dist/src/browser/cdp.d.ts +11 -1
  70. package/dist/src/browser/cdp.d.ts.map +1 -1
  71. package/dist/src/browser/cdp.js +60 -56
  72. package/dist/src/browser/cdp.js.map +1 -1
  73. package/dist/src/browser/index.d.ts +3 -0
  74. package/dist/src/browser/index.d.ts.map +1 -1
  75. package/dist/src/browser/index.js +3 -0
  76. package/dist/src/browser/index.js.map +1 -1
  77. package/dist/src/browser/profiler.d.ts.map +1 -1
  78. package/dist/src/browser/profiler.js +19 -22
  79. package/dist/src/browser/profiler.js.map +1 -1
  80. package/dist/src/browser/transport.d.ts +62 -0
  81. package/dist/src/browser/transport.d.ts.map +1 -0
  82. package/dist/src/browser/transport.js +65 -0
  83. package/dist/src/browser/transport.js.map +1 -0
  84. package/dist/src/cli/commander-adapter.d.ts +16 -0
  85. package/dist/src/cli/commander-adapter.d.ts.map +1 -0
  86. package/dist/src/cli/commander-adapter.js +68 -0
  87. package/dist/src/cli/commander-adapter.js.map +1 -0
  88. package/dist/src/cli/commands-context.d.ts +14 -0
  89. package/dist/src/cli/commands-context.d.ts.map +1 -0
  90. package/dist/src/cli/commands-context.js +351 -0
  91. package/dist/src/cli/commands-context.js.map +1 -0
  92. package/dist/src/cli/commands-data.d.ts +12 -0
  93. package/dist/src/cli/commands-data.d.ts.map +1 -0
  94. package/dist/src/cli/commands-data.js +246 -0
  95. package/dist/src/cli/commands-data.js.map +1 -0
  96. package/dist/src/cli/commands-element.d.ts +15 -0
  97. package/dist/src/cli/commands-element.d.ts.map +1 -0
  98. package/dist/src/cli/commands-element.js +262 -0
  99. package/dist/src/cli/commands-element.js.map +1 -0
  100. package/dist/src/cli/commands-files.d.ts +9 -0
  101. package/dist/src/cli/commands-files.d.ts.map +1 -0
  102. package/dist/src/cli/commands-files.js +207 -0
  103. package/dist/src/cli/commands-files.js.map +1 -0
  104. package/dist/src/cli/commands-input.d.ts +26 -0
  105. package/dist/src/cli/commands-input.d.ts.map +1 -0
  106. package/dist/src/cli/commands-input.js +399 -0
  107. package/dist/src/cli/commands-input.js.map +1 -0
  108. package/dist/src/cli/commands-navigate.d.ts +12 -0
  109. package/dist/src/cli/commands-navigate.d.ts.map +1 -0
  110. package/dist/src/cli/commands-navigate.js +389 -0
  111. package/dist/src/cli/commands-navigate.js.map +1 -0
  112. package/dist/src/cli/commands-page.d.ts +25 -0
  113. package/dist/src/cli/commands-page.d.ts.map +1 -0
  114. package/dist/src/cli/commands-page.js +377 -0
  115. package/dist/src/cli/commands-page.js.map +1 -0
  116. package/dist/src/cli/commands-report.d.ts +8 -0
  117. package/dist/src/cli/commands-report.d.ts.map +1 -0
  118. package/dist/src/cli/commands-report.js +256 -0
  119. package/dist/src/cli/commands-report.js.map +1 -0
  120. package/dist/src/cli/commands-script.d.ts +43 -0
  121. package/dist/src/cli/commands-script.d.ts.map +1 -0
  122. package/dist/src/cli/commands-script.js +168 -0
  123. package/dist/src/cli/commands-script.js.map +1 -0
  124. package/dist/src/cli/commands-session.d.ts +15 -0
  125. package/dist/src/cli/commands-session.d.ts.map +1 -0
  126. package/dist/src/cli/commands-session.js +357 -0
  127. package/dist/src/cli/commands-session.js.map +1 -0
  128. package/dist/src/cli/commands-trace.d.ts +12 -0
  129. package/dist/src/cli/commands-trace.d.ts.map +1 -0
  130. package/dist/src/cli/commands-trace.js +347 -0
  131. package/dist/src/cli/commands-trace.js.map +1 -0
  132. package/dist/src/cli/commands.d.ts +22 -42
  133. package/dist/src/cli/commands.d.ts.map +1 -1
  134. package/dist/src/cli/commands.js +48 -3400
  135. package/dist/src/cli/commands.js.map +1 -1
  136. package/dist/src/cli/session.d.ts +47 -0
  137. package/dist/src/cli/session.d.ts.map +1 -0
  138. package/dist/src/cli/session.js +343 -0
  139. package/dist/src/cli/session.js.map +1 -0
  140. package/dist/src/report/a11y.d.ts +52 -0
  141. package/dist/src/report/a11y.d.ts.map +1 -0
  142. package/dist/src/report/a11y.js +231 -0
  143. package/dist/src/report/a11y.js.map +1 -0
  144. package/dist/src/report/analyze.d.ts +20 -0
  145. package/dist/src/report/analyze.d.ts.map +1 -0
  146. package/dist/src/report/analyze.js +136 -0
  147. package/dist/src/report/analyze.js.map +1 -0
  148. package/dist/src/report/budget.d.ts +27 -0
  149. package/dist/src/report/budget.d.ts.map +1 -0
  150. package/dist/src/report/budget.js +143 -0
  151. package/dist/src/report/budget.js.map +1 -0
  152. package/dist/src/report/collect-a11y.d.ts +25 -0
  153. package/dist/src/report/collect-a11y.d.ts.map +1 -0
  154. package/dist/src/report/collect-a11y.js +154 -0
  155. package/dist/src/report/collect-a11y.js.map +1 -0
  156. package/dist/src/report/collect.d.ts +45 -0
  157. package/dist/src/report/collect.d.ts.map +1 -0
  158. package/dist/src/report/collect.js +275 -0
  159. package/dist/src/report/collect.js.map +1 -0
  160. package/dist/src/report/evidence.d.ts +80 -0
  161. package/dist/src/report/evidence.d.ts.map +1 -0
  162. package/dist/src/report/evidence.js +279 -0
  163. package/dist/src/report/evidence.js.map +1 -0
  164. package/dist/src/report/flake.d.ts +88 -0
  165. package/dist/src/report/flake.d.ts.map +1 -0
  166. package/dist/src/report/flake.js +232 -0
  167. package/dist/src/report/flake.js.map +1 -0
  168. package/dist/src/report/history.d.ts +85 -0
  169. package/dist/src/report/history.d.ts.map +1 -0
  170. package/dist/src/report/history.js +176 -0
  171. package/dist/src/report/history.js.map +1 -0
  172. package/dist/src/report/index.d.ts +102 -0
  173. package/dist/src/report/index.d.ts.map +1 -0
  174. package/dist/src/report/index.js +216 -0
  175. package/dist/src/report/index.js.map +1 -0
  176. package/dist/src/report/pixel-diff.d.ts +30 -0
  177. package/dist/src/report/pixel-diff.d.ts.map +1 -0
  178. package/dist/src/report/pixel-diff.js +175 -0
  179. package/dist/src/report/pixel-diff.js.map +1 -0
  180. package/dist/src/report/png.d.ts +28 -0
  181. package/dist/src/report/png.d.ts.map +1 -0
  182. package/dist/src/report/png.js +259 -0
  183. package/dist/src/report/png.js.map +1 -0
  184. package/dist/src/report/render-evidence.d.ts +16 -0
  185. package/dist/src/report/render-evidence.d.ts.map +1 -0
  186. package/dist/src/report/render-evidence.js +174 -0
  187. package/dist/src/report/render-evidence.js.map +1 -0
  188. package/dist/src/report/render-trend.d.ts +16 -0
  189. package/dist/src/report/render-trend.d.ts.map +1 -0
  190. package/dist/src/report/render-trend.js +194 -0
  191. package/dist/src/report/render-trend.js.map +1 -0
  192. package/dist/src/report/render.d.ts +21 -0
  193. package/dist/src/report/render.d.ts.map +1 -0
  194. package/dist/src/report/render.js +286 -0
  195. package/dist/src/report/render.js.map +1 -0
  196. package/dist/src/report/run-diff.d.ts +18 -0
  197. package/dist/src/report/run-diff.d.ts.map +1 -0
  198. package/dist/src/report/run-diff.js +51 -0
  199. package/dist/src/report/run-diff.js.map +1 -0
  200. package/dist/src/report/structure.d.ts +41 -0
  201. package/dist/src/report/structure.d.ts.map +1 -0
  202. package/dist/src/report/structure.js +210 -0
  203. package/dist/src/report/structure.js.map +1 -0
  204. package/dist/src/report/trend.d.ts +26 -0
  205. package/dist/src/report/trend.d.ts.map +1 -0
  206. package/dist/src/report/trend.js +164 -0
  207. package/dist/src/report/trend.js.map +1 -0
  208. package/dist/src/report/types.d.ts +320 -0
  209. package/dist/src/report/types.d.ts.map +1 -0
  210. package/dist/src/report/types.js +11 -0
  211. package/dist/src/report/types.js.map +1 -0
  212. package/dist/src/report/util.d.ts +17 -0
  213. package/dist/src/report/util.d.ts.map +1 -0
  214. package/dist/src/report/util.js +45 -0
  215. package/dist/src/report/util.js.map +1 -0
  216. package/dist/tsconfig.tsbuildinfo +1 -1
  217. package/package.json +1 -1
  218. package/src/__tests__/bridge-instruments.test.ts +180 -0
  219. package/src/__tests__/bridge-transport.test.ts +365 -0
  220. package/src/__tests__/cdp-transport.test.ts +198 -0
  221. package/src/__tests__/profiler-heap.test.ts +191 -0
  222. package/src/__tests__/report-a11y.test.ts +180 -0
  223. package/src/__tests__/report-analyze.test.ts +202 -0
  224. package/src/__tests__/report-budget.test.ts +92 -0
  225. package/src/__tests__/report-diff.test.ts +272 -0
  226. package/src/__tests__/report-evidence.test.ts +276 -0
  227. package/src/__tests__/report-flake.test.ts +214 -0
  228. package/src/__tests__/report-history.test.ts +293 -0
  229. package/src/__tests__/report-output.test.ts +210 -0
  230. package/src/__tests__/report-png.test.ts +95 -0
  231. package/src/__tests__/report-render-extras.test.ts +315 -0
  232. package/src/__tests__/report-render.test.ts +175 -0
  233. package/src/__tests__/report-run.test.ts +264 -0
  234. package/src/browser/bridge.ts +354 -0
  235. package/src/browser/cdp.ts +57 -52
  236. package/src/browser/index.ts +3 -0
  237. package/src/browser/profiler.ts +28 -21
  238. package/src/browser/transport.ts +111 -0
  239. package/src/cli/commander-adapter.ts +65 -0
  240. package/src/cli/commands-context.ts +367 -0
  241. package/src/cli/commands-data.ts +248 -0
  242. package/src/cli/commands-element.ts +305 -0
  243. package/src/cli/commands-files.ts +248 -0
  244. package/src/cli/commands-input.ts +434 -0
  245. package/src/cli/commands-navigate.ts +442 -0
  246. package/src/cli/commands-page.ts +420 -0
  247. package/src/cli/commands-report.ts +275 -0
  248. package/src/cli/commands-script.ts +183 -0
  249. package/src/cli/commands-session.ts +376 -0
  250. package/src/cli/commands-trace.ts +349 -0
  251. package/src/cli/commands.ts +101 -3677
  252. package/src/cli/session.ts +412 -0
  253. package/src/report/a11y.ts +242 -0
  254. package/src/report/analyze.ts +181 -0
  255. package/src/report/budget.ts +155 -0
  256. package/src/report/collect-a11y.ts +200 -0
  257. package/src/report/collect.ts +408 -0
  258. package/src/report/evidence.ts +379 -0
  259. package/src/report/flake.ts +303 -0
  260. package/src/report/history.ts +219 -0
  261. package/src/report/index.ts +307 -0
  262. package/src/report/pixel-diff.ts +197 -0
  263. package/src/report/png.ts +290 -0
  264. package/src/report/render-evidence.ts +194 -0
  265. package/src/report/render-trend.ts +225 -0
  266. package/src/report/render.ts +314 -0
  267. package/src/report/run-diff.ts +63 -0
  268. package/src/report/structure.ts +212 -0
  269. package/src/report/trend.ts +189 -0
  270. package/src/report/types.ts +356 -0
  271. package/src/report/util.ts +48 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * The orchestration seam in report/index.ts: history is read before the run
3
+ * is saved, evidence is only built when it is warranted, and `--repeat`
4
+ * judges the set rather than the last run.
5
+ *
6
+ * `collect` is mocked, so nothing here launches Chrome. Output and history
7
+ * both go to a scratch directory under the monomind data dir — never the
8
+ * shared /tmp.
9
+ */
10
+ import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises';
11
+ import { homedir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
14
+ import { historyKey, listRuns } from '../report/history.js';
15
+ import { encodePng, pngToDataUrl } from '../report/png.js';
16
+ const collect = vi.hoisted(() => vi.fn());
17
+ vi.mock('../report/collect.js', () => ({ collect }));
18
+ const { runReport, runReportRepeated, readHistory } = await import('../report/index.js');
19
+ const URL_UNDER_TEST = 'https://shop.test/checkout';
20
+ const client = {};
21
+ function shot(shade, width = 8, height = 8) {
22
+ const data = new Uint8Array(width * height * 4);
23
+ for (let i = 0; i < width * height; i++)
24
+ data.set([shade, shade, shade, 255], i * 4);
25
+ return {
26
+ label: 'page',
27
+ width,
28
+ height,
29
+ dataUrl: pngToDataUrl(encodePng({ width, height, data })),
30
+ };
31
+ }
32
+ function capture(partial = {}) {
33
+ return {
34
+ url: URL_UNDER_TEST,
35
+ finalUrl: URL_UNDER_TEST,
36
+ title: 'Checkout',
37
+ capturedAt: '2026-09-21T10:00:00.000Z',
38
+ durationMs: 1000,
39
+ console: [],
40
+ pageErrors: [],
41
+ requests: [],
42
+ vitals: { lcp: 2000, cls: 0.01, inp: 50 },
43
+ a11y: [],
44
+ structure: [{ path: '/main[1]/button[1]', role: 'button', name: 'Pay now', depth: 1 }],
45
+ screenshots: [shot(200)],
46
+ notes: [],
47
+ ...partial,
48
+ };
49
+ }
50
+ let root;
51
+ let outDir;
52
+ beforeEach(async () => {
53
+ await mkdir(join(homedir(), '.monomind'), { recursive: true });
54
+ root = await mkdtemp(join(homedir(), '.monomind', 'monobrowse-run-test-'));
55
+ outDir = join(root, 'out');
56
+ await mkdir(outDir, { recursive: true });
57
+ collect.mockReset();
58
+ });
59
+ afterEach(async () => {
60
+ await rm(root, { recursive: true, force: true });
61
+ });
62
+ const run = (data, extra = {}) => {
63
+ collect.mockResolvedValueOnce(data);
64
+ return runReport(client, 'S1', {
65
+ url: URL_UNDER_TEST,
66
+ out: join(outDir, `${Math.random().toString(36).slice(2)}.html`),
67
+ historyDir: root,
68
+ cwd: outDir,
69
+ ...extra,
70
+ });
71
+ };
72
+ describe('runReport and history', () => {
73
+ it('does not let the first run trend or diff against itself', async () => {
74
+ const result = await run(capture());
75
+ expect(result.report.trend?.runs).toBe(0);
76
+ expect(result.report.diff).toBeUndefined();
77
+ // ...but it is on disk for the next run to compare against.
78
+ expect((await listRuns(join(root, historyKey(URL_UNDER_TEST)))).length).toBe(1);
79
+ });
80
+ it('trends and diffs the second run against the first', async () => {
81
+ await run(capture());
82
+ const second = await run(capture({
83
+ capturedAt: '2026-09-21T11:00:00.000Z',
84
+ vitals: { lcp: 2600, cls: 0.01, inp: 50 },
85
+ structure: [{ path: '/main[1]/button[1]', role: 'button', name: 'Place order', depth: 1 }],
86
+ screenshots: [shot(40)],
87
+ }));
88
+ expect(second.report.trend?.runs).toBe(1);
89
+ const lcp = second.report.trend?.series.find((s) => s.key === 'lcp');
90
+ expect(lcp).toMatchObject({ previous: 2000, current: 2600, direction: 'regressed' });
91
+ expect(second.report.diff?.previousCapturedAt).toBe('2026-09-21T10:00:00.000Z');
92
+ expect(second.report.diff?.structure?.renamed).toEqual([
93
+ { path: '/main[1]/button[1]', role: 'button', from: 'Pay now', to: 'Place order' },
94
+ ]);
95
+ // Every pixel went from light grey to dark — a whole-screenshot change.
96
+ expect(second.report.diff?.pixels?.changedPercent).toBe(100);
97
+ });
98
+ it('reports the history directory it wrote to', async () => {
99
+ const result = await run(capture());
100
+ expect(result.historyDir).toBe(join(root, historyKey(URL_UNDER_TEST)));
101
+ });
102
+ it('prunes to the configured maximum across runs', async () => {
103
+ for (let i = 0; i < 4; i++) {
104
+ await run(capture({ capturedAt: `2026-09-2${i + 1}T10:00:00.000Z` }), { historyMax: 2 });
105
+ }
106
+ const runs = await listRuns(join(root, historyKey(URL_UNDER_TEST)));
107
+ expect(runs.map((r) => r.capturedAt)).toEqual([
108
+ '2026-09-23T10:00:00.000Z',
109
+ '2026-09-24T10:00:00.000Z',
110
+ ]);
111
+ });
112
+ it('writes nothing to the store when history is off, and says nothing about trends', async () => {
113
+ const result = await run(capture(), { history: false });
114
+ expect(result.report.trend).toBeUndefined();
115
+ expect(result.historyDir).toBeUndefined();
116
+ await expect(readdir(join(root, historyKey(URL_UNDER_TEST)))).rejects.toThrow();
117
+ });
118
+ it('keeps the report when the history store cannot be written', async () => {
119
+ // A regular file where the store's directory needs to be: mkdir fails
120
+ // with ENOTDIR, and the report still has to come out.
121
+ const blocked = join(root, 'blocked-store');
122
+ await writeFile(blocked, 'not a directory', 'utf8');
123
+ const result = await run(capture(), { historyDir: blocked });
124
+ expect(result.report.verdict).toBe('pass');
125
+ expect(result.report.notes.join(' ')).toMatch(/Run history unavailable/);
126
+ expect(result.historyDir).toBeUndefined();
127
+ });
128
+ it('exposes the stored runs through readHistory', async () => {
129
+ await run(capture());
130
+ const { runs } = await readHistory(URL_UNDER_TEST, { historyDir: root });
131
+ expect(runs.length).toBe(1);
132
+ expect(runs[0]).toMatchObject({ url: URL_UNDER_TEST, verdict: 'pass' });
133
+ });
134
+ });
135
+ describe('runReport and evidence', () => {
136
+ const recording = {
137
+ startedAtMs: 1_000_000,
138
+ frames: [
139
+ { offsetMs: 0, data: 'AAAA', bytes: 4 },
140
+ { offsetMs: 900, data: 'BBBB', bytes: 4 },
141
+ ],
142
+ droppedFrames: 0,
143
+ requested: false,
144
+ };
145
+ it('attaches evidence when the run breaches its budget', async () => {
146
+ const result = await run(capture({
147
+ recording,
148
+ console: [{ type: 'error', text: 'boom', timestamp: 1_000_800 }],
149
+ }));
150
+ expect(result.report.verdict).toBe('fail');
151
+ expect(result.report.evidence?.reason).toBe('budget-failure');
152
+ expect(result.report.evidence?.focusOffsetMs).toBe(800);
153
+ expect(result.report.evidence?.frames.length).toBe(2);
154
+ });
155
+ it('does not attach evidence to a passing run that did not ask for it', async () => {
156
+ const result = await run(capture({ recording }));
157
+ expect(result.report.verdict).toBe('pass');
158
+ expect(result.report.evidence).toBeUndefined();
159
+ });
160
+ it('attaches evidence to a passing run when --record asked for it', async () => {
161
+ const result = await run(capture({ recording: { ...recording, requested: true } }));
162
+ expect(result.report.evidence?.reason).toBe('requested');
163
+ });
164
+ it('never leaves the raw recording on the report it returns', async () => {
165
+ const result = await run(capture({ recording: { ...recording, requested: true } }));
166
+ expect(result.report.recording).toBeUndefined();
167
+ });
168
+ it('keeps the frame images out of the JSON sibling', async () => {
169
+ const result = await run(capture({ recording: { ...recording, requested: true } }));
170
+ const json = JSON.parse(await readFile(result.jsonPath, 'utf8'));
171
+ expect(json.evidence.frames).toEqual([
172
+ { offsetMs: 0, bytes: 4 },
173
+ { offsetMs: 900, bytes: 4 },
174
+ ]);
175
+ });
176
+ });
177
+ describe('runReportRepeated', () => {
178
+ const failing = capture({ console: [{ type: 'error', text: 'boom', timestamp: 0 }] });
179
+ it('runs the URL N times and judges the set', async () => {
180
+ for (const data of [capture(), failing, capture()])
181
+ collect.mockResolvedValueOnce(data);
182
+ const result = await runReportRepeated(client, 'S1', {
183
+ url: URL_UNDER_TEST,
184
+ out: join(outDir, 'repeat.html'),
185
+ historyDir: root,
186
+ cwd: outDir,
187
+ repeat: 3,
188
+ });
189
+ expect(collect).toHaveBeenCalledTimes(3);
190
+ expect(result.reports.length).toBe(3);
191
+ expect(result.flake.verdict).toBe('flaky');
192
+ expect(result.summary).toMatch(/^FLAKY/);
193
+ });
194
+ it('refuses to call a sometimes-failing URL a pass, even when the last run is green', async () => {
195
+ for (const data of [failing, capture()])
196
+ collect.mockResolvedValueOnce(data);
197
+ const result = await runReportRepeated(client, 'S1', {
198
+ url: URL_UNDER_TEST,
199
+ out: join(outDir, 'repeat2.html'),
200
+ historyDir: root,
201
+ cwd: outDir,
202
+ repeat: 2,
203
+ });
204
+ expect(result.report.verdict).toBe('pass'); // the last run alone
205
+ expect(result.flake.verdict).toBe('flaky'); // what the command reports
206
+ });
207
+ it('writes one history entry for the whole command, not one per run', async () => {
208
+ for (const data of [capture(), capture(), capture()])
209
+ collect.mockResolvedValueOnce(data);
210
+ await runReportRepeated(client, 'S1', {
211
+ url: URL_UNDER_TEST,
212
+ out: join(outDir, 'repeat3.html'),
213
+ historyDir: root,
214
+ cwd: outDir,
215
+ repeat: 3,
216
+ });
217
+ expect((await listRuns(join(root, historyKey(URL_UNDER_TEST)))).length).toBe(1);
218
+ });
219
+ it('puts the flake analysis in the JSON sibling', async () => {
220
+ for (const data of [capture(), failing])
221
+ collect.mockResolvedValueOnce(data);
222
+ const result = await runReportRepeated(client, 'S1', {
223
+ url: URL_UNDER_TEST,
224
+ out: join(outDir, 'repeat4.html'),
225
+ historyDir: root,
226
+ cwd: outDir,
227
+ repeat: 2,
228
+ });
229
+ const json = JSON.parse(await readFile(result.jsonPath, 'utf8'));
230
+ expect(json.flake.verdict).toBe('flaky');
231
+ expect(json.flake.runs).toBe(2);
232
+ });
233
+ });
234
+ //# sourceMappingURL=report-run.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-run.test.js","sourceRoot":"","sources":["../../../src/__tests__/report-run.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import { CdpClient } from './cdp.js';
2
+ import type { CdpTransport, CdpTransportHandlers } from './transport.js';
3
+ /**
4
+ * CDP over the MonoAgent extension bridge.
5
+ *
6
+ * The extension already holds Chrome's `debugger` permission and attaches to
7
+ * tabs with `chrome.debugger.attach`; mono-agent's Go server already owns the
8
+ * WebSocket to it. This transport puts CDP frames on that path — out through
9
+ * the relay's `/monoagent/cdp` socket, into `chrome.debugger.sendCommand`,
10
+ * and back — so a CdpClient can drive the browser the user is actually
11
+ * signed into rather than a headless Chrome we launched. Every instrument
12
+ * built on CdpClient (console, network, HAR, vitals, trace, CPU profiler,
13
+ * snapshot, screenshot, emulation) comes along unchanged.
14
+ *
15
+ * Two mismatches have to be reconciled here, and they are the whole job:
16
+ *
17
+ * **Sessions.** Against a raw socket, monobrowse attaches to a target and
18
+ * carries the resulting `sessionId` on every command and every event.
19
+ * `chrome.debugger` has no such id for the tab: the debuggee *is* the page
20
+ * session, and a `sessionId` on the wire means a *child* session (an
21
+ * out-of-process iframe). So this transport mints a stable synthetic id for
22
+ * the tab, strips it on the way out, stamps it onto relayed events on the
23
+ * way in, and passes any other id through untouched for real child sessions.
24
+ *
25
+ * **Attachment.** A socket is opened; a debuggee is attached. open() does the
26
+ * attach and fails if it cannot, rather than handing back a transport whose
27
+ * first command would fail. close() detaches, which is what takes Chrome's
28
+ * "…started debugging this browser" banner off the user's tab.
29
+ *
30
+ * Known gaps, which are Chrome's and not this file's: `chrome.debugger`
31
+ * exposes a fixed set of domains, and `HeapProfiler` and `Browser` are not
32
+ * among them, so heap snapshots and `Browser.close` fail over the bridge.
33
+ */
34
+ /** Header the Go relay authenticates with; mirrors internal/extension/token.go. */
35
+ export declare const BRIDGE_TOKEN_HEADER = "X-Monoagent-Extension-Token";
36
+ /** Default relay endpoint; mirrors the extension server's default port. */
37
+ export declare const DEFAULT_BRIDGE_URL = "ws://127.0.0.1:9222/monoagent/cdp";
38
+ /** The synthetic target id standing in for a Chrome tab. */
39
+ export declare function bridgeTargetId(tabId: number): string;
40
+ /** The synthetic session id standing in for a tab's page session. */
41
+ export declare function bridgeSessionId(tabId: number): string;
42
+ /** True for ids this transport minted, as opposed to real CDP child sessions. */
43
+ export declare function isBridgeSessionId(sessionId: string | undefined): boolean;
44
+ export interface BridgeTransportOptions {
45
+ /**
46
+ * The Chrome tab to drive. Omit it to drive whichever tab the user is
47
+ * looking at: the extension resolves the active tab and reports the id
48
+ * back on attach, which is the normal way in — the caller has no tab ids
49
+ * to hand, only a browser with the user's session in it.
50
+ */
51
+ tabId?: number;
52
+ /** Relay endpoint. Defaults to DEFAULT_BRIDGE_URL. */
53
+ url?: string;
54
+ /** Relay token, from ~/.monoagent (the Go side writes it). */
55
+ token?: string;
56
+ /** Attach-handshake budget in ms. */
57
+ attachTimeoutMs?: number;
58
+ /**
59
+ * The byte pipe underneath. Defaults to a WebSocket to `url`; tests inject
60
+ * a scripted one so they can be the relay.
61
+ */
62
+ socket?: CdpTransport;
63
+ }
64
+ export declare class BridgeTransport implements CdpTransport {
65
+ /** 0 until the extension resolves the active tab (see open()). */
66
+ private tabId;
67
+ private readonly socket;
68
+ private readonly attachTimeoutMs;
69
+ private handlers;
70
+ private nextEnvelopeId;
71
+ private readonly pending;
72
+ private closed;
73
+ constructor(opts: BridgeTransportOptions);
74
+ open(handlers: CdpTransportHandlers): Promise<void>;
75
+ /** The tab this transport is driving; valid once open() has resolved. */
76
+ get attachedTabId(): number;
77
+ /** The session id every instrument should use against this transport. */
78
+ get sessionId(): string;
79
+ send(frame: string): Promise<void>;
80
+ close(): void;
81
+ /**
82
+ * Commands this transport answers itself. `Target.attachToTarget` against
83
+ * the tab is the one that matters: monobrowse's connect path asks for a
84
+ * session, and over the bridge there is nothing to ask — the debuggee is
85
+ * already the session. Any *other* target id is a genuine child target and
86
+ * goes to Chrome.
87
+ */
88
+ private answerLocally;
89
+ private command;
90
+ private onBridgeMessage;
91
+ private relayEvent;
92
+ private deliver;
93
+ private onGone;
94
+ private failPending;
95
+ }
96
+ /** What a bridge connection hands back: the same shape connectToTarget gives. */
97
+ export interface BridgeConnection {
98
+ client: CdpClient;
99
+ sessionId: string;
100
+ tabId: number;
101
+ transport: BridgeTransport;
102
+ }
103
+ /**
104
+ * Connect a CdpClient to the user's real Chrome through the extension
105
+ * bridge, with the same domains enabled that a local session gets. This is
106
+ * the whole of the "point the instruments at the logged-in browser" story
107
+ * at the call site:
108
+ *
109
+ * const { client, sessionId } = await connectBridge({ token });
110
+ * const vitals = await collectVitals(client, sessionId);
111
+ *
112
+ * `enableDomains` is on by default for parity with connectToTarget. Turn it
113
+ * off for a caller that wants to choose its own domains (a bridge attach is
114
+ * visible to the user, so a lighter footprint is sometimes the point).
115
+ */
116
+ export declare function connectBridge(opts?: BridgeTransportOptions & {
117
+ enableDomains?: boolean;
118
+ }): Promise<BridgeConnection>;
119
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../src/browser/bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAGzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,sCAAsC,CAAC;AAKtE,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAExE;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAiCD,qBAAa,eAAgB,YAAW,YAAY;IAClD,kEAAkE;IAClE,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmD;IAC3E,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,IAAI,EAAE,sBAAsB,EASvC;IAEK,IAAI,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBxD;IAED,yEAAyE;IACzE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,yEAAyE;IACzE,IAAI,SAAS,IAAI,MAAM,CAEtB;IAEK,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BvC;IAED,KAAK,IAAI,IAAI,CASZ;IAID;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,OAAO;IA6Bf,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,WAAW;CAMpB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,IAAI,GAAE,sBAAsB,GAAG;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9D,OAAO,CAAC,gBAAgB,CAAC,CAS3B"}
@@ -0,0 +1,264 @@
1
+ import { enableSessionDomains } from './browser.js';
2
+ import { CdpClient } from './cdp.js';
3
+ import { WebSocketTransport } from './transport.js';
4
+ /**
5
+ * CDP over the MonoAgent extension bridge.
6
+ *
7
+ * The extension already holds Chrome's `debugger` permission and attaches to
8
+ * tabs with `chrome.debugger.attach`; mono-agent's Go server already owns the
9
+ * WebSocket to it. This transport puts CDP frames on that path — out through
10
+ * the relay's `/monoagent/cdp` socket, into `chrome.debugger.sendCommand`,
11
+ * and back — so a CdpClient can drive the browser the user is actually
12
+ * signed into rather than a headless Chrome we launched. Every instrument
13
+ * built on CdpClient (console, network, HAR, vitals, trace, CPU profiler,
14
+ * snapshot, screenshot, emulation) comes along unchanged.
15
+ *
16
+ * Two mismatches have to be reconciled here, and they are the whole job:
17
+ *
18
+ * **Sessions.** Against a raw socket, monobrowse attaches to a target and
19
+ * carries the resulting `sessionId` on every command and every event.
20
+ * `chrome.debugger` has no such id for the tab: the debuggee *is* the page
21
+ * session, and a `sessionId` on the wire means a *child* session (an
22
+ * out-of-process iframe). So this transport mints a stable synthetic id for
23
+ * the tab, strips it on the way out, stamps it onto relayed events on the
24
+ * way in, and passes any other id through untouched for real child sessions.
25
+ *
26
+ * **Attachment.** A socket is opened; a debuggee is attached. open() does the
27
+ * attach and fails if it cannot, rather than handing back a transport whose
28
+ * first command would fail. close() detaches, which is what takes Chrome's
29
+ * "…started debugging this browser" banner off the user's tab.
30
+ *
31
+ * Known gaps, which are Chrome's and not this file's: `chrome.debugger`
32
+ * exposes a fixed set of domains, and `HeapProfiler` and `Browser` are not
33
+ * among them, so heap snapshots and `Browser.close` fail over the bridge.
34
+ */
35
+ /** Header the Go relay authenticates with; mirrors internal/extension/token.go. */
36
+ export const BRIDGE_TOKEN_HEADER = 'X-Monoagent-Extension-Token';
37
+ /** Default relay endpoint; mirrors the extension server's default port. */
38
+ export const DEFAULT_BRIDGE_URL = 'ws://127.0.0.1:9222/monoagent/cdp';
39
+ /** How long the attach handshake may take before open() gives up. */
40
+ const DEFAULT_ATTACH_TIMEOUT_MS = 15_000;
41
+ /** The synthetic target id standing in for a Chrome tab. */
42
+ export function bridgeTargetId(tabId) {
43
+ return `bridge-tab-${tabId}`;
44
+ }
45
+ /** The synthetic session id standing in for a tab's page session. */
46
+ export function bridgeSessionId(tabId) {
47
+ return `bridge-session-${tabId}`;
48
+ }
49
+ /** True for ids this transport minted, as opposed to real CDP child sessions. */
50
+ export function isBridgeSessionId(sessionId) {
51
+ return typeof sessionId === 'string' && sessionId.startsWith('bridge-session-');
52
+ }
53
+ export class BridgeTransport {
54
+ /** 0 until the extension resolves the active tab (see open()). */
55
+ tabId;
56
+ socket;
57
+ attachTimeoutMs;
58
+ handlers = null;
59
+ nextEnvelopeId = 1;
60
+ pending = new Map();
61
+ closed = false;
62
+ constructor(opts) {
63
+ this.tabId = opts.tabId ?? 0;
64
+ this.attachTimeoutMs = opts.attachTimeoutMs ?? DEFAULT_ATTACH_TIMEOUT_MS;
65
+ this.socket =
66
+ opts.socket ??
67
+ new WebSocketTransport(opts.url ?? DEFAULT_BRIDGE_URL, opts.token ? { [BRIDGE_TOKEN_HEADER]: opts.token } : undefined);
68
+ }
69
+ async open(handlers) {
70
+ this.handlers = handlers;
71
+ await this.socket.open({
72
+ message: (data) => this.onBridgeMessage(data),
73
+ close: (err) => this.onGone(err ?? new Error('extension bridge closed')),
74
+ error: (err) => this.onGone(err),
75
+ });
76
+ const reply = await this.command('cdp_attach', undefined, this.attachTimeoutMs);
77
+ if (reply.success === false) {
78
+ this.socket.close();
79
+ const where = this.tabId ? `tab ${this.tabId}` : 'the active tab';
80
+ throw new Error(`bridge attach to ${where} failed: ${reply.error ?? 'unknown'}`);
81
+ }
82
+ // The extension answers with the tab it actually attached to, which is
83
+ // the only way to learn the id when we asked for "whatever is active".
84
+ if (typeof reply.data?.tabId === 'number')
85
+ this.tabId = reply.data.tabId;
86
+ if (!this.tabId) {
87
+ this.socket.close();
88
+ throw new Error('bridge attach did not report a tab id');
89
+ }
90
+ }
91
+ /** The tab this transport is driving; valid once open() has resolved. */
92
+ get attachedTabId() {
93
+ return this.tabId;
94
+ }
95
+ /** The session id every instrument should use against this transport. */
96
+ get sessionId() {
97
+ return bridgeSessionId(this.tabId);
98
+ }
99
+ async send(frame) {
100
+ const cdp = JSON.parse(frame);
101
+ if (this.answerLocally(cdp))
102
+ return;
103
+ const params = {
104
+ method: cdp.method,
105
+ params: cdp.params ?? {},
106
+ };
107
+ // A synthetic id means "the tab itself", which chrome.debugger addresses
108
+ // by its debuggee alone. Anything else is a real child session id and
109
+ // has to travel, or the command lands in the wrong frame.
110
+ if (cdp.sessionId !== undefined && !isBridgeSessionId(cdp.sessionId)) {
111
+ params.sessionId = cdp.sessionId;
112
+ }
113
+ const reply = await this.command('cdp', params);
114
+ if (reply.success === false) {
115
+ this.deliver({
116
+ id: cdp.id,
117
+ // -32000 is CDP's own "server error" code; the bridge has no richer
118
+ // one to offer, and the message is what a caller actually reads.
119
+ error: { code: -32000, message: reply.error ?? 'bridge command failed' },
120
+ });
121
+ return;
122
+ }
123
+ this.deliver({ id: cdp.id, result: reply.data?.result ?? {} });
124
+ }
125
+ close() {
126
+ if (this.closed)
127
+ return;
128
+ this.closed = true;
129
+ // Best effort, and deliberately not awaited: leaving the debugger
130
+ // attached leaves Chrome's debugging banner on the user's tab. The
131
+ // extension's idle sweep would eventually detach anyway.
132
+ void this.command('cdp_detach').catch(() => { });
133
+ this.socket.close();
134
+ this.failPending(new Error('extension bridge closed'));
135
+ }
136
+ // --- internals -----------------------------------------------------------
137
+ /**
138
+ * Commands this transport answers itself. `Target.attachToTarget` against
139
+ * the tab is the one that matters: monobrowse's connect path asks for a
140
+ * session, and over the bridge there is nothing to ask — the debuggee is
141
+ * already the session. Any *other* target id is a genuine child target and
142
+ * goes to Chrome.
143
+ */
144
+ answerLocally(cdp) {
145
+ const targetId = cdp.params?.targetId;
146
+ if (cdp.method === 'Target.attachToTarget' && targetId === bridgeTargetId(this.tabId)) {
147
+ this.deliver({ id: cdp.id, result: { sessionId: bridgeSessionId(this.tabId) } });
148
+ return true;
149
+ }
150
+ if (cdp.method === 'Target.detachFromTarget' &&
151
+ isBridgeSessionId(cdp.params?.sessionId)) {
152
+ this.deliver({ id: cdp.id, result: {} });
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+ command(type, params, timeoutMs = 0) {
158
+ const id = `cdp-${this.nextEnvelopeId++}`;
159
+ const envelope = { id, type, params };
160
+ if (this.tabId)
161
+ envelope.tabId = this.tabId;
162
+ return new Promise((resolve, reject) => {
163
+ let timer;
164
+ if (timeoutMs > 0) {
165
+ timer = setTimeout(() => {
166
+ this.pending.delete(id);
167
+ reject(new Error(`bridge ${type} timed out after ${timeoutMs}ms`));
168
+ }, timeoutMs);
169
+ timer.unref?.();
170
+ }
171
+ this.pending.set(id, (reply) => {
172
+ if (timer)
173
+ clearTimeout(timer);
174
+ resolve(reply);
175
+ });
176
+ this.socket.send(JSON.stringify(envelope)).catch((err) => {
177
+ if (timer)
178
+ clearTimeout(timer);
179
+ this.pending.delete(id);
180
+ reject(err);
181
+ });
182
+ });
183
+ }
184
+ onBridgeMessage(data) {
185
+ let msg;
186
+ try {
187
+ msg = JSON.parse(data);
188
+ }
189
+ catch {
190
+ return; // malformed frames are ignored, as on a raw CDP socket
191
+ }
192
+ if (msg.type === 'cdp_event') {
193
+ this.relayEvent(msg);
194
+ return;
195
+ }
196
+ if (msg.id !== undefined) {
197
+ const settle = this.pending.get(msg.id);
198
+ if (settle) {
199
+ this.pending.delete(msg.id);
200
+ settle(msg);
201
+ }
202
+ }
203
+ }
204
+ relayEvent(msg) {
205
+ const ev = msg.data;
206
+ if (!ev?.method)
207
+ return;
208
+ // One relay socket can carry several tabs; take only our own.
209
+ if (ev.tabId !== undefined && ev.tabId !== this.tabId)
210
+ return;
211
+ // The debugger went away — the user hit Cancel on the banner, DevTools
212
+ // opened on the tab, or the tab closed. Nothing else will arrive, and a
213
+ // caller waiting on a command should hear that now rather than time out.
214
+ if (ev.method === 'Inspector.detached') {
215
+ const reason = ev.params?.reason ?? 'unknown';
216
+ this.onGone(new Error(`extension bridge detached from tab ${this.tabId}: ${reason}`));
217
+ return;
218
+ }
219
+ this.deliver({
220
+ method: ev.method,
221
+ params: ev.params ?? {},
222
+ // A real child session id wins; otherwise this is the tab's own
223
+ // session, which every instrument filters its events by.
224
+ sessionId: ev.sessionId ?? bridgeSessionId(this.tabId),
225
+ });
226
+ }
227
+ deliver(frame) {
228
+ this.handlers?.message(JSON.stringify(frame));
229
+ }
230
+ onGone(err) {
231
+ this.failPending(err);
232
+ this.handlers?.close(err);
233
+ }
234
+ failPending(err) {
235
+ for (const settle of this.pending.values()) {
236
+ settle({ success: false, error: err.message });
237
+ }
238
+ this.pending.clear();
239
+ }
240
+ }
241
+ /**
242
+ * Connect a CdpClient to the user's real Chrome through the extension
243
+ * bridge, with the same domains enabled that a local session gets. This is
244
+ * the whole of the "point the instruments at the logged-in browser" story
245
+ * at the call site:
246
+ *
247
+ * const { client, sessionId } = await connectBridge({ token });
248
+ * const vitals = await collectVitals(client, sessionId);
249
+ *
250
+ * `enableDomains` is on by default for parity with connectToTarget. Turn it
251
+ * off for a caller that wants to choose its own domains (a bridge attach is
252
+ * visible to the user, so a lighter footprint is sometimes the point).
253
+ */
254
+ export async function connectBridge(opts = {}) {
255
+ const transport = new BridgeTransport(opts);
256
+ const client = new CdpClient();
257
+ await client.connectTransport(transport);
258
+ const sessionId = transport.sessionId;
259
+ if (opts.enableDomains !== false) {
260
+ await enableSessionDomains(client, sessionId);
261
+ }
262
+ return { client, sessionId, tabId: transport.attachedTabId, transport };
263
+ }
264
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../src/browser/bridge.ts"],"names":[],"mappings":"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"}