@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,181 @@
1
+ /**
2
+ * Budget evaluation — the step that makes "the agent tested it" mean
3
+ * something. Pure function of CaptureData + Budget, so CI behaviour is
4
+ * testable without a browser.
5
+ */
6
+
7
+ import { countA11y } from './a11y.js';
8
+ import { budgetLabel, formatThreshold } from './budget.js';
9
+ import type {
10
+ A11yFinding,
11
+ Budget,
12
+ BudgetFailure,
13
+ CaptureData,
14
+ ConsoleEntry,
15
+ Report,
16
+ RequestEntry,
17
+ VerdictValue,
18
+ } from './types.js';
19
+
20
+ /** How many concrete examples to quote in a failure's `detail`. */
21
+ const DETAIL_SAMPLE = 3;
22
+
23
+ export function consoleErrors(entries: ConsoleEntry[]): ConsoleEntry[] {
24
+ return entries.filter((e) => e.type === 'error');
25
+ }
26
+
27
+ export function consoleWarnings(entries: ConsoleEntry[]): ConsoleEntry[] {
28
+ return entries.filter((e) => e.type === 'warn' || e.type === 'warning');
29
+ }
30
+
31
+ export function failedRequests(requests: RequestEntry[]): RequestEntry[] {
32
+ return requests.filter((r) => r.failed);
33
+ }
34
+
35
+ function truncate(text: string, max = 160): string {
36
+ const flat = text.replace(/\s+/g, ' ').trim();
37
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
38
+ }
39
+
40
+ function sample(items: string[]): string {
41
+ // Not `.map(truncate)`: map passes the index as the second argument, which
42
+ // would become truncate's `max` and shave every sample down to nothing.
43
+ const shown = items.slice(0, DETAIL_SAMPLE).map((item) => truncate(item));
44
+ const rest = items.length - shown.length;
45
+ return rest > 0 ? `${shown.join(' | ')} (+${rest} more)` : shown.join(' | ');
46
+ }
47
+
48
+ function describeRequest(r: RequestEntry): string {
49
+ const status = r.status !== undefined ? String(r.status) : (r.errorText ?? 'no response');
50
+ return `${status} ${r.method} ${r.url}`;
51
+ }
52
+
53
+ interface CountCheck {
54
+ key: keyof Budget;
55
+ actual: number;
56
+ detail: string;
57
+ }
58
+
59
+ interface MetricCheck {
60
+ key: keyof Budget;
61
+ actual: number | undefined;
62
+ format: (v: number) => string;
63
+ }
64
+
65
+ export interface Evaluation {
66
+ verdict: VerdictValue;
67
+ failures: BudgetFailure[];
68
+ /** Budgets that were set but could not be checked — the page never reported the metric. */
69
+ unmeasured: string[];
70
+ }
71
+
72
+ export function evaluateBudget(data: CaptureData, budget: Budget): Evaluation {
73
+ const failures: BudgetFailure[] = [];
74
+ const unmeasured: string[] = [];
75
+
76
+ const cErrors = consoleErrors(data.console);
77
+ const failed = failedRequests(data.requests);
78
+ const a11y = countA11y(data.a11y);
79
+
80
+ const counts: CountCheck[] = [
81
+ {
82
+ key: 'maxConsoleErrors',
83
+ actual: cErrors.length,
84
+ detail: sample(cErrors.map((e) => e.text)),
85
+ },
86
+ {
87
+ key: 'maxPageErrors',
88
+ actual: data.pageErrors.length,
89
+ detail: sample(data.pageErrors.map((e) => e.text)),
90
+ },
91
+ {
92
+ key: 'maxFailedRequests',
93
+ actual: failed.length,
94
+ detail: sample(failed.map(describeRequest)),
95
+ },
96
+ {
97
+ key: 'maxA11yErrors',
98
+ actual: a11y.errors,
99
+ detail: sample(
100
+ data.a11y
101
+ .filter((f) => f.impact === 'error')
102
+ .map((f: A11yFinding) => `${f.rule} at ${f.locator}`),
103
+ ),
104
+ },
105
+ ];
106
+
107
+ for (const check of counts) {
108
+ const limit = budget[check.key];
109
+ if (limit === null) continue;
110
+ if (check.actual > limit) {
111
+ failures.push({
112
+ budget: check.key,
113
+ expected: formatThreshold(check.key, limit),
114
+ actual: String(check.actual),
115
+ detail: check.detail || undefined,
116
+ });
117
+ }
118
+ }
119
+
120
+ const ms = (v: number) => `${Math.round(v)}ms`;
121
+ const score = (v: number) => v.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
122
+ const metrics: MetricCheck[] = [
123
+ { key: 'lcpMs', actual: data.vitals.lcp, format: ms },
124
+ { key: 'clsScore', actual: data.vitals.cls, format: score },
125
+ { key: 'inpMs', actual: data.vitals.inp, format: ms },
126
+ { key: 'fcpMs', actual: data.vitals.fcp, format: ms },
127
+ { key: 'ttfbMs', actual: data.vitals.ttfb, format: ms },
128
+ ];
129
+
130
+ for (const metric of metrics) {
131
+ const limit = budget[metric.key];
132
+ if (limit === null) continue;
133
+ if (metric.actual === undefined || !Number.isFinite(metric.actual)) {
134
+ // A page that never fires an LCP entry (no contentful paint, an
135
+ // observer the browser does not support, a collector that timed out)
136
+ // must not be silently graded as passing — but it is not a breach
137
+ // either. Surface it as unmeasured and let the report say so.
138
+ unmeasured.push(budgetLabel(metric.key));
139
+ continue;
140
+ }
141
+ if (metric.actual > limit) {
142
+ failures.push({
143
+ budget: metric.key,
144
+ expected: formatThreshold(metric.key, limit),
145
+ actual: metric.format(metric.actual),
146
+ });
147
+ }
148
+ }
149
+
150
+ return { verdict: failures.length > 0 ? 'fail' : 'pass', failures, unmeasured };
151
+ }
152
+
153
+ export function buildReport(data: CaptureData, budget: Budget): Report {
154
+ const evaluation = evaluateBudget(data, budget);
155
+ const a11y = countA11y(data.a11y);
156
+ return {
157
+ ...data,
158
+ budget,
159
+ verdict: evaluation.verdict,
160
+ failures: evaluation.failures,
161
+ unmeasured: evaluation.unmeasured,
162
+ counts: {
163
+ consoleErrors: consoleErrors(data.console).length,
164
+ consoleWarnings: consoleWarnings(data.console).length,
165
+ pageErrors: data.pageErrors.length,
166
+ requests: data.requests.length,
167
+ failedRequests: failedRequests(data.requests).length,
168
+ a11yErrors: a11y.errors,
169
+ a11yWarnings: a11y.warnings,
170
+ },
171
+ };
172
+ }
173
+
174
+ /** One line for a CI log / agent transcript. */
175
+ export function summarizeVerdict(report: Report): string {
176
+ if (report.verdict === 'pass') {
177
+ return `PASS — ${report.counts.requests} requests, 0 budget failures`;
178
+ }
179
+ const names = report.failures.map((f) => budgetLabel(f.budget)).join(', ');
180
+ return `FAIL — ${report.failures.length} budget failure(s): ${names}`;
181
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Budget parsing for `monobrowse report`.
3
+ *
4
+ * A budget is what turns "the agent looked at the page" into "the agent
5
+ * tested the page": the thresholds the run is gated on. Defaults are the
6
+ * strict ones you actually want in CI — zero errors, zero failed requests,
7
+ * Core Web Vitals at Google's "good" boundary.
8
+ */
9
+
10
+ import { readFile } from 'node:fs/promises';
11
+ import type { Budget } from './types.js';
12
+
13
+ export const DEFAULT_BUDGET: Budget = {
14
+ maxConsoleErrors: 0,
15
+ maxPageErrors: 0,
16
+ maxFailedRequests: 0,
17
+ maxA11yErrors: 0,
18
+ lcpMs: 2500,
19
+ clsScore: 0.1,
20
+ inpMs: 200,
21
+ fcpMs: null,
22
+ ttfbMs: null,
23
+ };
24
+
25
+ /** Every spelling we accept, normalized (lowercased, `_`/`-` stripped). */
26
+ const KEY_ALIASES: Record<string, keyof Budget> = {
27
+ maxconsoleerrors: 'maxConsoleErrors',
28
+ consoleerrors: 'maxConsoleErrors',
29
+ maxpageerrors: 'maxPageErrors',
30
+ pageerrors: 'maxPageErrors',
31
+ maxfailedrequests: 'maxFailedRequests',
32
+ failedrequests: 'maxFailedRequests',
33
+ maxa11yerrors: 'maxA11yErrors',
34
+ a11yerrors: 'maxA11yErrors',
35
+ maxaccessibilityerrors: 'maxA11yErrors',
36
+ lcp: 'lcpMs',
37
+ lcpms: 'lcpMs',
38
+ cls: 'clsScore',
39
+ clsscore: 'clsScore',
40
+ inp: 'inpMs',
41
+ inpms: 'inpMs',
42
+ fcp: 'fcpMs',
43
+ fcpms: 'fcpMs',
44
+ ttfb: 'ttfbMs',
45
+ ttfbms: 'ttfbMs',
46
+ };
47
+
48
+ const BUDGET_UNITS: Record<keyof Budget, 'ms' | 'score' | 'count'> = {
49
+ maxConsoleErrors: 'count',
50
+ maxPageErrors: 'count',
51
+ maxFailedRequests: 'count',
52
+ maxA11yErrors: 'count',
53
+ lcpMs: 'ms',
54
+ clsScore: 'score',
55
+ inpMs: 'ms',
56
+ fcpMs: 'ms',
57
+ ttfbMs: 'ms',
58
+ };
59
+
60
+ const BUDGET_LABELS: Record<keyof Budget, string> = {
61
+ maxConsoleErrors: 'Console errors',
62
+ maxPageErrors: 'Uncaught page errors',
63
+ maxFailedRequests: 'Failed requests',
64
+ maxA11yErrors: 'Accessibility errors',
65
+ lcpMs: 'LCP (Largest Contentful Paint)',
66
+ clsScore: 'CLS (Cumulative Layout Shift)',
67
+ inpMs: 'INP (Interaction to Next Paint)',
68
+ fcpMs: 'FCP (First Contentful Paint)',
69
+ ttfbMs: 'TTFB (Time to First Byte)',
70
+ };
71
+
72
+ export function budgetLabel(key: keyof Budget): string {
73
+ return BUDGET_LABELS[key];
74
+ }
75
+
76
+ /** `2500` -> `<= 2500ms`, `0` -> `<= 0`, `0.1` -> `<= 0.1`. */
77
+ export function formatThreshold(key: keyof Budget, value: number): string {
78
+ const unit = BUDGET_UNITS[key];
79
+ if (unit === 'ms') return `<= ${value}ms`;
80
+ return `<= ${value}`;
81
+ }
82
+
83
+ function normalizeKey(raw: string): string {
84
+ return raw.toLowerCase().replace(/[_\-\s]/g, '');
85
+ }
86
+
87
+ /**
88
+ * Turn a parsed JSON object into a Budget, merged over the defaults.
89
+ *
90
+ * Unknown keys throw rather than being ignored: a typo'd `max_console_error`
91
+ * silently loosening a CI gate is exactly the failure mode budgets exist to
92
+ * prevent.
93
+ */
94
+ export function parseBudget(input: unknown, source = 'budget'): Budget {
95
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
96
+ throw new Error(`Invalid ${source}: expected a JSON object of budget keys`);
97
+ }
98
+
99
+ const budget: Budget = { ...DEFAULT_BUDGET };
100
+ for (const [rawKey, rawValue] of Object.entries(input as Record<string, unknown>)) {
101
+ const key = KEY_ALIASES[normalizeKey(rawKey)];
102
+ if (!key) {
103
+ throw new Error(
104
+ `Unknown budget key "${rawKey}" in ${source}. Known keys: ${Object.keys(DEFAULT_BUDGET).join(', ')}`,
105
+ );
106
+ }
107
+ if (rawValue === null) {
108
+ budget[key] = null;
109
+ continue;
110
+ }
111
+ if (typeof rawValue !== 'number' || !Number.isFinite(rawValue)) {
112
+ throw new Error(
113
+ `Budget "${rawKey}" must be a number or null, got ${JSON.stringify(rawValue)}`,
114
+ );
115
+ }
116
+ if (rawValue < 0) {
117
+ throw new Error(`Budget "${rawKey}" must not be negative, got ${rawValue}`);
118
+ }
119
+ budget[key] = rawValue;
120
+ }
121
+ return budget;
122
+ }
123
+
124
+ /**
125
+ * Resolve `--budget`. The value is either inline JSON (starts with `{`) or a
126
+ * path to a JSON file. Omitted means DEFAULT_BUDGET.
127
+ */
128
+ export async function loadBudget(spec?: string): Promise<Budget> {
129
+ if (spec === undefined || spec === '') return { ...DEFAULT_BUDGET };
130
+
131
+ const trimmed = spec.trim();
132
+ if (trimmed.startsWith('{')) {
133
+ let parsed: unknown;
134
+ try {
135
+ parsed = JSON.parse(trimmed);
136
+ } catch (err) {
137
+ throw new Error(`--budget is not valid JSON: ${(err as Error).message}`);
138
+ }
139
+ return parseBudget(parsed, 'inline --budget');
140
+ }
141
+
142
+ let text: string;
143
+ try {
144
+ text = await readFile(trimmed, 'utf8');
145
+ } catch {
146
+ throw new Error(`Budget file not found or unreadable: ${trimmed}`);
147
+ }
148
+ let parsed: unknown;
149
+ try {
150
+ parsed = JSON.parse(text);
151
+ } catch (err) {
152
+ throw new Error(`Budget file ${trimmed} is not valid JSON: ${(err as Error).message}`);
153
+ }
154
+ return parseBudget(parsed, trimmed);
155
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * The browser-facing half of the accessibility check: fetch the AX tree,
3
+ * sweep the DOM for tabindex (which the AX tree does not expose at all), and
4
+ * turn `ax-node:<id>` placeholders into real CSS selectors a human or an
5
+ * agent can act on. The rules themselves live in a11y.ts and stay pure.
6
+ */
7
+
8
+ import { evaluateJs } from '../browser/actions.js';
9
+ import type { CdpClient } from '../browser/cdp.js';
10
+ import { runA11yRules } from './a11y.js';
11
+ import { buildStructure } from './structure.js';
12
+ import type { A11yFinding, AxNode, FocusCandidate, StructureNode } from './types.js';
13
+ import { message, STEP_TIMEOUT_MS, withTimeout } from './util.js';
14
+
15
+ /** Most AX findings we will spend a DOM round-trip on to get a real selector. */
16
+ const MAX_LOCATOR_LOOKUPS = 60;
17
+
18
+ /** Builds a short, stable CSS selector for an element, from inside the page. */
19
+ const LOCATOR_FN = `function(){
20
+ var el = this;
21
+ if (!el || el.nodeType !== 1) return '';
22
+ var esc = function(s){ return window.CSS && CSS.escape ? CSS.escape(s) : s; };
23
+ if (el.id) return '#' + esc(el.id);
24
+ var parts = [], node = el, depth = 0;
25
+ while (node && node.nodeType === 1 && depth < 5) {
26
+ var part = node.tagName.toLowerCase();
27
+ if (node.id) { parts.unshift('#' + esc(node.id)); break; }
28
+ var parent = node.parentElement;
29
+ if (parent) {
30
+ var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === node.tagName; });
31
+ if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(node) + 1) + ')';
32
+ }
33
+ parts.unshift(part);
34
+ node = parent;
35
+ depth++;
36
+ }
37
+ return parts.join(' > ');
38
+ }`;
39
+
40
+ /** DOM sweep for tabindex — the AX tree does not expose it at all. */
41
+ const FOCUS_CANDIDATES_JS = `(function(){
42
+ try {
43
+ var esc = function(s){ return window.CSS && CSS.escape ? CSS.escape(s) : s; };
44
+ var loc = function(el){
45
+ if (el.id) return '#' + esc(el.id);
46
+ var parts = [], node = el, depth = 0;
47
+ while (node && node.nodeType === 1 && depth < 5) {
48
+ var part = node.tagName.toLowerCase();
49
+ if (node.id) { parts.unshift('#' + esc(node.id)); break; }
50
+ var parent = node.parentElement;
51
+ if (parent) {
52
+ var sibs = Array.prototype.filter.call(parent.children, function(c){ return c.tagName === node.tagName; });
53
+ if (sibs.length > 1) part += ':nth-of-type(' + (sibs.indexOf(node) + 1) + ')';
54
+ }
55
+ parts.unshift(part);
56
+ node = parent;
57
+ depth++;
58
+ }
59
+ return parts.join(' > ');
60
+ };
61
+ var out = [];
62
+ var els = document.querySelectorAll('[tabindex]');
63
+ for (var i = 0; i < els.length && out.length < 100; i++) {
64
+ var el = els[i];
65
+ var ti = parseInt(el.getAttribute('tabindex'), 10);
66
+ if (!(ti < 0)) continue;
67
+ var name = (el.getAttribute('aria-label') || el.getAttribute('title') || (el.textContent || '')).trim();
68
+ out.push({
69
+ tag: el.tagName.toLowerCase(),
70
+ role: (el.getAttribute('role') || '').toLowerCase(),
71
+ name: name ? name.slice(0, 120) : null,
72
+ tabindex: ti,
73
+ locator: loc(el)
74
+ });
75
+ }
76
+ return out;
77
+ } catch (e) { return []; }
78
+ })()`;
79
+
80
+ async function resolveLocator(
81
+ client: CdpClient,
82
+ sessionId: string,
83
+ backendNodeId: number,
84
+ ): Promise<string | null> {
85
+ try {
86
+ const resolved = await client.send<{ object?: { objectId?: string } }>(
87
+ 'DOM.resolveNode',
88
+ { backendNodeId },
89
+ sessionId,
90
+ );
91
+ const objectId = resolved.object?.objectId;
92
+ if (!objectId) return null;
93
+ try {
94
+ const call = await client.send<{ result?: { value?: string } }>(
95
+ 'Runtime.callFunctionOn',
96
+ { functionDeclaration: LOCATOR_FN, objectId, returnByValue: true },
97
+ sessionId,
98
+ );
99
+ return call.result?.value || null;
100
+ } finally {
101
+ await client.send('Runtime.releaseObject', { objectId }, sessionId).catch(() => {});
102
+ }
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Rewrites `ax-node:<id>` placeholder locators into real CSS selectors.
110
+ * Bounded: a page with 400 unlabelled buttons must not cost 400 CDP
111
+ * round-trips, and nobody reads the 400th finding anyway.
112
+ */
113
+ export async function upgradeLocators(
114
+ client: CdpClient,
115
+ sessionId: string,
116
+ findings: A11yFinding[],
117
+ nodes: AxNode[],
118
+ ): Promise<A11yFinding[]> {
119
+ const backendByNodeId = new Map<string, number>();
120
+ for (const n of nodes) {
121
+ if (n.backendDOMNodeId !== undefined) backendByNodeId.set(String(n.nodeId), n.backendDOMNodeId);
122
+ }
123
+
124
+ let budget = MAX_LOCATOR_LOOKUPS;
125
+ const cache = new Map<number, string | null>();
126
+ const out: A11yFinding[] = [];
127
+ for (const finding of findings) {
128
+ if (!finding.locator.startsWith('ax-node:') || budget <= 0) {
129
+ out.push(finding);
130
+ continue;
131
+ }
132
+ const backendId = backendByNodeId.get(finding.locator.slice('ax-node:'.length));
133
+ if (backendId === undefined) {
134
+ out.push(finding);
135
+ continue;
136
+ }
137
+ if (!cache.has(backendId)) {
138
+ budget--;
139
+ cache.set(backendId, await resolveLocator(client, sessionId, backendId));
140
+ }
141
+ const selector = cache.get(backendId);
142
+ out.push(selector ? { ...finding, locator: selector } : finding);
143
+ }
144
+ return out;
145
+ }
146
+
147
+ export interface A11yResult {
148
+ findings: A11yFinding[];
149
+ /**
150
+ * Flat signature of the same tree, for the run-to-run structural diff. It
151
+ * rides along here because the AX tree is already in hand — fetching it
152
+ * twice would double the cost of the most expensive collector.
153
+ */
154
+ structure: StructureNode[];
155
+ }
156
+
157
+ export async function collectA11y(
158
+ client: CdpClient,
159
+ sessionId: string,
160
+ notes: string[],
161
+ ): Promise<A11yResult> {
162
+ let nodes: AxNode[] = [];
163
+ try {
164
+ const tree = await withTimeout(
165
+ client.send<{ nodes: AxNode[] }>('Accessibility.getFullAXTree', {}, sessionId),
166
+ STEP_TIMEOUT_MS,
167
+ 'accessibility tree',
168
+ );
169
+ nodes = tree.nodes ?? [];
170
+ } catch (err) {
171
+ notes.push(`Accessibility tree unavailable: ${message(err)}`);
172
+ return { findings: [], structure: [] };
173
+ }
174
+
175
+ let focusCandidates: FocusCandidate[] = [];
176
+ try {
177
+ const raw = await withTimeout(
178
+ evaluateJs(client, sessionId, FOCUS_CANDIDATES_JS),
179
+ STEP_TIMEOUT_MS,
180
+ 'tabindex sweep',
181
+ );
182
+ if (Array.isArray(raw)) focusCandidates = raw as FocusCandidate[];
183
+ } catch (err) {
184
+ notes.push(`Tabindex sweep skipped: ${message(err)}`);
185
+ }
186
+
187
+ const structure = buildStructure(nodes);
188
+ const findings = runA11yRules({ nodes, focusCandidates });
189
+ try {
190
+ const upgraded = await withTimeout(
191
+ upgradeLocators(client, sessionId, findings, nodes),
192
+ STEP_TIMEOUT_MS,
193
+ 'locator resolution',
194
+ );
195
+ return { findings: upgraded, structure };
196
+ } catch {
197
+ notes.push('Some accessibility locators could not be resolved to CSS selectors.');
198
+ return { findings, structure };
199
+ }
200
+ }