@itmetsam/samurai-framework 0.1.0 → 0.2.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 (328) hide show
  1. package/README.md +129 -0
  2. package/bin/samurai.mjs +31 -0
  3. package/dist/api.js +3 -0
  4. package/dist/assert/assertion-error.d.ts +23 -0
  5. package/dist/assert/assertion-error.js +45 -0
  6. package/dist/assert/expect.d.ts +55 -0
  7. package/dist/assert/expect.js +188 -0
  8. package/dist/browser/browser-context.d.ts +91 -0
  9. package/dist/browser/browser-context.js +146 -0
  10. package/dist/browser/browser-finder.d.ts +2 -0
  11. package/dist/browser/browser-finder.js +95 -0
  12. package/dist/browser/browser.d.ts +95 -0
  13. package/dist/browser/browser.js +372 -0
  14. package/dist/browser/context-tree.d.ts +23 -0
  15. package/dist/browser/context-tree.js +60 -0
  16. package/dist/browser/dialog.d.ts +37 -0
  17. package/dist/browser/dialog.js +61 -0
  18. package/dist/browser/download.d.ts +71 -0
  19. package/dist/browser/download.js +137 -0
  20. package/dist/browser/downloads-dir.d.ts +8 -0
  21. package/dist/browser/downloads-dir.js +38 -0
  22. package/dist/browser/emulation.d.ts +61 -0
  23. package/dist/browser/emulation.js +144 -0
  24. package/dist/browser/file-chooser.d.ts +39 -0
  25. package/dist/browser/file-chooser.js +86 -0
  26. package/dist/browser/file-paths.d.ts +4 -0
  27. package/dist/browser/file-paths.js +18 -0
  28. package/dist/browser/index.d.ts +4 -0
  29. package/dist/browser/index.js +4 -0
  30. package/dist/browser/navigation-error.d.ts +13 -0
  31. package/dist/browser/navigation-error.js +19 -0
  32. package/dist/browser/page-logs.d.ts +86 -0
  33. package/dist/browser/page-logs.js +149 -0
  34. package/dist/browser/page-wait.d.ts +9 -0
  35. package/dist/browser/page-wait.js +17 -0
  36. package/dist/browser/page.d.ts +370 -0
  37. package/dist/browser/page.js +1038 -0
  38. package/dist/browser/pdf.d.ts +72 -0
  39. package/dist/browser/pdf.js +37 -0
  40. package/dist/browser/screenshot.d.ts +29 -0
  41. package/dist/browser/screenshot.js +42 -0
  42. package/dist/cli/args.d.ts +35 -0
  43. package/dist/cli/args.js +69 -0
  44. package/dist/cli/cli.cli-test.d.ts +1 -0
  45. package/dist/cli/cli.cli-test.js +170 -0
  46. package/dist/cli/console-reporter.d.ts +15 -0
  47. package/dist/cli/console-reporter.js +52 -0
  48. package/dist/cli/init.d.ts +7 -0
  49. package/dist/cli/init.js +61 -0
  50. package/dist/cli/main.d.ts +12 -0
  51. package/dist/cli/main.js +156 -0
  52. package/dist/config/config.d.ts +24 -0
  53. package/dist/config/config.js +42 -0
  54. package/dist/config/default.d.ts +2 -0
  55. package/dist/config/default.js +3 -0
  56. package/dist/config/mask.d.ts +11 -0
  57. package/dist/config/mask.js +85 -0
  58. package/dist/config/run-settings.d.ts +28 -0
  59. package/dist/config/run-settings.js +61 -0
  60. package/dist/config/secrets.d.ts +10 -0
  61. package/dist/config/secrets.js +67 -0
  62. package/dist/config/variables.d.ts +11 -0
  63. package/dist/config/variables.js +26 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +15 -0
  66. package/dist/locator/action-timeout-error.d.ts +25 -0
  67. package/dist/locator/action-timeout-error.js +35 -0
  68. package/dist/locator/element-state.d.ts +44 -0
  69. package/dist/locator/element-state.js +109 -0
  70. package/dist/locator/label-locator.d.ts +13 -0
  71. package/dist/locator/label-locator.js +49 -0
  72. package/dist/locator/locator.d.ts +168 -0
  73. package/dist/locator/locator.js +591 -0
  74. package/dist/locator/selector-errors.d.ts +9 -0
  75. package/dist/locator/selector-errors.js +15 -0
  76. package/dist/locator/selector.d.ts +60 -0
  77. package/dist/locator/selector.js +113 -0
  78. package/dist/locator/text-locator.d.ts +13 -0
  79. package/dist/logger/index.d.ts +2 -0
  80. package/dist/logger/index.js +30 -0
  81. package/dist/network/data-collector.d.ts +26 -0
  82. package/dist/network/data-collector.js +77 -0
  83. package/dist/network/network-tracker.d.ts +94 -0
  84. package/dist/network/network-tracker.js +198 -0
  85. package/dist/network/network-wait.d.ts +16 -0
  86. package/dist/network/network-wait.js +24 -0
  87. package/dist/network/response.d.ts +22 -0
  88. package/dist/network/response.js +42 -0
  89. package/dist/network/router.d.ts +120 -0
  90. package/dist/network/router.js +343 -0
  91. package/dist/network/url-match.d.ts +8 -0
  92. package/dist/network/url-match.js +38 -0
  93. package/dist/recorder/apply.d.ts +3 -0
  94. package/dist/recorder/apply.js +10 -0
  95. package/dist/recorder/capture.d.ts +18 -0
  96. package/dist/recorder/capture.js +198 -0
  97. package/dist/recorder/index.d.ts +3 -0
  98. package/dist/recorder/index.js +3 -0
  99. package/dist/recorder/normalise.d.ts +68 -0
  100. package/dist/recorder/normalise.js +104 -0
  101. package/dist/recorder/ranking.d.ts +9 -0
  102. package/dist/recorder/ranking.js +21 -0
  103. package/dist/recorder/recorder.d.ts +52 -0
  104. package/dist/recorder/recorder.js +143 -0
  105. package/dist/recorder/session.d.ts +49 -0
  106. package/dist/recorder/session.js +113 -0
  107. package/dist/runner/module-type.d.ts +6 -0
  108. package/dist/runner/module-type.js +29 -0
  109. package/dist/runner/page-logs-report.d.ts +40 -0
  110. package/dist/runner/page-logs-report.js +44 -0
  111. package/dist/runner/prepare-run.d.ts +14 -0
  112. package/dist/runner/prepare-run.js +20 -0
  113. package/dist/runner/project.d.ts +17 -0
  114. package/dist/runner/project.js +33 -0
  115. package/dist/runner/registry.d.ts +7 -0
  116. package/dist/runner/registry.js +40 -0
  117. package/dist/runner/reporter.d.ts +43 -0
  118. package/dist/runner/reporter.js +83 -0
  119. package/dist/runner/run.d.ts +27 -0
  120. package/dist/runner/run.js +34 -0
  121. package/dist/runner/test-error.d.ts +3 -0
  122. package/dist/runner/test-error.js +18 -0
  123. package/dist/runner/test-runner.d.ts +44 -0
  124. package/dist/runner/test-runner.js +205 -0
  125. package/dist/script/call-function.d.ts +34 -0
  126. package/dist/script/call-function.js +71 -0
  127. package/dist/script/element-handle.d.ts +10 -0
  128. package/{src/script/element-handle.ts → dist/script/element-handle.js} +4 -4
  129. package/dist/script/helpers.d.ts +28 -0
  130. package/dist/script/helpers.js +50 -0
  131. package/dist/script/preload.d.ts +31 -0
  132. package/dist/script/preload.js +71 -0
  133. package/dist/script/serialize.d.ts +29 -0
  134. package/dist/script/serialize.js +207 -0
  135. package/dist/steps/build.d.ts +5 -0
  136. package/dist/steps/build.js +38 -0
  137. package/dist/steps/edit.d.ts +30 -0
  138. package/dist/steps/edit.js +80 -0
  139. package/dist/steps/emit.d.ts +6 -0
  140. package/dist/steps/emit.js +73 -0
  141. package/dist/steps/index.d.ts +5 -0
  142. package/dist/steps/index.js +4 -0
  143. package/dist/steps/model.d.ts +103 -0
  144. package/dist/steps/model.js +1 -0
  145. package/dist/steps/parse.d.ts +3 -0
  146. package/dist/steps/parse.js +272 -0
  147. package/dist/transport/bidi-connection.d.ts +46 -0
  148. package/dist/transport/bidi-connection.js +176 -0
  149. package/dist/transport/bidi-error.d.ts +10 -0
  150. package/dist/transport/bidi-error.js +10 -0
  151. package/dist/transport/subscriptions.d.ts +36 -0
  152. package/dist/transport/subscriptions.js +81 -0
  153. package/{src → dist}/types/test.d.ts +10 -4
  154. package/dist/wait/wait-until.d.ts +20 -0
  155. package/dist/wait/wait-until.js +63 -0
  156. package/package.json +44 -7
  157. package/.claude/settings.local.json +0 -7
  158. package/.env.example +0 -3
  159. package/.github/actions/setup/action.yml +0 -16
  160. package/.github/workflows/ci.yml +0 -71
  161. package/.trunk/configs/.markdownlint.yaml +0 -2
  162. package/.trunk/configs/.yamllint.yaml +0 -7
  163. package/.trunk/configs/svgo.config.mjs +0 -14
  164. package/.trunk/trunk.yaml +0 -34
  165. package/docs/superpowers/bidi-roadmap.md +0 -60
  166. package/docs/superpowers/plans/2026-09-29-auto-wait.md +0 -1827
  167. package/docs/superpowers/plans/2026-09-29-expect.md +0 -1350
  168. package/docs/superpowers/plans/2026-10-01-ci.md +0 -350
  169. package/docs/superpowers/plans/2026-10-01-test-api-alignment.md +0 -1821
  170. package/docs/superpowers/recorder-roadmap.md +0 -52
  171. package/docs/superpowers/specs/2026-09-29-auto-wait-design.md +0 -185
  172. package/docs/superpowers/specs/2026-09-29-bidi-foundation-design.md +0 -130
  173. package/docs/superpowers/specs/2026-09-29-contexts-and-storage-design.md +0 -88
  174. package/docs/superpowers/specs/2026-09-29-downloads-and-file-dialogs-design.md +0 -94
  175. package/docs/superpowers/specs/2026-09-29-emulation-design.md +0 -88
  176. package/docs/superpowers/specs/2026-09-29-expect-design.md +0 -130
  177. package/docs/superpowers/specs/2026-09-29-locate-nodes-design.md +0 -90
  178. package/docs/superpowers/specs/2026-09-29-logs-and-errors-design.md +0 -87
  179. package/docs/superpowers/specs/2026-09-29-navigation-waits-design.md +0 -83
  180. package/docs/superpowers/specs/2026-09-29-network-data-design.md +0 -78
  181. package/docs/superpowers/specs/2026-09-29-network-interception-design.md +0 -91
  182. package/docs/superpowers/specs/2026-09-29-network-tracking-design.md +0 -86
  183. package/docs/superpowers/specs/2026-09-29-page-api-design.md +0 -90
  184. package/docs/superpowers/specs/2026-09-29-preload-scripts-design.md +0 -82
  185. package/docs/superpowers/specs/2026-09-29-script-call-function-design.md +0 -107
  186. package/docs/superpowers/specs/2026-09-29-set-files-design.md +0 -62
  187. package/docs/superpowers/specs/2026-09-29-user-prompts-design.md +0 -75
  188. package/docs/superpowers/specs/2026-10-01-ci-design.md +0 -106
  189. package/docs/superpowers/specs/2026-10-01-test-api-alignment-design.md +0 -241
  190. package/public/logo-dark.svg +0 -236
  191. package/public/logo-light.svg +0 -262
  192. package/samurai.config.ts +0 -7
  193. package/src/assert/assertion-error.test.ts +0 -55
  194. package/src/assert/assertion-error.ts +0 -62
  195. package/src/assert/expect-locator.test.ts +0 -217
  196. package/src/assert/expect.test.ts +0 -73
  197. package/src/assert/expect.ts +0 -299
  198. package/src/browser/browser-context.browser-test.ts +0 -201
  199. package/src/browser/browser-context.test.ts +0 -407
  200. package/src/browser/browser-context.ts +0 -208
  201. package/src/browser/browser-finder.ts +0 -110
  202. package/src/browser/browser.test.ts +0 -88
  203. package/src/browser/browser.ts +0 -515
  204. package/src/browser/context-tree.test.ts +0 -86
  205. package/src/browser/context-tree.ts +0 -70
  206. package/src/browser/dialog.browser-test.ts +0 -144
  207. package/src/browser/dialog.test.ts +0 -322
  208. package/src/browser/dialog.ts +0 -83
  209. package/src/browser/download.test.ts +0 -161
  210. package/src/browser/download.ts +0 -172
  211. package/src/browser/downloads-dir.test.ts +0 -75
  212. package/src/browser/downloads-dir.ts +0 -39
  213. package/src/browser/emulation.browser-test.ts +0 -186
  214. package/src/browser/emulation.test.ts +0 -200
  215. package/src/browser/emulation.ts +0 -204
  216. package/src/browser/file-chooser.test.ts +0 -142
  217. package/src/browser/file-chooser.ts +0 -99
  218. package/src/browser/file-paths.ts +0 -18
  219. package/src/browser/goto.browser-test.ts +0 -44
  220. package/src/browser/navigation-error.ts +0 -26
  221. package/src/browser/navigation.browser-test.ts +0 -172
  222. package/src/browser/page-api.browser-test.ts +0 -228
  223. package/src/browser/page-logs.browser-test.ts +0 -119
  224. package/src/browser/page-logs.test.ts +0 -212
  225. package/src/browser/page-logs.ts +0 -208
  226. package/src/browser/page-wait.ts +0 -19
  227. package/src/browser/page.test.ts +0 -1567
  228. package/src/browser/page.ts +0 -1176
  229. package/src/browser/pdf.ts +0 -62
  230. package/src/browser/screenshot.ts +0 -65
  231. package/src/browser/transfers.browser-test.ts +0 -180
  232. package/src/config/config.ts +0 -33
  233. package/src/config/default.ts +0 -5
  234. package/src/config/fixtures.test.ts +0 -137
  235. package/src/config/mask.test.ts +0 -143
  236. package/src/config/mask.ts +0 -87
  237. package/src/config/run-settings.test.ts +0 -163
  238. package/src/config/run-settings.ts +0 -114
  239. package/src/config/secrets.ts +0 -70
  240. package/src/config/variables.ts +0 -31
  241. package/src/index.ts +0 -10
  242. package/src/locator/action-timeout-error.test.ts +0 -83
  243. package/src/locator/action-timeout-error.ts +0 -72
  244. package/src/locator/element-state.test.ts +0 -181
  245. package/src/locator/element-state.ts +0 -159
  246. package/src/locator/locate-nodes.browser-test.ts +0 -169
  247. package/src/locator/locator-actions.test.ts +0 -355
  248. package/src/locator/locator-chain.test.ts +0 -292
  249. package/src/locator/locator.browser-test.ts +0 -137
  250. package/src/locator/locator.test.ts +0 -179
  251. package/src/locator/locator.ts +0 -656
  252. package/src/locator/selector-errors.ts +0 -18
  253. package/src/locator/selector.test.ts +0 -124
  254. package/src/locator/selector.ts +0 -107
  255. package/src/locator/set-files.browser-test.ts +0 -141
  256. package/src/locator/set-files.test.ts +0 -191
  257. package/src/locator/text-locator.test.ts +0 -118
  258. package/src/logger/index.test.ts +0 -28
  259. package/src/logger/index.ts +0 -40
  260. package/src/network/data-collector.test.ts +0 -111
  261. package/src/network/data-collector.ts +0 -92
  262. package/src/network/network-data.browser-test.ts +0 -192
  263. package/src/network/network-tracker.browser-test.ts +0 -151
  264. package/src/network/network-tracker.test.ts +0 -307
  265. package/src/network/network-tracker.ts +0 -286
  266. package/src/network/network-wait.ts +0 -28
  267. package/src/network/response.test.ts +0 -93
  268. package/src/network/response.ts +0 -58
  269. package/src/network/router.browser-test.ts +0 -260
  270. package/src/network/router.test.ts +0 -528
  271. package/src/network/router.ts +0 -420
  272. package/src/network/url-match.test.ts +0 -69
  273. package/src/network/url-match.ts +0 -41
  274. package/src/runner/page-logs-report.test.ts +0 -144
  275. package/src/runner/page-logs-report.ts +0 -82
  276. package/src/runner/prepare-run.test.ts +0 -33
  277. package/src/runner/prepare-run.ts +0 -36
  278. package/src/runner/registry.test.ts +0 -59
  279. package/src/runner/registry.ts +0 -46
  280. package/src/runner/reporter.test.ts +0 -73
  281. package/src/runner/reporter.ts +0 -109
  282. package/src/runner/test-error.test.ts +0 -39
  283. package/src/runner/test-error.ts +0 -20
  284. package/src/runner/test-runner.ts +0 -184
  285. package/src/script/call-function.browser-test.ts +0 -112
  286. package/src/script/call-function.test.ts +0 -174
  287. package/src/script/call-function.ts +0 -113
  288. package/src/script/helpers.test.ts +0 -116
  289. package/src/script/helpers.ts +0 -68
  290. package/src/script/preload.browser-test.ts +0 -144
  291. package/src/script/preload.test.ts +0 -180
  292. package/src/script/preload.ts +0 -102
  293. package/src/script/serialize.test.ts +0 -182
  294. package/src/script/serialize.ts +0 -203
  295. package/src/testing/browser-fixture.ts +0 -18
  296. package/src/testing/fake-websocket.ts +0 -72
  297. package/src/testing/stub-connector.ts +0 -154
  298. package/src/tests/index.spec.ts +0 -30
  299. package/src/transport/bidi-connection.test.ts +0 -155
  300. package/src/transport/bidi-connection.ts +0 -256
  301. package/src/transport/bidi-error.ts +0 -23
  302. package/src/transport/foundation.browser-test.ts +0 -73
  303. package/src/transport/subscriptions.test.ts +0 -140
  304. package/src/transport/subscriptions.ts +0 -110
  305. package/src/types/bidi-commands.test-d.ts +0 -55
  306. package/src/wait/wait-until.test.ts +0 -117
  307. package/src/wait/wait-until.ts +0 -86
  308. package/tsconfig.json +0 -44
  309. /package/{src/api.ts → dist/api.d.ts} +0 -0
  310. /package/{src/locator/text-locator.ts → dist/locator/text-locator.js} +0 -0
  311. /package/{src → dist}/types/bidi-modules/browser.d.ts +0 -0
  312. /package/{src → dist}/types/bidi-modules/browsing-context.d.ts +0 -0
  313. /package/{src → dist}/types/bidi-modules/emulation.d.ts +0 -0
  314. /package/{src → dist}/types/bidi-modules/input.d.ts +0 -0
  315. /package/{src → dist}/types/bidi-modules/log.d.ts +0 -0
  316. /package/{src → dist}/types/bidi-modules/network.d.ts +0 -0
  317. /package/{src → dist}/types/bidi-modules/permissions.d.ts +0 -0
  318. /package/{src → dist}/types/bidi-modules/script.d.ts +0 -0
  319. /package/{src → dist}/types/bidi-modules/session.d.ts +0 -0
  320. /package/{src → dist}/types/bidi-modules/storage.d.ts +0 -0
  321. /package/{src → dist}/types/bidi-modules/web-extensions.d.ts +0 -0
  322. /package/{src → dist}/types/bidi-protocols/command.d.ts +0 -0
  323. /package/{src → dist}/types/bidi-protocols/error.d.ts +0 -0
  324. /package/{src → dist}/types/bidi-protocols/event.d.ts +0 -0
  325. /package/{src → dist}/types/bidi.d.ts +0 -0
  326. /package/{src → dist}/types/browser.d.ts +0 -0
  327. /package/{src → dist}/types/config.d.ts +0 -0
  328. /package/{src → dist}/types/env.d.ts +0 -0
@@ -1,212 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { afterEach, describe, it } from "node:test";
3
- import { autoReply, FakeWebSocket, tick } from "../testing/fake-websocket.js";
4
- import { BiDiConnector } from "../transport/bidi-connection.js";
5
- import { ContextTree } from "./context-tree.js";
6
- import { ConsoleMessage, PageError, PageLogs } from "./page-logs.js";
7
-
8
- const TREE = [
9
- { context: "top", parent: null, children: [{ context: "frame", parent: "top", children: [] }] },
10
- { context: "other", parent: null, children: [] },
11
- ];
12
- const stops: Array<() => void> = [];
13
- afterEach(() => stops.splice(0).forEach((stop) => stop()));
14
-
15
- async function setup(options?: { maxEntries?: number }) {
16
- const ws = new FakeWebSocket();
17
- const connector = new BiDiConnector(ws as unknown as WebSocket);
18
- stops.push(autoReply(ws, { "browsingContext.getTree": () => ({ contexts: TREE }) }));
19
- const tree = await ContextTree.create(connector);
20
- const logs = await PageLogs.start(connector, tree, "top", options);
21
- return { ws, logs };
22
- }
23
-
24
- const source = (context: string | undefined) => ({ realm: "r", userContext: "default", ...(context && { context }) });
25
- const str = (value: string) => ({ type: "string", value });
26
- const consoleEntry = (context: string | undefined, method: string, text: string, extra: object = {}) => ({
27
- type: "console",
28
- method,
29
- level: method === "error" ? "error" : method === "warn" ? "warn" : "info",
30
- text,
31
- args: [str(text)],
32
- timestamp: 1000,
33
- source: source(context),
34
- ...extra,
35
- });
36
- const errorEntry = (context: string | undefined, text: string, extra: object = {}) => ({
37
- type: "javascript",
38
- level: "error",
39
- text,
40
- timestamp: 2000,
41
- source: source(context),
42
- ...extra,
43
- });
44
- const emit = (ws: FakeWebSocket, entry: object) => ws.emitEvent("log.entryAdded", entry);
45
-
46
- describe("PageLogs console messages", () => {
47
- it("turns a console entry into a ConsoleMessage with deserialized arguments", async () => {
48
- const { ws, logs } = await setup();
49
- const seen: ConsoleMessage[] = [];
50
- logs.on("console", (message) => seen.push(message));
51
- emit(ws, {
52
- ...consoleEntry("top", "log", "a 1 {…}", {
53
- args: [str("a"), { type: "number", value: 1 }, { type: "object", value: [["k", str("v")]] }],
54
- stackTrace: { callFrames: [{ functionName: "run", url: "https://x.test/app.js", lineNumber: 4, columnNumber: 2 }] },
55
- }),
56
- });
57
- assert.equal(seen.length, 1);
58
- const [message] = seen as [ConsoleMessage];
59
- assert.ok(message instanceof ConsoleMessage);
60
- assert.equal(message.type(), "log");
61
- assert.equal(message.text(), "a 1 {…}");
62
- assert.equal(message.level, "info");
63
- assert.deepEqual(message.args, ["a", 1, { k: "v" }]);
64
- assert.equal(message.timestamp, 1000);
65
- assert.deepEqual(message.location(), { url: "https://x.test/app.js", lineNumber: 4, columnNumber: 2 });
66
- });
67
-
68
- it("has no location without a stack, and an empty text for null", async () => {
69
- const { ws, logs } = await setup();
70
- let message!: ConsoleMessage;
71
- logs.on("console", (m) => (message = m));
72
- emit(ws, consoleEntry("top", "warn", "x", { text: null }));
73
- assert.equal(message.location(), undefined);
74
- assert.equal(message.text(), "");
75
- assert.equal(message.type(), "warn");
76
- assert.equal(message.level, "warn");
77
- });
78
-
79
- it("console.error is a log entry, not a page error", async () => {
80
- const { ws, logs } = await setup();
81
- emit(ws, consoleEntry("top", "error", "handled problem"));
82
- assert.equal(logs.entries.length, 1);
83
- assert.equal(logs.entries[0]!.level, "error");
84
- assert.equal(logs.errors.length, 0);
85
- });
86
- });
87
-
88
- describe("PageLogs page errors", () => {
89
- it("turns an uncaught exception into a PageError with a readable stack", async () => {
90
- const { ws, logs } = await setup();
91
- const seen: PageError[] = [];
92
- logs.on("pageerror", (error) => seen.push(error));
93
- emit(ws, errorEntry("top", "TypeError: x is null", {
94
- stackTrace: {
95
- callFrames: [
96
- { functionName: "click", url: "https://x.test/app.js", lineNumber: 10, columnNumber: 5 },
97
- { functionName: "", url: "https://x.test/app.js", lineNumber: 20, columnNumber: 1 },
98
- ],
99
- },
100
- }));
101
- assert.equal(seen.length, 1);
102
- const [error] = seen as [PageError];
103
- assert.ok(error instanceof Error);
104
- assert.ok(error instanceof PageError);
105
- assert.equal(error.message, "TypeError: x is null");
106
- assert.equal(error.name, "PageError");
107
- assert.equal(error.timestamp, 2000);
108
- assert.equal(
109
- error.stack,
110
- "TypeError: x is null\n at click (https://x.test/app.js:10:5)\n at https://x.test/app.js:20:1",
111
- );
112
- assert.deepEqual(logs.errors, [error]);
113
- assert.deepEqual(logs.entries.map((entry) => entry.type), ["javascript"]);
114
- });
115
-
116
- it("a PageError without a stack is just its text", async () => {
117
- const { ws, logs } = await setup();
118
- emit(ws, errorEntry("top", "Error: bare"));
119
- assert.equal(logs.errors[0]!.stack, "Error: bare");
120
- });
121
- });
122
-
123
- describe("PageLogs scope", () => {
124
- it("keeps entries of iframes, drops other pages and entries without a context", async () => {
125
- const { ws, logs } = await setup();
126
- emit(ws, consoleEntry("frame", "log", "from frame"));
127
- emit(ws, consoleEntry("other", "log", "from another page"));
128
- emit(ws, consoleEntry(undefined, "log", "from a worker"));
129
- assert.deepEqual(logs.entries.map((entry) => entry.text), ["from frame"]);
130
- });
131
-
132
- it("records entries of other types without emitting an event", async () => {
133
- const { ws, logs } = await setup();
134
- let events = 0;
135
- logs.on("console", () => events++);
136
- logs.on("pageerror", () => events++);
137
- emit(ws, { type: "deprecation", level: "warn", text: "old api", timestamp: 3, source: source("top") });
138
- assert.equal(events, 0);
139
- assert.deepEqual(logs.entries.map((entry) => [entry.type, entry.text]), [["deprecation", "old api"]]);
140
- });
141
- });
142
-
143
- describe("PageLogs buffer", () => {
144
- it("keeps the newest entries and counts what it dropped", async () => {
145
- const { ws, logs } = await setup({ maxEntries: 3 });
146
- for (let i = 1; i <= 5; i++) emit(ws, consoleEntry("top", "log", `m${i}`));
147
- assert.deepEqual(logs.entries.map((entry) => entry.text), ["m3", "m4", "m5"]);
148
- assert.equal(logs.dropped, 2);
149
- });
150
-
151
- it("records level, type, method, text and timestamp", async () => {
152
- const { ws, logs } = await setup();
153
- emit(ws, consoleEntry("top", "warn", "careful"));
154
- assert.deepEqual(logs.entries[0], { level: "warn", type: "console", method: "warn", text: "careful", timestamp: 1000 });
155
- emit(ws, errorEntry("top", "Error: x"));
156
- assert.deepEqual(logs.entries[1], { level: "error", type: "javascript", text: "Error: x", timestamp: 2000 });
157
- });
158
-
159
- it("clear empties entries, errors and the dropped count", async () => {
160
- const { ws, logs } = await setup({ maxEntries: 1 });
161
- emit(ws, consoleEntry("top", "log", "a"));
162
- emit(ws, consoleEntry("top", "log", "b"));
163
- emit(ws, errorEntry("top", "Error: x"));
164
- logs.clear();
165
- assert.equal(logs.entries.length, 0);
166
- assert.equal(logs.errors.length, 0);
167
- assert.equal(logs.dropped, 0);
168
- });
169
-
170
- it("entries is a snapshot: later entries do not change what was read", async () => {
171
- const { ws, logs } = await setup();
172
- emit(ws, consoleEntry("top", "log", "a"));
173
- const before = logs.entries;
174
- emit(ws, consoleEntry("top", "log", "b"));
175
- assert.equal(before.length, 1);
176
- });
177
- });
178
-
179
- describe("PageLogs lifecycle", () => {
180
- it("a throwing listener does not stop the others or the recording", async () => {
181
- const { ws, logs } = await setup();
182
- let second = 0;
183
- logs.on("console", () => {
184
- throw new Error("listener failed");
185
- });
186
- logs.on("console", () => second++);
187
- emit(ws, consoleEntry("top", "log", "a"));
188
- assert.equal(second, 1);
189
- assert.equal(logs.entries.length, 1);
190
- });
191
-
192
- it("off removes a listener", async () => {
193
- const { ws, logs } = await setup();
194
- let count = 0;
195
- const listener = () => count++;
196
- logs.on("console", listener);
197
- logs.off("console", listener);
198
- emit(ws, consoleEntry("top", "log", "a"));
199
- assert.equal(count, 0);
200
- });
201
-
202
- it("subscribes to log.entryAdded and releases it on dispose", async () => {
203
- const { ws, logs } = await setup();
204
- assert.ok(ws.sent.some((m) => m.method === "session.subscribe" && (m.params as any).events[0] === "log.entryAdded"));
205
- await logs.dispose();
206
- await tick(10);
207
- emit(ws, consoleEntry("top", "log", "late"));
208
- assert.equal(logs.entries.length, 0);
209
- assert.ok(ws.sent.some((m) => m.method === "session.unsubscribe"), "the log subscription was released");
210
- await assert.doesNotReject(logs.dispose());
211
- });
212
- });
@@ -1,208 +0,0 @@
1
- import EventEmitter from "node:events";
2
- import logger from "../logger/index.js";
3
- import { fromRemoteValue } from "../script/serialize.js";
4
- import type { BiDiConnector } from "../transport/bidi-connection.js";
5
- import type { Subscription } from "../transport/subscriptions.js";
6
- import type { ConsoleLogEntry, Entry, Level } from "../types/bidi-modules/log.js";
7
- import type { StackFrame, StackTrace } from "../types/bidi-modules/script.js";
8
- import type { ContextTree } from "./context-tree.js";
9
-
10
- /** How many entries a page keeps before the oldest are dropped */
11
- const DEFAULT_MAX_ENTRIES = 1000;
12
-
13
- /** One line of the browser's log, as kept for reports */
14
- export interface LogRecord {
15
- level: Level;
16
- /** "console", "javascript" (an uncaught exception), or another type the browser reports */
17
- type: string;
18
- /** The console method, for `type: "console"` */
19
- method?: string;
20
- text: string;
21
- timestamp: number;
22
- }
23
-
24
- export interface SourceLocation {
25
- url: string;
26
- lineNumber: number;
27
- columnNumber: number;
28
- }
29
-
30
- /** A call to `console.log`, `console.warn`, … in the page */
31
- export class ConsoleMessage {
32
- constructor(
33
- /** "debug" | "info" | "warn" | "error" */
34
- public readonly level: Level,
35
- private readonly method: string,
36
- private readonly message: string,
37
- /** The arguments as plain values; DOM nodes and functions stay opaque (ElementHandle, RemoteObject) */
38
- public readonly args: unknown[],
39
- public readonly timestamp: number,
40
- public readonly stackTrace?: StackTrace,
41
- ) {}
42
-
43
- /** The console method: "log", "warn", "error", "info", "debug", … */
44
- public type(): string {
45
- return this.method;
46
- }
47
-
48
- /** What the browser prints for the call */
49
- public text(): string {
50
- return this.message;
51
- }
52
-
53
- /** Where the call was made, when the browser says */
54
- public location(): SourceLocation | undefined {
55
- const frame = this.stackTrace?.callFrames[0];
56
- return frame && { url: frame.url, lineNumber: frame.lineNumber, columnNumber: frame.columnNumber };
57
- }
58
- }
59
-
60
- /** An exception nobody caught in the page */
61
- export class PageError extends Error {
62
- public readonly timestamp: number;
63
-
64
- constructor(text: string, timestamp: number, stackTrace?: StackTrace) {
65
- super(text);
66
- this.name = "PageError";
67
- this.timestamp = timestamp;
68
- this.stack = [text, ...(stackTrace?.callFrames ?? []).map(formatFrame)].join("\n");
69
- }
70
- }
71
-
72
- function formatFrame({ functionName, url, lineNumber, columnNumber }: StackFrame): string {
73
- const where = `${url}:${lineNumber}:${columnNumber}`;
74
- return functionName ? ` at ${functionName} (${where})` : ` at ${where}`;
75
- }
76
-
77
- interface EventMap {
78
- console: [ConsoleMessage];
79
- pageerror: [PageError];
80
- }
81
- export type LogEventName = keyof EventMap;
82
-
83
- /**
84
- * Collects the browser's log (`console.*` calls and uncaught exceptions) of one page, frames included,
85
- * in a ring buffer, and emits `console` and `pageerror` events.
86
- */
87
- export class PageLogs {
88
- private events = new EventEmitter();
89
- private buffer: LogRecord[] = [];
90
- private uncaught: PageError[] = [];
91
- private droppedCount = 0;
92
- private disposed = false;
93
- private subscription: Subscription | undefined;
94
-
95
- private constructor(
96
- private connector: BiDiConnector,
97
- private tree: ContextTree,
98
- private contextId: string,
99
- private maxEntries: number,
100
- ) {}
101
-
102
- public static async start(
103
- connector: BiDiConnector,
104
- tree: ContextTree,
105
- contextId: string,
106
- options: { maxEntries?: number } = {},
107
- ): Promise<PageLogs> {
108
- const logs = new PageLogs(connector, tree, contextId, options.maxEntries ?? DEFAULT_MAX_ENTRIES);
109
- connector.onEvent("log.entryAdded", logs.onEntry);
110
- try {
111
- logs.subscription = await connector.subscribe(["log.entryAdded"]);
112
- } catch (err) {
113
- connector.offEvent("log.entryAdded", logs.onEntry);
114
- throw err;
115
- }
116
- return logs;
117
- }
118
-
119
- /** The newest entries, oldest first (a copy) */
120
- public get entries(): readonly LogRecord[] {
121
- return [...this.buffer];
122
- }
123
-
124
- /** Uncaught exceptions seen since the start or the last `clear()` */
125
- public get errors(): readonly PageError[] {
126
- return [...this.uncaught];
127
- }
128
-
129
- /** How many entries fell out of the buffer */
130
- public get dropped(): number {
131
- return this.droppedCount;
132
- }
133
-
134
- public on<E extends LogEventName>(event: E, listener: (...args: EventMap[E]) => void): void {
135
- this.events.on(event, listener as (...args: unknown[]) => void);
136
- }
137
-
138
- public off<E extends LogEventName>(event: E, listener: (...args: EventMap[E]) => void): void {
139
- this.events.off(event, listener as (...args: unknown[]) => void);
140
- }
141
-
142
- public clear(): void {
143
- this.buffer = [];
144
- this.uncaught = [];
145
- this.droppedCount = 0;
146
- }
147
-
148
- /** Idempotent */
149
- public async dispose(): Promise<void> {
150
- if (this.disposed) return;
151
- this.disposed = true;
152
- this.connector.offEvent("log.entryAdded", this.onEntry);
153
- this.buffer = [];
154
- this.uncaught = [];
155
- await this.subscription?.unsubscribe();
156
- }
157
-
158
- private onEntry = (entry: Entry) => {
159
- const context = entry.source.context;
160
- // Worker realms have no context; entries of other pages are not ours
161
- if (!context || !this.tree.isWithin(context, this.contextId)) return;
162
-
163
- const text = entry.text ?? "";
164
- const record: LogRecord = {
165
- level: entry.level,
166
- type: entry.type,
167
- ...(entry.type === "console" && { method: (entry as ConsoleLogEntry).method }),
168
- text,
169
- timestamp: entry.timestamp,
170
- };
171
- this.buffer.push(record);
172
- if (this.buffer.length > this.maxEntries) {
173
- this.buffer.shift();
174
- this.droppedCount++;
175
- }
176
-
177
- if (entry.type === "console") {
178
- const consoleEntry = entry as ConsoleLogEntry;
179
- this.emit(
180
- "console",
181
- new ConsoleMessage(
182
- entry.level,
183
- consoleEntry.method,
184
- text,
185
- (consoleEntry.args ?? []).map((arg) => fromRemoteValue(arg)),
186
- entry.timestamp,
187
- entry.stackTrace,
188
- ),
189
- );
190
- } else if (entry.type === "javascript") {
191
- const error = new PageError(text, entry.timestamp, entry.stackTrace);
192
- this.uncaught.push(error);
193
- if (this.uncaught.length > this.maxEntries) this.uncaught.shift();
194
- this.emit("pageerror", error);
195
- }
196
- };
197
-
198
- /** A listener that throws must not stop the recording or the other listeners */
199
- private emit<E extends LogEventName>(event: E, ...args: EventMap[E]) {
200
- for (const listener of this.events.rawListeners(event)) {
201
- try {
202
- listener(...args);
203
- } catch (err) {
204
- logger.error("A %s listener threw", event, { err });
205
- }
206
- }
207
- }
208
- }
@@ -1,19 +0,0 @@
1
- import { WaitTimeoutError } from "../wait/wait-until.js";
2
-
3
- /** No download started in time */
4
- export class DownloadWaitTimeoutError extends WaitTimeoutError<undefined> {
5
- constructor(timeout: number) {
6
- super(timeout, undefined);
7
- this.name = "DownloadWaitTimeoutError";
8
- this.message = `waitForDownload(): no download started within ${timeout}ms`;
9
- }
10
- }
11
-
12
- /** No file picker opened in time */
13
- export class FileChooserTimeoutError extends WaitTimeoutError<undefined> {
14
- constructor(timeout: number) {
15
- super(timeout, undefined);
16
- this.name = "FileChooserTimeoutError";
17
- this.message = `waitForFileChooser(): no file chooser opened within ${timeout}ms`;
18
- }
19
- }