@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
@@ -0,0 +1,149 @@
1
+ import EventEmitter from "node:events";
2
+ import logger from "../logger/index.js";
3
+ import { fromRemoteValue } from "../script/serialize.js";
4
+ /** How many entries a page keeps before the oldest are dropped */
5
+ const DEFAULT_MAX_ENTRIES = 1000;
6
+ /** A call to `console.log`, `console.warn`, … in the page */
7
+ export class ConsoleMessage {
8
+ constructor(
9
+ /** "debug" | "info" | "warn" | "error" */
10
+ level, method, message,
11
+ /** The arguments as plain values; DOM nodes and functions stay opaque (ElementHandle, RemoteObject) */
12
+ args, timestamp, stackTrace) {
13
+ this.level = level;
14
+ this.method = method;
15
+ this.message = message;
16
+ this.args = args;
17
+ this.timestamp = timestamp;
18
+ this.stackTrace = stackTrace;
19
+ }
20
+ /** The console method: "log", "warn", "error", "info", "debug", … */
21
+ type() {
22
+ return this.method;
23
+ }
24
+ /** What the browser prints for the call */
25
+ text() {
26
+ return this.message;
27
+ }
28
+ /** Where the call was made, when the browser says */
29
+ location() {
30
+ const frame = this.stackTrace?.callFrames[0];
31
+ return frame && { url: frame.url, lineNumber: frame.lineNumber, columnNumber: frame.columnNumber };
32
+ }
33
+ }
34
+ /** An exception nobody caught in the page */
35
+ export class PageError extends Error {
36
+ constructor(text, timestamp, stackTrace) {
37
+ super(text);
38
+ this.name = "PageError";
39
+ this.timestamp = timestamp;
40
+ this.stack = [text, ...(stackTrace?.callFrames ?? []).map(formatFrame)].join("\n");
41
+ }
42
+ }
43
+ function formatFrame({ functionName, url, lineNumber, columnNumber }) {
44
+ const where = `${url}:${lineNumber}:${columnNumber}`;
45
+ return functionName ? ` at ${functionName} (${where})` : ` at ${where}`;
46
+ }
47
+ /**
48
+ * Collects the browser's log (`console.*` calls and uncaught exceptions) of one page, frames included,
49
+ * in a ring buffer, and emits `console` and `pageerror` events.
50
+ */
51
+ export class PageLogs {
52
+ constructor(connector, tree, contextId, maxEntries) {
53
+ this.connector = connector;
54
+ this.tree = tree;
55
+ this.contextId = contextId;
56
+ this.maxEntries = maxEntries;
57
+ this.events = new EventEmitter();
58
+ this.buffer = [];
59
+ this.uncaught = [];
60
+ this.droppedCount = 0;
61
+ this.disposed = false;
62
+ this.onEntry = (entry) => {
63
+ const context = entry.source.context;
64
+ // Worker realms have no context; entries of other pages are not ours
65
+ if (!context || !this.tree.isWithin(context, this.contextId))
66
+ return;
67
+ const text = entry.text ?? "";
68
+ const record = {
69
+ level: entry.level,
70
+ type: entry.type,
71
+ ...(entry.type === "console" && { method: entry.method }),
72
+ text,
73
+ timestamp: entry.timestamp,
74
+ };
75
+ this.buffer.push(record);
76
+ if (this.buffer.length > this.maxEntries) {
77
+ this.buffer.shift();
78
+ this.droppedCount++;
79
+ }
80
+ if (entry.type === "console") {
81
+ const consoleEntry = entry;
82
+ this.emit("console", new ConsoleMessage(entry.level, consoleEntry.method, text, (consoleEntry.args ?? []).map((arg) => fromRemoteValue(arg)), entry.timestamp, entry.stackTrace));
83
+ }
84
+ else if (entry.type === "javascript") {
85
+ const error = new PageError(text, entry.timestamp, entry.stackTrace);
86
+ this.uncaught.push(error);
87
+ if (this.uncaught.length > this.maxEntries)
88
+ this.uncaught.shift();
89
+ this.emit("pageerror", error);
90
+ }
91
+ };
92
+ }
93
+ static async start(connector, tree, contextId, options = {}) {
94
+ const logs = new PageLogs(connector, tree, contextId, options.maxEntries ?? DEFAULT_MAX_ENTRIES);
95
+ connector.onEvent("log.entryAdded", logs.onEntry);
96
+ try {
97
+ logs.subscription = await connector.subscribe(["log.entryAdded"]);
98
+ }
99
+ catch (err) {
100
+ connector.offEvent("log.entryAdded", logs.onEntry);
101
+ throw err;
102
+ }
103
+ return logs;
104
+ }
105
+ /** The newest entries, oldest first (a copy) */
106
+ get entries() {
107
+ return [...this.buffer];
108
+ }
109
+ /** Uncaught exceptions seen since the start or the last `clear()` */
110
+ get errors() {
111
+ return [...this.uncaught];
112
+ }
113
+ /** How many entries fell out of the buffer */
114
+ get dropped() {
115
+ return this.droppedCount;
116
+ }
117
+ on(event, listener) {
118
+ this.events.on(event, listener);
119
+ }
120
+ off(event, listener) {
121
+ this.events.off(event, listener);
122
+ }
123
+ clear() {
124
+ this.buffer = [];
125
+ this.uncaught = [];
126
+ this.droppedCount = 0;
127
+ }
128
+ /** Idempotent */
129
+ async dispose() {
130
+ if (this.disposed)
131
+ return;
132
+ this.disposed = true;
133
+ this.connector.offEvent("log.entryAdded", this.onEntry);
134
+ this.buffer = [];
135
+ this.uncaught = [];
136
+ await this.subscription?.unsubscribe();
137
+ }
138
+ /** A listener that throws must not stop the recording or the other listeners */
139
+ emit(event, ...args) {
140
+ for (const listener of this.events.rawListeners(event)) {
141
+ try {
142
+ listener(...args);
143
+ }
144
+ catch (err) {
145
+ logger.error("A %s listener threw", event, { err });
146
+ }
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,9 @@
1
+ import { WaitTimeoutError } from "../wait/wait-until.js";
2
+ /** No download started in time */
3
+ export declare class DownloadWaitTimeoutError extends WaitTimeoutError<undefined> {
4
+ constructor(timeout: number);
5
+ }
6
+ /** No file picker opened in time */
7
+ export declare class FileChooserTimeoutError extends WaitTimeoutError<undefined> {
8
+ constructor(timeout: number);
9
+ }
@@ -0,0 +1,17 @@
1
+ import { WaitTimeoutError } from "../wait/wait-until.js";
2
+ /** No download started in time */
3
+ export class DownloadWaitTimeoutError extends WaitTimeoutError {
4
+ constructor(timeout) {
5
+ super(timeout, undefined);
6
+ this.name = "DownloadWaitTimeoutError";
7
+ this.message = `waitForDownload(): no download started within ${timeout}ms`;
8
+ }
9
+ }
10
+ /** No file picker opened in time */
11
+ export class FileChooserTimeoutError extends WaitTimeoutError {
12
+ constructor(timeout) {
13
+ super(timeout, undefined);
14
+ this.name = "FileChooserTimeoutError";
15
+ this.message = `waitForFileChooser(): no file chooser opened within ${timeout}ms`;
16
+ }
17
+ }
@@ -0,0 +1,370 @@
1
+ import Locator, { type TextOptions } from "../locator/locator.js";
2
+ import type { NetworkRequest, FailedRequest } from "../network/network-tracker.js";
3
+ import { Recorder, type RecorderOptions } from "../recorder/recorder.js";
4
+ import { ContextTree } from "./context-tree.js";
5
+ import { Dialog } from "./dialog.js";
6
+ import type { BrowserContext } from "./browser-context.js";
7
+ import { type PdfOptions } from "./pdf.js";
8
+ import { type EmulationOptions } from "./emulation.js";
9
+ import { Download } from "./download.js";
10
+ import { FileChooser } from "./file-chooser.js";
11
+ import { type ScreenshotOptions } from "./screenshot.js";
12
+ import { type ConsoleMessage, type LogRecord, type PageError } from "./page-logs.js";
13
+ import { type RouteHandler } from "../network/router.js";
14
+ import { Response } from "../network/response.js";
15
+ import { type NetworkMatch } from "../network/network-wait.js";
16
+ import type { UrlPattern } from "../network/url-match.js";
17
+ import { type PreloadHandle } from "../script/preload.js";
18
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
19
+ import type { BiDiCommands } from "../types/bidi.js";
20
+ import type { SamuraiTestConfig } from "../types/config.js";
21
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
22
+ /** Options that otherwise come from `samurai.config.ts` */
23
+ export type PageSettings = Pick<SamuraiTestConfig, "network" | "navigation" | "use">;
24
+ export type ReadinessState = NonNullable<BiDiCommands["browsingContext.navigate"]["params"]["wait"]>;
25
+ export type LoadState = "load" | "domcontentloaded" | "networkidle";
26
+ export interface NavigateOptions {
27
+ /** When navigateTo resolves. @default config `navigation.waitUntil`, then "complete" */
28
+ wait?: ReadinessState;
29
+ /** Time (ms) the navigation may take. @default config `navigation.timeout`, then 30000 */
30
+ timeout?: number;
31
+ /** Scheme added to a URL without one. @default "https" */
32
+ protocol?: "http" | "https";
33
+ }
34
+ interface NetworkEvents {
35
+ request: [NetworkRequest];
36
+ response: [Response];
37
+ requestfailed: [FailedRequest];
38
+ }
39
+ /** Everything `page.on` can listen for */
40
+ interface PageEvents extends NetworkEvents {
41
+ download: [Download];
42
+ filechooser: [FileChooser];
43
+ dialog: [Dialog];
44
+ console: [ConsoleMessage];
45
+ pageerror: [PageError];
46
+ }
47
+ export type PageEventName = keyof PageEvents;
48
+ export default class Page {
49
+ private biDiConnector;
50
+ private id;
51
+ private contextTree;
52
+ private helperRealm;
53
+ private initScripts;
54
+ private settings;
55
+ private networkTracker;
56
+ private pageEvents;
57
+ /** The navigation started by the last navigateTo; failures of it fail a wait */
58
+ private lastNavigation;
59
+ /** Base for relative page.goto URLs; falls back to the active run's baseURL */
60
+ private baseURL;
61
+ /** How far navigations that did not wait for the load got, by navigation id (the latest few) */
62
+ private navigationProgress;
63
+ private documentEvents;
64
+ private dialogHandling;
65
+ private pageLogs;
66
+ private loadedLogs;
67
+ private errorsAllowed;
68
+ private isClosed;
69
+ private browserContext;
70
+ private downloadTracker;
71
+ private fileChooserTracker;
72
+ private collectorPromise;
73
+ private responses;
74
+ private routerPromise;
75
+ private loadedRouter;
76
+ /** The context tree once loaded, for handlers that cannot wait for it */
77
+ private loadedTree;
78
+ private openDialogs;
79
+ /** `settings` override what `samurai.config.ts` says; without them the config is read when needed */
80
+ constructor(biDiConnector: BiDiConnector, contextId: string, settings?: PageSettings);
81
+ /** The BiDi browsing context id of this page's top-level context */
82
+ get contextId(): BrowsingContext;
83
+ /** The context tree, created on first use and shared by everything that needs to attribute frame events to this page */
84
+ tree(): Promise<ContextTree>;
85
+ /** Locates an element on the page and returns an interactable object */
86
+ locator(xpath: string): Locator;
87
+ /** Locates elements with a CSS selector */
88
+ getByCss(css: string): Locator;
89
+ /** Locates elements by their text: an exact match unless `match: "partial"` */
90
+ getByText(text: string, options?: TextOptions): Locator;
91
+ /** Locates form controls by their label text: an exact match unless `match: "partial"` */
92
+ getByLabel(text: string, options?: TextOptions): Locator;
93
+ /** Locates elements by their `data-testid` attribute */
94
+ getByTestId(testId: string): Locator;
95
+ /** Locates elements by ARIA role and, optionally, accessible name */
96
+ getByRole(role: string, options?: {
97
+ name?: string;
98
+ }): Locator;
99
+ /**
100
+ * Runs `script` before any page script in every document this page loads from now on, and once in
101
+ * the document that is loaded now. It runs in the page's own realm, so the page sees what it sets.
102
+ * A function receives `arg` (which must be JSON-serializable) as its only argument;
103
+ * a string is script source (statements) and takes no argument.
104
+ * @example
105
+ * await page.addInitScript(() => { window.__testMode = true; });
106
+ * await page.addInitScript((flags) => { window.flags = flags; }, { beta: true });
107
+ */
108
+ addInitScript(script: string | ((arg?: any) => void), arg?: unknown): Promise<PreloadHandle>;
109
+ /**
110
+ * Starts recording what a person does in the page as steps (see `Recorder`). The page must be driven by
111
+ * a person; stop with `recorder.stop()`.
112
+ */
113
+ record(options: RecorderOptions): Promise<Recorder>;
114
+ /** Removes everything the page registered in the browser: init scripts, framework helpers and event subscriptions */
115
+ dispose(): Promise<void>;
116
+ /** Sets the base that relative `goto` URLs resolve against; `undefined` falls back to the run's baseURL */
117
+ setBaseURL(url: string | undefined): void;
118
+ /**
119
+ * Navigates like `navigateTo`, but a URL starting with "/", "./" or "../" resolves against the base URL
120
+ * (`setBaseURL`, else the environment's `baseURL`).
121
+ * @example
122
+ * await page.goto("/products");
123
+ * await page.goto("https://example.com", { wait: "interactive" });
124
+ */
125
+ goto(url: string, options?: NavigateOptions): Promise<{
126
+ navigation: string | null;
127
+ url: string;
128
+ }>;
129
+ /**
130
+ * Navigates to `url` and resolves when the page reached `wait` (default "complete", the load event).
131
+ * A URL without a scheme gets `https://`. Throws NavigationError when the navigation fails or takes too long.
132
+ * The positional form `navigateTo(url, wait, protocol)` still works.
133
+ * @example
134
+ * await page.navigateTo("example.com");
135
+ * await page.navigateTo("localhost:3000", { wait: "interactive", protocol: "http" });
136
+ */
137
+ navigateTo(url: string, options?: NavigateOptions | ReadinessState, protocol?: "http" | "https"): Promise<{
138
+ navigation: string | null;
139
+ url: string;
140
+ }>;
141
+ /**
142
+ * Runs a navigation command and settles what waiting for it means: failures become NavigationErrors, and a
143
+ * navigation that did not wait for the load is watched so `waitForLoadState` can tell it from the old page.
144
+ */
145
+ private navigation;
146
+ /**
147
+ * Reloads the page and resolves when it reached `wait` (default like `navigateTo`). Firefox does not support
148
+ * `ignoreCache`; asking for it throws UnsupportedOperationError.
149
+ */
150
+ reload(options?: {
151
+ wait?: ReadinessState;
152
+ timeout?: number;
153
+ ignoreCache?: boolean;
154
+ }): Promise<{
155
+ navigation: string | null;
156
+ url: string;
157
+ }>;
158
+ /** Goes back one entry in the history. False when there is none to go back to */
159
+ goBack(): Promise<boolean>;
160
+ /** Goes forward one entry in the history. False when there is none */
161
+ goForward(): Promise<boolean>;
162
+ private traverse;
163
+ /** The URL of the page now */
164
+ url(): Promise<string>;
165
+ /** The title of the document now */
166
+ title(): Promise<string>;
167
+ /**
168
+ * Sets the size of the viewport (and, optionally, the device pixel ratio); `null` puts the browser's own size back.
169
+ * @example
170
+ * await page.setViewport({ width: 390, height: 844, devicePixelRatio: 3 });
171
+ */
172
+ setViewport(viewport: {
173
+ width: number;
174
+ height: number;
175
+ devicePixelRatio?: number;
176
+ } | null): Promise<void>;
177
+ /** Gives the page the size from config `use.viewport` (1280x720 by default); the browser does this when it opens a page */
178
+ applyDefaultViewport(): Promise<void>;
179
+ /**
180
+ * Takes a screenshot and returns the image (also written to `options.path`).
181
+ * @example
182
+ * await page.screenshot({ path: "out/home.png", fullPage: true });
183
+ */
184
+ screenshot(options?: ScreenshotOptions): Promise<Buffer>;
185
+ /** Renders the page as a PDF and returns it (also written to `options.path`) */
186
+ pdf(options?: PdfOptions): Promise<Buffer>;
187
+ /** The browser context (cookies, storage) this page belongs to */
188
+ context(): BrowserContext;
189
+ /** @internal Called when the page is opened in a context */
190
+ setContext(context: BrowserContext): void;
191
+ /** @internal Marks the page closed after its tab is gone (its context was removed) and releases what it registered */
192
+ detach(): Promise<void>;
193
+ /**
194
+ * Pretends things about this page only: locale, timezone, user agent, geolocation, offline, orientation, screen
195
+ * size. It wins over what the page's context says; `null` puts the real (or the context's) value back.
196
+ * @example
197
+ * await page.emulate({ locale: "nl-NL", offline: true });
198
+ */
199
+ emulate(options: EmulationOptions): Promise<void>;
200
+ /**
201
+ * Grants a browser permission ("geolocation", "notifications", "camera", …) to the origin this page is on,
202
+ * or to `options.origin`. Geolocation emulation only answers once this is granted.
203
+ */
204
+ grantPermission(name: string, options?: {
205
+ origin?: string;
206
+ }): Promise<void>;
207
+ /** True once `close()` has run */
208
+ get closed(): boolean;
209
+ private assertOpen;
210
+ /**
211
+ * Closes the tab and releases everything the page registered. Later calls on the page throw "page closed".
212
+ * With `runBeforeUnload` the page's `beforeunload` handlers run first. Closing the browser's last tab may end the browser.
213
+ */
214
+ close(options?: {
215
+ runBeforeUnload?: boolean;
216
+ }): Promise<void>;
217
+ /**
218
+ * Waits until the page's document reached `state` (default "load"): "domcontentloaded" and "load" follow the
219
+ * document, "networkidle" is "load" plus a quiet network (see `waitForNetworkIdle`). Resolves at once when it
220
+ * is already there. Call it once the navigation has started; a document that is still the old one counts.
221
+ * @example
222
+ * await page.locator("a[@id='next']").click();
223
+ * await page.waitForLoadState("domcontentloaded");
224
+ */
225
+ waitForLoadState(state?: LoadState, options?: {
226
+ timeout?: number;
227
+ }): Promise<void>;
228
+ private progressOf;
229
+ private onDocumentEvent;
230
+ private onDomContentLoaded;
231
+ private onLoad;
232
+ /** Starts recording which navigations reached DOMContentLoaded and load; kept until dispose */
233
+ private watchDocumentEvents;
234
+ private canBeAborted;
235
+ /** Subscribes to what a wait listens for; a browser that lacks the navigationAborted event (Firefox) is asked only once */
236
+ private subscribeToDocumentEvents;
237
+ private waitForDocumentState;
238
+ private networkSettings;
239
+ private navigationSettings;
240
+ /** The network tracker, created on first use. Rejects with NetworkTrackingDisabledError when config turned tracking off */
241
+ private network;
242
+ /**
243
+ * Starts following this page's requests now. Tracking only sees requests made after it started, so the
244
+ * browser calls this when it opens a page; without it, tracking starts on first use. Does nothing when
245
+ * config `network.track` is false.
246
+ */
247
+ startNetworkTracking(): Promise<void>;
248
+ /**
249
+ * Waits until no request has been in flight for `idleTime` ms (default config `network.idleTime`, then 500).
250
+ * Long-lived requests (server-sent events, long polling) keep the page busy until `timeout`.
251
+ */
252
+ waitForNetworkIdle(options?: {
253
+ idleTime?: number;
254
+ timeout?: number;
255
+ }): Promise<void>;
256
+ /**
257
+ * Listens for page events: `request`, `response`, `requestfailed` (network), `dialog` (a JavaScript dialog or
258
+ * `beforeunload` prompt opened; answer it with `dialog.accept()` / `dialog.dismiss()`), `console` (a `console.*` call)
259
+ * and `pageerror` (an uncaught exception). Starts what the event needs.
260
+ * A dialog nobody answers is dismissed automatically (a `beforeunload` prompt is accepted), so it cannot hang a test.
261
+ * @example
262
+ * page.on("dialog", (dialog) => dialog.accept());
263
+ */
264
+ on<E extends PageEventName>(event: E, listener: (...args: PageEvents[E]) => void): this;
265
+ once<E extends PageEventName>(event: E, listener: (...args: PageEvents[E]) => void): this;
266
+ off<E extends PageEventName>(event: E, listener: (...args: PageEvents[E]) => void): this;
267
+ private trackInBackground;
268
+ /** Starts following downloads; the browser does this when it opens a page, `page.on("download")` and `waitForDownload` when needed */
269
+ startDownloadTracking(): Promise<void>;
270
+ /** Starts following file pickers; the browser does this when it opens a page, `page.on("filechooser")` and `waitForFileChooser` when needed */
271
+ startFileChooserTracking(): Promise<void>;
272
+ /**
273
+ * Resolves with the next download this page starts (when it starts, not when it ends: use `download.path()` for that).
274
+ * Call it before the download is triggered, or pass `trigger`, which runs once the wait is set up.
275
+ * @example
276
+ * const download = await page.waitForDownload({ trigger: () => link.click() });
277
+ * const file = await download.path();
278
+ */
279
+ waitForDownload(options?: {
280
+ timeout?: number;
281
+ trigger?: () => Promise<unknown>;
282
+ }): Promise<Download>;
283
+ /**
284
+ * Resolves with the next file picker the page opens (a click on an `<input type=file>` or its label). Nothing shows
285
+ * on screen: answer it with `chooser.setFiles(...)`.
286
+ * @example
287
+ * const chooser = await page.waitForFileChooser({ trigger: () => page.locator("label[@for='avatar']").click() });
288
+ * await chooser.setFiles("./fixtures/avatar.png");
289
+ */
290
+ waitForFileChooser(options?: {
291
+ timeout?: number;
292
+ trigger?: () => Promise<unknown>;
293
+ }): Promise<FileChooser>;
294
+ /**
295
+ * Starts keeping response bodies, which is what makes `response.body()` possible: the browser only keeps a body
296
+ * when this ran before the request started. `page.on("response")` and `waitForResponse` call it; config
297
+ * `network.collectBodies` calls it when the browser opens the page.
298
+ */
299
+ startBodyCollection(): Promise<void>;
300
+ /** One Response per network response, whoever asks, so a body is read from the browser once */
301
+ private responseFor;
302
+ private loadBody;
303
+ /**
304
+ * Resolves with the next response of this page whose URL matches (a pattern, see `route`) or that the function
305
+ * accepts. Call it before the request is made, or pass `trigger`, which runs once the wait is set up.
306
+ * @example
307
+ * const response = await page.waitForResponse("**\/api/users", { trigger: () => button.click() });
308
+ * const users = await response.json();
309
+ */
310
+ waitForResponse(match: NetworkMatch<Response>, options?: {
311
+ timeout?: number;
312
+ trigger?: () => Promise<unknown>;
313
+ }): Promise<Response>;
314
+ /** Like `waitForResponse`, for requests */
315
+ waitForRequest(match: NetworkMatch<NetworkRequest>, options?: {
316
+ timeout?: number;
317
+ trigger?: () => Promise<unknown>;
318
+ }): Promise<NetworkRequest>;
319
+ private waitForNetworkEvent;
320
+ /**
321
+ * Resolves with the first `event` `matches` accepts. Listens before `trigger` runs, so what the trigger causes cannot
322
+ * be missed; a trigger that throws rejects the wait with its error. `onTimeout` builds the error for a wait that ran out.
323
+ */
324
+ private waitForPageEvent;
325
+ /** Turns the browser's HTTP cache off (`true`) or back on for this page: every request then goes to the server */
326
+ setCacheDisabled(disabled: boolean): Promise<void>;
327
+ private router;
328
+ /**
329
+ * Sends requests whose URL matches to `handler`, which must answer with `route.fulfill()`, `route.continue()`,
330
+ * `route.abort()` or `route.fallback()` (await the answer: a handler that returns first lets the request continue).
331
+ * A string without `*` is an exact URL, with `*` / `**` a glob (`*` stops at `/`), a RegExp is tested on the URL.
332
+ * Routes added later are asked first. Errors a handler throws fail the request and, in the runner, the test.
333
+ * @example
334
+ * await page.route("**\/api/users", (route) => route.fulfill({ json: [{ id: 1 }] }));
335
+ * await page.route(/\.(png|jpg)$/, (route) => route.abort());
336
+ */
337
+ route(url: UrlPattern, handler: RouteHandler): Promise<void>;
338
+ /** Removes the routes for `url` (only the one with `handler`, when given) */
339
+ unroute(url: UrlPattern, handler?: RouteHandler): Promise<void>;
340
+ unrouteAll(): Promise<void>;
341
+ /** Errors thrown by route handlers so far */
342
+ routeErrors(): readonly Error[];
343
+ /**
344
+ * Starts collecting the page's console output and uncaught exceptions. The browser does this when it opens a page so
345
+ * early output is kept; `page.on("console" | "pageerror")` starts it when needed.
346
+ */
347
+ startLogging(): Promise<void>;
348
+ /** The newest console and error entries of this page (empty until logging started), oldest first */
349
+ getLogs(): readonly LogRecord[];
350
+ /** Uncaught exceptions of the page since logging started or `clearLogs()` */
351
+ pageErrors(): readonly PageError[];
352
+ /** How many entries fell out of the page's log buffer */
353
+ get logsDropped(): number;
354
+ clearLogs(): void;
355
+ /** Resolves once everything the page logged before the call has arrived: events and replies share one ordered connection */
356
+ syncLogs(): Promise<void>;
357
+ /** Tells the runner not to fail this test on uncaught page exceptions (config `logs.failOnPageError`) */
358
+ allowPageErrors(): void;
359
+ get pageErrorsAllowed(): boolean;
360
+ /** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
361
+ startDialogHandling(): Promise<void>;
362
+ private ownsContext;
363
+ private onPromptOpened;
364
+ private onPromptClosed;
365
+ /** Lets the listeners answer; a dialog still open afterwards gets the default answer, so nothing hangs */
366
+ private dispatchDialog;
367
+ /** Captures an image of the given navigable, and returns it as a Base64-encoded string */
368
+ captureScreenshot(clip?: BiDiCommands["browsingContext.captureScreenshot"]["params"]["clip"], format?: BiDiCommands["browsingContext.captureScreenshot"]["params"]["format"], origin?: BiDiCommands["browsingContext.captureScreenshot"]["params"]["origin"]): Promise<void>;
369
+ }
370
+ export {};