devharness 0.8.0

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 (627) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/build/atomic-write.d.ts +28 -0
  4. package/build/atomic-write.d.ts.map +1 -0
  5. package/build/atomic-write.js +126 -0
  6. package/build/atomic-write.js.map +1 -0
  7. package/build/cdp-manager.d.ts +524 -0
  8. package/build/cdp-manager.d.ts.map +1 -0
  9. package/build/cdp-manager.js +1828 -0
  10. package/build/cdp-manager.js.map +1 -0
  11. package/build/cdp-manager.test.d.ts +6 -0
  12. package/build/cdp-manager.test.d.ts.map +1 -0
  13. package/build/cdp-manager.test.js +546 -0
  14. package/build/cdp-manager.test.js.map +1 -0
  15. package/build/chrome-launcher.d.ts +400 -0
  16. package/build/chrome-launcher.d.ts.map +1 -0
  17. package/build/chrome-launcher.js +1071 -0
  18. package/build/chrome-launcher.js.map +1 -0
  19. package/build/chrome-launcher.test.d.ts +11 -0
  20. package/build/chrome-launcher.test.d.ts.map +1 -0
  21. package/build/chrome-launcher.test.js +204 -0
  22. package/build/chrome-launcher.test.js.map +1 -0
  23. package/build/clickable-cache.d.ts +73 -0
  24. package/build/clickable-cache.d.ts.map +1 -0
  25. package/build/clickable-cache.js +90 -0
  26. package/build/clickable-cache.js.map +1 -0
  27. package/build/command-recorder.d.ts +401 -0
  28. package/build/command-recorder.d.ts.map +1 -0
  29. package/build/command-recorder.js +701 -0
  30. package/build/command-recorder.js.map +1 -0
  31. package/build/config.d.ts +390 -0
  32. package/build/config.d.ts.map +1 -0
  33. package/build/config.js +826 -0
  34. package/build/config.js.map +1 -0
  35. package/build/config.test.d.ts +2 -0
  36. package/build/config.test.d.ts.map +1 -0
  37. package/build/config.test.js +112 -0
  38. package/build/config.test.js.map +1 -0
  39. package/build/connection-manager.d.ts +159 -0
  40. package/build/connection-manager.d.ts.map +1 -0
  41. package/build/connection-manager.js +432 -0
  42. package/build/connection-manager.js.map +1 -0
  43. package/build/connection-manager.test.d.ts +11 -0
  44. package/build/connection-manager.test.d.ts.map +1 -0
  45. package/build/connection-manager.test.js +114 -0
  46. package/build/connection-manager.test.js.map +1 -0
  47. package/build/console-monitor.d.ts +150 -0
  48. package/build/console-monitor.d.ts.map +1 -0
  49. package/build/console-monitor.js +454 -0
  50. package/build/console-monitor.js.map +1 -0
  51. package/build/dashboard/bundle.js +1 -0
  52. package/build/dashboard/dashboard-client.d.ts +42 -0
  53. package/build/dashboard/dashboard-client.d.ts.map +1 -0
  54. package/build/dashboard/dashboard-client.js +184 -0
  55. package/build/dashboard/dashboard-client.js.map +1 -0
  56. package/build/dashboard/dashboard-hub.d.ts +50 -0
  57. package/build/dashboard/dashboard-hub.d.ts.map +1 -0
  58. package/build/dashboard/dashboard-hub.js +654 -0
  59. package/build/dashboard/dashboard-hub.js.map +1 -0
  60. package/build/dashboard/hub-lock.d.ts +16 -0
  61. package/build/dashboard/hub-lock.d.ts.map +1 -0
  62. package/build/dashboard/hub-lock.js +77 -0
  63. package/build/dashboard/hub-lock.js.map +1 -0
  64. package/build/dashboard/index.d.ts +25 -0
  65. package/build/dashboard/index.d.ts.map +1 -0
  66. package/build/dashboard/index.js +77 -0
  67. package/build/dashboard/index.js.map +1 -0
  68. package/build/dashboard/types.d.ts +83 -0
  69. package/build/dashboard/types.d.ts.map +1 -0
  70. package/build/dashboard/types.js +6 -0
  71. package/build/dashboard/types.js.map +1 -0
  72. package/build/debug-logger.d.ts +85 -0
  73. package/build/debug-logger.d.ts.map +1 -0
  74. package/build/debug-logger.js +195 -0
  75. package/build/debug-logger.js.map +1 -0
  76. package/build/debugger-aware-wrapper.d.ts +37 -0
  77. package/build/debugger-aware-wrapper.d.ts.map +1 -0
  78. package/build/debugger-aware-wrapper.js +128 -0
  79. package/build/debugger-aware-wrapper.js.map +1 -0
  80. package/build/dom-change-monitor.d.ts +92 -0
  81. package/build/dom-change-monitor.d.ts.map +1 -0
  82. package/build/dom-change-monitor.js +437 -0
  83. package/build/dom-change-monitor.js.map +1 -0
  84. package/build/element-collector.d.ts +16 -0
  85. package/build/element-collector.d.ts.map +1 -0
  86. package/build/element-collector.js +207 -0
  87. package/build/element-collector.js.map +1 -0
  88. package/build/error-helpers.d.ts +43 -0
  89. package/build/error-helpers.d.ts.map +1 -0
  90. package/build/error-helpers.js +143 -0
  91. package/build/error-helpers.js.map +1 -0
  92. package/build/formatters/console-formatter.d.ts +160 -0
  93. package/build/formatters/console-formatter.d.ts.map +1 -0
  94. package/build/formatters/console-formatter.js +338 -0
  95. package/build/formatters/console-formatter.js.map +1 -0
  96. package/build/helpers/parser-plugins.d.ts +44 -0
  97. package/build/helpers/parser-plugins.d.ts.map +1 -0
  98. package/build/helpers/parser-plugins.js +102 -0
  99. package/build/helpers/parser-plugins.js.map +1 -0
  100. package/build/helpers/paths.d.ts +79 -0
  101. package/build/helpers/paths.d.ts.map +1 -0
  102. package/build/helpers/paths.js +168 -0
  103. package/build/helpers/paths.js.map +1 -0
  104. package/build/helpers/process-liveness.d.ts +24 -0
  105. package/build/helpers/process-liveness.d.ts.map +1 -0
  106. package/build/helpers/process-liveness.js +36 -0
  107. package/build/helpers/process-liveness.js.map +1 -0
  108. package/build/helpers/process-liveness.test.d.ts +2 -0
  109. package/build/helpers/process-liveness.test.d.ts.map +1 -0
  110. package/build/helpers/process-liveness.test.js +40 -0
  111. package/build/helpers/process-liveness.test.js.map +1 -0
  112. package/build/helpers/sequence-tree.d.ts +27 -0
  113. package/build/helpers/sequence-tree.d.ts.map +1 -0
  114. package/build/helpers/sequence-tree.js +80 -0
  115. package/build/helpers/sequence-tree.js.map +1 -0
  116. package/build/helpers/sequence-tree.test.d.ts +2 -0
  117. package/build/helpers/sequence-tree.test.d.ts.map +1 -0
  118. package/build/helpers/sequence-tree.test.js +122 -0
  119. package/build/helpers/sequence-tree.test.js.map +1 -0
  120. package/build/index.d.ts +3 -0
  121. package/build/index.d.ts.map +1 -0
  122. package/build/index.js +2007 -0
  123. package/build/index.js.map +1 -0
  124. package/build/interaction-recorder.d.ts +214 -0
  125. package/build/interaction-recorder.d.ts.map +1 -0
  126. package/build/interaction-recorder.js +2007 -0
  127. package/build/interaction-recorder.js.map +1 -0
  128. package/build/issue-tracker.d.ts +107 -0
  129. package/build/issue-tracker.d.ts.map +1 -0
  130. package/build/issue-tracker.js +582 -0
  131. package/build/issue-tracker.js.map +1 -0
  132. package/build/issue-tracker.test.d.ts +2 -0
  133. package/build/issue-tracker.test.d.ts.map +1 -0
  134. package/build/issue-tracker.test.js +88 -0
  135. package/build/issue-tracker.test.js.map +1 -0
  136. package/build/launch-chrome-port.test.d.ts +14 -0
  137. package/build/launch-chrome-port.test.d.ts.map +1 -0
  138. package/build/launch-chrome-port.test.js +97 -0
  139. package/build/launch-chrome-port.test.js.map +1 -0
  140. package/build/log-processor/orchestrator.d.ts +32 -0
  141. package/build/log-processor/orchestrator.d.ts.map +1 -0
  142. package/build/log-processor/orchestrator.js +27 -0
  143. package/build/log-processor/orchestrator.js.map +1 -0
  144. package/build/logpoint-execution-tracker.d.ts +57 -0
  145. package/build/logpoint-execution-tracker.d.ts.map +1 -0
  146. package/build/logpoint-execution-tracker.js +112 -0
  147. package/build/logpoint-execution-tracker.js.map +1 -0
  148. package/build/mcp-supervisor.d.ts +3 -0
  149. package/build/mcp-supervisor.d.ts.map +1 -0
  150. package/build/mcp-supervisor.js +211 -0
  151. package/build/mcp-supervisor.js.map +1 -0
  152. package/build/messages.d.ts +165 -0
  153. package/build/messages.d.ts.map +1 -0
  154. package/build/messages.js +574 -0
  155. package/build/messages.js.map +1 -0
  156. package/build/network-monitor.d.ts +172 -0
  157. package/build/network-monitor.d.ts.map +1 -0
  158. package/build/network-monitor.js +350 -0
  159. package/build/network-monitor.js.map +1 -0
  160. package/build/overlays.d.ts +49 -0
  161. package/build/overlays.d.ts.map +1 -0
  162. package/build/overlays.js +409 -0
  163. package/build/overlays.js.map +1 -0
  164. package/build/persistent-profiles.test.d.ts +15 -0
  165. package/build/persistent-profiles.test.d.ts.map +1 -0
  166. package/build/persistent-profiles.test.js +470 -0
  167. package/build/persistent-profiles.test.js.map +1 -0
  168. package/build/port-reserver.d.ts +32 -0
  169. package/build/port-reserver.d.ts.map +1 -0
  170. package/build/port-reserver.js +118 -0
  171. package/build/port-reserver.js.map +1 -0
  172. package/build/puppeteer-manager.d.ts +47 -0
  173. package/build/puppeteer-manager.d.ts.map +1 -0
  174. package/build/puppeteer-manager.js +116 -0
  175. package/build/puppeteer-manager.js.map +1 -0
  176. package/build/recording-coverage.test.d.ts +12 -0
  177. package/build/recording-coverage.test.d.ts.map +1 -0
  178. package/build/recording-coverage.test.js +72 -0
  179. package/build/recording-coverage.test.js.map +1 -0
  180. package/build/reference-validator.d.ts +47 -0
  181. package/build/reference-validator.d.ts.map +1 -0
  182. package/build/reference-validator.js +86 -0
  183. package/build/reference-validator.js.map +1 -0
  184. package/build/reference-validator.test.d.ts +5 -0
  185. package/build/reference-validator.test.d.ts.map +1 -0
  186. package/build/reference-validator.test.js +243 -0
  187. package/build/reference-validator.test.js.map +1 -0
  188. package/build/replay-cursor.d.ts +27 -0
  189. package/build/replay-cursor.d.ts.map +1 -0
  190. package/build/replay-cursor.js +240 -0
  191. package/build/replay-cursor.js.map +1 -0
  192. package/build/runners/docker-compose-runner.d.ts +69 -0
  193. package/build/runners/docker-compose-runner.d.ts.map +1 -0
  194. package/build/runners/docker-compose-runner.js +438 -0
  195. package/build/runners/docker-compose-runner.js.map +1 -0
  196. package/build/runners/docker-runner.d.ts +65 -0
  197. package/build/runners/docker-runner.d.ts.map +1 -0
  198. package/build/runners/docker-runner.js +414 -0
  199. package/build/runners/docker-runner.js.map +1 -0
  200. package/build/runners/index.d.ts +19 -0
  201. package/build/runners/index.d.ts.map +1 -0
  202. package/build/runners/index.js +35 -0
  203. package/build/runners/index.js.map +1 -0
  204. package/build/runners/native-runner.d.ts +95 -0
  205. package/build/runners/native-runner.d.ts.map +1 -0
  206. package/build/runners/native-runner.js +370 -0
  207. package/build/runners/native-runner.js.map +1 -0
  208. package/build/runners/types.d.ts +147 -0
  209. package/build/runners/types.d.ts.map +1 -0
  210. package/build/runners/types.js +25 -0
  211. package/build/runners/types.js.map +1 -0
  212. package/build/self-restart.d.ts +12 -0
  213. package/build/self-restart.d.ts.map +1 -0
  214. package/build/self-restart.js +41 -0
  215. package/build/self-restart.js.map +1 -0
  216. package/build/self-restart.test.d.ts +2 -0
  217. package/build/self-restart.test.d.ts.map +1 -0
  218. package/build/self-restart.test.js +34 -0
  219. package/build/self-restart.test.js.map +1 -0
  220. package/build/server-claims.d.ts +161 -0
  221. package/build/server-claims.d.ts.map +1 -0
  222. package/build/server-claims.js +382 -0
  223. package/build/server-claims.js.map +1 -0
  224. package/build/server-claims.test.d.ts +2 -0
  225. package/build/server-claims.test.d.ts.map +1 -0
  226. package/build/server-claims.test.js +197 -0
  227. package/build/server-claims.test.js.map +1 -0
  228. package/build/server-manager.d.ts +438 -0
  229. package/build/server-manager.d.ts.map +1 -0
  230. package/build/server-manager.js +1674 -0
  231. package/build/server-manager.js.map +1 -0
  232. package/build/server-manager.test.d.ts +17 -0
  233. package/build/server-manager.test.d.ts.map +1 -0
  234. package/build/server-manager.test.js +122 -0
  235. package/build/server-manager.test.js.map +1 -0
  236. package/build/server-ownership.test.d.ts +2 -0
  237. package/build/server-ownership.test.d.ts.map +1 -0
  238. package/build/server-ownership.test.js +238 -0
  239. package/build/server-ownership.test.js.map +1 -0
  240. package/build/server-watcher.d.ts +21 -0
  241. package/build/server-watcher.d.ts.map +1 -0
  242. package/build/server-watcher.js +105 -0
  243. package/build/server-watcher.js.map +1 -0
  244. package/build/session-detector.d.ts +47 -0
  245. package/build/session-detector.d.ts.map +1 -0
  246. package/build/session-detector.js +229 -0
  247. package/build/session-detector.js.map +1 -0
  248. package/build/session-detector.test.d.ts +9 -0
  249. package/build/session-detector.test.d.ts.map +1 -0
  250. package/build/session-detector.test.js +45 -0
  251. package/build/session-detector.test.js.map +1 -0
  252. package/build/sourcemap-handler.d.ts +114 -0
  253. package/build/sourcemap-handler.d.ts.map +1 -0
  254. package/build/sourcemap-handler.js +479 -0
  255. package/build/sourcemap-handler.js.map +1 -0
  256. package/build/supervisor/child-manager.d.ts +43 -0
  257. package/build/supervisor/child-manager.d.ts.map +1 -0
  258. package/build/supervisor/child-manager.js +148 -0
  259. package/build/supervisor/child-manager.js.map +1 -0
  260. package/build/supervisor/child-manager.test.d.ts +2 -0
  261. package/build/supervisor/child-manager.test.d.ts.map +1 -0
  262. package/build/supervisor/child-manager.test.js +107 -0
  263. package/build/supervisor/child-manager.test.js.map +1 -0
  264. package/build/supervisor/client-watcher.d.ts +48 -0
  265. package/build/supervisor/client-watcher.d.ts.map +1 -0
  266. package/build/supervisor/client-watcher.js +186 -0
  267. package/build/supervisor/client-watcher.js.map +1 -0
  268. package/build/supervisor/client-watcher.test.d.ts +2 -0
  269. package/build/supervisor/client-watcher.test.d.ts.map +1 -0
  270. package/build/supervisor/client-watcher.test.js +121 -0
  271. package/build/supervisor/client-watcher.test.js.map +1 -0
  272. package/build/supervisor/idle-config.d.ts +30 -0
  273. package/build/supervisor/idle-config.d.ts.map +1 -0
  274. package/build/supervisor/idle-config.js +76 -0
  275. package/build/supervisor/idle-config.js.map +1 -0
  276. package/build/supervisor/idle-config.test.d.ts +2 -0
  277. package/build/supervisor/idle-config.test.d.ts.map +1 -0
  278. package/build/supervisor/idle-config.test.js +119 -0
  279. package/build/supervisor/idle-config.test.js.map +1 -0
  280. package/build/supervisor/ndjson-reader.d.ts +47 -0
  281. package/build/supervisor/ndjson-reader.d.ts.map +1 -0
  282. package/build/supervisor/ndjson-reader.js +75 -0
  283. package/build/supervisor/ndjson-reader.js.map +1 -0
  284. package/build/supervisor/ndjson-reader.test.d.ts +2 -0
  285. package/build/supervisor/ndjson-reader.test.d.ts.map +1 -0
  286. package/build/supervisor/ndjson-reader.test.js +92 -0
  287. package/build/supervisor/ndjson-reader.test.js.map +1 -0
  288. package/build/supervisor/pidfile.d.ts +11 -0
  289. package/build/supervisor/pidfile.d.ts.map +1 -0
  290. package/build/supervisor/pidfile.js +29 -0
  291. package/build/supervisor/pidfile.js.map +1 -0
  292. package/build/supervisor/pidfile.test.d.ts +8 -0
  293. package/build/supervisor/pidfile.test.d.ts.map +1 -0
  294. package/build/supervisor/pidfile.test.js +46 -0
  295. package/build/supervisor/pidfile.test.js.map +1 -0
  296. package/build/supervisor/restart-coordinator.d.ts +73 -0
  297. package/build/supervisor/restart-coordinator.d.ts.map +1 -0
  298. package/build/supervisor/restart-coordinator.js +300 -0
  299. package/build/supervisor/restart-coordinator.js.map +1 -0
  300. package/build/supervisor/restart-coordinator.test.d.ts +2 -0
  301. package/build/supervisor/restart-coordinator.test.d.ts.map +1 -0
  302. package/build/supervisor/restart-coordinator.test.js +434 -0
  303. package/build/supervisor/restart-coordinator.test.js.map +1 -0
  304. package/build/test-support/fake-execute-tool-call.d.ts +11 -0
  305. package/build/test-support/fake-execute-tool-call.d.ts.map +1 -0
  306. package/build/test-support/fake-execute-tool-call.js +19 -0
  307. package/build/test-support/fake-execute-tool-call.js.map +1 -0
  308. package/build/tool-error.d.ts +14 -0
  309. package/build/tool-error.d.ts.map +1 -0
  310. package/build/tool-error.js +18 -0
  311. package/build/tool-error.js.map +1 -0
  312. package/build/tool-response.d.ts +293 -0
  313. package/build/tool-response.d.ts.map +1 -0
  314. package/build/tool-response.js +365 -0
  315. package/build/tool-response.js.map +1 -0
  316. package/build/tools/assert-tools.d.ts +62 -0
  317. package/build/tools/assert-tools.d.ts.map +1 -0
  318. package/build/tools/assert-tools.js +330 -0
  319. package/build/tools/assert-tools.js.map +1 -0
  320. package/build/tools/breakpoint-await-abort.test.d.ts +2 -0
  321. package/build/tools/breakpoint-await-abort.test.d.ts.map +1 -0
  322. package/build/tools/breakpoint-await-abort.test.js +124 -0
  323. package/build/tools/breakpoint-await-abort.test.js.map +1 -0
  324. package/build/tools/breakpoint-tools.d.ts +99 -0
  325. package/build/tools/breakpoint-tools.d.ts.map +1 -0
  326. package/build/tools/breakpoint-tools.js +1197 -0
  327. package/build/tools/breakpoint-tools.js.map +1 -0
  328. package/build/tools/config-tools.d.ts +58 -0
  329. package/build/tools/config-tools.d.ts.map +1 -0
  330. package/build/tools/config-tools.js +180 -0
  331. package/build/tools/config-tools.js.map +1 -0
  332. package/build/tools/connection-routing.test.d.ts +12 -0
  333. package/build/tools/connection-routing.test.d.ts.map +1 -0
  334. package/build/tools/connection-routing.test.js +206 -0
  335. package/build/tools/connection-routing.test.js.map +1 -0
  336. package/build/tools/console-tools.d.ts +84 -0
  337. package/build/tools/console-tools.d.ts.map +1 -0
  338. package/build/tools/console-tools.js +241 -0
  339. package/build/tools/console-tools.js.map +1 -0
  340. package/build/tools/content-tools.d.ts +74 -0
  341. package/build/tools/content-tools.d.ts.map +1 -0
  342. package/build/tools/content-tools.js +582 -0
  343. package/build/tools/content-tools.js.map +1 -0
  344. package/build/tools/dashboard-tools.d.ts +38 -0
  345. package/build/tools/dashboard-tools.d.ts.map +1 -0
  346. package/build/tools/dashboard-tools.js +123 -0
  347. package/build/tools/dashboard-tools.js.map +1 -0
  348. package/build/tools/dom-tools.d.ts +41 -0
  349. package/build/tools/dom-tools.d.ts.map +1 -0
  350. package/build/tools/dom-tools.js +277 -0
  351. package/build/tools/dom-tools.js.map +1 -0
  352. package/build/tools/download-tools.d.ts +55 -0
  353. package/build/tools/download-tools.d.ts.map +1 -0
  354. package/build/tools/download-tools.js +280 -0
  355. package/build/tools/download-tools.js.map +1 -0
  356. package/build/tools/execution-tools.d.ts +40 -0
  357. package/build/tools/execution-tools.d.ts.map +1 -0
  358. package/build/tools/execution-tools.js +121 -0
  359. package/build/tools/execution-tools.js.map +1 -0
  360. package/build/tools/input-abort.test.d.ts +2 -0
  361. package/build/tools/input-abort.test.d.ts.map +1 -0
  362. package/build/tools/input-abort.test.js +227 -0
  363. package/build/tools/input-abort.test.js.map +1 -0
  364. package/build/tools/input-ambiguous-selector.test.d.ts +2 -0
  365. package/build/tools/input-ambiguous-selector.test.d.ts.map +1 -0
  366. package/build/tools/input-ambiguous-selector.test.js +34 -0
  367. package/build/tools/input-ambiguous-selector.test.js.map +1 -0
  368. package/build/tools/input-tools.d.ts +168 -0
  369. package/build/tools/input-tools.d.ts.map +1 -0
  370. package/build/tools/input-tools.js +1397 -0
  371. package/build/tools/input-tools.js.map +1 -0
  372. package/build/tools/inspection-tools.d.ts +112 -0
  373. package/build/tools/inspection-tools.d.ts.map +1 -0
  374. package/build/tools/inspection-tools.js +637 -0
  375. package/build/tools/inspection-tools.js.map +1 -0
  376. package/build/tools/interpolation.d.ts +30 -0
  377. package/build/tools/interpolation.d.ts.map +1 -0
  378. package/build/tools/interpolation.js +143 -0
  379. package/build/tools/interpolation.js.map +1 -0
  380. package/build/tools/issues-abort-hygiene.test.d.ts +2 -0
  381. package/build/tools/issues-abort-hygiene.test.d.ts.map +1 -0
  382. package/build/tools/issues-abort-hygiene.test.js +79 -0
  383. package/build/tools/issues-abort-hygiene.test.js.map +1 -0
  384. package/build/tools/issues-tools.d.ts +98 -0
  385. package/build/tools/issues-tools.d.ts.map +1 -0
  386. package/build/tools/issues-tools.js +821 -0
  387. package/build/tools/issues-tools.js.map +1 -0
  388. package/build/tools/issues-tools.test.d.ts +8 -0
  389. package/build/tools/issues-tools.test.d.ts.map +1 -0
  390. package/build/tools/issues-tools.test.js +184 -0
  391. package/build/tools/issues-tools.test.js.map +1 -0
  392. package/build/tools/modal-tools.d.ts +73 -0
  393. package/build/tools/modal-tools.d.ts.map +1 -0
  394. package/build/tools/modal-tools.js +161 -0
  395. package/build/tools/modal-tools.js.map +1 -0
  396. package/build/tools/network-tools.d.ts +72 -0
  397. package/build/tools/network-tools.d.ts.map +1 -0
  398. package/build/tools/network-tools.js +432 -0
  399. package/build/tools/network-tools.js.map +1 -0
  400. package/build/tools/page-tools.d.ts +78 -0
  401. package/build/tools/page-tools.d.ts.map +1 -0
  402. package/build/tools/page-tools.js +255 -0
  403. package/build/tools/page-tools.js.map +1 -0
  404. package/build/tools/plugin-tools.d.ts +16 -0
  405. package/build/tools/plugin-tools.d.ts.map +1 -0
  406. package/build/tools/plugin-tools.js +17 -0
  407. package/build/tools/plugin-tools.js.map +1 -0
  408. package/build/tools/replay-abort.test.d.ts +2 -0
  409. package/build/tools/replay-abort.test.d.ts.map +1 -0
  410. package/build/tools/replay-abort.test.js +259 -0
  411. package/build/tools/replay-abort.test.js.map +1 -0
  412. package/build/tools/replay-add-conditional.test.d.ts +2 -0
  413. package/build/tools/replay-add-conditional.test.d.ts.map +1 -0
  414. package/build/tools/replay-add-conditional.test.js +205 -0
  415. package/build/tools/replay-add-conditional.test.js.map +1 -0
  416. package/build/tools/replay-background-run.test.d.ts +2 -0
  417. package/build/tools/replay-background-run.test.d.ts.map +1 -0
  418. package/build/tools/replay-background-run.test.js +176 -0
  419. package/build/tools/replay-background-run.test.js.map +1 -0
  420. package/build/tools/replay-capture-variables.test.d.ts +2 -0
  421. package/build/tools/replay-capture-variables.test.d.ts.map +1 -0
  422. package/build/tools/replay-capture-variables.test.js +331 -0
  423. package/build/tools/replay-capture-variables.test.js.map +1 -0
  424. package/build/tools/replay-conditional-idb-and-launch.test.d.ts +11 -0
  425. package/build/tools/replay-conditional-idb-and-launch.test.d.ts.map +1 -0
  426. package/build/tools/replay-conditional-idb-and-launch.test.js +429 -0
  427. package/build/tools/replay-conditional-idb-and-launch.test.js.map +1 -0
  428. package/build/tools/replay-conditional-timeout.test.d.ts +14 -0
  429. package/build/tools/replay-conditional-timeout.test.d.ts.map +1 -0
  430. package/build/tools/replay-conditional-timeout.test.js +126 -0
  431. package/build/tools/replay-conditional-timeout.test.js.map +1 -0
  432. package/build/tools/replay-declare.test.d.ts +2 -0
  433. package/build/tools/replay-declare.test.d.ts.map +1 -0
  434. package/build/tools/replay-declare.test.js +106 -0
  435. package/build/tools/replay-declare.test.js.map +1 -0
  436. package/build/tools/replay-declared-browser-cleanup.test.d.ts +2 -0
  437. package/build/tools/replay-declared-browser-cleanup.test.d.ts.map +1 -0
  438. package/build/tools/replay-declared-browser-cleanup.test.js +103 -0
  439. package/build/tools/replay-declared-browser-cleanup.test.js.map +1 -0
  440. package/build/tools/replay-declared-profile.test.d.ts +2 -0
  441. package/build/tools/replay-declared-profile.test.d.ts.map +1 -0
  442. package/build/tools/replay-declared-profile.test.js +116 -0
  443. package/build/tools/replay-declared-profile.test.js.map +1 -0
  444. package/build/tools/replay-executor.d.ts +556 -0
  445. package/build/tools/replay-executor.d.ts.map +1 -0
  446. package/build/tools/replay-executor.js +2347 -0
  447. package/build/tools/replay-executor.js.map +1 -0
  448. package/build/tools/replay-folder-save.test.d.ts +2 -0
  449. package/build/tools/replay-folder-save.test.d.ts.map +1 -0
  450. package/build/tools/replay-folder-save.test.js +63 -0
  451. package/build/tools/replay-folder-save.test.js.map +1 -0
  452. package/build/tools/replay-formatters.d.ts +148 -0
  453. package/build/tools/replay-formatters.d.ts.map +1 -0
  454. package/build/tools/replay-formatters.js +856 -0
  455. package/build/tools/replay-formatters.js.map +1 -0
  456. package/build/tools/replay-kill-chrome-on-finish.test.d.ts +2 -0
  457. package/build/tools/replay-kill-chrome-on-finish.test.d.ts.map +1 -0
  458. package/build/tools/replay-kill-chrome-on-finish.test.js +175 -0
  459. package/build/tools/replay-kill-chrome-on-finish.test.js.map +1 -0
  460. package/build/tools/replay-multi-connection.test.d.ts +2 -0
  461. package/build/tools/replay-multi-connection.test.d.ts.map +1 -0
  462. package/build/tools/replay-multi-connection.test.js +547 -0
  463. package/build/tools/replay-multi-connection.test.js.map +1 -0
  464. package/build/tools/replay-output-and-record-options.test.d.ts +2 -0
  465. package/build/tools/replay-output-and-record-options.test.d.ts.map +1 -0
  466. package/build/tools/replay-output-and-record-options.test.js +240 -0
  467. package/build/tools/replay-output-and-record-options.test.js.map +1 -0
  468. package/build/tools/replay-production-error-shape.test.d.ts +7 -0
  469. package/build/tools/replay-production-error-shape.test.d.ts.map +1 -0
  470. package/build/tools/replay-production-error-shape.test.js +93 -0
  471. package/build/tools/replay-production-error-shape.test.js.map +1 -0
  472. package/build/tools/replay-rebase.test.d.ts +2 -0
  473. package/build/tools/replay-rebase.test.d.ts.map +1 -0
  474. package/build/tools/replay-rebase.test.js +58 -0
  475. package/build/tools/replay-rebase.test.js.map +1 -0
  476. package/build/tools/replay-retry-classification.test.d.ts +9 -0
  477. package/build/tools/replay-retry-classification.test.d.ts.map +1 -0
  478. package/build/tools/replay-retry-classification.test.js +57 -0
  479. package/build/tools/replay-retry-classification.test.js.map +1 -0
  480. package/build/tools/replay-run-registry.d.ts +55 -0
  481. package/build/tools/replay-run-registry.d.ts.map +1 -0
  482. package/build/tools/replay-run-registry.js +65 -0
  483. package/build/tools/replay-run-registry.js.map +1 -0
  484. package/build/tools/replay-sequence-reload.test.d.ts +2 -0
  485. package/build/tools/replay-sequence-reload.test.d.ts.map +1 -0
  486. package/build/tools/replay-sequence-reload.test.js +82 -0
  487. package/build/tools/replay-sequence-reload.test.js.map +1 -0
  488. package/build/tools/replay-socket-scope.test.d.ts +2 -0
  489. package/build/tools/replay-socket-scope.test.d.ts.map +1 -0
  490. package/build/tools/replay-socket-scope.test.js +75 -0
  491. package/build/tools/replay-socket-scope.test.js.map +1 -0
  492. package/build/tools/replay-step-connection.test.d.ts +2 -0
  493. package/build/tools/replay-step-connection.test.d.ts.map +1 -0
  494. package/build/tools/replay-step-connection.test.js +292 -0
  495. package/build/tools/replay-step-connection.test.js.map +1 -0
  496. package/build/tools/replay-step-timeout.test.d.ts +2 -0
  497. package/build/tools/replay-step-timeout.test.d.ts.map +1 -0
  498. package/build/tools/replay-step-timeout.test.js +204 -0
  499. package/build/tools/replay-step-timeout.test.js.map +1 -0
  500. package/build/tools/replay-tags.test.d.ts +2 -0
  501. package/build/tools/replay-tags.test.d.ts.map +1 -0
  502. package/build/tools/replay-tags.test.js +99 -0
  503. package/build/tools/replay-tags.test.js.map +1 -0
  504. package/build/tools/replay-teardown-and-foreach.test.d.ts +13 -0
  505. package/build/tools/replay-teardown-and-foreach.test.d.ts.map +1 -0
  506. package/build/tools/replay-teardown-and-foreach.test.js +318 -0
  507. package/build/tools/replay-teardown-and-foreach.test.js.map +1 -0
  508. package/build/tools/replay-tool-name-validation.test.d.ts +2 -0
  509. package/build/tools/replay-tool-name-validation.test.d.ts.map +1 -0
  510. package/build/tools/replay-tool-name-validation.test.js +280 -0
  511. package/build/tools/replay-tool-name-validation.test.js.map +1 -0
  512. package/build/tools/replay-tools.d.ts +288 -0
  513. package/build/tools/replay-tools.d.ts.map +1 -0
  514. package/build/tools/replay-tools.js +2994 -0
  515. package/build/tools/replay-tools.js.map +1 -0
  516. package/build/tools/request-abort.test.d.ts +2 -0
  517. package/build/tools/request-abort.test.d.ts.map +1 -0
  518. package/build/tools/request-abort.test.js +130 -0
  519. package/build/tools/request-abort.test.js.map +1 -0
  520. package/build/tools/request-tools.d.ts +56 -0
  521. package/build/tools/request-tools.d.ts.map +1 -0
  522. package/build/tools/request-tools.js +174 -0
  523. package/build/tools/request-tools.js.map +1 -0
  524. package/build/tools/screenshot-tools.d.ts +135 -0
  525. package/build/tools/screenshot-tools.d.ts.map +1 -0
  526. package/build/tools/screenshot-tools.js +636 -0
  527. package/build/tools/screenshot-tools.js.map +1 -0
  528. package/build/tools/server-tools.d.ts +88 -0
  529. package/build/tools/server-tools.d.ts.map +1 -0
  530. package/build/tools/server-tools.js +485 -0
  531. package/build/tools/server-tools.js.map +1 -0
  532. package/build/tools/source-tools.d.ts +46 -0
  533. package/build/tools/source-tools.d.ts.map +1 -0
  534. package/build/tools/source-tools.js +47 -0
  535. package/build/tools/source-tools.js.map +1 -0
  536. package/build/tools/storage-tools.d.ts +141 -0
  537. package/build/tools/storage-tools.d.ts.map +1 -0
  538. package/build/tools/storage-tools.js +894 -0
  539. package/build/tools/storage-tools.js.map +1 -0
  540. package/build/tools/storage-tools.test.d.ts +13 -0
  541. package/build/tools/storage-tools.test.d.ts.map +1 -0
  542. package/build/tools/storage-tools.test.js +707 -0
  543. package/build/tools/storage-tools.test.js.map +1 -0
  544. package/build/tools/tab-tools.d.ts +42 -0
  545. package/build/tools/tab-tools.d.ts.map +1 -0
  546. package/build/tools/tab-tools.js +296 -0
  547. package/build/tools/tab-tools.js.map +1 -0
  548. package/build/tools/wait-tools.d.ts +85 -0
  549. package/build/tools/wait-tools.d.ts.map +1 -0
  550. package/build/tools/wait-tools.js +236 -0
  551. package/build/tools/wait-tools.js.map +1 -0
  552. package/build/tools/wait-tools.test.d.ts +9 -0
  553. package/build/tools/wait-tools.test.d.ts.map +1 -0
  554. package/build/tools/wait-tools.test.js +193 -0
  555. package/build/tools/wait-tools.test.js.map +1 -0
  556. package/build/types.d.ts +133 -0
  557. package/build/types.d.ts.map +1 -0
  558. package/build/types.js +5 -0
  559. package/build/types.js.map +1 -0
  560. package/build/ui-verifier.d.ts +88 -0
  561. package/build/ui-verifier.d.ts.map +1 -0
  562. package/build/ui-verifier.js +540 -0
  563. package/build/ui-verifier.js.map +1 -0
  564. package/build/utils/abort.d.ts +68 -0
  565. package/build/utils/abort.d.ts.map +1 -0
  566. package/build/utils/abort.js +159 -0
  567. package/build/utils/abort.js.map +1 -0
  568. package/build/utils/abort.test.d.ts +2 -0
  569. package/build/utils/abort.test.d.ts.map +1 -0
  570. package/build/utils/abort.test.js +174 -0
  571. package/build/utils/abort.test.js.map +1 -0
  572. package/build/utils/modal-detection-core.d.ts +150 -0
  573. package/build/utils/modal-detection-core.d.ts.map +1 -0
  574. package/build/utils/modal-detection-core.js +332 -0
  575. package/build/utils/modal-detection-core.js.map +1 -0
  576. package/build/utils/modal-detection-core.test.d.ts +5 -0
  577. package/build/utils/modal-detection-core.test.d.ts.map +1 -0
  578. package/build/utils/modal-detection-core.test.js +492 -0
  579. package/build/utils/modal-detection-core.test.js.map +1 -0
  580. package/build/utils/modal-detector.d.ts +29 -0
  581. package/build/utils/modal-detector.d.ts.map +1 -0
  582. package/build/utils/modal-detector.js +281 -0
  583. package/build/utils/modal-detector.js.map +1 -0
  584. package/build/utils/modal-dismissal.d.ts +32 -0
  585. package/build/utils/modal-dismissal.d.ts.map +1 -0
  586. package/build/utils/modal-dismissal.js +190 -0
  587. package/build/utils/modal-dismissal.js.map +1 -0
  588. package/build/utils/port-check.d.ts +31 -0
  589. package/build/utils/port-check.d.ts.map +1 -0
  590. package/build/utils/port-check.js +87 -0
  591. package/build/utils/port-check.js.map +1 -0
  592. package/build/utils/port-check.test.d.ts +2 -0
  593. package/build/utils/port-check.test.d.ts.map +1 -0
  594. package/build/utils/port-check.test.js +66 -0
  595. package/build/utils/port-check.test.js.map +1 -0
  596. package/build/utils/selector-resolver.d.ts +66 -0
  597. package/build/utils/selector-resolver.d.ts.map +1 -0
  598. package/build/utils/selector-resolver.js +200 -0
  599. package/build/utils/selector-resolver.js.map +1 -0
  600. package/build/validation-helpers.d.ts +41 -0
  601. package/build/validation-helpers.d.ts.map +1 -0
  602. package/build/validation-helpers.js +236 -0
  603. package/build/validation-helpers.js.map +1 -0
  604. package/build/window-sizing.d.ts +26 -0
  605. package/build/window-sizing.d.ts.map +1 -0
  606. package/build/window-sizing.js +87 -0
  607. package/build/window-sizing.js.map +1 -0
  608. package/build/window-sizing.test.d.ts +8 -0
  609. package/build/window-sizing.test.d.ts.map +1 -0
  610. package/build/window-sizing.test.js +113 -0
  611. package/build/window-sizing.test.js.map +1 -0
  612. package/docs/README.md +74 -0
  613. package/docs/automation.md +562 -0
  614. package/docs/debugging.md +229 -0
  615. package/docs/installation.md +133 -0
  616. package/docs/instructions.md +227 -0
  617. package/docs/mcp-instructions.md +15 -0
  618. package/docs/message-system.md +327 -0
  619. package/docs/messages.md +3480 -0
  620. package/docs/parser-plugins.md +132 -0
  621. package/docs/replay.md +1411 -0
  622. package/docs/troubleshooting.md +272 -0
  623. package/docs/working_with_llm_notes.md +5 -0
  624. package/package.json +87 -0
  625. package/skills/devharness/SKILL.md +195 -0
  626. package/skills/devharness/references/sequences.md +613 -0
  627. package/skills/devharness/references/tool-categories.md +99 -0
@@ -0,0 +1,707 @@
1
+ /**
2
+ * Tests for feature-011 (IndexedDB read/write actions) and feature-012
3
+ * (sessionStorage get/set + single-key delete) in the `storage` tool.
4
+ *
5
+ * The interesting half is `describeStructuredValue`: IndexedDB holds
6
+ * structured-clone values, and the ones worth asserting on (a non-extractable
7
+ * CryptoKey, a Blob, an ArrayBuffer) all JSON-serialize to `{}`. The serializer
8
+ * is deliberately written as a pure, self-contained function so it can be
9
+ * exercised here without a browser - which is also what makes it safe to
10
+ * stringify and eval inside the page.
11
+ */
12
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
13
+ import { createStorageTools, describeStructuredValue } from './storage-tools.js';
14
+ // ---------------------------------------------------------------------------
15
+ // describeStructuredValue
16
+ // ---------------------------------------------------------------------------
17
+ describe('describeStructuredValue - primitives', () => {
18
+ it('passes JSON-expressible primitives through untouched', () => {
19
+ expect(describeStructuredValue('hi')).toBe('hi');
20
+ expect(describeStructuredValue(42)).toBe(42);
21
+ expect(describeStructuredValue(0)).toBe(0);
22
+ expect(describeStructuredValue(false)).toBe(false);
23
+ expect(describeStructuredValue(null)).toBeNull();
24
+ });
25
+ it('keeps values JSON would silently drop or corrupt', () => {
26
+ expect(describeStructuredValue(undefined)).toEqual({ __type: 'Undefined' });
27
+ expect(describeStructuredValue(NaN)).toEqual({ __type: 'Number', value: 'NaN' });
28
+ expect(describeStructuredValue(Infinity)).toEqual({ __type: 'Number', value: 'Infinity' });
29
+ expect(describeStructuredValue(-Infinity)).toEqual({ __type: 'Number', value: '-Infinity' });
30
+ expect(describeStructuredValue(10n ** 30n)).toEqual({ __type: 'BigInt', value: '1000000000000000000000000000000' });
31
+ expect(describeStructuredValue(Symbol('tok'))).toEqual({ __type: 'Symbol', description: 'Symbol(tok)' });
32
+ expect(describeStructuredValue(function named() { })).toEqual({ __type: 'Function', name: 'named' });
33
+ });
34
+ it('does not lose an undefined property inside an object', () => {
35
+ // JSON.stringify({a: undefined}) is '{}' - the key vanishes entirely.
36
+ expect(describeStructuredValue({ a: undefined, b: 1 })).toEqual({ a: { __type: 'Undefined' }, b: 1 });
37
+ });
38
+ });
39
+ describe('describeStructuredValue - built-ins', () => {
40
+ it('describes Date, including an invalid one', () => {
41
+ const d = new Date('2026-07-26T02:30:36.000Z');
42
+ expect(describeStructuredValue(d)).toEqual({
43
+ __type: 'Date',
44
+ iso: '2026-07-26T02:30:36.000Z',
45
+ time: d.getTime(),
46
+ });
47
+ expect(describeStructuredValue(new Date('nope'))).toEqual({ __type: 'Date', iso: null, time: null });
48
+ });
49
+ it('describes RegExp and Error', () => {
50
+ expect(describeStructuredValue(/ab+c/gi)).toEqual({ __type: 'RegExp', source: 'ab+c', flags: 'gi' });
51
+ expect(describeStructuredValue(new TypeError('bad'))).toEqual({ __type: 'Error', name: 'TypeError', message: 'bad' });
52
+ });
53
+ it('describes Map, including non-string keys', () => {
54
+ const m = new Map([
55
+ ['a', 1],
56
+ [{ id: 7 }, new Date(0)],
57
+ ]);
58
+ expect(describeStructuredValue(m)).toEqual({
59
+ __type: 'Map',
60
+ size: 2,
61
+ entries: [
62
+ ['a', 1],
63
+ [{ id: 7 }, { __type: 'Date', iso: '1970-01-01T00:00:00.000Z', time: 0 }],
64
+ ],
65
+ });
66
+ });
67
+ it('describes Set, including nested collections', () => {
68
+ expect(describeStructuredValue(new Set([1, 'two', new Set([3])]))).toEqual({
69
+ __type: 'Set',
70
+ size: 3,
71
+ values: [1, 'two', { __type: 'Set', size: 1, values: [3] }],
72
+ });
73
+ });
74
+ it('describes Blob and File without reading their bytes', () => {
75
+ const blob = new Blob(['hello'], { type: 'text/plain' });
76
+ expect(describeStructuredValue(blob)).toEqual({ __type: 'Blob', size: 5, mimeType: 'text/plain' });
77
+ const file = new File(['abc'], 'note.txt', { type: 'text/plain', lastModified: 1234 });
78
+ expect(describeStructuredValue(file)).toEqual({
79
+ __type: 'File',
80
+ name: 'note.txt',
81
+ size: 3,
82
+ mimeType: 'text/plain',
83
+ lastModified: 1234,
84
+ });
85
+ });
86
+ it('describes ArrayBuffer, typed arrays and DataView', () => {
87
+ expect(describeStructuredValue(new ArrayBuffer(8))).toEqual({ __type: 'ArrayBuffer', byteLength: 8 });
88
+ expect(describeStructuredValue(new Uint8Array([1, 2, 3]))).toEqual({
89
+ __type: 'Uint8Array',
90
+ byteLength: 3,
91
+ byteOffset: 0,
92
+ length: 3,
93
+ preview: [1, 2, 3],
94
+ });
95
+ const big = describeStructuredValue(new Uint8Array(40).fill(9));
96
+ expect(big.length).toBe(40);
97
+ expect(big.preview).toHaveLength(16);
98
+ expect(big.previewTruncated).toBe(true);
99
+ const view = describeStructuredValue(new DataView(new ArrayBuffer(16), 4));
100
+ expect(view).toEqual({ __type: 'DataView', byteLength: 12, byteOffset: 4 });
101
+ expect(view.preview).toBeUndefined();
102
+ });
103
+ it('stringifies BigInt64Array previews so the result stays JSON-safe', () => {
104
+ const out = describeStructuredValue(new BigInt64Array([1n, 2n]));
105
+ expect(out.preview).toEqual(['1', '2']);
106
+ expect(() => JSON.stringify(out)).not.toThrow();
107
+ });
108
+ });
109
+ describe('describeStructuredValue - CryptoKey (the motivating case)', () => {
110
+ it('reports a real non-extractable key instead of {}', async () => {
111
+ const pair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']);
112
+ // What we are replacing: JSON sees nothing at all.
113
+ expect(JSON.stringify(pair.privateKey)).toBe('{}');
114
+ const out = describeStructuredValue(pair.privateKey);
115
+ expect(out.__type).toBe('CryptoKey');
116
+ expect(out.keyType).toBe('private');
117
+ expect(out.extractable).toBe(false);
118
+ expect(out.usages).toEqual(['sign']);
119
+ expect(out.algorithm).toMatchObject({ name: 'ECDSA', namedCurve: 'P-256' });
120
+ });
121
+ it('finds a key nested inside a stored record, alongside ordinary fields', async () => {
122
+ const pair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']);
123
+ const record = { id: 'device-1', createdAt: new Date(0), keys: { signing: pair.privateKey } };
124
+ const out = describeStructuredValue(record);
125
+ expect(out.id).toBe('device-1');
126
+ expect(out.createdAt.__type).toBe('Date');
127
+ expect(out.keys.signing).toMatchObject({ __type: 'CryptoKey', keyType: 'private', extractable: false });
128
+ });
129
+ it('recognises a cross-realm key by constructor name, without instanceof', () => {
130
+ // A value handed back from another realm fails `instanceof` even when it is
131
+ // genuinely a CryptoKey, so the constructor name is checked too.
132
+ class CryptoKey {
133
+ type = 'secret';
134
+ extractable = true;
135
+ usages = ['encrypt'];
136
+ algorithm = { name: 'AES-GCM', length: 256 };
137
+ }
138
+ expect(describeStructuredValue(new CryptoKey())).toEqual({
139
+ __type: 'CryptoKey',
140
+ keyType: 'secret',
141
+ extractable: true,
142
+ usages: ['encrypt'],
143
+ algorithm: { name: 'AES-GCM', length: 256 },
144
+ });
145
+ });
146
+ });
147
+ describe('describeStructuredValue - cycles, depth and size', () => {
148
+ it('reports a self-reference as circular with its path', () => {
149
+ const o = { name: 'root' };
150
+ o.self = o;
151
+ expect(describeStructuredValue(o)).toEqual({ name: 'root', self: { __type: 'Circular', path: '$' } });
152
+ });
153
+ it('reports a deep back-reference with the path of the ancestor', () => {
154
+ const root = { a: { b: {} } };
155
+ root.a.b.up = root.a;
156
+ expect(describeStructuredValue(root).a.b.up).toEqual({ __type: 'Circular', path: '$.a' });
157
+ });
158
+ it('handles cycles through arrays, Maps and Sets', () => {
159
+ const arr = [1];
160
+ arr.push(arr);
161
+ expect(describeStructuredValue(arr)[1]).toEqual({ __type: 'Circular', path: '$' });
162
+ const m = new Map();
163
+ m.set('self', m);
164
+ expect(describeStructuredValue(m).entries[0][1]).toEqual({ __type: 'Circular', path: '$' });
165
+ const s = new Set();
166
+ s.add(s);
167
+ expect(describeStructuredValue(s).values[0]).toEqual({ __type: 'Circular', path: '$' });
168
+ });
169
+ it('treats a shared reference as a DAG, not a cycle', () => {
170
+ const shared = { v: 1 };
171
+ const out = describeStructuredValue({ a: shared, b: shared });
172
+ expect(out.a).toEqual({ v: 1 });
173
+ expect(out.b).toEqual({ v: 1 });
174
+ });
175
+ it('never throws or recurses forever on a mutually cyclic pair', () => {
176
+ const a = { name: 'a' };
177
+ const b = { name: 'b', a };
178
+ a.b = b;
179
+ expect(() => JSON.stringify(describeStructuredValue(a))).not.toThrow();
180
+ });
181
+ it('stops at maxDepth', () => {
182
+ const deep = { l1: { l2: { l3: { l4: 'bottom' } } } };
183
+ expect(describeStructuredValue(deep, { maxDepth: 2 })).toEqual({
184
+ l1: { l2: { l3: { __type: 'MaxDepth', className: 'Object' } } },
185
+ });
186
+ // The default is deep enough for a realistically nested record.
187
+ expect(describeStructuredValue(deep).l1.l2.l3.l4).toBe('bottom');
188
+ });
189
+ it('truncates oversized arrays, objects, Maps and Sets', () => {
190
+ const arr = describeStructuredValue([1, 2, 3, 4, 5], { maxItems: 2 });
191
+ expect(arr).toEqual([1, 2, { __type: 'Truncated', omitted: 3 }]);
192
+ const obj = describeStructuredValue({ a: 1, b: 2, c: 3 }, { maxItems: 2 });
193
+ expect(obj).toEqual({ a: 1, b: 2, __truncated: 1 });
194
+ const map = describeStructuredValue(new Map([['a', 1], ['b', 2]]), { maxItems: 1 });
195
+ expect(map).toEqual({ __type: 'Map', size: 2, entries: [['a', 1]], truncated: true });
196
+ const set = describeStructuredValue(new Set([1, 2]), { maxItems: 1 });
197
+ expect(set).toEqual({ __type: 'Set', size: 2, values: [1], truncated: true });
198
+ });
199
+ });
200
+ describe('describeStructuredValue - total budgets', () => {
201
+ /**
202
+ * The bug this covers: maxItems is per container and maxDepth is per path, so
203
+ * they multiply. A 500-wide array whose every element points at the *same*
204
+ * 500-wide array (deliberately re-serialized, since a shared reference is a
205
+ * DAG and not a cycle) is 500^6 visited nodes at the default depth - enough to
206
+ * pin the page's main thread until the protocol call times out. A large
207
+ * normalized cache with shared sub-objects gets there without malice.
208
+ */
209
+ function compoundingSharedStructure(levels, width) {
210
+ let level = { leaf: true };
211
+ for (let i = 0; i < levels; i++) {
212
+ const shared = level;
213
+ const next = [];
214
+ for (let j = 0; j < width; j++)
215
+ next.push(shared);
216
+ level = next;
217
+ }
218
+ return level;
219
+ }
220
+ it('stops a compounding shared-reference structure in bounded time', () => {
221
+ // 500^6 if the per-level caps were the only bound.
222
+ const value = compoundingSharedStructure(6, 500);
223
+ const started = Date.now();
224
+ const out = describeStructuredValue(value);
225
+ const elapsed = Date.now() - started;
226
+ expect(elapsed).toBeLessThan(2000);
227
+ expect(out.__budgetExceeded ?? out[out.length - 1]?.limit).toBeTruthy();
228
+ // The budget, not luck, is what stopped it: the emitted node count is at
229
+ // most the cap, and the marker names which budget tripped.
230
+ const flat = JSON.stringify(out);
231
+ expect(flat.length).toBeLessThan(2_000_000);
232
+ expect(flat).toContain('"__type":"BudgetExceeded"');
233
+ expect(flat).toContain('maxNodes');
234
+ });
235
+ it('honours a lowered node budget exactly, and marks where it stopped', () => {
236
+ const value = compoundingSharedStructure(6, 10);
237
+ const out = describeStructuredValue(value, { maxNodes: 50 });
238
+ const flat = JSON.stringify(out);
239
+ // 50 nodes visited => far fewer than 10^6 BudgetExceeded-free values.
240
+ expect((flat.match(/"BudgetExceeded"/g) || []).length).toBeGreaterThan(0);
241
+ expect(flat).toContain('"limit":"maxNodes"');
242
+ });
243
+ it('trips the total character budget on many mid-sized strings', () => {
244
+ const chunk = 'x'.repeat(1000);
245
+ const value = {};
246
+ for (let i = 0; i < 200; i++)
247
+ value['k' + i] = chunk;
248
+ const out = describeStructuredValue(value, { maxTotalChars: 5000 });
249
+ expect(out.__budgetExceeded).toBe('maxTotalChars');
250
+ expect(JSON.stringify(out).length).toBeLessThan(50_000);
251
+ });
252
+ it('leaves an ordinary record completely untouched and unflagged', () => {
253
+ const record = { id: 'a', nested: { list: [1, 2, 3], when: new Date(0) } };
254
+ const out = describeStructuredValue(record);
255
+ expect(out.__budgetExceeded).toBeUndefined();
256
+ expect(JSON.stringify(out)).not.toContain('BudgetExceeded');
257
+ expect(out.nested.list).toEqual([1, 2, 3]);
258
+ });
259
+ it('caps a single huge string and reports its real length', () => {
260
+ const huge = 'a'.repeat(50_000);
261
+ const out = describeStructuredValue({ blobText: huge });
262
+ expect(out.blobText.__type).toBe('String');
263
+ expect(out.blobText.length).toBe(50_000);
264
+ expect(out.blobText.truncated).toBe(true);
265
+ expect(out.blobText.value).toHaveLength(10_000);
266
+ // Short strings still pass straight through.
267
+ expect(describeStructuredValue({ s: 'small' }).s).toBe('small');
268
+ expect(describeStructuredValue('ab', { maxStringLength: 1 })).toEqual({
269
+ __type: 'String', length: 2, truncated: true, value: 'a',
270
+ });
271
+ });
272
+ it('survives a throwing Symbol.toStringTag instead of aborting the whole read', () => {
273
+ const hostile = { ok: 1 };
274
+ Object.defineProperty(hostile, Symbol.toStringTag, {
275
+ get() { throw new Error('toStringTag boom'); },
276
+ });
277
+ const out = describeStructuredValue({ before: 1, hostile, after: 2 });
278
+ expect(out.before).toBe(1);
279
+ expect(out.after).toBe(2);
280
+ expect(out.hostile.ok).toBe(1);
281
+ });
282
+ });
283
+ describe('describeStructuredValue - objects', () => {
284
+ it('labels class instances with their constructor name', () => {
285
+ class Session {
286
+ id;
287
+ constructor(id) {
288
+ this.id = id;
289
+ }
290
+ }
291
+ expect(describeStructuredValue(new Session('s1'))).toEqual({ __class: 'Session', id: 's1' });
292
+ });
293
+ it('survives a null-prototype object and a throwing getter', () => {
294
+ const bare = Object.create(null);
295
+ bare.x = 1;
296
+ expect(describeStructuredValue(bare)).toEqual({ x: 1 });
297
+ const hostile = {
298
+ get boom() {
299
+ throw new Error('nope');
300
+ },
301
+ ok: 1,
302
+ };
303
+ const out = describeStructuredValue(hostile);
304
+ expect(out.ok).toBe(1);
305
+ expect(out.boom).toEqual({ __type: 'Unreadable', reason: 'nope' });
306
+ });
307
+ it('always produces something JSON.stringify can handle', () => {
308
+ const cyclic = { d: new Date(0), m: new Map([['k', new Uint8Array([1])]]) };
309
+ cyclic.self = cyclic;
310
+ const kitchenSink = [cyclic, 1n, undefined, NaN, new Blob(['x']), Symbol('s'), () => { }];
311
+ expect(() => JSON.stringify(describeStructuredValue(kitchenSink))).not.toThrow();
312
+ });
313
+ it('is self-contained enough to be stringified and re-created in another realm', () => {
314
+ // This mirrors exactly what the tool does inside page.evaluate: the source
315
+ // is eval'd with no access to this module's scope. If the function ever
316
+ // grows a closure dependency, this is what catches it.
317
+ const rebuilt = new Function('return (' + describeStructuredValue.toString() + ')')();
318
+ const value = { when: new Date(0), bytes: new Uint8Array([1, 2]), tags: new Set(['a']) };
319
+ expect(rebuilt(value)).toEqual(describeStructuredValue(value));
320
+ });
321
+ });
322
+ // ---------------------------------------------------------------------------
323
+ // storage tool
324
+ // ---------------------------------------------------------------------------
325
+ /** A CDPManager that is never paused, so executeWithPauseDetection just runs. */
326
+ function fakeCdpManager() {
327
+ return {
328
+ isPaused: () => false,
329
+ getPausedInfo: () => ({}),
330
+ waitForPause: () => new Promise(() => { }), // never settles
331
+ };
332
+ }
333
+ /**
334
+ * A page whose `evaluate` actually runs the callback in this process. The
335
+ * in-page code only touches globals (localStorage/sessionStorage/indexedDB), so
336
+ * running it here exercises the real logic rather than a stub.
337
+ */
338
+ function fakePage() {
339
+ return {
340
+ evaluate: vi.fn(async (fn, ...args) => fn(...args)),
341
+ cookies: vi.fn(async () => []),
342
+ setCookie: vi.fn(async () => { }),
343
+ deleteCookie: vi.fn(async () => { }),
344
+ };
345
+ }
346
+ function buildTool(page = fakePage()) {
347
+ const puppeteerManager = { isConnected: () => true, getPage: () => page };
348
+ const tools = createStorageTools(puppeteerManager, fakeCdpManager());
349
+ return { tools, page };
350
+ }
351
+ function textOf(result) {
352
+ return result.content.map((c) => c.text).join('\n');
353
+ }
354
+ /**
355
+ * A Web Storage shim. Neither happy-dom nor Node exposes a usable
356
+ * localStorage/sessionStorage pair here, and the in-page code only ever touches
357
+ * the globals, so installing our own is enough to run it for real.
358
+ */
359
+ function makeStorage() {
360
+ const data = new Map();
361
+ return {
362
+ get length() { return data.size; },
363
+ key: (i) => Array.from(data.keys())[i] ?? null,
364
+ getItem: (k) => (data.has(k) ? data.get(k) : null),
365
+ setItem: (k, v) => { data.set(String(k), String(v)); },
366
+ removeItem: (k) => { data.delete(k); },
367
+ clear: () => { data.clear(); },
368
+ };
369
+ }
370
+ let localStorage;
371
+ let sessionStorage;
372
+ beforeEach(() => {
373
+ localStorage = makeStorage();
374
+ sessionStorage = makeStorage();
375
+ Object.defineProperty(globalThis, 'localStorage', { value: localStorage, configurable: true, writable: true });
376
+ Object.defineProperty(globalThis, 'sessionStorage', { value: sessionStorage, configurable: true, writable: true });
377
+ });
378
+ describe('storage - sessionStorage actions (feature-012)', () => {
379
+ it('reads the whole sessionStorage when no key is given', async () => {
380
+ sessionStorage.setItem('lockScreen', 'pending');
381
+ sessionStorage.setItem('other', '1');
382
+ const { tools } = buildTool();
383
+ const text = textOf(await tools.storage.handler({ action: 'getSessionStorage' }));
384
+ expect(text).toContain('sessionStorage');
385
+ expect(text).toContain('lockScreen');
386
+ expect(text).toContain('pending');
387
+ expect(text).toContain('other');
388
+ });
389
+ it('reads a single sessionStorage key, and reports a missing one as null', async () => {
390
+ sessionStorage.setItem('lockScreen', 'pending');
391
+ const { tools } = buildTool();
392
+ expect(textOf(await tools.storage.handler({ action: 'getSessionStorage', key: 'lockScreen' })))
393
+ .toContain('"lockScreen": "pending"');
394
+ expect(textOf(await tools.storage.handler({ action: 'getSessionStorage', key: 'absent' })))
395
+ .toContain('"absent": null');
396
+ });
397
+ it('writes sessionStorage without touching localStorage', async () => {
398
+ const { tools } = buildTool();
399
+ const result = await tools.storage.handler({ action: 'setSessionStorage', key: 'flag', value: 'on' });
400
+ expect(result.isError).toBeFalsy();
401
+ expect(sessionStorage.getItem('flag')).toBe('on');
402
+ expect(localStorage.getItem('flag')).toBeNull();
403
+ });
404
+ it('removes a single key from each store and says whether it was there', async () => {
405
+ localStorage.setItem('keep', '1');
406
+ localStorage.setItem('drop', '2');
407
+ sessionStorage.setItem('drop', '3');
408
+ const { tools } = buildTool();
409
+ const local = await tools.storage.handler({ action: 'removeLocalStorage', key: 'drop' });
410
+ expect(local.isError).toBeFalsy();
411
+ expect(localStorage.getItem('drop')).toBeNull();
412
+ expect(localStorage.getItem('keep')).toBe('1');
413
+ const session = await tools.storage.handler({ action: 'removeSessionStorage', key: 'drop' });
414
+ expect(session.isError).toBeFalsy();
415
+ expect(sessionStorage.getItem('drop')).toBeNull();
416
+ const absent = await tools.storage.handler({ action: 'removeLocalStorage', key: 'never-existed' });
417
+ expect(absent.isError).toBeFalsy();
418
+ expect(textOf(absent)).toContain('not present');
419
+ });
420
+ it('requires key and value where the action needs them', async () => {
421
+ const { tools } = buildTool();
422
+ expect((await tools.storage.handler({ action: 'setSessionStorage', value: 'v' })).isError).toBe(true);
423
+ expect((await tools.storage.handler({ action: 'setSessionStorage', key: 'k' })).isError).toBe(true);
424
+ expect((await tools.storage.handler({ action: 'removeSessionStorage' })).isError).toBe(true);
425
+ expect((await tools.storage.handler({ action: 'removeLocalStorage' })).isError).toBe(true);
426
+ });
427
+ it('writes an empty string and a zero key instead of calling them missing', async () => {
428
+ // Clearing a flag by setting '' is a normal operation, and 0 is a valid key.
429
+ localStorage.setItem('flag', 'on');
430
+ sessionStorage.setItem('flag', 'on');
431
+ const { tools } = buildTool();
432
+ const session = await tools.storage.handler({ action: 'setSessionStorage', key: 'flag', value: '' });
433
+ expect(session.isError).toBeFalsy();
434
+ expect(sessionStorage.getItem('flag')).toBe('');
435
+ const local = await tools.storage.handler({ action: 'setLocalStorage', key: 'flag', value: '' });
436
+ expect(local.isError).toBeFalsy();
437
+ expect(localStorage.getItem('flag')).toBe('');
438
+ const zeroKey = await tools.storage.handler({ action: 'setSessionStorage', key: 0, value: 'v' });
439
+ expect(zeroKey.isError).toBeFalsy();
440
+ expect(sessionStorage.getItem('0')).toBe('v');
441
+ const removed = await tools.storage.handler({ action: 'removeSessionStorage', key: 0 });
442
+ expect(removed.isError).toBeFalsy();
443
+ expect(sessionStorage.getItem('0')).toBeNull();
444
+ expect(textOf(removed)).not.toContain('not present');
445
+ });
446
+ it('sets an empty cookie value rather than rejecting it', async () => {
447
+ const { tools, page } = buildTool();
448
+ const result = await tools.storage.handler({ action: 'setCookie', name: 'session', value: '' });
449
+ expect(result.isError).toBeFalsy();
450
+ expect(page.setCookie).toHaveBeenCalledWith(expect.objectContaining({ name: 'session', value: '' }));
451
+ });
452
+ it('accepts the new actions and the indexedDB clear target in the schema', () => {
453
+ const { tools } = buildTool();
454
+ const schema = tools.storage.zodSchema;
455
+ for (const action of ['getSessionStorage', 'setSessionStorage', 'removeSessionStorage', 'removeLocalStorage',
456
+ 'idbListDatabases', 'idbListStores', 'idbGet', 'idbGetAll', 'idbPut', 'idbDelete']) {
457
+ expect(schema.safeParse({ action }).success, action).toBe(true);
458
+ }
459
+ expect(schema.safeParse({ action: 'clear', reason: 'test', types: ['indexedDB'] }).success).toBe(true);
460
+ expect(schema.safeParse({ action: 'idbGet', db: 'a', store: 'b', key: 7 }).success).toBe(true);
461
+ expect(schema.safeParse({ action: 'idbPut', db: 'a', store: 'b', record: { nested: [1] } }).success).toBe(true);
462
+ });
463
+ });
464
+ // ---------------------------------------------------------------------------
465
+ // A minimal in-memory IndexedDB, just enough to drive the tool's in-page code.
466
+ // happy-dom provides no IndexedDB, and the point here is to exercise the real
467
+ // request/transaction plumbing (async callbacks, tx settling, missing db/store
468
+ // handling), not to re-test the browser.
469
+ // ---------------------------------------------------------------------------
470
+ function makeFakeIndexedDB(spec) {
471
+ const databases = {};
472
+ for (const [dbName, dbSpec] of Object.entries(spec)) {
473
+ const stores = {};
474
+ for (const [storeName, s] of Object.entries(dbSpec.stores)) {
475
+ stores[storeName] = {
476
+ keyPath: s.keyPath ?? null,
477
+ autoIncrement: s.autoIncrement ?? false,
478
+ data: new Map(s.records ?? []),
479
+ };
480
+ }
481
+ databases[dbName] = { version: dbSpec.version ?? 1, stores };
482
+ }
483
+ const request = (run) => {
484
+ const req = { onsuccess: null, onerror: null, onupgradeneeded: null, onblocked: null, result: undefined, error: null };
485
+ queueMicrotask(() => {
486
+ try {
487
+ req.result = run();
488
+ req.onsuccess && req.onsuccess();
489
+ }
490
+ catch (e) {
491
+ req.error = e;
492
+ req.onerror && req.onerror();
493
+ }
494
+ });
495
+ return req;
496
+ };
497
+ const nameList = (names) => {
498
+ const list = names.slice();
499
+ list.contains = (n) => names.indexOf(n) !== -1;
500
+ return list;
501
+ };
502
+ function makeDb(name) {
503
+ const state = databases[name];
504
+ return {
505
+ name,
506
+ version: state.version,
507
+ objectStoreNames: nameList(Object.keys(state.stores)),
508
+ closed: false,
509
+ close() { this.closed = true; },
510
+ transaction(storeNames, _mode) {
511
+ const tx = { oncomplete: null, onerror: null, onabort: null, error: null, aborted: false };
512
+ tx.abort = () => { tx.aborted = true; tx.onabort && tx.onabort(); };
513
+ tx.objectStore = (storeName) => {
514
+ const s = state.stores[storeName];
515
+ const arr = () => Array.from(s.data.entries());
516
+ return {
517
+ keyPath: s.keyPath,
518
+ autoIncrement: s.autoIncrement,
519
+ indexNames: nameList([]),
520
+ count: () => request(() => s.data.size),
521
+ get: (key) => request(() => s.data.get(key)),
522
+ getAll: (_q, limit) => request(() => arr().slice(0, limit ?? Infinity).map(([, v]) => v)),
523
+ getAllKeys: (_q, limit) => request(() => arr().slice(0, limit ?? Infinity).map(([k]) => k)),
524
+ put: (value, key) => request(() => {
525
+ const finalKey = s.keyPath ? value[s.keyPath] : key ?? s.data.size + 1;
526
+ s.data.set(finalKey, value);
527
+ return finalKey;
528
+ }),
529
+ delete: (key) => request(() => { s.data.delete(key); return undefined; }),
530
+ };
531
+ };
532
+ // Real transactions auto-commit once their requests drain; a macrotask
533
+ // lands after the awaited (microtask) requests, which is close enough.
534
+ setTimeout(() => { if (!tx.aborted)
535
+ tx.oncomplete && tx.oncomplete(); }, 0);
536
+ return tx;
537
+ },
538
+ };
539
+ }
540
+ return {
541
+ databases: async () => Object.entries(databases).map(([name, d]) => ({ name, version: d.version })),
542
+ open(name) {
543
+ const req = { onsuccess: null, onerror: null, onupgradeneeded: null, onblocked: null, result: undefined, error: null };
544
+ queueMicrotask(() => {
545
+ if (!databases[name]) {
546
+ // Matches the browser: opening an unknown name creates it.
547
+ databases[name] = { version: 1, stores: {} };
548
+ req.result = makeDb(name);
549
+ req.onupgradeneeded && req.onupgradeneeded();
550
+ }
551
+ else {
552
+ req.result = makeDb(name);
553
+ }
554
+ req.onsuccess && req.onsuccess();
555
+ });
556
+ return req;
557
+ },
558
+ deleteDatabase(name) {
559
+ return request(() => { delete databases[name]; return undefined; });
560
+ },
561
+ __state: databases,
562
+ };
563
+ }
564
+ describe('storage - IndexedDB actions (feature-011)', () => {
565
+ let fakeIdb;
566
+ afterEach(() => {
567
+ delete globalThis.indexedDB;
568
+ });
569
+ function install(spec) {
570
+ fakeIdb = makeFakeIndexedDB(spec);
571
+ globalThis.indexedDB = fakeIdb;
572
+ return fakeIdb;
573
+ }
574
+ it('lists databases', async () => {
575
+ install({ app: { version: 3, stores: { keys: {} } }, other: { stores: {} } });
576
+ const { tools } = buildTool();
577
+ const text = textOf(await tools.storage.handler({ action: 'idbListDatabases' }));
578
+ expect(text).toContain('app');
579
+ expect(text).toContain('other');
580
+ expect(text).toContain('"version": 3');
581
+ });
582
+ it('lists object stores with their key configuration and record counts', async () => {
583
+ install({ app: { stores: { keys: { keyPath: 'id', records: [['a', { id: 'a' }]] }, blobs: { autoIncrement: true } } } });
584
+ const { tools } = buildTool();
585
+ const text = textOf(await tools.storage.handler({ action: 'idbListStores', db: 'app' }));
586
+ expect(text).toContain('keys');
587
+ expect(text).toContain('"keyPath": "id"');
588
+ expect(text).toContain('"count": 1');
589
+ expect(text).toContain('blobs');
590
+ expect(text).toContain('"autoIncrement": true');
591
+ });
592
+ it('gets a record and reports a missing key as not found', async () => {
593
+ install({ app: { stores: { kv: { records: [['token', { value: 'abc' }]] } } } });
594
+ const { tools } = buildTool();
595
+ const found = await tools.storage.handler({ action: 'idbGet', db: 'app', store: 'kv', key: 'token' });
596
+ expect(found.isError).toBeFalsy();
597
+ expect(textOf(found)).toContain('abc');
598
+ const missing = await tools.storage.handler({ action: 'idbGet', db: 'app', store: 'kv', key: 'nope' });
599
+ expect(missing.isError).toBeFalsy();
600
+ expect(textOf(missing)).toContain('No record found');
601
+ });
602
+ it('returns a non-extractable CryptoKey as a descriptor rather than {}', async () => {
603
+ const pair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']);
604
+ install({ identity: { stores: { keys: { records: [['device', { id: 'device', signing: pair.privateKey }]] } } } });
605
+ const { tools } = buildTool();
606
+ const text = textOf(await tools.storage.handler({ action: 'idbGet', db: 'identity', store: 'keys', key: 'device' }));
607
+ expect(text).toContain('"__type": "CryptoKey"');
608
+ expect(text).toContain('"extractable": false');
609
+ expect(text).toContain('ECDSA');
610
+ expect(text).toContain('P-256');
611
+ });
612
+ it('reads all records, honouring limit and flagging truncation', async () => {
613
+ const records = [];
614
+ for (let i = 0; i < 5; i++)
615
+ records.push([i, { i }]);
616
+ install({ app: { stores: { items: { records } } } });
617
+ const { tools } = buildTool();
618
+ const all = textOf(await tools.storage.handler({ action: 'idbGetAll', db: 'app', store: 'items' }));
619
+ expect(all).toContain('**Count:** 5');
620
+ const limited = textOf(await tools.storage.handler({ action: 'idbGetAll', db: 'app', store: 'items', limit: 2 }));
621
+ expect(limited).toContain('**Count:** 2');
622
+ expect(limited).toContain('showing 2 of 5');
623
+ });
624
+ it('writes a record with an out-of-line key', async () => {
625
+ const idb = install({ app: { stores: { kv: {} } } });
626
+ const { tools } = buildTool();
627
+ const result = await tools.storage.handler({
628
+ action: 'idbPut', db: 'app', store: 'kv', key: 'flag', record: { enabled: true },
629
+ });
630
+ expect(result.isError).toBeFalsy();
631
+ expect(idb.__state.app.stores.kv.data.get('flag')).toEqual({ enabled: true });
632
+ });
633
+ it('writes a record with an in-line key, and rejects a redundant key parameter', async () => {
634
+ const idb = install({ app: { stores: { docs: { keyPath: 'id' } } } });
635
+ const { tools } = buildTool();
636
+ const ok = await tools.storage.handler({ action: 'idbPut', db: 'app', store: 'docs', record: { id: 'doc-1', title: 'x' } });
637
+ expect(ok.isError).toBeFalsy();
638
+ expect(idb.__state.app.stores.docs.data.get('doc-1')).toEqual({ id: 'doc-1', title: 'x' });
639
+ const conflict = await tools.storage.handler({
640
+ action: 'idbPut', db: 'app', store: 'docs', key: 'doc-2', record: { id: 'doc-2' },
641
+ });
642
+ expect(conflict.isError).toBe(true);
643
+ expect(textOf(conflict)).toContain('in-line key');
644
+ });
645
+ it('refuses an out-of-line put with no key rather than writing under a guessed one', async () => {
646
+ install({ app: { stores: { kv: {} } } });
647
+ const { tools } = buildTool();
648
+ const result = await tools.storage.handler({ action: 'idbPut', db: 'app', store: 'kv', record: { a: 1 } });
649
+ expect(result.isError).toBe(true);
650
+ expect(textOf(result)).toContain('"key" parameter is required');
651
+ });
652
+ it('deletes a record and reports when there was nothing to delete', async () => {
653
+ const idb = install({ app: { stores: { kv: { records: [['gone', 1]] } } } });
654
+ const { tools } = buildTool();
655
+ const deleted = await tools.storage.handler({ action: 'idbDelete', db: 'app', store: 'kv', key: 'gone' });
656
+ expect(deleted.isError).toBeFalsy();
657
+ expect(idb.__state.app.stores.kv.data.has('gone')).toBe(false);
658
+ const absent = await tools.storage.handler({ action: 'idbDelete', db: 'app', store: 'kv', key: 'never' });
659
+ expect(absent.isError).toBeFalsy();
660
+ expect(textOf(absent)).toContain('no record existed');
661
+ });
662
+ it('does not leave an empty database behind when the name does not exist', async () => {
663
+ const idb = install({ app: { stores: { kv: {} } } });
664
+ const { tools } = buildTool();
665
+ const result = await tools.storage.handler({ action: 'idbGet', db: 'typo', store: 'kv', key: 'x' });
666
+ expect(result.isError).toBe(true);
667
+ expect(textOf(result)).toContain('does not exist');
668
+ // The read must not have created "typo" as a side effect.
669
+ expect(idb.__state.typo).toBeUndefined();
670
+ });
671
+ it('names the available stores when the requested one is missing', async () => {
672
+ install({ app: { stores: { kv: {}, other: {} } } });
673
+ const { tools } = buildTool();
674
+ const result = await tools.storage.handler({ action: 'idbGet', db: 'app', store: 'nope', key: 'x' });
675
+ expect(result.isError).toBe(true);
676
+ expect(textOf(result)).toContain('not found in database');
677
+ });
678
+ it('surfaces a browser without indexedDB.databases() as an error, not a crash', async () => {
679
+ globalThis.indexedDB = { open: () => { } };
680
+ const { tools } = buildTool();
681
+ const result = await tools.storage.handler({ action: 'idbListDatabases' });
682
+ expect(result.isError).toBe(true);
683
+ expect(textOf(result)).toContain('not supported');
684
+ });
685
+ it('requires db, store, key and record where the action needs them', async () => {
686
+ install({ app: { stores: { kv: {} } } });
687
+ const { tools } = buildTool();
688
+ expect((await tools.storage.handler({ action: 'idbListStores' })).isError).toBe(true);
689
+ expect((await tools.storage.handler({ action: 'idbGet', store: 'kv', key: 'a' })).isError).toBe(true);
690
+ expect((await tools.storage.handler({ action: 'idbGet', db: 'app', key: 'a' })).isError).toBe(true);
691
+ expect((await tools.storage.handler({ action: 'idbGet', db: 'app', store: 'kv' })).isError).toBe(true);
692
+ expect((await tools.storage.handler({ action: 'idbDelete', db: 'app', store: 'kv' })).isError).toBe(true);
693
+ expect((await tools.storage.handler({ action: 'idbPut', db: 'app', store: 'kv', key: 'k' })).isError).toBe(true);
694
+ });
695
+ it('clears IndexedDB only when asked, and reports what it deleted', async () => {
696
+ const idb = install({ app: { stores: { kv: {} } }, other: { stores: {} } });
697
+ const { tools } = buildTool();
698
+ const untouched = await tools.storage.handler({ action: 'clear', reason: 'test', types: ['localStorage'] });
699
+ expect(untouched.isError).toBeFalsy();
700
+ expect(Object.keys(idb.__state)).toHaveLength(2);
701
+ const cleared = await tools.storage.handler({ action: 'clear', reason: 'test', types: ['indexedDB'] });
702
+ expect(cleared.isError).toBeFalsy();
703
+ expect(textOf(cleared)).toContain('indexedDB (2 deleted');
704
+ expect(Object.keys(idb.__state)).toHaveLength(0);
705
+ });
706
+ });
707
+ //# sourceMappingURL=storage-tools.test.js.map