@monoes/monobrowse 1.0.11 → 1.0.13

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 (380) hide show
  1. package/dist/src/__tests__/adapters.test.js +5 -2
  2. package/dist/src/__tests__/adapters.test.js.map +1 -1
  3. package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
  4. package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
  5. package/dist/src/__tests__/box-model.test.js +9 -5
  6. package/dist/src/__tests__/box-model.test.js.map +1 -1
  7. package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
  8. package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
  9. package/dist/src/__tests__/browser-launch.test.js +17 -10
  10. package/dist/src/__tests__/browser-launch.test.js.map +1 -1
  11. package/dist/src/__tests__/cdp-client.test.js +9 -3
  12. package/dist/src/__tests__/cdp-client.test.js.map +1 -1
  13. package/dist/src/__tests__/close-browser.test.d.ts +13 -0
  14. package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
  15. package/dist/src/__tests__/close-browser.test.js +5 -5
  16. package/dist/src/__tests__/close-browser.test.js.map +1 -1
  17. package/dist/src/__tests__/console-log.test.js +17 -6
  18. package/dist/src/__tests__/console-log.test.js.map +1 -1
  19. package/dist/src/__tests__/find.test.js +26 -6
  20. package/dist/src/__tests__/find.test.js.map +1 -1
  21. package/dist/src/__tests__/frame-command.test.js +9 -3
  22. package/dist/src/__tests__/frame-command.test.js.map +1 -1
  23. package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
  24. package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
  25. package/dist/src/__tests__/output.test.js +1 -1
  26. package/dist/src/__tests__/output.test.js.map +1 -1
  27. package/dist/src/__tests__/profiler-heap.test.d.ts +2 -0
  28. package/dist/src/__tests__/profiler-heap.test.d.ts.map +1 -0
  29. package/dist/src/__tests__/profiler-heap.test.js +154 -0
  30. package/dist/src/__tests__/profiler-heap.test.js.map +1 -0
  31. package/dist/src/__tests__/reap-idle-browser.test.d.ts +15 -0
  32. package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
  33. package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
  34. package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
  35. package/dist/src/__tests__/ref-cache.test.js +18 -10
  36. package/dist/src/__tests__/ref-cache.test.js.map +1 -1
  37. package/dist/src/__tests__/report-a11y.test.d.ts +2 -0
  38. package/dist/src/__tests__/report-a11y.test.d.ts.map +1 -0
  39. package/dist/src/__tests__/report-a11y.test.js +147 -0
  40. package/dist/src/__tests__/report-a11y.test.js.map +1 -0
  41. package/dist/src/__tests__/report-analyze.test.d.ts +2 -0
  42. package/dist/src/__tests__/report-analyze.test.d.ts.map +1 -0
  43. package/dist/src/__tests__/report-analyze.test.js +157 -0
  44. package/dist/src/__tests__/report-analyze.test.js.map +1 -0
  45. package/dist/src/__tests__/report-budget.test.d.ts +2 -0
  46. package/dist/src/__tests__/report-budget.test.d.ts.map +1 -0
  47. package/dist/src/__tests__/report-budget.test.js +80 -0
  48. package/dist/src/__tests__/report-budget.test.js.map +1 -0
  49. package/dist/src/__tests__/report-diff.test.d.ts +2 -0
  50. package/dist/src/__tests__/report-diff.test.d.ts.map +1 -0
  51. package/dist/src/__tests__/report-diff.test.js +231 -0
  52. package/dist/src/__tests__/report-diff.test.js.map +1 -0
  53. package/dist/src/__tests__/report-evidence.test.d.ts +2 -0
  54. package/dist/src/__tests__/report-evidence.test.d.ts.map +1 -0
  55. package/dist/src/__tests__/report-evidence.test.js +214 -0
  56. package/dist/src/__tests__/report-evidence.test.js.map +1 -0
  57. package/dist/src/__tests__/report-flake.test.d.ts +2 -0
  58. package/dist/src/__tests__/report-flake.test.d.ts.map +1 -0
  59. package/dist/src/__tests__/report-flake.test.js +182 -0
  60. package/dist/src/__tests__/report-flake.test.js.map +1 -0
  61. package/dist/src/__tests__/report-history.test.d.ts +2 -0
  62. package/dist/src/__tests__/report-history.test.d.ts.map +1 -0
  63. package/dist/src/__tests__/report-history.test.js +229 -0
  64. package/dist/src/__tests__/report-history.test.js.map +1 -0
  65. package/dist/src/__tests__/report-output.test.d.ts +2 -0
  66. package/dist/src/__tests__/report-output.test.d.ts.map +1 -0
  67. package/dist/src/__tests__/report-output.test.js +169 -0
  68. package/dist/src/__tests__/report-output.test.js.map +1 -0
  69. package/dist/src/__tests__/report-png.test.d.ts +2 -0
  70. package/dist/src/__tests__/report-png.test.d.ts.map +1 -0
  71. package/dist/src/__tests__/report-png.test.js +84 -0
  72. package/dist/src/__tests__/report-png.test.js.map +1 -0
  73. package/dist/src/__tests__/report-render-extras.test.d.ts +2 -0
  74. package/dist/src/__tests__/report-render-extras.test.d.ts.map +1 -0
  75. package/dist/src/__tests__/report-render-extras.test.js +285 -0
  76. package/dist/src/__tests__/report-render-extras.test.js.map +1 -0
  77. package/dist/src/__tests__/report-render.test.d.ts +2 -0
  78. package/dist/src/__tests__/report-render.test.d.ts.map +1 -0
  79. package/dist/src/__tests__/report-render.test.js +143 -0
  80. package/dist/src/__tests__/report-render.test.js.map +1 -0
  81. package/dist/src/__tests__/report-run.test.d.ts +2 -0
  82. package/dist/src/__tests__/report-run.test.d.ts.map +1 -0
  83. package/dist/src/__tests__/report-run.test.js +234 -0
  84. package/dist/src/__tests__/report-run.test.js.map +1 -0
  85. package/dist/src/__tests__/session-state.test.d.ts +8 -0
  86. package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
  87. package/dist/src/__tests__/session-state.test.js +10 -10
  88. package/dist/src/__tests__/session-state.test.js.map +1 -1
  89. package/dist/src/__tests__/snapshot.test.js +44 -14
  90. package/dist/src/__tests__/snapshot.test.js.map +1 -1
  91. package/dist/src/__tests__/storage-eval.test.js +14 -6
  92. package/dist/src/__tests__/storage-eval.test.js.map +1 -1
  93. package/dist/src/__tests__/tabs.test.js +25 -9
  94. package/dist/src/__tests__/tabs.test.js.map +1 -1
  95. package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
  96. package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
  97. package/dist/src/browser/action-builder/analyzer.js +25 -9
  98. package/dist/src/browser/action-builder/analyzer.js.map +1 -1
  99. package/dist/src/browser/action-builder/types.d.ts.map +1 -1
  100. package/dist/src/browser/actions.d.ts +1 -1
  101. package/dist/src/browser/actions.d.ts.map +1 -1
  102. package/dist/src/browser/actions.js +16 -6
  103. package/dist/src/browser/actions.js.map +1 -1
  104. package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
  105. package/dist/src/browser/adapters/gemini.js.map +1 -1
  106. package/dist/src/browser/adapters/google.d.ts.map +1 -1
  107. package/dist/src/browser/adapters/google.js.map +1 -1
  108. package/dist/src/browser/adapters/index.d.ts.map +1 -1
  109. package/dist/src/browser/adapters/index.js +3 -3
  110. package/dist/src/browser/adapters/index.js.map +1 -1
  111. package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
  112. package/dist/src/browser/adapters/instagram.js.map +1 -1
  113. package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
  114. package/dist/src/browser/adapters/linkedin.js.map +1 -1
  115. package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
  116. package/dist/src/browser/adapters/microsoft.js.map +1 -1
  117. package/dist/src/browser/adapters/x.d.ts.map +1 -1
  118. package/dist/src/browser/adapters/x.js.map +1 -1
  119. package/dist/src/browser/browser.d.ts +30 -0
  120. package/dist/src/browser/browser.d.ts.map +1 -1
  121. package/dist/src/browser/browser.js +122 -15
  122. package/dist/src/browser/browser.js.map +1 -1
  123. package/dist/src/browser/cdp.d.ts.map +1 -1
  124. package/dist/src/browser/cdp.js +21 -8
  125. package/dist/src/browser/cdp.js.map +1 -1
  126. package/dist/src/browser/console-log.d.ts.map +1 -1
  127. package/dist/src/browser/console-log.js +2 -2
  128. package/dist/src/browser/console-log.js.map +1 -1
  129. package/dist/src/browser/dashboard/server.d.ts.map +1 -1
  130. package/dist/src/browser/dashboard/server.js +29 -19
  131. package/dist/src/browser/dashboard/server.js.map +1 -1
  132. package/dist/src/browser/dashboard/ui.html +153 -165
  133. package/dist/src/browser/dialog.d.ts.map +1 -1
  134. package/dist/src/browser/dialog.js +3 -1
  135. package/dist/src/browser/dialog.js.map +1 -1
  136. package/dist/src/browser/emulation.d.ts.map +1 -1
  137. package/dist/src/browser/emulation.js +21 -6
  138. package/dist/src/browser/emulation.js.map +1 -1
  139. package/dist/src/browser/find.d.ts +4 -4
  140. package/dist/src/browser/find.d.ts.map +1 -1
  141. package/dist/src/browser/find.js +14 -6
  142. package/dist/src/browser/find.js.map +1 -1
  143. package/dist/src/browser/har.d.ts.map +1 -1
  144. package/dist/src/browser/har.js +14 -7
  145. package/dist/src/browser/har.js.map +1 -1
  146. package/dist/src/browser/index.d.ts +17 -16
  147. package/dist/src/browser/index.d.ts.map +1 -1
  148. package/dist/src/browser/index.js +17 -16
  149. package/dist/src/browser/index.js.map +1 -1
  150. package/dist/src/browser/network.d.ts.map +1 -1
  151. package/dist/src/browser/network.js +14 -4
  152. package/dist/src/browser/network.js.map +1 -1
  153. package/dist/src/browser/pdf.d.ts.map +1 -1
  154. package/dist/src/browser/pdf.js +3 -3
  155. package/dist/src/browser/pdf.js.map +1 -1
  156. package/dist/src/browser/profiler.d.ts.map +1 -1
  157. package/dist/src/browser/profiler.js +22 -20
  158. package/dist/src/browser/profiler.js.map +1 -1
  159. package/dist/src/browser/record.d.ts +1 -1
  160. package/dist/src/browser/record.d.ts.map +1 -1
  161. package/dist/src/browser/record.js +18 -7
  162. package/dist/src/browser/record.js.map +1 -1
  163. package/dist/src/browser/ref-cache.d.ts.map +1 -1
  164. package/dist/src/browser/ref-cache.js +9 -4
  165. package/dist/src/browser/ref-cache.js.map +1 -1
  166. package/dist/src/browser/screenshot.d.ts.map +1 -1
  167. package/dist/src/browser/screenshot.js +11 -7
  168. package/dist/src/browser/screenshot.js.map +1 -1
  169. package/dist/src/browser/session.d.ts +1 -1
  170. package/dist/src/browser/session.d.ts.map +1 -1
  171. package/dist/src/browser/session.js +27 -8
  172. package/dist/src/browser/session.js.map +1 -1
  173. package/dist/src/browser/snapshot.d.ts +1 -1
  174. package/dist/src/browser/snapshot.d.ts.map +1 -1
  175. package/dist/src/browser/snapshot.js +8 -4
  176. package/dist/src/browser/snapshot.js.map +1 -1
  177. package/dist/src/browser/storage.d.ts.map +1 -1
  178. package/dist/src/browser/storage.js.map +1 -1
  179. package/dist/src/browser/tabs.d.ts.map +1 -1
  180. package/dist/src/browser/tabs.js +9 -3
  181. package/dist/src/browser/tabs.js.map +1 -1
  182. package/dist/src/browser/trace.d.ts.map +1 -1
  183. package/dist/src/browser/trace.js +3 -3
  184. package/dist/src/browser/trace.js.map +1 -1
  185. package/dist/src/browser/types.d.ts.map +1 -1
  186. package/dist/src/browser/types.js +23 -4
  187. package/dist/src/browser/types.js.map +1 -1
  188. package/dist/src/browser/vitals.d.ts.map +1 -1
  189. package/dist/src/browser/vitals.js +1 -1
  190. package/dist/src/browser/vitals.js.map +1 -1
  191. package/dist/src/browser/wait.d.ts.map +1 -1
  192. package/dist/src/browser/wait.js +11 -5
  193. package/dist/src/browser/wait.js.map +1 -1
  194. package/dist/src/cli/action.d.ts.map +1 -1
  195. package/dist/src/cli/action.js +14 -11
  196. package/dist/src/cli/action.js.map +1 -1
  197. package/dist/src/cli/commands.d.ts.map +1 -1
  198. package/dist/src/cli/commands.js +707 -159
  199. package/dist/src/cli/commands.js.map +1 -1
  200. package/dist/src/cli/output.js +5 -5
  201. package/dist/src/cli/output.js.map +1 -1
  202. package/dist/src/cli/platform.d.ts.map +1 -1
  203. package/dist/src/cli/platform.js +15 -11
  204. package/dist/src/cli/platform.js.map +1 -1
  205. package/dist/src/cli/types.d.ts +1 -1
  206. package/dist/src/cli/types.d.ts.map +1 -1
  207. package/dist/src/cli.js +21 -9
  208. package/dist/src/cli.js.map +1 -1
  209. package/dist/src/index.d.ts +2 -2
  210. package/dist/src/index.d.ts.map +1 -1
  211. package/dist/src/index.js +2 -2
  212. package/dist/src/index.js.map +1 -1
  213. package/dist/src/report/a11y.d.ts +52 -0
  214. package/dist/src/report/a11y.d.ts.map +1 -0
  215. package/dist/src/report/a11y.js +231 -0
  216. package/dist/src/report/a11y.js.map +1 -0
  217. package/dist/src/report/analyze.d.ts +20 -0
  218. package/dist/src/report/analyze.d.ts.map +1 -0
  219. package/dist/src/report/analyze.js +136 -0
  220. package/dist/src/report/analyze.js.map +1 -0
  221. package/dist/src/report/budget.d.ts +27 -0
  222. package/dist/src/report/budget.d.ts.map +1 -0
  223. package/dist/src/report/budget.js +143 -0
  224. package/dist/src/report/budget.js.map +1 -0
  225. package/dist/src/report/collect-a11y.d.ts +25 -0
  226. package/dist/src/report/collect-a11y.d.ts.map +1 -0
  227. package/dist/src/report/collect-a11y.js +154 -0
  228. package/dist/src/report/collect-a11y.js.map +1 -0
  229. package/dist/src/report/collect.d.ts +45 -0
  230. package/dist/src/report/collect.d.ts.map +1 -0
  231. package/dist/src/report/collect.js +275 -0
  232. package/dist/src/report/collect.js.map +1 -0
  233. package/dist/src/report/evidence.d.ts +80 -0
  234. package/dist/src/report/evidence.d.ts.map +1 -0
  235. package/dist/src/report/evidence.js +279 -0
  236. package/dist/src/report/evidence.js.map +1 -0
  237. package/dist/src/report/flake.d.ts +88 -0
  238. package/dist/src/report/flake.d.ts.map +1 -0
  239. package/dist/src/report/flake.js +232 -0
  240. package/dist/src/report/flake.js.map +1 -0
  241. package/dist/src/report/history.d.ts +85 -0
  242. package/dist/src/report/history.d.ts.map +1 -0
  243. package/dist/src/report/history.js +176 -0
  244. package/dist/src/report/history.js.map +1 -0
  245. package/dist/src/report/index.d.ts +102 -0
  246. package/dist/src/report/index.d.ts.map +1 -0
  247. package/dist/src/report/index.js +216 -0
  248. package/dist/src/report/index.js.map +1 -0
  249. package/dist/src/report/pixel-diff.d.ts +30 -0
  250. package/dist/src/report/pixel-diff.d.ts.map +1 -0
  251. package/dist/src/report/pixel-diff.js +175 -0
  252. package/dist/src/report/pixel-diff.js.map +1 -0
  253. package/dist/src/report/png.d.ts +28 -0
  254. package/dist/src/report/png.d.ts.map +1 -0
  255. package/dist/src/report/png.js +259 -0
  256. package/dist/src/report/png.js.map +1 -0
  257. package/dist/src/report/render-evidence.d.ts +16 -0
  258. package/dist/src/report/render-evidence.d.ts.map +1 -0
  259. package/dist/src/report/render-evidence.js +174 -0
  260. package/dist/src/report/render-evidence.js.map +1 -0
  261. package/dist/src/report/render-trend.d.ts +16 -0
  262. package/dist/src/report/render-trend.d.ts.map +1 -0
  263. package/dist/src/report/render-trend.js +194 -0
  264. package/dist/src/report/render-trend.js.map +1 -0
  265. package/dist/src/report/render.d.ts +21 -0
  266. package/dist/src/report/render.d.ts.map +1 -0
  267. package/dist/src/report/render.js +286 -0
  268. package/dist/src/report/render.js.map +1 -0
  269. package/dist/src/report/run-diff.d.ts +18 -0
  270. package/dist/src/report/run-diff.d.ts.map +1 -0
  271. package/dist/src/report/run-diff.js +51 -0
  272. package/dist/src/report/run-diff.js.map +1 -0
  273. package/dist/src/report/structure.d.ts +41 -0
  274. package/dist/src/report/structure.d.ts.map +1 -0
  275. package/dist/src/report/structure.js +210 -0
  276. package/dist/src/report/structure.js.map +1 -0
  277. package/dist/src/report/trend.d.ts +26 -0
  278. package/dist/src/report/trend.d.ts.map +1 -0
  279. package/dist/src/report/trend.js +164 -0
  280. package/dist/src/report/trend.js.map +1 -0
  281. package/dist/src/report/types.d.ts +320 -0
  282. package/dist/src/report/types.d.ts.map +1 -0
  283. package/dist/src/report/types.js +11 -0
  284. package/dist/src/report/types.js.map +1 -0
  285. package/dist/src/report/util.d.ts +17 -0
  286. package/dist/src/report/util.d.ts.map +1 -0
  287. package/dist/src/report/util.js +45 -0
  288. package/dist/src/report/util.js.map +1 -0
  289. package/dist/tsconfig.tsbuildinfo +1 -1
  290. package/package.json +1 -1
  291. package/src/__tests__/adapters.test.ts +15 -8
  292. package/src/__tests__/batch-eval-parsing.test.ts +4 -2
  293. package/src/__tests__/box-model.test.ts +9 -5
  294. package/src/__tests__/browser-launch.test.ts +26 -15
  295. package/src/__tests__/cdp-client.test.ts +25 -9
  296. package/src/__tests__/close-browser.test.ts +11 -6
  297. package/src/__tests__/console-log.test.ts +36 -13
  298. package/src/__tests__/find.test.ts +58 -20
  299. package/src/__tests__/frame-command.test.ts +12 -6
  300. package/src/__tests__/open-url-navigate-error.test.ts +2 -4
  301. package/src/__tests__/output.test.ts +1 -1
  302. package/src/__tests__/profiler-heap.test.ts +191 -0
  303. package/src/__tests__/reap-idle-browser.test.ts +276 -0
  304. package/src/__tests__/ref-cache.test.ts +25 -12
  305. package/src/__tests__/report-a11y.test.ts +180 -0
  306. package/src/__tests__/report-analyze.test.ts +202 -0
  307. package/src/__tests__/report-budget.test.ts +92 -0
  308. package/src/__tests__/report-diff.test.ts +272 -0
  309. package/src/__tests__/report-evidence.test.ts +276 -0
  310. package/src/__tests__/report-flake.test.ts +214 -0
  311. package/src/__tests__/report-history.test.ts +293 -0
  312. package/src/__tests__/report-output.test.ts +210 -0
  313. package/src/__tests__/report-png.test.ts +95 -0
  314. package/src/__tests__/report-render-extras.test.ts +315 -0
  315. package/src/__tests__/report-render.test.ts +175 -0
  316. package/src/__tests__/report-run.test.ts +264 -0
  317. package/src/__tests__/session-state.test.ts +40 -28
  318. package/src/__tests__/snapshot.test.ts +60 -21
  319. package/src/__tests__/storage-eval.test.ts +30 -16
  320. package/src/__tests__/tabs.test.ts +26 -10
  321. package/src/browser/action-builder/analyzer.ts +39 -15
  322. package/src/browser/action-builder/types.ts +45 -8
  323. package/src/browser/actions.ts +288 -123
  324. package/src/browser/adapters/gemini.ts +7 -3
  325. package/src/browser/adapters/google.ts +5 -3
  326. package/src/browser/adapters/index.ts +5 -4
  327. package/src/browser/adapters/instagram.ts +4 -2
  328. package/src/browser/adapters/linkedin.ts +4 -2
  329. package/src/browser/adapters/microsoft.ts +9 -5
  330. package/src/browser/adapters/x.ts +7 -3
  331. package/src/browser/browser.ts +188 -42
  332. package/src/browser/cdp.ts +43 -11
  333. package/src/browser/console-log.ts +24 -7
  334. package/src/browser/dashboard/server.ts +44 -25
  335. package/src/browser/dashboard/ui.html +153 -165
  336. package/src/browser/dialog.ts +25 -8
  337. package/src/browser/emulation.ts +86 -36
  338. package/src/browser/find.ts +110 -47
  339. package/src/browser/har.ts +58 -26
  340. package/src/browser/index.ts +17 -16
  341. package/src/browser/network.ts +147 -46
  342. package/src/browser/pdf.ts +4 -4
  343. package/src/browser/profiler.ts +39 -19
  344. package/src/browser/record.ts +38 -19
  345. package/src/browser/ref-cache.ts +38 -17
  346. package/src/browser/screenshot.ts +58 -32
  347. package/src/browser/session.ts +51 -18
  348. package/src/browser/snapshot.ts +34 -20
  349. package/src/browser/storage.ts +73 -17
  350. package/src/browser/tabs.ts +29 -17
  351. package/src/browser/trace.ts +14 -10
  352. package/src/browser/types.ts +23 -4
  353. package/src/browser/vitals.ts +33 -16
  354. package/src/browser/wait.ts +47 -21
  355. package/src/cli/action.ts +52 -21
  356. package/src/cli/commands.ts +1365 -385
  357. package/src/cli/output.ts +5 -5
  358. package/src/cli/platform.ts +44 -23
  359. package/src/cli/types.ts +1 -1
  360. package/src/cli.ts +34 -18
  361. package/src/index.ts +2 -2
  362. package/src/report/a11y.ts +242 -0
  363. package/src/report/analyze.ts +181 -0
  364. package/src/report/budget.ts +155 -0
  365. package/src/report/collect-a11y.ts +200 -0
  366. package/src/report/collect.ts +408 -0
  367. package/src/report/evidence.ts +379 -0
  368. package/src/report/flake.ts +303 -0
  369. package/src/report/history.ts +219 -0
  370. package/src/report/index.ts +307 -0
  371. package/src/report/pixel-diff.ts +197 -0
  372. package/src/report/png.ts +290 -0
  373. package/src/report/render-evidence.ts +194 -0
  374. package/src/report/render-trend.ts +225 -0
  375. package/src/report/render.ts +314 -0
  376. package/src/report/run-diff.ts +63 -0
  377. package/src/report/structure.ts +212 -0
  378. package/src/report/trend.ts +189 -0
  379. package/src/report/types.ts +356 -0
  380. package/src/report/util.ts +48 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Drives the browser and gathers the raw material for a report.
3
+ *
4
+ * Resilience is the design constraint: a page with no vitals, a site that
5
+ * never goes network-idle, a CSP that blocks our evaluate — each must
6
+ * degrade into a note in the report, never a hang and never a crash. Every
7
+ * collector is individually timed out and individually try/caught, and this
8
+ * function always returns a CaptureData.
9
+ */
10
+ import { evaluateJs } from '../browser/actions.js';
11
+ import { getCurrentTitle, getCurrentUrl, openUrl, waitForLoad } from '../browser/browser.js';
12
+ import { clearConsoleMessages, clearPageErrors, enableConsoleCapture, getConsoleMessages, getPageErrors, setupConsoleCapture, } from '../browser/console-log.js';
13
+ import { emulateDevice, listDevices } from '../browser/emulation.js';
14
+ import { clearCapturedRequests, getCapturedRequests, startRequestCapture, } from '../browser/network.js';
15
+ import { captureScreenshot } from '../browser/screenshot.js';
16
+ import { collectVitals } from '../browser/vitals.js';
17
+ import { waitFor } from '../browser/wait.js';
18
+ import { collectA11y } from './collect-a11y.js';
19
+ import { startEvidenceRecorder } from './evidence.js';
20
+ import { message, STEP_TIMEOUT_MS, sleep, withTimeout } from './util.js';
21
+ const DEFAULT_VITALS_WAIT_MS = 2500;
22
+ const DEFAULT_LOAD_TIMEOUT_MS = 20_000;
23
+ /** Cap on inlined screenshot bytes so the single HTML file stays openable. */
24
+ const MAX_INLINE_BYTES = 12 * 1024 * 1024;
25
+ /**
26
+ * CDP reports request timings on a monotonic clock with an arbitrary epoch,
27
+ * which cannot be compared with the `Date.now()` stamps on console entries.
28
+ * Sampling both clocks on the first request we see gives an offset that puts
29
+ * every request on the same timeline as everything else (RIG-11).
30
+ */
31
+ function trackClockOffset(client, sessionId) {
32
+ let offset;
33
+ const off = client.on('Network.requestWillBeSent', (params, sid) => {
34
+ if (sid !== sessionId || offset !== undefined)
35
+ return;
36
+ const p = params;
37
+ if (typeof p.timestamp !== 'number')
38
+ return;
39
+ offset = Date.now() - p.timestamp * 1000;
40
+ });
41
+ return { offset: () => offset, stop: off };
42
+ }
43
+ /**
44
+ * `startRequestCapture` records timings but drops `Network.loadingFailed`'s
45
+ * errorText, so a DNS failure and a still-in-flight request look identical in
46
+ * its output. Listen alongside it rather than editing it.
47
+ */
48
+ function trackLoadingFailures(client, sessionId) {
49
+ const failures = new Map();
50
+ const off = client.on('Network.loadingFailed', (params, sid) => {
51
+ if (sid !== sessionId)
52
+ return;
53
+ const p = params;
54
+ if (!p.requestId)
55
+ return;
56
+ failures.set(p.requestId, {
57
+ errorText: p.errorText,
58
+ blockedReason: p.blockedReason,
59
+ canceled: p.canceled,
60
+ });
61
+ });
62
+ return { failures, stop: off };
63
+ }
64
+ export function toRequestEntries(captured, failures, clockOffsetMs) {
65
+ return captured.map((r) => {
66
+ const failure = failures.get(r.id);
67
+ const httpError = r.status !== undefined && r.status >= 400;
68
+ // A cancelled request is usually the page itself aborting a fetch on
69
+ // navigation — noise, not a defect. A blockedReason (CSP, mixed content,
70
+ // an ad blocker) is a real failure even though CDP also marks it cancelled.
71
+ const hardFailure = failure !== undefined && (!failure.canceled || !!failure.blockedReason);
72
+ const errorText = failure?.blockedReason
73
+ ? `blocked: ${failure.blockedReason}`
74
+ : (failure?.errorText ?? undefined);
75
+ return {
76
+ url: r.url,
77
+ method: r.method,
78
+ startedAtMs: clockOffsetMs === undefined ? undefined : r.startTime + clockOffsetMs,
79
+ status: r.status,
80
+ mimeType: r.mimeType,
81
+ durationMs: r.endTime !== undefined ? Math.max(0, r.endTime - r.startTime) : undefined,
82
+ encodedSize: r.encodedSize,
83
+ failed: httpError || hardFailure,
84
+ errorText: hardFailure ? errorText : undefined,
85
+ };
86
+ });
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Screenshots
90
+ // ---------------------------------------------------------------------------
91
+ async function measure(client, sessionId, fullPage) {
92
+ try {
93
+ const raw = await evaluateJs(client, sessionId, fullPage
94
+ ? '({width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight})'
95
+ : '({width: window.innerWidth, height: window.innerHeight})');
96
+ const size = raw;
97
+ return { width: Math.round(size?.width ?? 0), height: Math.round(size?.height ?? 0) };
98
+ }
99
+ catch {
100
+ return { width: 0, height: 0 };
101
+ }
102
+ }
103
+ async function takeShot(client, sessionId, label, fullPage, dir, notes, inlineBudget) {
104
+ try {
105
+ const slug = label.replace(/[^a-z0-9]+/gi, '-').toLowerCase();
106
+ const path = dir ? `${dir}/${slug}.png` : undefined;
107
+ const shot = await withTimeout(captureScreenshot(client, sessionId, { fullPage, path }), STEP_TIMEOUT_MS, `screenshot (${label})`);
108
+ if (shot.dataUrl.length > inlineBudget.left) {
109
+ notes.push(`Screenshot "${label}" omitted from the HTML — inlining it would blow the size budget.`);
110
+ return null;
111
+ }
112
+ inlineBudget.left -= shot.dataUrl.length;
113
+ const size = await measure(client, sessionId, fullPage);
114
+ return {
115
+ label,
116
+ width: size.width,
117
+ height: size.height,
118
+ dataUrl: shot.dataUrl,
119
+ path: shot.path,
120
+ };
121
+ }
122
+ catch (err) {
123
+ notes.push(`Screenshot "${label}" failed: ${message(err)}`);
124
+ return null;
125
+ }
126
+ }
127
+ async function captureDeviceMatrix(client, sessionId, url, devices, dir, notes, inlineBudget) {
128
+ const known = new Set(listDevices());
129
+ const shots = [];
130
+ for (const device of devices) {
131
+ if (!known.has(device)) {
132
+ notes.push(`Unknown device "${device}" — known devices: ${[...known].join(', ')}`);
133
+ continue;
134
+ }
135
+ try {
136
+ await emulateDevice(client, sessionId, device);
137
+ // Re-navigate: responsive layout and media queries only settle on a
138
+ // fresh load, so resizing an already-rendered page can photograph a
139
+ // layout the device would never actually produce.
140
+ await openUrl(client, sessionId, url).catch(() => { });
141
+ await waitForLoad(client, sessionId, 'domcontentloaded', 5000).catch(() => { });
142
+ const shot = await takeShot(client, sessionId, device, false, dir, notes, inlineBudget);
143
+ if (shot)
144
+ shots.push(shot);
145
+ }
146
+ catch (err) {
147
+ notes.push(`Device "${device}" skipped: ${message(err)}`);
148
+ }
149
+ }
150
+ // Put the tab back the way we found it, so a later command in the same
151
+ // session is not silently stuck emulating an iPhone.
152
+ await client.send('Emulation.clearDeviceMetricsOverride', {}, sessionId).catch(() => { });
153
+ await client
154
+ .send('Emulation.setTouchEmulationEnabled', { enabled: false }, sessionId)
155
+ .catch(() => { });
156
+ await client.send('Emulation.setUserAgentOverride', { userAgent: '' }, sessionId).catch(() => { });
157
+ return shots;
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Orchestration
161
+ // ---------------------------------------------------------------------------
162
+ export async function collect(client, sessionId, options) {
163
+ const notes = [];
164
+ const startedAt = Date.now();
165
+ const capturedAt = new Date().toISOString();
166
+ const loadTimeoutMs = options.loadTimeoutMs ?? DEFAULT_LOAD_TIMEOUT_MS;
167
+ const fullPage = options.fullPage ?? true;
168
+ // Capture must be armed BEFORE navigating, or the errors and requests that
169
+ // fire during load — the interesting ones — are already gone.
170
+ setupConsoleCapture(client, sessionId);
171
+ await enableConsoleCapture(client, sessionId).catch(() => { });
172
+ clearConsoleMessages(sessionId);
173
+ clearPageErrors(sessionId);
174
+ startRequestCapture(client, sessionId);
175
+ clearCapturedRequests(sessionId);
176
+ const tracker = trackLoadingFailures(client, sessionId);
177
+ const clock = trackClockOffset(client, sessionId);
178
+ // Armed before navigation for the same reason console capture is: the
179
+ // frames worth having are the ones showing the page fail to load.
180
+ let recorder = null;
181
+ if (options.record) {
182
+ try {
183
+ recorder = await startEvidenceRecorder(client, sessionId, startedAt, options.recordOptions);
184
+ }
185
+ catch (err) {
186
+ notes.push(`Screen recording unavailable: ${message(err)}`);
187
+ }
188
+ }
189
+ try {
190
+ const navigation = openUrl(client, sessionId, options.url)
191
+ .then(() => 'ok')
192
+ .catch((err) => {
193
+ notes.push(`Navigation reported an error: ${message(err)}`);
194
+ return 'error';
195
+ });
196
+ const outcome = await Promise.race([
197
+ navigation,
198
+ sleep(loadTimeoutMs).then(() => 'timeout'),
199
+ ]);
200
+ if (outcome === 'timeout') {
201
+ notes.push(`Page never went network-idle within ${loadTimeoutMs}ms — reporting on what had loaded by then.`);
202
+ await waitForLoad(client, sessionId, 'domcontentloaded', 5000).catch(() => { });
203
+ }
204
+ if (options.wait !== undefined) {
205
+ const waitMs = typeof options.wait === 'number' ? options.wait : Number(options.wait);
206
+ try {
207
+ if (Number.isFinite(waitMs)) {
208
+ await sleep(waitMs);
209
+ }
210
+ else {
211
+ await waitFor(client, sessionId, { selector: String(options.wait), timeout: 15_000 });
212
+ }
213
+ }
214
+ catch (err) {
215
+ notes.push(`--wait not satisfied: ${message(err)}`);
216
+ }
217
+ }
218
+ let vitals = {};
219
+ const vitalsWaitMs = options.vitalsWaitMs ?? DEFAULT_VITALS_WAIT_MS;
220
+ try {
221
+ vitals = await withTimeout(collectVitals(client, sessionId, vitalsWaitMs), vitalsWaitMs + STEP_TIMEOUT_MS, 'web vitals');
222
+ }
223
+ catch (err) {
224
+ notes.push(`Web vitals unavailable: ${message(err)}`);
225
+ }
226
+ const a11y = await collectA11y(client, sessionId, notes);
227
+ // Stop before the screenshots: a device matrix re-navigates the page
228
+ // several times, and those frames belong to no failure worth explaining.
229
+ if (recorder)
230
+ await recorder.stop().catch(() => { });
231
+ const finalUrl = await getCurrentUrl(client, sessionId).catch(() => options.url);
232
+ const title = await getCurrentTitle(client, sessionId).catch(() => '');
233
+ const consoleEntries = getConsoleMessages(sessionId);
234
+ const pageErrors = getPageErrors(sessionId);
235
+ const requests = toRequestEntries(getCapturedRequests(sessionId), tracker.failures, clock.offset());
236
+ const inlineBudget = { left: MAX_INLINE_BYTES };
237
+ const screenshots = [];
238
+ const main = await takeShot(client, sessionId, 'page', fullPage, options.screenshotDir, notes, inlineBudget);
239
+ if (main)
240
+ screenshots.push(main);
241
+ if (options.devices?.length) {
242
+ screenshots.push(...(await captureDeviceMatrix(client, sessionId, options.url, options.devices, options.screenshotDir, notes, inlineBudget)));
243
+ }
244
+ return {
245
+ url: options.url,
246
+ finalUrl,
247
+ title,
248
+ capturedAt,
249
+ durationMs: Date.now() - startedAt,
250
+ console: consoleEntries,
251
+ pageErrors,
252
+ requests,
253
+ vitals,
254
+ a11y: a11y.findings,
255
+ structure: a11y.structure,
256
+ screenshots,
257
+ notes,
258
+ recording: recorder
259
+ ? {
260
+ startedAtMs: startedAt,
261
+ frames: recorder.frames,
262
+ droppedFrames: recorder.droppedFrames,
263
+ requested: true,
264
+ }
265
+ : undefined,
266
+ };
267
+ }
268
+ finally {
269
+ tracker.stop();
270
+ clock.stop();
271
+ if (recorder)
272
+ await recorder.stop().catch(() => { });
273
+ }
274
+ }
275
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.js","sourceRoot":"","sources":["../../../src/report/collect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Recorded evidence for a bug report (RIG-11).
3
+ *
4
+ * When a run breaches its budget, "LCP was 4.1s" is a number; the frames
5
+ * either side of the moment it went wrong, lined up against the console and
6
+ * network entries from the same instant, is a bug report. This module records
7
+ * the frames and assembles that timeline.
8
+ *
9
+ * Size is the hard constraint. A screencast is tens of megabytes and a report
10
+ * is a single self-contained HTML file, so there are three separate bounds:
11
+ * the recorder's ring buffer (what we hold in memory), the frame selection
12
+ * (what we attach), and a byte budget applied over the selection. Nothing
13
+ * here can produce a 200MB report.
14
+ *
15
+ * `startEvidenceRecorder` talks to Chrome; everything below it is pure and
16
+ * unit-tested against fixture frames.
17
+ */
18
+ import type { CdpClient } from '../browser/cdp.js';
19
+ import type { BudgetFailure, ConsoleEntry, Evidence, PageErrorEntry, RawFrame, RequestEntry } from './types.js';
20
+ export type { RawFrame };
21
+ export interface EvidenceRecorderOptions {
22
+ /** Frames held in memory before the buffer is decimated. */
23
+ maxFrames?: number;
24
+ /** Base64 bytes held in memory before the buffer is decimated. */
25
+ maxBytes?: number;
26
+ everyNthFrame?: number;
27
+ quality?: number;
28
+ maxWidth?: number;
29
+ maxHeight?: number;
30
+ }
31
+ export interface EvidenceRecorder {
32
+ frames: RawFrame[];
33
+ droppedFrames: number;
34
+ stop: () => Promise<void>;
35
+ }
36
+ export declare function startEvidenceRecorder(client: CdpClient, sessionId: string, startedAtMs: number, options?: EvidenceRecorderOptions): Promise<EvidenceRecorder>;
37
+ export interface SelectFramesOptions {
38
+ /** Frames to attach to the report. */
39
+ maxFrames?: number;
40
+ /** Total base64 bytes to attach. */
41
+ maxBytes?: number;
42
+ /** Half-width of the window around the focus, in ms. */
43
+ windowMs?: number;
44
+ }
45
+ /**
46
+ * Pick the frames to attach.
47
+ *
48
+ * With a focus offset, frames nearest it win — that is the moment being
49
+ * explained. Without one (a vitals-only breach has no instant), the run is
50
+ * sampled evenly so the reader still sees it load. The byte budget is applied
51
+ * last, evicting the least relevant frame first, so the cap is never reached
52
+ * by truncating the interesting end.
53
+ */
54
+ export declare function selectFrames(frames: RawFrame[], focusOffsetMs: number | null, options?: SelectFramesOptions): {
55
+ frames: RawFrame[];
56
+ dropped: number;
57
+ };
58
+ export interface BuildEvidenceInput {
59
+ reason: Evidence['reason'];
60
+ /** `Date.now()` at the start of the run — the zero of every offset. */
61
+ startedAtMs: number;
62
+ frames: RawFrame[];
63
+ /** Frames the recorder already dropped to stay inside its buffer. */
64
+ bufferDropped?: number;
65
+ console: ConsoleEntry[];
66
+ pageErrors: PageErrorEntry[];
67
+ requests: RequestEntry[];
68
+ failures: BudgetFailure[];
69
+ select?: SelectFramesOptions;
70
+ /** Cap on non-frame timeline rows. */
71
+ maxEvents?: number;
72
+ }
73
+ /**
74
+ * The instant the run went wrong: the earliest hard error we have a clock
75
+ * reading for. Returns null when the breach has no moment attached — a slow
76
+ * LCP is a property of the whole load, not an event.
77
+ */
78
+ export declare function findFocusOffset(input: BuildEvidenceInput): number | null;
79
+ export declare function buildEvidence(input: BuildEvidenceInput): Evidence;
80
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../../src/report/evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,QAAQ,EAER,cAAc,EACd,QAAQ,EACR,YAAY,EAEb,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,QAAQ,EAAE,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAkBD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAiE3B;AAMD,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,QAAQ,EAAE,EAClB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,OAAO,GAAE,mBAAwB,GAChC;IAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA+CzC;AAMD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAexE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CA2GjE"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Recorded evidence for a bug report (RIG-11).
3
+ *
4
+ * When a run breaches its budget, "LCP was 4.1s" is a number; the frames
5
+ * either side of the moment it went wrong, lined up against the console and
6
+ * network entries from the same instant, is a bug report. This module records
7
+ * the frames and assembles that timeline.
8
+ *
9
+ * Size is the hard constraint. A screencast is tens of megabytes and a report
10
+ * is a single self-contained HTML file, so there are three separate bounds:
11
+ * the recorder's ring buffer (what we hold in memory), the frame selection
12
+ * (what we attach), and a byte budget applied over the selection. Nothing
13
+ * here can produce a 200MB report.
14
+ *
15
+ * `startEvidenceRecorder` talks to Chrome; everything below it is pure and
16
+ * unit-tested against fixture frames.
17
+ */
18
+ const DEFAULT_MAX_BUFFER_FRAMES = 160;
19
+ const DEFAULT_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
20
+ /**
21
+ * Halve the temporal resolution of the buffer instead of dropping its head.
22
+ *
23
+ * A plain ring buffer keeps the end of the run, which is the wrong half: a
24
+ * page that throws during load has its evidence at the start. Decimating
25
+ * keeps coverage of the whole run and merely coarsens it, which degrades the
26
+ * timeline gracefully however long the run turns out to be.
27
+ */
28
+ function decimate(frames) {
29
+ const kept = frames.filter((_, i) => i % 2 === 0);
30
+ return { kept, dropped: frames.length - kept.length };
31
+ }
32
+ export async function startEvidenceRecorder(client, sessionId, startedAtMs, options = {}) {
33
+ const maxFrames = options.maxFrames ?? DEFAULT_MAX_BUFFER_FRAMES;
34
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BUFFER_BYTES;
35
+ const recorder = {
36
+ frames: [],
37
+ droppedFrames: 0,
38
+ stop: async () => { },
39
+ };
40
+ let bytes = 0;
41
+ const off = client.on('Page.screencastFrame', (params, sid) => {
42
+ if (sid !== sessionId)
43
+ return;
44
+ const p = params;
45
+ // Ack first and unconditionally: Chrome stops sending frames until the
46
+ // previous one is acknowledged, so a failure to ack silently ends the
47
+ // recording.
48
+ if (p.sessionId !== undefined) {
49
+ void client
50
+ .send('Page.screencastFrameAck', { sessionId: p.sessionId }, sessionId)
51
+ .catch(() => { });
52
+ }
53
+ if (!p.data)
54
+ return;
55
+ recorder.frames.push({
56
+ offsetMs: Math.max(0, Date.now() - startedAtMs),
57
+ data: p.data,
58
+ bytes: p.data.length,
59
+ });
60
+ bytes += p.data.length;
61
+ if (recorder.frames.length > maxFrames || bytes > maxBytes) {
62
+ const { kept, dropped } = decimate(recorder.frames);
63
+ recorder.frames = kept;
64
+ recorder.droppedFrames += dropped;
65
+ bytes = kept.reduce((sum, f) => sum + f.bytes, 0);
66
+ }
67
+ });
68
+ // Idempotent: collect() stops the recorder once the interesting part of the
69
+ // run is over and again in its `finally`, and Chrome errors on stopping a
70
+ // screencast that is already stopped.
71
+ let stopped = false;
72
+ recorder.stop = async () => {
73
+ if (stopped)
74
+ return;
75
+ stopped = true;
76
+ off();
77
+ await client.send('Page.stopScreencast', {}, sessionId).catch(() => { });
78
+ };
79
+ try {
80
+ await client.send('Page.startScreencast', {
81
+ format: 'jpeg',
82
+ quality: options.quality ?? 60,
83
+ everyNthFrame: options.everyNthFrame ?? 2,
84
+ maxWidth: options.maxWidth ?? 900,
85
+ maxHeight: options.maxHeight ?? 1600,
86
+ }, sessionId);
87
+ }
88
+ catch (err) {
89
+ off();
90
+ throw err;
91
+ }
92
+ return recorder;
93
+ }
94
+ const DEFAULT_ATTACH_FRAMES = 12;
95
+ const DEFAULT_ATTACH_BYTES = 6 * 1024 * 1024;
96
+ const DEFAULT_WINDOW_MS = 4000;
97
+ /**
98
+ * Pick the frames to attach.
99
+ *
100
+ * With a focus offset, frames nearest it win — that is the moment being
101
+ * explained. Without one (a vitals-only breach has no instant), the run is
102
+ * sampled evenly so the reader still sees it load. The byte budget is applied
103
+ * last, evicting the least relevant frame first, so the cap is never reached
104
+ * by truncating the interesting end.
105
+ */
106
+ export function selectFrames(frames, focusOffsetMs, options = {}) {
107
+ const maxFrames = Math.max(1, options.maxFrames ?? DEFAULT_ATTACH_FRAMES);
108
+ const maxBytes = options.maxBytes ?? DEFAULT_ATTACH_BYTES;
109
+ const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
110
+ if (!frames.length)
111
+ return { frames: [], dropped: 0 };
112
+ let ranked;
113
+ if (focusOffsetMs === null) {
114
+ if (frames.length <= maxFrames) {
115
+ ranked = [...frames];
116
+ }
117
+ else {
118
+ const step = (frames.length - 1) / (maxFrames - 1 || 1);
119
+ const picked = new Set();
120
+ for (let i = 0; i < maxFrames; i++)
121
+ picked.add(Math.round(i * step));
122
+ ranked = [...picked].sort((a, b) => a - b).map((i) => frames[i]);
123
+ }
124
+ }
125
+ else {
126
+ const distance = (f) => Math.abs(f.offsetMs - focusOffsetMs);
127
+ const inWindow = frames.filter((f) => distance(f) <= windowMs);
128
+ // Fall back to the nearest frames overall rather than attaching none: a
129
+ // failure whose frames all fell outside the window still deserves context.
130
+ const pool = inWindow.length ? inWindow : [...frames];
131
+ ranked = [...pool].sort((a, b) => distance(a) - distance(b)).slice(0, maxFrames);
132
+ }
133
+ let dropped = frames.length - ranked.length;
134
+ let total = ranked.reduce((sum, f) => sum + f.bytes, 0);
135
+ if (total > maxBytes) {
136
+ const order = focusOffsetMs === null
137
+ ? [...ranked]
138
+ : [...ranked].sort((a, b) => Math.abs(a.offsetMs - focusOffsetMs) - Math.abs(b.offsetMs - focusOffsetMs));
139
+ const keep = new Set();
140
+ let used = 0;
141
+ for (const frame of order) {
142
+ if (used + frame.bytes > maxBytes)
143
+ continue;
144
+ keep.add(frame);
145
+ used += frame.bytes;
146
+ }
147
+ dropped += ranked.length - keep.size;
148
+ ranked = ranked.filter((f) => keep.has(f));
149
+ total = used;
150
+ }
151
+ return { frames: ranked.sort((a, b) => a.offsetMs - b.offsetMs), dropped };
152
+ }
153
+ const DEFAULT_MAX_EVENTS = 60;
154
+ function truncate(text, max = 200) {
155
+ const flat = String(text ?? '')
156
+ .replace(/\s+/g, ' ')
157
+ .trim();
158
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
159
+ }
160
+ /**
161
+ * The instant the run went wrong: the earliest hard error we have a clock
162
+ * reading for. Returns null when the breach has no moment attached — a slow
163
+ * LCP is a property of the whole load, not an event.
164
+ */
165
+ export function findFocusOffset(input) {
166
+ const candidates = [];
167
+ for (const e of input.console) {
168
+ if (e.type === 'error' && Number.isFinite(e.timestamp)) {
169
+ candidates.push(e.timestamp - input.startedAtMs);
170
+ }
171
+ }
172
+ for (const e of input.pageErrors) {
173
+ if (Number.isFinite(e.timestamp))
174
+ candidates.push(e.timestamp - input.startedAtMs);
175
+ }
176
+ for (const r of input.requests) {
177
+ if (r.failed && r.startedAtMs !== undefined)
178
+ candidates.push(r.startedAtMs - input.startedAtMs);
179
+ }
180
+ const usable = candidates.filter((v) => Number.isFinite(v) && v >= 0);
181
+ return usable.length ? Math.min(...usable) : null;
182
+ }
183
+ export function buildEvidence(input) {
184
+ const notes = [];
185
+ const focusOffsetMs = findFocusOffset(input);
186
+ const picked = selectFrames(input.frames, focusOffsetMs, input.select);
187
+ const frames = picked.frames.map((f) => ({
188
+ offsetMs: f.offsetMs,
189
+ dataUrl: `data:image/jpeg;base64,${f.data}`,
190
+ bytes: f.bytes,
191
+ }));
192
+ const events = [];
193
+ for (const [i, frame] of picked.frames.entries()) {
194
+ events.push({
195
+ offsetMs: frame.offsetMs,
196
+ kind: 'frame',
197
+ label: 'frame',
198
+ severity: 'info',
199
+ frameIndex: i,
200
+ });
201
+ }
202
+ for (const e of input.console) {
203
+ if (e.type !== 'error' && e.type !== 'warn' && e.type !== 'warning')
204
+ continue;
205
+ events.push({
206
+ offsetMs: e.timestamp - input.startedAtMs,
207
+ kind: 'console',
208
+ label: `console.${e.type}`,
209
+ detail: truncate(e.text),
210
+ severity: e.type === 'error' ? 'error' : 'warning',
211
+ });
212
+ }
213
+ for (const e of input.pageErrors) {
214
+ events.push({
215
+ offsetMs: e.timestamp - input.startedAtMs,
216
+ kind: 'pageerror',
217
+ label: 'uncaught exception',
218
+ detail: truncate(e.text),
219
+ severity: 'error',
220
+ });
221
+ }
222
+ for (const r of input.requests) {
223
+ if (!r.failed)
224
+ continue;
225
+ if (r.startedAtMs === undefined)
226
+ continue;
227
+ events.push({
228
+ offsetMs: r.startedAtMs - input.startedAtMs,
229
+ kind: 'request',
230
+ label: `${r.status ?? r.errorText ?? 'no response'} ${r.method}`,
231
+ detail: truncate(r.url),
232
+ severity: 'error',
233
+ });
234
+ }
235
+ if (focusOffsetMs !== null) {
236
+ events.push({
237
+ offsetMs: focusOffsetMs,
238
+ kind: 'marker',
239
+ label: 'first failure',
240
+ detail: input.failures.map((f) => `${f.budget} ${f.actual}`).join(', ') || undefined,
241
+ severity: 'error',
242
+ });
243
+ }
244
+ // Trim the non-frame rows worst-first, so a page with 400 console warnings
245
+ // still shows its one uncaught exception.
246
+ const frameRows = events.filter((e) => e.kind === 'frame');
247
+ let otherRows = events.filter((e) => e.kind !== 'frame');
248
+ const maxEvents = input.maxEvents ?? DEFAULT_MAX_EVENTS;
249
+ if (otherRows.length > maxEvents) {
250
+ const weight = { error: 0, warning: 1, info: 2 };
251
+ const kept = [...otherRows]
252
+ .sort((a, b) => weight[a.severity] - weight[b.severity] ||
253
+ Math.abs(a.offsetMs - (focusOffsetMs ?? 0)) - Math.abs(b.offsetMs - (focusOffsetMs ?? 0)))
254
+ .slice(0, maxEvents);
255
+ notes.push(`Timeline trimmed to the ${maxEvents} most relevant of ${otherRows.length} events.`);
256
+ otherRows = kept;
257
+ }
258
+ const timeline = [...frameRows, ...otherRows].sort((a, b) => a.offsetMs - b.offsetMs || a.kind.localeCompare(b.kind));
259
+ const droppedFrames = (input.bufferDropped ?? 0) + picked.dropped;
260
+ if (droppedFrames > 0) {
261
+ notes.push(`${droppedFrames} recorded frame(s) not attached — the report keeps the ${frames.length} nearest the failure to stay inside its size budget.`);
262
+ }
263
+ if (!frames.length && input.frames.length === 0) {
264
+ notes.push('No screencast frames were captured for this run.');
265
+ }
266
+ if (focusOffsetMs === null && input.failures.length) {
267
+ notes.push('No single moment of failure could be located — the breach is a load-wide metric, so the frames sample the whole run instead.');
268
+ }
269
+ return {
270
+ reason: input.reason,
271
+ focusOffsetMs,
272
+ frames,
273
+ timeline,
274
+ droppedFrames,
275
+ totalBytes: frames.reduce((sum, f) => sum + f.bytes, 0),
276
+ notes,
277
+ };
278
+ }
279
+ //# sourceMappingURL=evidence.js.map