@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,203 +0,0 @@
1
- import type {
2
- ArgumentValue,
3
- RemoteValue,
4
- } from "../types/bidi-modules/script.js";
5
- import { ElementHandle } from "./element-handle.js";
6
-
7
- /** A value passed to a page function */
8
- export type Arg = unknown;
9
-
10
- /** Deepest nesting `toLocalValue` accepts; a second guard next to cycle detection */
11
- const MAX_DEPTH = 100;
12
-
13
- /** A remote value that cannot be copied to plain JS (function, window, promise, …), kept by type and handle */
14
- export class RemoteObject {
15
- constructor(
16
- public readonly type: string,
17
- public readonly handle?: string,
18
- /** The deserialized value, when the browser sent one and handles were kept */
19
- public readonly value?: unknown,
20
- ) {}
21
- }
22
-
23
- /** Serializes `value` for `script.callFunction` arguments; throws TypeError for what cannot cross the wire */
24
- export function toLocalValue(value: Arg): ArgumentValue {
25
- return serialize(value, new Set(), 0);
26
- }
27
-
28
- function serialize(value: unknown, ancestors: Set<object>, depth: number): ArgumentValue {
29
- if (depth > MAX_DEPTH) throw new TypeError("cannot serialize a value that is too deeply nested");
30
-
31
- switch (typeof value) {
32
- case "undefined":
33
- return { type: "undefined" };
34
- case "boolean":
35
- return { type: "boolean", value };
36
- case "string":
37
- return { type: "string", value };
38
- case "number":
39
- return { type: "number", value: numberValue(value) };
40
- case "bigint":
41
- return { type: "bigint", value: value.toString() };
42
- case "function":
43
- case "symbol":
44
- throw new TypeError(`cannot serialize ${typeof value}`);
45
- }
46
-
47
- if (value === null) return { type: "null" };
48
- if (value instanceof ElementHandle) {
49
- return value.handle === undefined
50
- ? { sharedId: value.sharedId }
51
- : { sharedId: value.sharedId, handle: value.handle };
52
- }
53
- if (value instanceof Date) return { type: "date", value: value.toISOString() };
54
- if (value instanceof RegExp) {
55
- return { type: "regexp", value: { pattern: value.source, flags: value.flags } };
56
- }
57
-
58
- const object = value as object;
59
- if (ancestors.has(object)) throw new TypeError("cannot serialize a circular structure");
60
- ancestors.add(object);
61
- try {
62
- const next = (item: unknown) => serialize(item, ancestors, depth + 1);
63
- if (Array.isArray(object)) return { type: "array", value: object.map(next) };
64
- if (object instanceof Set) return { type: "set", value: [...object].map(next) };
65
- if (object instanceof Map) {
66
- return {
67
- type: "map",
68
- value: [...object].map(([key, item]) => [
69
- typeof key === "string" ? key : next(key),
70
- next(item),
71
- ]),
72
- };
73
- }
74
- const prototype = Object.getPrototypeOf(object);
75
- if (prototype !== Object.prototype && prototype !== null) {
76
- throw new TypeError(`cannot serialize ${prototype.constructor?.name ?? "object"}`);
77
- }
78
- return {
79
- type: "object",
80
- value: Object.entries(object).map(([key, item]) => [key, next(item)]),
81
- };
82
- } finally {
83
- ancestors.delete(object);
84
- }
85
- }
86
-
87
- function numberValue(value: number): number | string {
88
- if (Number.isNaN(value)) return "NaN";
89
- if (value === Infinity) return "Infinity";
90
- if (value === -Infinity) return "-Infinity";
91
- if (Object.is(value, -0)) return "-0";
92
- return value;
93
- }
94
-
95
- export interface DeserializeOptions {
96
- /** Return objects that carry a handle as `RemoteObject`s (with `.value`), so the caller can disown them later */
97
- keepHandles?: boolean;
98
- }
99
-
100
- /** Converts a remote value to plain JS; what cannot be copied becomes a `RemoteObject` */
101
- export function fromRemoteValue(value: RemoteValue, options: DeserializeOptions = {}): unknown {
102
- return deserialize(value, new Map(), options);
103
- }
104
-
105
- function deserialize(
106
- value: RemoteValue,
107
- known: Map<string, unknown>,
108
- options: DeserializeOptions,
109
- ): unknown {
110
- const loose = value as RemoteValue & {
111
- value?: unknown;
112
- handle?: string;
113
- internalId?: string;
114
- sharedId?: string;
115
- };
116
-
117
- switch (value.type) {
118
- case "undefined":
119
- return undefined;
120
- case "null":
121
- return null;
122
- case "string":
123
- case "boolean":
124
- return value.value;
125
- case "number":
126
- return specialNumber(value.value);
127
- case "bigint":
128
- return BigInt(value.value);
129
- case "date":
130
- return new Date(value.value);
131
- case "regexp": {
132
- const { pattern, flags } = loose.value as { pattern: string; flags?: string };
133
- return new RegExp(pattern, flags);
134
- }
135
- case "node":
136
- return new ElementHandle(loose.sharedId ?? "", loose.handle);
137
- }
138
-
139
- const nested = (item: RemoteValue) => deserialize(item, known, options);
140
- const remember = <T>(result: T): T => {
141
- if (loose.internalId !== undefined) known.set(loose.internalId, result);
142
- return result;
143
- };
144
- const backReference = () =>
145
- loose.internalId !== undefined && known.has(loose.internalId)
146
- ? { found: true as const, value: known.get(loose.internalId) }
147
- : undefined;
148
-
149
- let result: unknown;
150
- switch (value.type) {
151
- case "array":
152
- case "set":
153
- case "object":
154
- case "map": {
155
- const reference = backReference();
156
- if (reference && loose.value === undefined) return reference.value;
157
- if (loose.value === undefined) break; // cut off by the serialization depth
158
- if (value.type === "array") {
159
- const list: unknown[] = remember([]);
160
- (loose.value as RemoteValue[]).forEach((item) => list.push(nested(item)));
161
- result = list;
162
- } else if (value.type === "set") {
163
- const set = remember(new Set<unknown>());
164
- (loose.value as RemoteValue[]).forEach((item) => set.add(nested(item)));
165
- result = set;
166
- } else {
167
- const entries = loose.value as Array<[RemoteValue | string, RemoteValue]>;
168
- const key = (raw: RemoteValue | string) =>
169
- typeof raw === "string" ? raw : nested(raw);
170
- if (value.type === "map") {
171
- const map = remember(new Map<unknown, unknown>());
172
- entries.forEach(([k, item]) => map.set(key(k), nested(item)));
173
- result = map;
174
- } else {
175
- const object: Record<string, unknown> = remember({});
176
- entries.forEach(([k, item]) => {
177
- object[String(key(k))] = nested(item);
178
- });
179
- result = object;
180
- }
181
- }
182
- return options.keepHandles && loose.handle !== undefined
183
- ? new RemoteObject(value.type, loose.handle, result)
184
- : result;
185
- }
186
- }
187
- return new RemoteObject(value.type, loose.handle);
188
- }
189
-
190
- function specialNumber(value: number | string): number {
191
- switch (value) {
192
- case "NaN":
193
- return NaN;
194
- case "Infinity":
195
- return Infinity;
196
- case "-Infinity":
197
- return -Infinity;
198
- case "-0":
199
- return -0;
200
- default:
201
- return Number(value);
202
- }
203
- }
@@ -1,18 +0,0 @@
1
- import type Page from "../browser/page.js";
2
- import type { BiDiConnector } from "../transport/bidi-connection.js";
3
-
4
- /**
5
- * Test-only: replaces the page body with `html`, then runs `setup` (JavaScript source,
6
- * wrapped in its own function scope) in the page. Reaches into Page's private fields.
7
- */
8
- export async function setContent(page: Page, html: string, setup = "") {
9
- const internals = page as unknown as { biDiConnector: BiDiConnector; contextId: string };
10
- const result = await internals.biDiConnector.send("script.evaluate", {
11
- expression: `document.body.innerHTML = ${JSON.stringify(html)}; (() => { ${setup} })();`,
12
- awaitPromise: false,
13
- target: { context: internals.contextId },
14
- });
15
- if (result.type === "exception") {
16
- throw new Error(`setContent failed: ${result.exceptionDetails.text}`);
17
- }
18
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * Minimal WebSocket stand-in for BiDiConnector unit tests. Commands the connector sends are
3
- * recorded in `sent`; tests answer them with `reply` and push events with `emitEvent`.
4
- */
5
- export class FakeWebSocket extends EventTarget {
6
- /** Every command the connector sent, parsed */
7
- public readonly sent: Array<{ id: number; method: string; params?: unknown }> = [];
8
- public closed = false;
9
-
10
- send(data: string) {
11
- this.sent.push(JSON.parse(data));
12
- }
13
-
14
- close() {
15
- this.closed = true;
16
- }
17
-
18
- private receive(message: object) {
19
- this.dispatchEvent(new MessageEvent("message", { data: JSON.stringify(message) }));
20
- }
21
-
22
- reply(id: number, result: object = {}) {
23
- this.receive({ type: "success", id, result });
24
- }
25
-
26
- replyError(id: number | null, error: string, message = "boom", stacktrace?: string) {
27
- this.receive({ type: "error", id, error, message, stacktrace });
28
- }
29
-
30
- emitEvent(method: string, params: object) {
31
- this.receive({ type: "event", method, params });
32
- }
33
-
34
- /** Simulates the socket closing */
35
- drop() {
36
- this.dispatchEvent(new Event("close"));
37
- }
38
- }
39
-
40
- /** Waits one macrotask so the connector's promise callbacks and timers have run */
41
- export function tick(ms = 0) {
42
- return new Promise((resolve) => setTimeout(resolve, ms));
43
- }
44
-
45
- /**
46
- * Answers every command the connector sends. `handlers` maps a method to its result;
47
- * unknown methods get `{}`; `session.subscribe` gets a fresh subscription id unless a handler answers it.
48
- * A handler that returns an Error with a `code` property answers with that BiDi error code.
49
- */
50
- export function autoReply(
51
- ws: FakeWebSocket,
52
- handlers: Record<string, (params: any) => object | Error> = {},
53
- ) {
54
- let answered = 0;
55
- let subscriptions = 0;
56
- const pump = setInterval(() => {
57
- while (answered < ws.sent.length) {
58
- const { id, method, params } = ws.sent[answered++]!;
59
- const handler = handlers[method];
60
- if (method === "session.subscribe" && !handler) {
61
- ws.reply(id, { subscription: `sub-${++subscriptions}` });
62
- continue;
63
- }
64
- const result = handler?.(params) ?? {};
65
- // An Error with a `code` answers with that BiDi error code
66
- if (result instanceof Error) ws.replyError(id, (result as { code?: string }).code ?? "unknown error", result.message);
67
- else if (method === "session.subscribe") ws.reply(id, { subscription: `sub-${++subscriptions}`, ...result });
68
- else ws.reply(id, result);
69
- }
70
- }, 1).unref();
71
- return () => clearInterval(pump);
72
- }
@@ -1,154 +0,0 @@
1
- import type { BiDiConnector } from "../transport/bidi-connection.js";
2
- import { BiDiError } from "../transport/bidi-error.js";
3
- import type { ErrorCode } from "../types/bidi-protocols/error.js";
4
- import type { RemoteValue } from "../types/bidi-modules/script.js";
5
-
6
- /** `{ error }` rejects the command like a BiDi error reply (e.g. "no such node") */
7
- export type StubResponse =
8
- | RemoteValue
9
- | { exception: string }
10
- | { hang: true }
11
- | { error: ErrorCode };
12
-
13
- /** Converts plain JS (JSON-like) to the RemoteValue the browser would answer with */
14
- export function remote(value: unknown): RemoteValue {
15
- if (value === undefined) return { type: "undefined" };
16
- if (value === null) return { type: "null" };
17
- if (typeof value === "string") return { type: "string", value };
18
- if (typeof value === "number") return { type: "number", value };
19
- if (typeof value === "boolean") return { type: "boolean", value };
20
- if (Array.isArray(value)) return { type: "array", value: value.map(remote) };
21
- return {
22
- type: "object",
23
- value: Object.entries(value as object).map(([key, item]) => [key, remote(item)]),
24
- };
25
- }
26
-
27
- /** Plain JS for a serialized argument, so tests can assert on what the page function received */
28
- function decodeArgument(argument: any): unknown {
29
- if (argument.sharedId !== undefined) return { sharedId: argument.sharedId };
30
- switch (argument.type) {
31
- case "undefined":
32
- return undefined;
33
- case "null":
34
- return null;
35
- case "array":
36
- return argument.value.map(decodeArgument);
37
- case "object":
38
- return Object.fromEntries(
39
- argument.value.map(([key, item]: [string, unknown]) => [key, decodeArgument(item)]),
40
- );
41
- default:
42
- return argument.value;
43
- }
44
- }
45
-
46
- /** A `script.callFunction` the stub received */
47
- export interface StubCall {
48
- functionDeclaration: string;
49
- /** Arguments decoded to plain JS */
50
- args: unknown[];
51
- }
52
-
53
- /**
54
- * Fake BiDiConnector for unit tests. Every `script.evaluate` and `script.callFunction` answers
55
- * with the next queued response; the last response repeats. `{ hang: true }` never answers.
56
- * `input.performActions`, `input.setFiles` and `browsingContext.navigate` resolve empty. Sent commands are recorded.
57
- * `browsingContext.locateNodes` answers with as many nodes as the next queued count (see `nodeCounts`;
58
- * default: always one), capped by `maxNodeCount`, named `stub-node-<n>`.
59
- * `expressions` holds one entry per script call: the expression, or the function declaration
60
- * followed by its arguments as a JSON comment, so tests can match on either.
61
- */
62
- export function stubConnector(...responses: StubResponse[]) {
63
- if (responses.length === 0) {
64
- throw new Error("stubConnector needs at least one response");
65
- }
66
- const expressions: string[] = [];
67
- /** Every `script.callFunction`, in order */
68
- const calls: StubCall[] = [];
69
- /** Every command sent, in order */
70
- const sent: Array<{ method: string; params: unknown }> = [];
71
- let scriptCalls = 0;
72
- let counts: number[] = [1];
73
- let locateCalls = 0;
74
- let locateFailure: Error | undefined;
75
- const unsupported = new Set<string>();
76
- const failures = new Map<string, Error>();
77
-
78
- const connector = {
79
- async send(method: string, params: any) {
80
- sent.push({ method, params });
81
- const failure = failures.get(method);
82
- if (failure) throw failure;
83
- if (method === "browsingContext.locateNodes") {
84
- if (locateFailure) throw locateFailure;
85
- if (unsupported.has(params.locator.type)) {
86
- throw new BiDiError(method, "unsupported operation", `${params.locator.type} is not supported`);
87
- }
88
- const available = counts[Math.min(locateCalls++, counts.length - 1)]!;
89
- const length = Math.min(available, params.maxNodeCount ?? Infinity);
90
- return {
91
- nodes: Array.from({ length }, (_, index) => ({
92
- type: "node",
93
- sharedId: `stub-node-${index}`,
94
- })),
95
- };
96
- }
97
- if (
98
- method === "input.performActions" ||
99
- method === "input.setFiles" ||
100
- method === "browsingContext.navigate"
101
- ) {
102
- return {};
103
- }
104
- if (method === "script.evaluate") {
105
- expressions.push(params.expression);
106
- } else if (method === "script.callFunction") {
107
- const args = (params.arguments ?? []).map(decodeArgument);
108
- calls.push({ functionDeclaration: params.functionDeclaration, args });
109
- expressions.push(`${params.functionDeclaration}\n/* args: ${JSON.stringify(args)} */`);
110
- } else {
111
- throw new Error(`stubConnector does not handle ${method}`);
112
- }
113
- const response = responses[Math.min(scriptCalls++, responses.length - 1)]!;
114
- if ("hang" in response) {
115
- return new Promise(() => {});
116
- }
117
- if ("error" in response) {
118
- throw new BiDiError(method, response.error, "stub error");
119
- }
120
- if ("exception" in response) {
121
- return {
122
- type: "exception",
123
- exceptionDetails: { text: response.exception },
124
- realm: "stub",
125
- };
126
- }
127
- return { type: "success", result: response, realm: "stub" };
128
- },
129
- };
130
-
131
- return {
132
- connector: connector as unknown as BiDiConnector,
133
- expressions,
134
- calls,
135
- sent,
136
- /** Queues how many nodes each `browsingContext.locateNodes` finds; the last count repeats. Restarts the queue */
137
- nodeCounts(...queue: number[]) {
138
- counts = queue.length > 0 ? queue : [1];
139
- locateCalls = 0;
140
- },
141
- /** Makes `browsingContext.locateNodes` answer "unsupported operation" for these locator types (e.g. "innerText") */
142
- unsupportedLocators(...types: string[]) {
143
- types.forEach((type) => unsupported.add(type));
144
- },
145
- /** Makes every `browsingContext.locateNodes` reject with `error` */
146
- failLocate(error: Error) {
147
- locateFailure = error;
148
- },
149
- /** Makes every send of `method` reject with `error` */
150
- failCommand(method: string, error: Error) {
151
- failures.set(method, error);
152
- },
153
- };
154
- }
@@ -1,30 +0,0 @@
1
- import { expect, test } from "../api.js";
2
-
3
- test("My first test", async ({ page }) => {
4
- await page.goto("https://itmetsam.nl");
5
- const link = page.locator("a[text()='Start een project']");
6
- await link.click();
7
- await page.waitForNetworkIdle();
8
- const nameField = page.locator("input[@id='contact-name']");
9
- await nameField.fill("test");
10
- const emailField = page.locator("input[@id='contact-email']");
11
- await emailField.fill("sammiedegroot@gmail.com");
12
- const subjectField = page.locator("input[@id='contact-subject']");
13
- await subjectField.fill("Test subject");
14
- const messageField = page.locator("textarea[@id='contact-message']");
15
- await messageField.fill(
16
- "This is a very long message which describes what I would like",
17
- );
18
- await expect(nameField).toBeVisible();
19
- await expect(nameField).toHaveValue("test");
20
- await expect(emailField).toHaveValue("sammiedegroot@gmail.com");
21
- await expect(subjectField).toHaveValue("Test subject");
22
- await expect(messageField).toHaveValue(/very long message/);
23
-
24
- const submitButton = page.locator("button[@type='submit']");
25
- await submitButton.click();
26
- const successAlert = page.locator("div[@class='alert-description']");
27
- await expect(successAlert).toContainText(
28
- "Je aanvraag is ontvangen! Je ontvangt automatisch een kopie van de aanvraag op het opgegeven E-mailadres. Ik neem binnen 3 werkdagen contact met je op over je aanvraag.",
29
- );
30
- });
@@ -1,155 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { FakeWebSocket, tick } from "../testing/fake-websocket.js";
4
- import { WaitTimeoutError } from "../wait/wait-until.js";
5
- import { BiDiError } from "./bidi-error.js";
6
- import { BiDiConnector } from "./bidi-connection.js";
7
-
8
- function setup(options?: { commandTimeout?: number }) {
9
- const ws = new FakeWebSocket();
10
- const connector = new BiDiConnector(ws as unknown as WebSocket, options);
11
- return { ws, connector };
12
- }
13
-
14
- describe("BiDiConnector error replies", () => {
15
- it("rejects only the matching command with a BiDiError", async () => {
16
- const { ws, connector } = setup();
17
- const first = connector.send("session.status", {});
18
- const second = connector.send("browser.close", {});
19
- ws.replyError(0, "no such frame", "frame is gone", "stack…");
20
- await assert.rejects(first, (err: unknown) => {
21
- assert.ok(err instanceof BiDiError);
22
- assert.equal(err.code, "no such frame");
23
- assert.equal(err.command, "session.status");
24
- assert.equal(err.remoteStack, "stack…");
25
- assert.match(err.message, /frame is gone/);
26
- return true;
27
- });
28
- ws.reply(1);
29
- assert.deepEqual(await second, {});
30
- });
31
-
32
- it("ignores an error reply for an unknown or null id", async () => {
33
- const { ws, connector } = setup();
34
- ws.replyError(99, "unknown error");
35
- ws.replyError(null, "invalid session id");
36
- const pending = connector.send("session.status", {});
37
- ws.reply(0, { ready: true });
38
- assert.deepEqual(await pending, { ready: true });
39
- });
40
- });
41
-
42
- describe("BiDiConnector timeouts", () => {
43
- it("rejects with code timeout and ignores a late reply", async () => {
44
- const { ws, connector } = setup();
45
- const pending = connector.send("session.status", {}, { timeout: 20 });
46
- await assert.rejects(pending, (err: unknown) => {
47
- assert.ok(err instanceof BiDiError);
48
- assert.equal(err.code, "timeout");
49
- assert.equal(err.command, "session.status");
50
- return true;
51
- });
52
- assert.doesNotThrow(() => ws.reply(0));
53
- });
54
-
55
- it("uses the constructor commandTimeout by default", async () => {
56
- const { connector } = setup({ commandTimeout: 15 });
57
- await assert.rejects(connector.send("session.status", {}), { code: "timeout" });
58
- });
59
-
60
- it("does not leave a timer running after a reply", async () => {
61
- const { ws, connector } = setup({ commandTimeout: 30 });
62
- const pending = connector.send("session.status", {});
63
- ws.reply(0);
64
- await pending;
65
- await tick(60); // an uncleared timer would try to reject a settled command: harmless, but must not throw
66
- });
67
- });
68
-
69
- describe("BiDiConnector socket close", () => {
70
- it("rejects pending commands and names them", async () => {
71
- const { ws, connector } = setup();
72
- const pending = connector.send("script.evaluate", {} as never);
73
- ws.drop();
74
- await assert.rejects(pending, /Websocket closed.*script\.evaluate/);
75
- });
76
- });
77
-
78
- describe("BiDiConnector.waitForEvent", () => {
79
- it("resolves with the params of the first matching event and removes its listener", async () => {
80
- const { ws, connector } = setup();
81
- const waiting = connector.waitForEvent(
82
- "browsingContext.load",
83
- (p) => p.context === "b",
84
- { timeout: 1000 },
85
- );
86
- ws.emitEvent("browsingContext.load", { context: "a" });
87
- ws.emitEvent("browsingContext.load", { context: "b", url: "x" });
88
- assert.deepEqual(await waiting, { context: "b", url: "x" });
89
- // listener gone: further events do not throw or leak
90
- ws.emitEvent("browsingContext.load", { context: "b" });
91
- });
92
-
93
- it("is registered before it returns, so an event emitted right after is seen", async () => {
94
- const { ws, connector } = setup();
95
- const waiting = connector.waitForEvent("browsingContext.load", () => true, { timeout: 1000 });
96
- ws.emitEvent("browsingContext.load", { context: "a" });
97
- assert.deepEqual(await waiting, { context: "a" });
98
- });
99
-
100
- it("rejects with WaitTimeoutError on timeout", async () => {
101
- const { connector } = setup();
102
- await assert.rejects(
103
- connector.waitForEvent("browsingContext.load", () => true, { timeout: 15 }),
104
- (err: unknown) => err instanceof WaitTimeoutError && err.timeout === 15,
105
- );
106
- });
107
-
108
- it("rejects with the abort reason and stops listening", async () => {
109
- const { ws, connector } = setup();
110
- const controller = new AbortController();
111
- const waiting = connector.waitForEvent("browsingContext.load", () => true, {
112
- timeout: 1000,
113
- signal: controller.signal,
114
- });
115
- controller.abort(new Error("stop"));
116
- await assert.rejects(waiting, /stop/);
117
- ws.emitEvent("browsingContext.load", { context: "a" });
118
- });
119
-
120
- it("rejects immediately when the signal is already aborted", async () => {
121
- const { connector } = setup();
122
- const signal = AbortSignal.abort(new Error("already"));
123
- await assert.rejects(
124
- connector.waitForEvent("browsingContext.load", () => true, { timeout: 1000, signal }),
125
- /already/,
126
- );
127
- });
128
- });
129
-
130
- describe("BiDiConnector.subscribe", () => {
131
- it("sends session.subscribe once for two subscribers and unsubscribes after both leave", async () => {
132
- const { ws, connector } = setup();
133
- const answer = async () => {
134
- await tick();
135
- const last = ws.sent[ws.sent.length - 1]!;
136
- ws.reply(last.id, last.method === "session.subscribe" ? { subscription: "s1" } : {});
137
- };
138
-
139
- const a = connector.subscribe(["browsingContext.load"]);
140
- await answer();
141
- const subA = await a;
142
- const subB = await connector.subscribe(["browsingContext.load"]);
143
- assert.equal(ws.sent.filter((m) => m.method === "session.subscribe").length, 1);
144
-
145
- await subA.unsubscribe();
146
- assert.equal(ws.sent.filter((m) => m.method === "session.unsubscribe").length, 0);
147
-
148
- const leaving = subB.unsubscribe();
149
- await answer();
150
- await leaving;
151
- const unsub = ws.sent.filter((m) => m.method === "session.unsubscribe");
152
- assert.equal(unsub.length, 1);
153
- assert.deepEqual(unsub[0]!.params, { subscriptions: ["s1"] });
154
- });
155
- });