@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,182 @@
1
+ /**
2
+ * RIG-14 — flake detection across repeated runs.
3
+ *
4
+ * The load-bearing assertion in here is that a check which passed some runs
5
+ * and failed others never produces a `pass` verdict. That is the whole story:
6
+ * the failure mode is a red check being re-run until it is green.
7
+ */
8
+ import { describe, expect, it } from 'vitest';
9
+ import { buildReport } from '../report/analyze.js';
10
+ import { DEFAULT_BUDGET, parseBudget } from '../report/budget.js';
11
+ import { analyzeFlake, ruledOutFailureRate, signalSignature, summarizeFlake, } from '../report/flake.js';
12
+ function capture(partial = {}) {
13
+ return {
14
+ url: 'https://shop.test/checkout',
15
+ finalUrl: 'https://shop.test/checkout',
16
+ title: 'Checkout',
17
+ capturedAt: '2026-09-21T10:00:00.000Z',
18
+ durationMs: 1000,
19
+ console: [],
20
+ pageErrors: [],
21
+ requests: [],
22
+ vitals: { lcp: 2000, cls: 0.01, inp: 50 },
23
+ a11y: [],
24
+ screenshots: [],
25
+ notes: [],
26
+ ...partial,
27
+ };
28
+ }
29
+ const run = (partial = {}, budget = DEFAULT_BUDGET) => buildReport(capture(partial), budget);
30
+ const err = (text) => ({ type: 'error', text, timestamp: 0 });
31
+ describe('analyzeFlake verdict', () => {
32
+ it('passes when every check held across every run', () => {
33
+ const flake = analyzeFlake([run(), run(), run(), run(), run()]);
34
+ expect(flake.verdict).toBe('pass');
35
+ expect(flake.runs).toBe(5);
36
+ expect(flake.checks.every((c) => c.stable)).toBe(true);
37
+ expect(flake.headline).toContain('PASS');
38
+ });
39
+ it('refuses to call a check that passed sometimes a pass', () => {
40
+ // Three clean runs, two with a console error — exactly the case that gets
41
+ // shipped as green after a re-run.
42
+ const runs = [
43
+ run(),
44
+ run({ console: [err('boom')] }),
45
+ run(),
46
+ run({ console: [err('boom')] }),
47
+ run(),
48
+ ];
49
+ const flake = analyzeFlake(runs);
50
+ expect(flake.verdict).toBe('flaky');
51
+ const check = flake.checks.find((c) => c.key === 'maxConsoleErrors');
52
+ expect(check).toMatchObject({ runs: 5, passed: 3, failed: 2, stable: false });
53
+ expect(check?.flakeRate).toBeCloseTo(0.4, 5);
54
+ expect(flake.headline).toContain('FLAKY');
55
+ expect(flake.headline).toContain('failed 2 of 5');
56
+ });
57
+ it('fails outright when a check failed in every run', () => {
58
+ const runs = [run({ console: [err('boom')] }), run({ console: [err('boom')] })];
59
+ const flake = analyzeFlake(runs);
60
+ expect(flake.verdict).toBe('fail');
61
+ expect(flake.headline).toBe('FAIL — 1 check(s) failed in all 2 runs.');
62
+ });
63
+ it('prefers flaky over fail when one check is unstable and another always fails', () => {
64
+ const budget = parseBudget({ lcp: 1000 });
65
+ const runs = [
66
+ run({ console: [err('boom')] }, budget),
67
+ run({}, budget), // lcp still breaches; the console error does not recur
68
+ ];
69
+ const flake = analyzeFlake(runs);
70
+ expect(flake.checks.find((c) => c.key === 'lcpMs')?.failed).toBe(2);
71
+ expect(flake.verdict).toBe('flaky');
72
+ });
73
+ it('records the measured value for every run, not just the failing ones', () => {
74
+ const runs = [run({ vitals: { lcp: 1900 } }), run({ vitals: { lcp: 3400 } })];
75
+ const lcp = analyzeFlake(runs).checks.find((c) => c.key === 'lcpMs');
76
+ expect(lcp?.actuals).toEqual(['1900ms', '3400ms']);
77
+ });
78
+ it('ignores budgets that are not enforced', () => {
79
+ const budget = parseBudget({ ttfb: null, fcp: null });
80
+ const keys = analyzeFlake([run({}, budget)]).checks.map((c) => c.key);
81
+ expect(keys).not.toContain('ttfbMs');
82
+ expect(keys).not.toContain('fcpMs');
83
+ });
84
+ it('throws rather than inventing a verdict from no runs', () => {
85
+ expect(() => analyzeFlake([])).toThrow(/at least one run/);
86
+ });
87
+ });
88
+ describe('analyzeFlake signals', () => {
89
+ it('reports how many runs an intermittent console error appeared in', () => {
90
+ const runs = [run(), run({ console: [err('Failed to fetch /cart')] }), run(), run(), run()];
91
+ const signal = analyzeFlake(runs).signals.find((s) => s.kind === 'console');
92
+ expect(signal).toMatchObject({ runs: 1, total: 5, signature: 'Failed to fetch /cart' });
93
+ });
94
+ it('does not call an error that happened every run a flake', () => {
95
+ const runs = [run({ console: [err('always')] }), run({ console: [err('always')] })];
96
+ expect(analyzeFlake(runs).signals).toEqual([]);
97
+ });
98
+ it('groups the same error carrying a different request id', () => {
99
+ const runs = [
100
+ run({ console: [err('Request 1043321 failed')] }),
101
+ run({ console: [err('Request 9910412 failed')] }),
102
+ run(),
103
+ ];
104
+ const signals = analyzeFlake(runs).signals;
105
+ expect(signals.length).toBe(1);
106
+ expect(signals[0]).toMatchObject({ runs: 2, total: 3 });
107
+ });
108
+ it('reports an intermittently failing request', () => {
109
+ const failing = {
110
+ requests: [
111
+ { url: 'https://api.test/cart', method: 'GET', status: 503, failed: true },
112
+ { url: 'https://api.test/ok', method: 'GET', status: 200, failed: false },
113
+ ],
114
+ };
115
+ const signals = analyzeFlake([run(), run(failing), run()]).signals;
116
+ expect(signals).toEqual([
117
+ { kind: 'request', signature: '503 https://api.test/cart', runs: 1, total: 3 },
118
+ ]);
119
+ });
120
+ });
121
+ describe('signalSignature', () => {
122
+ it('normalises ids, hex and long numbers so one error is one signal', () => {
123
+ expect(signalSignature('Request 1043321 failed')).toBe('Request <n> failed');
124
+ expect(signalSignature('trace 3f2a1b4c-0000-4aaa-8bbb-1234567890ab lost')).toBe('trace <uuid> lost');
125
+ expect(signalSignature('at 0xDEADBEEF')).toBe('at <hex>');
126
+ });
127
+ it('leaves short numbers alone — a 404 is part of the message', () => {
128
+ expect(signalSignature('GET returned 404')).toBe('GET returned 404');
129
+ });
130
+ });
131
+ describe('analyzeFlake metrics', () => {
132
+ it('reports the range a metric varied across, which is the readable form', () => {
133
+ const runs = [1900, 3400, 2100, 2000, 2050].map((lcp) => run({ vitals: { lcp } }));
134
+ const lcp = analyzeFlake(runs).metrics.find((m) => m.key === 'lcp');
135
+ expect(lcp).toMatchObject({ min: 1900, max: 3400, measured: 5, total: 5, unstable: true });
136
+ expect(lcp?.spread).toBe(1500);
137
+ });
138
+ it('does not flag a metric that barely moved', () => {
139
+ const runs = [2000, 2050, 2010].map((lcp) => run({ vitals: { lcp } }));
140
+ expect(analyzeFlake(runs).metrics.find((m) => m.key === 'lcp')?.unstable).toBe(false);
141
+ });
142
+ it('counts how many runs actually measured the metric', () => {
143
+ const runs = [
144
+ run({ vitals: { lcp: 2000 } }),
145
+ run({ vitals: {} }),
146
+ run({ vitals: { lcp: 2100 } }),
147
+ ];
148
+ expect(analyzeFlake(runs).metrics.find((m) => m.key === 'lcp')?.measured).toBe(2);
149
+ });
150
+ });
151
+ describe('confidence', () => {
152
+ it('states what a clean window actually rules out rather than implying certainty', () => {
153
+ const flake = analyzeFlake([run(), run(), run(), run(), run()]);
154
+ expect(flake.confidence).toBe('high');
155
+ expect(flake.confidenceNote).toContain('95% confidence');
156
+ expect(flake.confidenceNote).toContain('45%');
157
+ });
158
+ it('is low after a single run, and says why', () => {
159
+ const flake = analyzeFlake([run()]);
160
+ expect(flake.confidence).toBe('low');
161
+ expect(flake.confidenceNote).toContain('1 run');
162
+ });
163
+ it('is high for an observed flake, because both outcomes were seen', () => {
164
+ const flake = analyzeFlake([run(), run({ console: [err('boom')] })]);
165
+ expect(flake.confidence).toBe('high');
166
+ expect(flake.confidenceNote).toContain('observed, not inferred');
167
+ });
168
+ it('shrinks the ruled-out rate as runs are added', () => {
169
+ expect(ruledOutFailureRate(1)).toBeCloseTo(0.95, 5);
170
+ expect(ruledOutFailureRate(5)).toBeCloseTo(0.4507, 3);
171
+ expect(ruledOutFailureRate(20)).toBeCloseTo(0.1391, 3);
172
+ expect(ruledOutFailureRate(5)).toBeGreaterThan(ruledOutFailureRate(20));
173
+ });
174
+ });
175
+ describe('summarizeFlake', () => {
176
+ it('is one line naming the verdict and the confidence', () => {
177
+ const line = summarizeFlake(analyzeFlake([run(), run({ console: [err('boom')] })]));
178
+ expect(line).toMatch(/^FLAKY — /);
179
+ expect(line).toContain('Confidence: high.');
180
+ });
181
+ });
182
+ //# sourceMappingURL=report-flake.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-flake.test.js","sourceRoot":"","sources":["../../../src/__tests__/report-flake.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=report-history.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-history.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/report-history.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,229 @@
1
+ /**
2
+ * RIG-13 — run history on disk and the trends computed from it.
3
+ *
4
+ * The filesystem half uses a scratch directory under the monomind data dir
5
+ * (never the shared /tmp) and points MONOBROWSE_HISTORY_DIR at it, so no test
6
+ * can touch a real history.
7
+ */
8
+ import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
+ import { buildReport } from '../report/analyze.js';
13
+ import { DEFAULT_BUDGET } from '../report/budget.js';
14
+ import { historyKey, listRuns, loadRunScreenshot, pruneHistory, saveRun, toHistoryRun, } from '../report/history.js';
15
+ import { encodePng, pngToDataUrl } from '../report/png.js';
16
+ import { buildTrend, formatTrendDelta, formatTrendValue } from '../report/trend.js';
17
+ function capture(partial = {}) {
18
+ return {
19
+ url: 'https://shop.test/checkout',
20
+ finalUrl: 'https://shop.test/checkout',
21
+ title: 'Checkout',
22
+ capturedAt: '2026-09-21T10:00:00.000Z',
23
+ durationMs: 1200,
24
+ console: [],
25
+ pageErrors: [],
26
+ requests: [],
27
+ vitals: { lcp: 2000, cls: 0.01, inp: 50 },
28
+ a11y: [],
29
+ screenshots: [],
30
+ notes: [],
31
+ ...partial,
32
+ };
33
+ }
34
+ const report = (partial = {}, budget = DEFAULT_BUDGET) => buildReport(capture(partial), budget);
35
+ /** A prior run, shaped exactly as it comes back off disk. */
36
+ function historyRun(capturedAt, vitals, counts = {}) {
37
+ const run = toHistoryRun(report({ capturedAt, vitals }), `id-${capturedAt}`);
38
+ run.counts = { ...run.counts, ...counts };
39
+ run.verdict = (counts.consoleErrors ?? 0) > 0 ? 'fail' : 'pass';
40
+ return run;
41
+ }
42
+ function tinyPng(width = 4, height = 4, shade = 200) {
43
+ const data = new Uint8Array(width * height * 4);
44
+ for (let i = 0; i < width * height; i++)
45
+ data.set([shade, shade, shade, 255], i * 4);
46
+ return encodePng({ width, height, data });
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Storage
50
+ // ---------------------------------------------------------------------------
51
+ describe('historyKey', () => {
52
+ it('is readable and includes a hash of the full URL', () => {
53
+ expect(historyKey('https://shop.test/checkout')).toMatch(/^shop\.test-checkout-[0-9a-f]{8}$/);
54
+ });
55
+ it('does not collide across URLs that differ only in the query string', () => {
56
+ expect(historyKey('https://a.test/list?page=2')).not.toBe(historyKey('https://a.test/list?page=3'));
57
+ });
58
+ it('survives a string that is not a URL at all', () => {
59
+ expect(historyKey('not a url')).toMatch(/^not-a-url-[0-9a-f]{8}$/);
60
+ });
61
+ });
62
+ describe('run history on disk', () => {
63
+ let root;
64
+ beforeEach(async () => {
65
+ await mkdir(join(homedir(), '.monomind'), { recursive: true });
66
+ root = await mkdtemp(join(homedir(), '.monomind', 'monobrowse-history-test-'));
67
+ process.env.MONOBROWSE_HISTORY_DIR = root;
68
+ });
69
+ afterEach(async () => {
70
+ delete process.env.MONOBROWSE_HISTORY_DIR;
71
+ await rm(root, { recursive: true, force: true });
72
+ });
73
+ it('writes one JSON record per run, keyed by URL', async () => {
74
+ const saved = await saveRun(report(), { root, id: '20260921T100000Z-aaaaaaaa' });
75
+ expect(saved.dir).toBe(join(root, historyKey('https://shop.test/checkout')));
76
+ const files = await readdir(saved.dir);
77
+ expect(files).toContain('20260921T100000Z-aaaaaaaa.json');
78
+ expect(files).toContain('url.txt');
79
+ });
80
+ it('archives the main screenshot alongside so the next run can diff pixels', async () => {
81
+ const png = tinyPng();
82
+ const saved = await saveRun(report({
83
+ screenshots: [{ label: 'page', width: 4, height: 4, dataUrl: pngToDataUrl(png) }],
84
+ }), { root, id: '20260921T100000Z-bbbbbbbb' });
85
+ expect(saved.run.screenshotFile).toBe('20260921T100000Z-bbbbbbbb.png');
86
+ const loaded = await loadRunScreenshot(saved.dir, saved.run);
87
+ expect(loaded && Buffer.from(loaded).equals(png)).toBe(true);
88
+ });
89
+ it('returns null for a screenshot that was never archived', async () => {
90
+ const saved = await saveRun(report(), { root, id: '20260921T100000Z-cccccccc' });
91
+ expect(await loadRunScreenshot(saved.dir, saved.run)).toBeNull();
92
+ });
93
+ it('lists runs oldest first', async () => {
94
+ for (const ts of ['2026-09-21T12:00:00.000Z', '2026-09-21T10:00:00.000Z']) {
95
+ await saveRun(report({ capturedAt: ts }), { root });
96
+ }
97
+ const dir = join(root, historyKey('https://shop.test/checkout'));
98
+ expect((await listRuns(dir)).map((r) => r.capturedAt)).toEqual([
99
+ '2026-09-21T10:00:00.000Z',
100
+ '2026-09-21T12:00:00.000Z',
101
+ ]);
102
+ });
103
+ it('prunes to the configured maximum, oldest first, taking the PNGs with them', async () => {
104
+ const dir = join(root, historyKey('https://shop.test/checkout'));
105
+ for (let i = 0; i < 5; i++) {
106
+ await saveRun(report({
107
+ capturedAt: `2026-09-2${i + 1}T10:00:00.000Z`,
108
+ screenshots: [{ label: 'page', width: 4, height: 4, dataUrl: pngToDataUrl(tinyPng()) }],
109
+ }), { root, max: 3, id: `2026092${i + 1}T100000Z-0000000${i}` });
110
+ }
111
+ const runs = await listRuns(dir);
112
+ expect(runs.map((r) => r.capturedAt)).toEqual([
113
+ '2026-09-23T10:00:00.000Z',
114
+ '2026-09-24T10:00:00.000Z',
115
+ '2026-09-25T10:00:00.000Z',
116
+ ]);
117
+ const files = await readdir(dir);
118
+ expect(files.filter((f) => f.endsWith('.png')).length).toBe(3);
119
+ });
120
+ it('keeps everything when the history is shorter than the maximum', async () => {
121
+ const dir = join(root, historyKey('https://shop.test/checkout'));
122
+ await saveRun(report(), { root, max: 10 });
123
+ expect(await pruneHistory(dir, 10)).toEqual([]);
124
+ });
125
+ it('skips a corrupt record instead of failing the whole read', async () => {
126
+ const dir = join(root, historyKey('https://shop.test/checkout'));
127
+ await saveRun(report(), { root, id: '20260921T100000Z-dddddddd' });
128
+ await writeFile(join(dir, '20260922T100000Z-eeeeeeee.json'), '{ truncated', 'utf8');
129
+ const runs = await listRuns(dir);
130
+ expect(runs.map((r) => r.id)).toEqual(['20260921T100000Z-dddddddd']);
131
+ });
132
+ it('returns an empty history for a URL never reported on', async () => {
133
+ expect(await listRuns(join(root, 'never-seen-00000000'))).toEqual([]);
134
+ });
135
+ });
136
+ // ---------------------------------------------------------------------------
137
+ // Trends
138
+ // ---------------------------------------------------------------------------
139
+ describe('buildTrend', () => {
140
+ it('reports no prior runs on a first run without inventing a direction', () => {
141
+ const trend = buildTrend(report(), []);
142
+ expect(trend.runs).toBe(0);
143
+ expect(trend.windowFrom).toBeNull();
144
+ expect(trend.series.every((s) => s.direction === 'new')).toBe(true);
145
+ expect(trend.headlines).toEqual([]);
146
+ });
147
+ it('names the slow decay the story is about', () => {
148
+ // LCP walks 2000 -> 2400ms across nine prior runs, never once crossing
149
+ // the 2500ms budget, so no individual run ever failed.
150
+ const history = [];
151
+ for (let i = 0; i < 9; i++) {
152
+ history.push(historyRun(`2026-09-${10 + i}T10:00:00.000Z`, { lcp: 2000 + i * 50 }));
153
+ }
154
+ const trend = buildTrend(report({ capturedAt: '2026-09-21T10:00:00.000Z', vitals: { lcp: 2400 } }), history);
155
+ const lcp = trend.series.find((s) => s.key === 'lcp');
156
+ expect(lcp?.deltaFromOldest).toBe(400);
157
+ expect(lcp?.creep).toBe('LCP has crept up 400ms over 10 runs (2.00s → 2.40s).');
158
+ expect(trend.headlines[0]).toContain('crept up 400ms');
159
+ });
160
+ it('does not call rasteriser-scale jitter a regression', () => {
161
+ const history = [historyRun('2026-09-20T10:00:00.000Z', { lcp: 2000 })];
162
+ const trend = buildTrend(report({ vitals: { lcp: 2010 } }), history);
163
+ expect(trend.series.find((s) => s.key === 'lcp')?.direction).toBe('flat');
164
+ });
165
+ it('calls a real movement a regression, and the reverse an improvement', () => {
166
+ const history = [historyRun('2026-09-20T10:00:00.000Z', { lcp: 2000 })];
167
+ expect(buildTrend(report({ vitals: { lcp: 2600 } }), history).series.find((s) => s.key === 'lcp')
168
+ ?.direction).toBe('regressed');
169
+ expect(buildTrend(report({ vitals: { lcp: 1400 } }), history).series.find((s) => s.key === 'lcp')
170
+ ?.direction).toBe('improved');
171
+ });
172
+ it('treats a single new console error as a regression', () => {
173
+ const history = [historyRun('2026-09-20T10:00:00.000Z', { lcp: 2000 })];
174
+ const current = report({
175
+ console: [{ type: 'error', text: 'boom', timestamp: 0 }],
176
+ });
177
+ const errors = buildTrend(current, history).series.find((s) => s.key === 'consoleErrors');
178
+ expect(errors).toMatchObject({
179
+ current: 1,
180
+ previous: 0,
181
+ deltaFromPrevious: 1,
182
+ direction: 'regressed',
183
+ });
184
+ });
185
+ it('leads with a first failure after a clean window', () => {
186
+ const history = [1, 2, 3].map((i) => historyRun(`2026-09-1${i}T10:00:00.000Z`, { lcp: 2000 }));
187
+ const failing = report({ console: [{ type: 'error', text: 'boom', timestamp: 0 }] });
188
+ expect(failing.verdict).toBe('fail');
189
+ expect(buildTrend(failing, history).headlines[0]).toBe('First failure in this window — the previous 3 run(s) all passed.');
190
+ });
191
+ it('includes this run as the last point of every series', () => {
192
+ const history = [historyRun('2026-09-20T10:00:00.000Z', { lcp: 2000 })];
193
+ const trend = buildTrend(report({ capturedAt: '2026-09-21T10:00:00.000Z', vitals: { lcp: 2100 } }), history);
194
+ const lcp = trend.series.find((s) => s.key === 'lcp');
195
+ expect(lcp?.points).toEqual([
196
+ { capturedAt: '2026-09-20T10:00:00.000Z', value: 2000 },
197
+ { capturedAt: '2026-09-21T10:00:00.000Z', value: 2100 },
198
+ ]);
199
+ expect(trend.verdictHistory.at(-1)).toEqual({
200
+ capturedAt: '2026-09-21T10:00:00.000Z',
201
+ verdict: 'pass',
202
+ });
203
+ });
204
+ it('ignores runs that never measured the metric when picking a baseline', () => {
205
+ const history = [
206
+ historyRun('2026-09-18T10:00:00.000Z', { lcp: 2000 }),
207
+ historyRun('2026-09-19T10:00:00.000Z', {}),
208
+ ];
209
+ const lcp = buildTrend(report({ vitals: { lcp: 2600 } }), history).series.find((s) => s.key === 'lcp');
210
+ expect(lcp?.previous).toBe(2000);
211
+ expect(lcp?.deltaFromPrevious).toBe(600);
212
+ });
213
+ });
214
+ describe('trend formatting', () => {
215
+ it('switches to seconds past a second so a reader can see the scale', () => {
216
+ expect(formatTrendValue('ms', 412)).toBe('412ms');
217
+ expect(formatTrendValue('ms', 2450)).toBe('2.45s');
218
+ expect(formatTrendValue('score', 0.1234)).toBe('0.123');
219
+ expect(formatTrendValue('count', 3)).toBe('3');
220
+ expect(formatTrendValue('ms', null)).toBe('—');
221
+ });
222
+ it('signs deltas, and says so plainly when there is no change', () => {
223
+ expect(formatTrendDelta('ms', 400)).toBe('+400ms');
224
+ expect(formatTrendDelta('ms', -400)).toBe('−400ms');
225
+ expect(formatTrendDelta('count', 0)).toBe('no change');
226
+ expect(formatTrendDelta('count', null)).toBe('no change');
227
+ });
228
+ });
229
+ //# sourceMappingURL=report-history.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-history.test.js","sourceRoot":"","sources":["../../../src/__tests__/report-history.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=report-output.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-output.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/report-output.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Everything around the two files the command leaves behind: where they go,
3
+ * what the JSON carries, plus the two pure helpers from the collectors
4
+ * (request classification and locator upgrading) that a stub client can
5
+ * exercise without launching Chrome.
6
+ */
7
+ import { mkdtemp, readFile } from 'node:fs/promises';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { describe, expect, it, vi } from 'vitest';
11
+ import { buildReport } from '../report/analyze.js';
12
+ import { DEFAULT_BUDGET } from '../report/budget.js';
13
+ import { toRequestEntries } from '../report/collect.js';
14
+ import { upgradeLocators } from '../report/collect-a11y.js';
15
+ import { resolveOutPath, toJsonReport, writeReportFiles } from '../report/index.js';
16
+ function capture(partial = {}) {
17
+ return {
18
+ url: 'https://shop.test/checkout',
19
+ finalUrl: 'https://shop.test/checkout',
20
+ title: 'Checkout',
21
+ capturedAt: '2026-09-21T10:00:00.000Z',
22
+ durationMs: 3200,
23
+ console: [],
24
+ pageErrors: [],
25
+ requests: [],
26
+ vitals: { lcp: 100, cls: 0, inp: 10 },
27
+ a11y: [],
28
+ screenshots: [],
29
+ notes: [],
30
+ ...partial,
31
+ };
32
+ }
33
+ const report = (partial = {}) => buildReport(capture(partial), DEFAULT_BUDGET);
34
+ describe('resolveOutPath', () => {
35
+ const when = '2026-09-21T10:00:00.000Z';
36
+ it('derives a dated, host-and-path filename when --out is omitted', async () => {
37
+ const path = await resolveOutPath(undefined, 'https://shop.test/checkout', when, '/work');
38
+ expect(path).toBe('/work/monobrowse-report-shop.test-checkout-20260921T100000Z.html');
39
+ });
40
+ it('uses an explicit .html path as given', async () => {
41
+ expect(await resolveOutPath('/tmp/out.html', 'https://a.test/', when, '/work')).toBe('/tmp/out.html');
42
+ });
43
+ it('resolves a relative path against the working directory', async () => {
44
+ expect(await resolveOutPath('reports/run.html', 'https://a.test/', when, '/work')).toBe('/work/reports/run.html');
45
+ });
46
+ it('adds .html to a path that has no extension and is not a directory', async () => {
47
+ expect(await resolveOutPath('/tmp/run', 'https://a.test/', when, '/work')).toBe('/tmp/run.html');
48
+ });
49
+ it('treats a trailing-separator path as a directory to drop the report into', async () => {
50
+ const path = await resolveOutPath('reports/', 'https://a.test/', when, '/work');
51
+ expect(path).toBe('/work/reports/monobrowse-report-a.test-20260921T100000Z.html');
52
+ });
53
+ it('treats an existing directory as a directory', async () => {
54
+ const dir = await mkdtemp(join(tmpdir(), 'monobrowse-out-'));
55
+ const path = await resolveOutPath(dir, 'https://a.test/', when, '/work');
56
+ expect(path).toBe(join(dir, 'monobrowse-report-a.test-20260921T100000Z.html'));
57
+ });
58
+ });
59
+ describe('writeReportFiles', () => {
60
+ it('writes the HTML and a sibling JSON carrying the verdict and failures', async () => {
61
+ const dir = await mkdtemp(join(tmpdir(), 'monobrowse-write-'));
62
+ const r = report({ pageErrors: [{ text: 'boom', timestamp: 0 }] });
63
+ const paths = await writeReportFiles(r, join(dir, 'run.html'));
64
+ expect(paths.jsonPath).toBe(join(dir, 'run.json'));
65
+ const html = await readFile(paths.htmlPath, 'utf8');
66
+ expect(html).toContain('<!doctype html>');
67
+ const json = JSON.parse(await readFile(paths.jsonPath, 'utf8'));
68
+ expect(json.verdict).toBe('fail');
69
+ expect(json.failures[0]).toMatchObject({ budget: 'maxPageErrors', actual: '1' });
70
+ });
71
+ it('creates the output directory if it does not exist yet', async () => {
72
+ const dir = await mkdtemp(join(tmpdir(), 'monobrowse-write-'));
73
+ const paths = await writeReportFiles(report(), join(dir, 'nested', 'deep', 'run.html'));
74
+ await expect(readFile(paths.htmlPath, 'utf8')).resolves.toContain('PASS');
75
+ });
76
+ });
77
+ describe('toJsonReport', () => {
78
+ it('drops base64 image payloads but keeps the screenshot metadata', () => {
79
+ const json = toJsonReport(report({
80
+ screenshots: [
81
+ {
82
+ label: 'page',
83
+ width: 1280,
84
+ height: 900,
85
+ dataUrl: 'data:image/png;base64,AAAA',
86
+ path: '/tmp/page.png',
87
+ },
88
+ ],
89
+ }));
90
+ const shots = json.screenshots;
91
+ expect(shots[0]).toEqual({ label: 'page', width: 1280, height: 900, path: '/tmp/page.png' });
92
+ expect(JSON.stringify(json)).not.toContain('base64');
93
+ });
94
+ });
95
+ describe('toRequestEntries', () => {
96
+ const base = { id: '1', url: 'https://a.test/x', method: 'GET', startTime: 100 };
97
+ it('marks a 4xx/5xx response as failed', () => {
98
+ const [entry] = toRequestEntries([{ ...base, status: 503, endTime: 400 }], new Map());
99
+ expect(entry.failed).toBe(true);
100
+ expect(entry.durationMs).toBe(300);
101
+ });
102
+ it('leaves a 3xx and a 2xx alone', () => {
103
+ const entries = toRequestEntries([
104
+ { ...base, status: 200 },
105
+ { ...base, id: '2', status: 302 },
106
+ ], new Map());
107
+ expect(entries.map((e) => e.failed)).toEqual([false, false]);
108
+ });
109
+ it('marks a transport failure as failed and carries its errorText', () => {
110
+ const failures = new Map([['1', { errorText: 'net::ERR_CONNECTION_REFUSED' }]]);
111
+ const [entry] = toRequestEntries([base], failures);
112
+ expect(entry).toMatchObject({ failed: true, errorText: 'net::ERR_CONNECTION_REFUSED' });
113
+ });
114
+ it('does not count a request the page itself cancelled', () => {
115
+ const failures = new Map([['1', { errorText: 'net::ERR_ABORTED', canceled: true }]]);
116
+ expect(toRequestEntries([base], failures)[0].failed).toBe(false);
117
+ });
118
+ it('still counts a blocked request even though CDP also marks it cancelled', () => {
119
+ const failures = new Map([['1', { canceled: true, blockedReason: 'csp' }]]);
120
+ const [entry] = toRequestEntries([base], failures);
121
+ expect(entry).toMatchObject({ failed: true, errorText: 'blocked: csp' });
122
+ });
123
+ it('does not treat a still-in-flight request as a failure', () => {
124
+ expect(toRequestEntries([base], new Map())[0].failed).toBe(false);
125
+ });
126
+ });
127
+ describe('upgradeLocators', () => {
128
+ const finding = (locator) => ({
129
+ rule: 'unlabelled-control',
130
+ impact: 'error',
131
+ role: 'button',
132
+ name: null,
133
+ locator,
134
+ detail: 'no name',
135
+ });
136
+ function stubClient(selector) {
137
+ return {
138
+ send: vi.fn(async (method) => {
139
+ if (method === 'DOM.resolveNode')
140
+ return { object: { objectId: 'obj-1' } };
141
+ if (method === 'Runtime.callFunctionOn')
142
+ return { result: { value: selector } };
143
+ return {};
144
+ }),
145
+ };
146
+ }
147
+ const nodes = [{ nodeId: '7', backendDOMNodeId: 42 }];
148
+ it('replaces the ax-node placeholder with a resolved CSS selector', async () => {
149
+ const out = await upgradeLocators(stubClient('#submit'), 'sid', [finding('ax-node:7')], nodes);
150
+ expect(out[0].locator).toBe('#submit');
151
+ });
152
+ it('keeps the placeholder when the DOM cannot resolve the node', async () => {
153
+ const out = await upgradeLocators(stubClient(null), 'sid', [finding('ax-node:7')], nodes);
154
+ expect(out[0].locator).toBe('ax-node:7');
155
+ });
156
+ it('leaves DOM-derived locators untouched and costs no round-trip', async () => {
157
+ const client = stubClient('#other');
158
+ const out = await upgradeLocators(client, 'sid', [finding('#already-known')], nodes);
159
+ expect(out[0].locator).toBe('#already-known');
160
+ expect(client.send).not.toHaveBeenCalled();
161
+ });
162
+ it('resolves each backend node once, however many findings point at it', async () => {
163
+ const client = stubClient('#submit');
164
+ await upgradeLocators(client, 'sid', [finding('ax-node:7'), finding('ax-node:7')], nodes);
165
+ const resolves = client.send.mock.calls.filter((c) => c[0] === 'DOM.resolveNode');
166
+ expect(resolves).toHaveLength(1);
167
+ });
168
+ });
169
+ //# sourceMappingURL=report-output.test.js.map