@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,175 @@
1
+ /**
2
+ * The renderer's contract is that the HTML is SELF-CONTAINED: it must render
3
+ * identically on a machine with no network, because monomind's document
4
+ * pipeline ingests it as an ordinary document and because an archived report
5
+ * has to still work years later. These tests enforce that mechanically.
6
+ */
7
+ import { describe, expect, it } from 'vitest';
8
+ import { buildReport } from '../report/analyze.js';
9
+ import { DEFAULT_BUDGET, parseBudget } from '../report/budget.js';
10
+ import { escapeHtml, renderHtml } from '../report/render.js';
11
+ import type { CaptureData, Report } from '../report/types.js';
12
+
13
+ const PNG =
14
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
15
+
16
+ function capture(partial: Partial<CaptureData> = {}): CaptureData {
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: {},
27
+ a11y: [],
28
+ screenshots: [],
29
+ notes: [],
30
+ ...partial,
31
+ };
32
+ }
33
+
34
+ function report(partial: Partial<CaptureData> = {}, budget = DEFAULT_BUDGET): Report {
35
+ return buildReport(capture(partial), budget);
36
+ }
37
+
38
+ /** Anything that would make the browser go to the network to render the page. */
39
+ function externalReferences(html: string): string[] {
40
+ const patterns = [
41
+ /<script[^>]+\ssrc\s*=/gi,
42
+ /<link\b/gi,
43
+ /@import/gi,
44
+ /url\(\s*['"]?https?:/gi,
45
+ /\s(?:src|href|poster|action|data)\s*=\s*["']https?:/gi,
46
+ /\s(?:src|href)\s*=\s*["']\/\//gi,
47
+ ];
48
+ return patterns.flatMap((p) => html.match(p) ?? []);
49
+ }
50
+
51
+ describe('renderHtml — self-containment', () => {
52
+ it('references no external resource of any kind', () => {
53
+ const html = renderHtml(
54
+ report({
55
+ screenshots: [{ label: 'page', width: 1280, height: 3000, dataUrl: PNG }],
56
+ requests: [{ url: 'https://cdn.test/app.js', method: 'GET', status: 404, failed: true }],
57
+ console: [{ type: 'error', text: 'boom', url: 'https://cdn.test/app.js', timestamp: 0 }],
58
+ }),
59
+ );
60
+ expect(externalReferences(html)).toEqual([]);
61
+ });
62
+
63
+ it('inlines its CSS in a style element and inlines screenshots as data URIs', () => {
64
+ const html = renderHtml(
65
+ report({ screenshots: [{ label: 'page', width: 800, height: 600, dataUrl: PNG }] }),
66
+ );
67
+ expect(html).toContain('<style>');
68
+ expect(html).toContain('src="data:image/png;base64,');
69
+ });
70
+
71
+ it('themes for both light and dark without JavaScript', () => {
72
+ const html = renderHtml(report());
73
+ expect(html).toContain('@media (prefers-color-scheme: dark)');
74
+ expect(html).not.toMatch(/<script(?![^>]*type="application\/json")/i);
75
+ });
76
+ });
77
+
78
+ describe('renderHtml — content', () => {
79
+ it('leads with a PASS verdict for a clean page', () => {
80
+ const html = renderHtml(report({ vitals: { lcp: 800, cls: 0.01, inp: 20 } }));
81
+ const verdictAt = html.indexOf('PASS');
82
+ expect(verdictAt).toBeGreaterThan(-1);
83
+ expect(verdictAt).toBeLessThan(html.indexOf('Failed requests'));
84
+ expect(html).toContain('All budgets met.');
85
+ });
86
+
87
+ it('orders the sections verdict, errors, failed requests, vitals, a11y, screenshots', () => {
88
+ const html = renderHtml(report());
89
+ const order = [
90
+ 'PASS',
91
+ 'Errors',
92
+ 'Failed requests',
93
+ 'Web vitals',
94
+ 'Accessibility',
95
+ 'Screenshots',
96
+ ];
97
+ const positions = order.map((s) => html.indexOf(s));
98
+ expect(positions.every((p) => p > -1)).toBe(true);
99
+ expect([...positions].sort((a, b) => a - b)).toEqual(positions);
100
+ });
101
+
102
+ it('shows each budget failure with its expected and actual values', () => {
103
+ const html = renderHtml(
104
+ report({ vitals: { lcp: 4100, cls: 0, inp: 0 } }, parseBudget({ lcp: 2500 })),
105
+ );
106
+ expect(html).toContain('FAIL');
107
+ expect(html).toContain('LCP (Largest Contentful Paint)');
108
+ expect(html).toContain('&lt;= 2500ms');
109
+ expect(html).toContain('4100ms');
110
+ });
111
+
112
+ it('lists failed requests and omits the successful ones', () => {
113
+ const html = renderHtml(
114
+ report({
115
+ requests: [
116
+ { url: 'https://shop.test/api/cart', method: 'POST', status: 500, failed: true },
117
+ { url: 'https://shop.test/ok', method: 'GET', status: 200, failed: false },
118
+ ],
119
+ }),
120
+ );
121
+ expect(html).toContain('https://shop.test/api/cart');
122
+ expect(html).not.toContain('https://shop.test/ok');
123
+ });
124
+
125
+ it('says a metric was not measured rather than showing a fake zero', () => {
126
+ const html = renderHtml(report({ vitals: { cls: 0.05 } }));
127
+ expect(html).toContain('not measured');
128
+ });
129
+
130
+ it('renders a11y findings with role, name state and locator', () => {
131
+ const html = renderHtml(
132
+ report({
133
+ a11y: [
134
+ {
135
+ rule: 'unlabelled-control',
136
+ impact: 'error',
137
+ role: 'button',
138
+ name: null,
139
+ locator: 'form > button:nth-of-type(2)',
140
+ detail: 'button has no accessible name',
141
+ },
142
+ ],
143
+ }),
144
+ );
145
+ expect(html).toContain('unlabelled-control');
146
+ expect(html).toContain('form &gt; button:nth-of-type(2)');
147
+ expect(html).toContain('(no accessible name)');
148
+ });
149
+
150
+ it('states plainly that contrast was not evaluated', () => {
151
+ expect(renderHtml(report())).toMatch(/[Cc]olour contrast is not evaluated/);
152
+ });
153
+
154
+ it('surfaces degraded-run notes', () => {
155
+ const html = renderHtml(report({ notes: ['Page never went network-idle within 20000ms'] }));
156
+ expect(html).toContain('Run notes');
157
+ expect(html).toContain('never went network-idle');
158
+ });
159
+ });
160
+
161
+ describe('escapeHtml', () => {
162
+ it('neutralizes markup from page-controlled strings', () => {
163
+ expect(escapeHtml('<img src=x onerror="alert(1)">')).toBe(
164
+ '&lt;img src=x onerror=&quot;alert(1)&quot;&gt;',
165
+ );
166
+ });
167
+
168
+ it('keeps an injected error message from breaking out of the document', () => {
169
+ const html = renderHtml(
170
+ report({ pageErrors: [{ text: '</table><script>alert(1)</script>', timestamp: 0 }] }),
171
+ );
172
+ expect(html).not.toContain('<script>alert(1)</script>');
173
+ expect(html).toContain('&lt;script&gt;');
174
+ });
175
+ });
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The orchestration seam in report/index.ts: history is read before the run
3
+ * is saved, evidence is only built when it is warranted, and `--repeat`
4
+ * judges the set rather than the last run.
5
+ *
6
+ * `collect` is mocked, so nothing here launches Chrome. Output and history
7
+ * both go to a scratch directory under the monomind data dir — never the
8
+ * shared /tmp.
9
+ */
10
+ import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises';
11
+ import { homedir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
14
+ import type { CdpClient } from '../browser/cdp.js';
15
+ import { historyKey, listRuns } from '../report/history.js';
16
+ import { encodePng, pngToDataUrl } from '../report/png.js';
17
+ import type { CaptureData } from '../report/types.js';
18
+
19
+ const collect = vi.hoisted(() => vi.fn());
20
+ vi.mock('../report/collect.js', () => ({ collect }));
21
+
22
+ const { runReport, runReportRepeated, readHistory } = await import('../report/index.js');
23
+
24
+ const URL_UNDER_TEST = 'https://shop.test/checkout';
25
+ const client = {} as CdpClient;
26
+
27
+ function shot(shade: number, width = 8, height = 8) {
28
+ const data = new Uint8Array(width * height * 4);
29
+ for (let i = 0; i < width * height; i++) data.set([shade, shade, shade, 255], i * 4);
30
+ return {
31
+ label: 'page',
32
+ width,
33
+ height,
34
+ dataUrl: pngToDataUrl(encodePng({ width, height, data })),
35
+ };
36
+ }
37
+
38
+ function capture(partial: Partial<CaptureData> = {}): CaptureData {
39
+ return {
40
+ url: URL_UNDER_TEST,
41
+ finalUrl: URL_UNDER_TEST,
42
+ title: 'Checkout',
43
+ capturedAt: '2026-09-21T10:00:00.000Z',
44
+ durationMs: 1000,
45
+ console: [],
46
+ pageErrors: [],
47
+ requests: [],
48
+ vitals: { lcp: 2000, cls: 0.01, inp: 50 },
49
+ a11y: [],
50
+ structure: [{ path: '/main[1]/button[1]', role: 'button', name: 'Pay now', depth: 1 }],
51
+ screenshots: [shot(200)],
52
+ notes: [],
53
+ ...partial,
54
+ };
55
+ }
56
+
57
+ let root: string;
58
+ let outDir: string;
59
+
60
+ beforeEach(async () => {
61
+ await mkdir(join(homedir(), '.monomind'), { recursive: true });
62
+ root = await mkdtemp(join(homedir(), '.monomind', 'monobrowse-run-test-'));
63
+ outDir = join(root, 'out');
64
+ await mkdir(outDir, { recursive: true });
65
+ collect.mockReset();
66
+ });
67
+
68
+ afterEach(async () => {
69
+ await rm(root, { recursive: true, force: true });
70
+ });
71
+
72
+ const run = (data: CaptureData, extra: Record<string, unknown> = {}) => {
73
+ collect.mockResolvedValueOnce(data);
74
+ return runReport(client, 'S1', {
75
+ url: URL_UNDER_TEST,
76
+ out: join(outDir, `${Math.random().toString(36).slice(2)}.html`),
77
+ historyDir: root,
78
+ cwd: outDir,
79
+ ...extra,
80
+ });
81
+ };
82
+
83
+ describe('runReport and history', () => {
84
+ it('does not let the first run trend or diff against itself', async () => {
85
+ const result = await run(capture());
86
+ expect(result.report.trend?.runs).toBe(0);
87
+ expect(result.report.diff).toBeUndefined();
88
+ // ...but it is on disk for the next run to compare against.
89
+ expect((await listRuns(join(root, historyKey(URL_UNDER_TEST)))).length).toBe(1);
90
+ });
91
+
92
+ it('trends and diffs the second run against the first', async () => {
93
+ await run(capture());
94
+ const second = await run(
95
+ capture({
96
+ capturedAt: '2026-09-21T11:00:00.000Z',
97
+ vitals: { lcp: 2600, cls: 0.01, inp: 50 },
98
+ structure: [{ path: '/main[1]/button[1]', role: 'button', name: 'Place order', depth: 1 }],
99
+ screenshots: [shot(40)],
100
+ }),
101
+ );
102
+
103
+ expect(second.report.trend?.runs).toBe(1);
104
+ const lcp = second.report.trend?.series.find((s) => s.key === 'lcp');
105
+ expect(lcp).toMatchObject({ previous: 2000, current: 2600, direction: 'regressed' });
106
+
107
+ expect(second.report.diff?.previousCapturedAt).toBe('2026-09-21T10:00:00.000Z');
108
+ expect(second.report.diff?.structure?.renamed).toEqual([
109
+ { path: '/main[1]/button[1]', role: 'button', from: 'Pay now', to: 'Place order' },
110
+ ]);
111
+ // Every pixel went from light grey to dark — a whole-screenshot change.
112
+ expect(second.report.diff?.pixels?.changedPercent).toBe(100);
113
+ });
114
+
115
+ it('reports the history directory it wrote to', async () => {
116
+ const result = await run(capture());
117
+ expect(result.historyDir).toBe(join(root, historyKey(URL_UNDER_TEST)));
118
+ });
119
+
120
+ it('prunes to the configured maximum across runs', async () => {
121
+ for (let i = 0; i < 4; i++) {
122
+ await run(capture({ capturedAt: `2026-09-2${i + 1}T10:00:00.000Z` }), { historyMax: 2 });
123
+ }
124
+ const runs = await listRuns(join(root, historyKey(URL_UNDER_TEST)));
125
+ expect(runs.map((r) => r.capturedAt)).toEqual([
126
+ '2026-09-23T10:00:00.000Z',
127
+ '2026-09-24T10:00:00.000Z',
128
+ ]);
129
+ });
130
+
131
+ it('writes nothing to the store when history is off, and says nothing about trends', async () => {
132
+ const result = await run(capture(), { history: false });
133
+ expect(result.report.trend).toBeUndefined();
134
+ expect(result.historyDir).toBeUndefined();
135
+ await expect(readdir(join(root, historyKey(URL_UNDER_TEST)))).rejects.toThrow();
136
+ });
137
+
138
+ it('keeps the report when the history store cannot be written', async () => {
139
+ // A regular file where the store's directory needs to be: mkdir fails
140
+ // with ENOTDIR, and the report still has to come out.
141
+ const blocked = join(root, 'blocked-store');
142
+ await writeFile(blocked, 'not a directory', 'utf8');
143
+ const result = await run(capture(), { historyDir: blocked });
144
+ expect(result.report.verdict).toBe('pass');
145
+ expect(result.report.notes.join(' ')).toMatch(/Run history unavailable/);
146
+ expect(result.historyDir).toBeUndefined();
147
+ });
148
+
149
+ it('exposes the stored runs through readHistory', async () => {
150
+ await run(capture());
151
+ const { runs } = await readHistory(URL_UNDER_TEST, { historyDir: root });
152
+ expect(runs.length).toBe(1);
153
+ expect(runs[0]).toMatchObject({ url: URL_UNDER_TEST, verdict: 'pass' });
154
+ });
155
+ });
156
+
157
+ describe('runReport and evidence', () => {
158
+ const recording = {
159
+ startedAtMs: 1_000_000,
160
+ frames: [
161
+ { offsetMs: 0, data: 'AAAA', bytes: 4 },
162
+ { offsetMs: 900, data: 'BBBB', bytes: 4 },
163
+ ],
164
+ droppedFrames: 0,
165
+ requested: false,
166
+ };
167
+
168
+ it('attaches evidence when the run breaches its budget', async () => {
169
+ const result = await run(
170
+ capture({
171
+ recording,
172
+ console: [{ type: 'error', text: 'boom', timestamp: 1_000_800 }],
173
+ }),
174
+ );
175
+ expect(result.report.verdict).toBe('fail');
176
+ expect(result.report.evidence?.reason).toBe('budget-failure');
177
+ expect(result.report.evidence?.focusOffsetMs).toBe(800);
178
+ expect(result.report.evidence?.frames.length).toBe(2);
179
+ });
180
+
181
+ it('does not attach evidence to a passing run that did not ask for it', async () => {
182
+ const result = await run(capture({ recording }));
183
+ expect(result.report.verdict).toBe('pass');
184
+ expect(result.report.evidence).toBeUndefined();
185
+ });
186
+
187
+ it('attaches evidence to a passing run when --record asked for it', async () => {
188
+ const result = await run(capture({ recording: { ...recording, requested: true } }));
189
+ expect(result.report.evidence?.reason).toBe('requested');
190
+ });
191
+
192
+ it('never leaves the raw recording on the report it returns', async () => {
193
+ const result = await run(capture({ recording: { ...recording, requested: true } }));
194
+ expect(result.report.recording).toBeUndefined();
195
+ });
196
+
197
+ it('keeps the frame images out of the JSON sibling', async () => {
198
+ const result = await run(capture({ recording: { ...recording, requested: true } }));
199
+ const json = JSON.parse(await readFile(result.jsonPath, 'utf8'));
200
+ expect(json.evidence.frames).toEqual([
201
+ { offsetMs: 0, bytes: 4 },
202
+ { offsetMs: 900, bytes: 4 },
203
+ ]);
204
+ });
205
+ });
206
+
207
+ describe('runReportRepeated', () => {
208
+ const failing = capture({ console: [{ type: 'error', text: 'boom', timestamp: 0 }] });
209
+
210
+ it('runs the URL N times and judges the set', async () => {
211
+ for (const data of [capture(), failing, capture()]) collect.mockResolvedValueOnce(data);
212
+ const result = await runReportRepeated(client, 'S1', {
213
+ url: URL_UNDER_TEST,
214
+ out: join(outDir, 'repeat.html'),
215
+ historyDir: root,
216
+ cwd: outDir,
217
+ repeat: 3,
218
+ });
219
+
220
+ expect(collect).toHaveBeenCalledTimes(3);
221
+ expect(result.reports.length).toBe(3);
222
+ expect(result.flake.verdict).toBe('flaky');
223
+ expect(result.summary).toMatch(/^FLAKY/);
224
+ });
225
+
226
+ it('refuses to call a sometimes-failing URL a pass, even when the last run is green', async () => {
227
+ for (const data of [failing, capture()]) collect.mockResolvedValueOnce(data);
228
+ const result = await runReportRepeated(client, 'S1', {
229
+ url: URL_UNDER_TEST,
230
+ out: join(outDir, 'repeat2.html'),
231
+ historyDir: root,
232
+ cwd: outDir,
233
+ repeat: 2,
234
+ });
235
+ expect(result.report.verdict).toBe('pass'); // the last run alone
236
+ expect(result.flake.verdict).toBe('flaky'); // what the command reports
237
+ });
238
+
239
+ it('writes one history entry for the whole command, not one per run', async () => {
240
+ for (const data of [capture(), capture(), capture()]) collect.mockResolvedValueOnce(data);
241
+ await runReportRepeated(client, 'S1', {
242
+ url: URL_UNDER_TEST,
243
+ out: join(outDir, 'repeat3.html'),
244
+ historyDir: root,
245
+ cwd: outDir,
246
+ repeat: 3,
247
+ });
248
+ expect((await listRuns(join(root, historyKey(URL_UNDER_TEST)))).length).toBe(1);
249
+ });
250
+
251
+ it('puts the flake analysis in the JSON sibling', async () => {
252
+ for (const data of [capture(), failing]) collect.mockResolvedValueOnce(data);
253
+ const result = await runReportRepeated(client, 'S1', {
254
+ url: URL_UNDER_TEST,
255
+ out: join(outDir, 'repeat4.html'),
256
+ historyDir: root,
257
+ cwd: outDir,
258
+ repeat: 2,
259
+ });
260
+ const json = JSON.parse(await readFile(result.jsonPath, 'utf8'));
261
+ expect(json.flake.verdict).toBe('flaky');
262
+ expect(json.flake.runs).toBe(2);
263
+ });
264
+ });
@@ -6,23 +6,21 @@
6
6
  * validation logic: the CdpClient is a stub that answers the handful of CDP
7
7
  * commands the round trip issues.
8
8
  */
9
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
10
- import { mkdtemp, rm, readFile, stat, mkdir } from 'fs/promises';
11
- import { join } from 'path';
12
- import { tmpdir } from 'os';
9
+
10
+ import { mkdir, mkdtemp, readFile, rm, stat } from 'node:fs/promises';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13
14
  import type { CdpClient } from '../browser/cdp.js';
14
- import {
15
- saveStateFile,
16
- loadStateFile,
17
- saveSession,
18
- loadSession,
19
- listSessions,
20
- } from '../browser/session.js';
15
+ import { loadSession, loadStateFile, saveSession, saveStateFile } from '../browser/session.js';
21
16
 
22
17
  const COOKIES = [{ name: 'sid', value: 'abc', domain: 'x.test', path: '/' }];
23
18
 
24
19
  /** Records every CDP command and answers the ones session.ts depends on. */
25
- function stubClient(): { client: CdpClient; calls: Array<{ method: string; params: unknown; sid?: string }> } {
20
+ function stubClient(): {
21
+ client: CdpClient;
22
+ calls: Array<{ method: string; params: unknown; sid?: string }>;
23
+ } {
26
24
  const calls: Array<{ method: string; params: unknown; sid?: string }> = [];
27
25
  const client = {
28
26
  send: vi.fn(async (method: string, params: unknown, sid?: string) => {
@@ -30,8 +28,10 @@ function stubClient(): { client: CdpClient; calls: Array<{ method: string; param
30
28
  if (method === 'Network.getCookies') return { cookies: COOKIES };
31
29
  if (method === 'Runtime.evaluate') {
32
30
  const expr = (params as { expression: string }).expression;
33
- if (expr.includes('Object.entries(localStorage)')) return { result: { value: '{"k":"v"}' } };
34
- if (expr.includes('Object.entries(sessionStorage)')) return { result: { value: '{"s":"1"}' } };
31
+ if (expr.includes('Object.entries(localStorage)'))
32
+ return { result: { value: '{"k":"v"}' } };
33
+ if (expr.includes('Object.entries(sessionStorage)'))
34
+ return { result: { value: '{"s":"1"}' } };
35
35
  }
36
36
  return {};
37
37
  }),
@@ -69,7 +69,7 @@ describe('validateSessionName (via saveSession/loadSession)', () => {
69
69
  for (const [label, name] of bad) {
70
70
  it(`rejects ${label}`, async () => {
71
71
  await expect(saveSession(client, 'S1', 'T1', name, 'https://x.test', 'T')).rejects.toThrow(
72
- /Invalid session name/
72
+ /Invalid session name/,
73
73
  );
74
74
  await expect(loadSession(client, 'S1', name)).rejects.toThrow(/Invalid session name/);
75
75
  });
@@ -77,7 +77,7 @@ describe('validateSessionName (via saveSession/loadSession)', () => {
77
77
 
78
78
  it('accepts an ordinary name (fails later, on the missing file, not on validation)', async () => {
79
79
  await expect(loadSession(client, 'S1', 'no-such-session-xyz')).rejects.toThrow(
80
- 'Session not found: no-such-session-xyz'
80
+ 'Session not found: no-such-session-xyz',
81
81
  );
82
82
  });
83
83
 
@@ -103,7 +103,7 @@ describe('validateFilePath (via saveStateFile/loadStateFile)', () => {
103
103
  for (const [label, filePath] of bad) {
104
104
  it(`rejects ${label}`, async () => {
105
105
  await expect(
106
- saveStateFile(client, 'S1', 'T1', filePath, 'https://x.test', 'T')
106
+ saveStateFile(client, 'S1', 'T1', filePath, 'https://x.test', 'T'),
107
107
  ).rejects.toThrow(/Invalid file path/);
108
108
  await expect(loadStateFile(client, 'S1', filePath)).rejects.toThrow(/Invalid file path/);
109
109
  });
@@ -111,7 +111,9 @@ describe('validateFilePath (via saveStateFile/loadStateFile)', () => {
111
111
 
112
112
  it('allows a plain absolute path', async () => {
113
113
  const p = join(dir, 'state.json');
114
- await expect(saveStateFile(client, 'S1', 'T1', p, 'https://x.test', 'T')).resolves.toBeUndefined();
114
+ await expect(
115
+ saveStateFile(client, 'S1', 'T1', p, 'https://x.test', 'T'),
116
+ ).resolves.toBeUndefined();
115
117
  });
116
118
  });
117
119
 
@@ -164,38 +166,41 @@ describe('state file round trip', () => {
164
166
  it('rejects a state file whose cookies field is not an array', async () => {
165
167
  const { client } = stubClient();
166
168
  const p = join(dir, 'bad.json');
167
- const { writeFile } = await import('fs/promises');
169
+ const { writeFile } = await import('node:fs/promises');
168
170
  await writeFile(p, JSON.stringify({ cookies: { name: 'sid' } }));
169
171
  await expect(loadStateFile(client, 'S1', p)).rejects.toThrow(
170
- 'Invalid state file: cookies is not an array'
172
+ 'Invalid state file: cookies is not an array',
171
173
  );
172
174
  });
173
175
 
174
176
  it('rejects a state file whose localStorage is an array rather than an object', async () => {
175
177
  const { client } = stubClient();
176
178
  const p = join(dir, 'bad2.json');
177
- const { writeFile } = await import('fs/promises');
179
+ const { writeFile } = await import('node:fs/promises');
178
180
  await writeFile(p, JSON.stringify({ cookies: [], localStorage: ['nope'] }));
179
181
  await expect(loadStateFile(client, 'S1', p)).rejects.toThrow(
180
- 'Invalid state file: localStorage is not a plain object'
182
+ 'Invalid state file: localStorage is not a plain object',
181
183
  );
182
184
  });
183
185
 
184
186
  it('rejects a state file whose sessionStorage is an array', async () => {
185
187
  const { client } = stubClient();
186
188
  const p = join(dir, 'bad3.json');
187
- const { writeFile } = await import('fs/promises');
189
+ const { writeFile } = await import('node:fs/promises');
188
190
  await writeFile(p, JSON.stringify({ cookies: [], sessionStorage: ['nope'] }));
189
191
  await expect(loadStateFile(client, 'S1', p)).rejects.toThrow(
190
- 'Invalid state file: sessionStorage is not a plain object'
192
+ 'Invalid state file: sessionStorage is not a plain object',
191
193
  );
192
194
  });
193
195
 
194
196
  it('accepts a state file with neither storage map present', async () => {
195
197
  const { client, calls } = stubClient();
196
198
  const p = join(dir, 'minimal.json');
197
- const { writeFile } = await import('fs/promises');
198
- await writeFile(p, JSON.stringify({ targetId: 'T', sessionId: 'S', url: '', title: '', cookies: [] }));
199
+ const { writeFile } = await import('node:fs/promises');
200
+ await writeFile(
201
+ p,
202
+ JSON.stringify({ targetId: 'T', sessionId: 'S', url: '', title: '', cookies: [] }),
203
+ );
199
204
  await expect(loadStateFile(client, 'S1', p)).resolves.toMatchObject({ cookies: [] });
200
205
  expect(calls.filter((c) => c.method === 'Runtime.evaluate')).toEqual([]);
201
206
  });
@@ -224,7 +229,7 @@ describe('listSessions', () => {
224
229
  it('lists only .json files, with the extension stripped', async () => {
225
230
  const sessionDir = join(dir, '.monomind', 'browser-sessions');
226
231
  await mkdir(sessionDir, { recursive: true });
227
- const { writeFile } = await import('fs/promises');
232
+ const { writeFile } = await import('node:fs/promises');
228
233
  await writeFile(join(sessionDir, 'alpha.json'), '{}');
229
234
  await writeFile(join(sessionDir, 'beta.json'), '{}');
230
235
  await writeFile(join(sessionDir, 'notes.txt'), 'ignore me');
@@ -236,7 +241,14 @@ describe('listSessions', () => {
236
241
  it('saveSession writes into the session dir and loadSession reads it back', async () => {
237
242
  const mod = await withHome(dir);
238
243
  const saver = stubClient();
239
- const filePath = await mod.saveSession(saver.client, 'S1', 'T1', 'my-session', 'https://x.test', 'T');
244
+ const filePath = await mod.saveSession(
245
+ saver.client,
246
+ 'S1',
247
+ 'T1',
248
+ 'my-session',
249
+ 'https://x.test',
250
+ 'T',
251
+ );
240
252
  expect(filePath).toBe(join(dir, '.monomind', 'browser-sessions', 'my-session.json'));
241
253
  expect((await stat(filePath)).mode & 0o777).toBe(0o600);
242
254
  expect((await stat(join(dir, '.monomind', 'browser-sessions'))).mode & 0o777).toBe(0o700);