@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,44 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createServer, type Server } from "node:http";
3
- import type { AddressInfo, Socket } from "node:net";
4
- import { after, before, test } from "node:test";
5
- import { Browser } from "./browser.js";
6
- import type Page from "./page.js";
7
-
8
- let browser: Browser;
9
- let page: Page;
10
- let server: Server;
11
- let base: string;
12
- const sockets = new Set<Socket>();
13
-
14
- before(async () => {
15
- server = createServer((req, res) => {
16
- res.setHeader("content-type", "text/html").end(`<!doctype html><title>${req.url}</title>`);
17
- });
18
- server.on("connection", (socket) => {
19
- sockets.add(socket);
20
- socket.on("close", () => sockets.delete(socket));
21
- });
22
- await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
23
- base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
24
- ({ browser, page } = await Browser.launch("firefox", { port: 9251, headless: true }, AbortSignal.timeout(30000)));
25
- });
26
-
27
- after(async () => {
28
- try {
29
- await page?.dispose().catch(() => {});
30
- await browser?.close().catch(() => {});
31
- } finally {
32
- sockets.forEach((socket) => socket.destroy());
33
- if (server) await new Promise((resolve) => server.close(resolve));
34
- }
35
- });
36
-
37
- test("goto resolves a path against the base URL", { timeout: 40000 }, async () => {
38
- page.setBaseURL(`${base}/shop/`);
39
- await page.goto("/products?id=1");
40
- assert.equal(await page.url(), `${base}/products?id=1`);
41
- assert.equal(await page.title(), "/products?id=1");
42
- await page.goto("./cart");
43
- assert.equal(await page.url(), `${base}/shop/cart`);
44
- });
@@ -1,26 +0,0 @@
1
- import { WaitTimeoutError } from "../wait/wait-until.js";
2
-
3
- /** A navigation did not end in a loaded page */
4
- export class NavigationError extends Error {
5
- public readonly url: string;
6
- public readonly reason: string;
7
- /** The BiDi error code, or "timeout" */
8
- public readonly code: string | undefined;
9
-
10
- constructor(url: string, reason: string, code?: string, operation = "navigateTo") {
11
- super(`${operation}(): ${url} failed: ${reason}`);
12
- this.name = "NavigationError";
13
- this.url = url;
14
- this.reason = reason;
15
- this.code = code;
16
- }
17
- }
18
-
19
- /** The page did not reach the awaited load state in time */
20
- export class LoadStateTimeoutError extends WaitTimeoutError<undefined> {
21
- constructor(state: string, timeout: number) {
22
- super(timeout, undefined);
23
- this.name = "LoadStateTimeoutError";
24
- this.message = `waitForLoadState("${state}") did not finish within ${timeout}ms`;
25
- }
26
- }
@@ -1,172 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createServer, type Server } from "node:http";
3
- import type { AddressInfo, Socket } from "node:net";
4
- import { after, before, test } from "node:test";
5
- import { callFunction } from "../script/call-function.js";
6
- import type { BiDiConnector } from "../transport/bidi-connection.js";
7
- import { Browser } from "./browser.js";
8
- import { LoadStateTimeoutError, NavigationError } from "./navigation-error.js";
9
- import type Page from "./page.js";
10
-
11
- let browser: Browser;
12
- let page: Page;
13
- let server: Server;
14
- let base: string;
15
- const sockets = new Set<Socket>();
16
-
17
- const GIF = Buffer.from("R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", "base64");
18
-
19
- before(async () => {
20
- server = createServer((req, res) => {
21
- const url = new URL(req.url ?? "/", "http://localhost");
22
- switch (url.pathname) {
23
- case "/late": // DOMContentLoaded at once, load only after the slow image
24
- res.setHeader("content-type", "text/html").end('<!doctype html><body>late<img src="/img?ms=700"></body>');
25
- break;
26
- case "/img":
27
- setTimeout(() => res.setHeader("content-type", "image/gif").end(GIF), Number(url.searchParams.get("ms")));
28
- break;
29
- case "/hang":
30
- break;
31
- case "/link":
32
- res.setHeader("content-type", "text/html").end('<!doctype html><body><a id="go" href="/late">go</a></body>');
33
- break;
34
- default:
35
- res.setHeader("content-type", "text/html").end(`<!doctype html><body>page ${url.pathname}</body>`);
36
- }
37
- });
38
- server.on("connection", (socket) => {
39
- sockets.add(socket);
40
- socket.on("close", () => sockets.delete(socket));
41
- });
42
- await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
43
- base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
44
-
45
- ({ browser, page } = await Browser.launch("firefox", { port: 9238, headless: true }));
46
- });
47
-
48
- after(async () => {
49
- await page.dispose();
50
- await browser?.close();
51
- sockets.forEach((socket) => socket.destroy());
52
- await new Promise((resolve) => server.close(resolve));
53
- });
54
-
55
- const OPTIONS = { timeout: 40000 };
56
- const readyState = () =>
57
- callFunction<string>(
58
- (page as unknown as { biDiConnector: BiDiConnector }).biDiConnector,
59
- page.contextId,
60
- "() => document.readyState",
61
- );
62
- const imageLoaded = () =>
63
- callFunction<boolean>(
64
- (page as unknown as { biDiConnector: BiDiConnector }).biDiConnector,
65
- page.contextId,
66
- "() => Array.from(document.images).every((image) => image.complete)",
67
- );
68
-
69
- async function timed<T>(action: () => Promise<T>) {
70
- const started = Date.now();
71
- const value = await action();
72
- return { value, took: Date.now() - started };
73
- }
74
-
75
- test("complete (the default) resolves after the slow subresource, interactive before it", OPTIONS, async () => {
76
- const complete = await timed(() => page.navigateTo(`${base}/late?complete`));
77
- assert.ok(complete.took >= 650, `resolved before the image loaded: ${complete.took}ms`);
78
- assert.equal(await imageLoaded(), true);
79
- assert.equal(typeof complete.value.navigation, "string");
80
- assert.equal(complete.value.url, `${base}/late?complete`);
81
-
82
- const interactive = await timed(() => page.navigateTo(`${base}/late?interactive`, { wait: "interactive" }));
83
- assert.ok(interactive.took < 500, `interactive waited for the image: ${interactive.took}ms`);
84
- assert.equal(await imageLoaded(), false);
85
- await page.waitForLoadState("load", { timeout: 5000 });
86
- assert.equal(await imageLoaded(), true);
87
- });
88
-
89
- test("none returns at once; waitForLoadState then waits for that navigation, not the old document", OPTIONS, async () => {
90
- await page.navigateTo(`${base}/quiet`); // a loaded document to start from
91
- const none = await timed(() => page.navigateTo(`${base}/late?none`, { wait: "none" }));
92
- assert.ok(none.took < 400, `none waited: ${none.took}ms`);
93
- const load = await timed(() => page.waitForLoadState("load", { timeout: 10000 }));
94
- assert.ok(none.took + load.took >= 600 || load.took >= 400, `resolved on the old document: ${load.took}ms`);
95
- assert.equal(await imageLoaded(), true);
96
- assert.equal(await readyState(), "complete");
97
- });
98
-
99
- test("domcontentloaded comes before load", OPTIONS, async () => {
100
- await page.navigateTo(`${base}/quiet`);
101
- await page.navigateTo(`${base}/late?dcl`, { wait: "none" });
102
- const dcl = await timed(() => page.waitForLoadState("domcontentloaded", { timeout: 10000 }));
103
- assert.ok(dcl.took < 600, `domcontentloaded waited for the image: ${dcl.took}ms`);
104
- await page.waitForLoadState("load", { timeout: 10000 });
105
- });
106
-
107
- test("networkidle waits for the load and a quiet network", OPTIONS, async () => {
108
- await page.navigateTo(`${base}/quiet`);
109
- await page.navigateTo(`${base}/late?idle`, { wait: "none" });
110
- const idle = await timed(() => page.waitForLoadState("networkidle", { timeout: 10000 }));
111
- assert.ok(idle.took >= 500, `did not wait for the image: ${idle.took}ms`);
112
- assert.equal(await imageLoaded(), true);
113
- });
114
-
115
- test("an already loaded page is ready at once", OPTIONS, async () => {
116
- await page.navigateTo(`${base}/quiet`);
117
- const load = await timed(() => page.waitForLoadState("load", { timeout: 5000 }));
118
- assert.ok(load.took < 300, `took ${load.took}ms`);
119
- });
120
-
121
- test("a refused connection is a NavigationError naming the url", OPTIONS, async () => {
122
- const probe = createServer();
123
- await new Promise<void>((resolve) => probe.listen(0, "127.0.0.1", resolve));
124
- const closedPort = (probe.address() as AddressInfo).port;
125
- await new Promise((resolve) => probe.close(resolve));
126
- const url = `http://127.0.0.1:${closedPort}/`;
127
- await assert.rejects(page.navigateTo(url), (err) => {
128
- assert.ok(err instanceof NavigationError, String(err));
129
- assert.equal(err.url, url);
130
- assert.ok(err.reason.length > 0);
131
- assert.match(err.message, /^navigateTo\(\): http:\/\/127\.0\.0\.1:\d+\/ failed: /);
132
- return true;
133
- });
134
- });
135
-
136
- test("Firefox reports a refused connection from navigate itself, even with wait none", OPTIONS, async () => {
137
- const probe = createServer();
138
- await new Promise<void>((resolve) => probe.listen(0, "127.0.0.1", resolve));
139
- const closedPort = (probe.address() as AddressInfo).port;
140
- await new Promise((resolve) => probe.close(resolve));
141
- await page.navigateTo(`${base}/quiet`);
142
- await assert.rejects(page.navigateTo(`http://127.0.0.1:${closedPort}/`, { wait: "none" }), NavigationError);
143
- });
144
-
145
- test("a link click followed by waitForLoadState ends up on the loaded target", OPTIONS, async () => {
146
- await page.navigateTo(`${base}/link`);
147
- await page.locator("a[@id='go']").click({ timeout: 5000 });
148
- // the navigation has started once the URL changes
149
- await page.locator("img").waitFor({ state: "attached", timeout: 10000 });
150
- await page.waitForLoadState("load", { timeout: 10000 });
151
- assert.equal(await imageLoaded(), true);
152
- });
153
-
154
- test("a server that never answers hits the navigation timeout", OPTIONS, async () => {
155
- const { took } = await timed(async () => {
156
- await assert.rejects(page.navigateTo(`${base}/hang`, { timeout: 800 }), (err) => {
157
- assert.ok(err instanceof NavigationError);
158
- assert.equal(err.reason, "timeout after 800ms");
159
- assert.equal(err.code, "timeout");
160
- return true;
161
- });
162
- });
163
- assert.ok(took >= 750 && took < 3000, `took ${took}ms`);
164
- });
165
-
166
- test("waitForLoadState times out with its own error", OPTIONS, async () => {
167
- await page.navigateTo(`${base}/quiet`);
168
- await page.navigateTo(`${base}/late?timeout`, { wait: "none" });
169
- // the image needs 700ms: the load state is not reached in 250ms
170
- await assert.rejects(page.waitForLoadState("load", { timeout: 250 }), LoadStateTimeoutError);
171
- await page.waitForLoadState("load", { timeout: 5000 });
172
- });
@@ -1,228 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
- import { inflateSync } from "node:zlib";
4
- import { createServer, type Server } from "node:http";
5
- import type { AddressInfo, Socket } from "node:net";
6
- import { tmpdir } from "node:os";
7
- import path from "node:path";
8
- import { after, before, test } from "node:test";
9
- import { UnsupportedOperationError } from "../locator/selector-errors.js";
10
- import { callFunction } from "../script/call-function.js";
11
- import { setContent } from "../testing/browser-fixture.js";
12
- import type { BiDiConnector } from "../transport/bidi-connection.js";
13
- import { Browser } from "./browser.js";
14
- import type Page from "./page.js";
15
-
16
- let browser: Browser;
17
- let page: Page;
18
- let server: Server;
19
- let base: string;
20
- const sockets = new Set<Socket>();
21
- let requests = 0;
22
- const dir = mkdtempSync(path.join(tmpdir(), "samurai-page-api-"));
23
-
24
- before(async () => {
25
- server = createServer((req, res) => {
26
- requests++;
27
- const url = new URL(req.url ?? "/", "http://localhost");
28
- res.setHeader("content-type", "text/html").setHeader("cache-control", "no-store").end(
29
- `<!doctype html><title>Title ${url.pathname}</title><body style="margin:0">` +
30
- `<div id="tall" style="width:200px;height:3000px;background:linear-gradient(red,blue)">${url.pathname} hit ${requests}</div>` +
31
- `<div id="card" style="position:absolute;top:100px;left:20px;width:120px;height:80px;background:#3a7"></div></body>`,
32
- );
33
- });
34
- server.on("connection", (socket) => {
35
- sockets.add(socket);
36
- socket.on("close", () => sockets.delete(socket));
37
- });
38
- await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
39
- base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
40
-
41
- ({ browser, page } = await Browser.launch("firefox", { port: 9244, headless: true }));
42
- await page.navigateTo(`${base}/one`);
43
- });
44
-
45
- after(async () => {
46
- await page.dispose();
47
- await browser?.close();
48
- sockets.forEach((socket) => socket.destroy());
49
- await new Promise((resolve) => server.close(resolve));
50
- rmSync(dir, { recursive: true, force: true });
51
- });
52
-
53
- const OPTIONS = { timeout: 40000 };
54
- const connector = () => (page as unknown as { biDiConnector: BiDiConnector }).biDiConnector;
55
- const run = <T>(source: string) => callFunction<T>(connector(), page.contextId, source);
56
- const size = () => run<[number, number, number]>("() => [innerWidth, innerHeight, devicePixelRatio]");
57
- const png = (image: Buffer) => ({
58
- magic: image.subarray(0, 8).toString("hex"),
59
- width: image.readUInt32BE(16),
60
- height: image.readUInt32BE(20),
61
- });
62
- const PNG_MAGIC = "89504e470d0a1a0a";
63
-
64
- test("a page opens at the default viewport, 1280x720", OPTIONS, async () => {
65
- assert.deepEqual((await size()).slice(0, 2), [1280, 720]);
66
- });
67
-
68
- test("setViewport changes the size and the pixel ratio, null puts the browser's size back", OPTIONS, async () => {
69
- await page.setViewport({ width: 400, height: 300 });
70
- assert.deepEqual(await size(), [400, 300, 1]);
71
- await page.setViewport({ width: 500, height: 320, devicePixelRatio: 2 });
72
- assert.deepEqual(await size(), [500, 320, 2]);
73
- await page.setViewport(null);
74
- const reset = await size();
75
- assert.notDeepEqual(reset.slice(0, 2), [500, 320]);
76
- await page.applyDefaultViewport();
77
- assert.deepEqual((await size()).slice(0, 2), [1280, 720]);
78
- });
79
-
80
- test("screenshot returns a PNG of the viewport, with the viewport's size", OPTIONS, async () => {
81
- await page.setViewport({ width: 400, height: 300 });
82
- const image = await page.screenshot();
83
- assert.deepEqual(png(image), { magic: PNG_MAGIC, width: 400, height: 300 });
84
- });
85
-
86
- test("a device pixel ratio of 2 doubles the image", OPTIONS, async () => {
87
- await page.setViewport({ width: 400, height: 300, devicePixelRatio: 2 });
88
- assert.deepEqual(png(await page.screenshot()), { magic: PNG_MAGIC, width: 800, height: 600 });
89
- await page.setViewport(null); // an omitted ratio keeps the current one, so put both back
90
- await page.applyDefaultViewport();
91
- });
92
-
93
- test("fullPage captures the whole document", OPTIONS, async () => {
94
- await page.setViewport({ width: 400, height: 300 });
95
- const { width, height } = png(await page.screenshot({ fullPage: true }));
96
- assert.ok(width > 350 && width <= 400, `width ${width} (a scrollbar takes some of the 400)`);
97
- assert.ok(height >= 3000, `height ${height}`);
98
- });
99
-
100
- test("clip captures a box, in viewport or document coordinates", OPTIONS, async () => {
101
- await page.setViewport({ width: 400, height: 300 });
102
- assert.deepEqual(png(await page.screenshot({ clip: { x: 10, y: 20, width: 50, height: 60 } })), { magic: PNG_MAGIC, width: 50, height: 60 });
103
- const inDocument = png(await page.screenshot({ fullPage: true, clip: { x: 0, y: 2000, width: 40, height: 70 } }));
104
- assert.deepEqual([inDocument.width, inDocument.height], [40, 70]);
105
- });
106
-
107
- test("jpeg is a JPEG, and a lower quality is smaller", OPTIONS, async () => {
108
- await page.setViewport({ width: 400, height: 300 });
109
- const high = await page.screenshot({ type: "jpeg", quality: 95 });
110
- const low = await page.screenshot({ type: "jpeg", quality: 5 });
111
- assert.equal(high.subarray(0, 3).toString("hex"), "ffd8ff");
112
- assert.equal(low.subarray(0, 3).toString("hex"), "ffd8ff");
113
- assert.ok(low.length < high.length, `q5 ${low.length} bytes vs q95 ${high.length} bytes`);
114
- });
115
-
116
- test("path writes the screenshot, folders included", OPTIONS, async () => {
117
- const file = path.join(dir, "shots", "home.png");
118
- const image = await page.screenshot({ path: file });
119
- assert.ok(existsSync(file));
120
- assert.deepEqual([...readFileSync(file)], [...image]);
121
- });
122
-
123
- test("a locator's screenshot is the size of its element", OPTIONS, async () => {
124
- await page.setViewport({ width: 400, height: 300 });
125
- const image = await page.locator("div[@id='card']").screenshot({ timeout: 5000 });
126
- assert.deepEqual(png(image), { magic: PNG_MAGIC, width: 120, height: 80 });
127
- });
128
-
129
- test("a locator's screenshot scrolls an element below the fold into view", OPTIONS, async () => {
130
- await setContent(page, `<div style="height:2000px"></div><div id="low" style="width:90px;height:50px;background:#c33"></div>`);
131
- await page.setViewport({ width: 400, height: 300 });
132
- const image = await page.locator("div[@id='low']").screenshot({ timeout: 5000 });
133
- assert.deepEqual(png(image), { magic: PNG_MAGIC, width: 90, height: 50 });
134
- await page.navigateTo(`${base}/one`);
135
- });
136
-
137
- /** The page box of the first page, looked up in the PDF text and in its compressed streams */
138
- function mediaBox(pdf: Buffer): number[] | undefined {
139
- const pattern = /\/MediaBox\s*\[\s*(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)\s*\]/;
140
- const raw = pdf.toString("latin1");
141
- const texts = [raw];
142
- for (const match of raw.matchAll(/stream\r?\n/g)) {
143
- const start = match.index! + match[0].length;
144
- const end = raw.indexOf("endstream", start);
145
- try {
146
- texts.push(inflateSync(pdf.subarray(start, end)).toString("latin1"));
147
- } catch {
148
- // not a Flate stream
149
- }
150
- }
151
- for (const text of texts) {
152
- const found = pattern.exec(text);
153
- if (found) return [Math.round(Number(found[3]) - Number(found[1])), Math.round(Number(found[4]) - Number(found[2]))];
154
- }
155
- return undefined;
156
- }
157
-
158
- test("pdf returns a PDF with the requested paper size", OPTIONS, async () => {
159
- const a4 = await page.pdf({ format: "A4" });
160
- assert.equal(a4.subarray(0, 5).toString(), "%PDF-");
161
- const near = (actual: number[] | undefined, expected: number[]) =>
162
- assert.ok(actual && actual.every((value, i) => Math.abs(value - expected[i]!) <= 2), `${actual} is not about ${expected}`);
163
- near(mediaBox(a4), [595, 842]);
164
- near(mediaBox(await page.pdf({ format: "A4", landscape: true })), [842, 595]);
165
- near(mediaBox(await page.pdf({ format: "Letter" })), [612, 792]);
166
- const file = path.join(dir, "pdfs", "page.pdf");
167
- await page.pdf({ path: file });
168
- assert.equal(readFileSync(file).subarray(0, 5).toString(), "%PDF-");
169
- });
170
-
171
- test("url and title describe the page now", OPTIONS, async () => {
172
- await page.navigateTo(`${base}/titled`);
173
- assert.equal(await page.url(), `${base}/titled`);
174
- assert.equal(await page.title(), "Title /titled");
175
- });
176
-
177
- test("goBack and goForward walk the history; at the ends they say false", OPTIONS, async () => {
178
- const tab = await browser.newPage({ type: "tab" }); // its initial about:blank is not a history entry
179
- try {
180
- await tab.navigateTo(`${base}/first`);
181
- await tab.navigateTo(`${base}/second`);
182
- assert.equal(await tab.goBack(), true);
183
- assert.equal(await tab.url(), `${base}/first`);
184
- assert.equal(await tab.goForward(), true);
185
- assert.equal(await tab.url(), `${base}/second`);
186
- assert.equal(await tab.goForward(), false, "nothing ahead");
187
- assert.equal(await tab.goBack(), true);
188
- assert.equal(await tab.goBack(), false, "nothing behind the first page");
189
- assert.equal(await tab.goBack(), false, "however often it is asked");
190
- assert.equal(await tab.url(), `${base}/first`);
191
- assert.equal(await tab.goForward(), true, "and the way forward is still there");
192
- assert.equal(await tab.url(), `${base}/second`);
193
- } finally {
194
- await tab.close();
195
- }
196
- });
197
-
198
- test("reload fetches the page again and waits for it", OPTIONS, async () => {
199
- await page.navigateTo(`${base}/reloaded`);
200
- const before = requests;
201
- const result = await page.reload();
202
- assert.ok(requests > before, "the server was asked again");
203
- assert.equal(result.url, `${base}/reloaded`);
204
- assert.equal(typeof result.navigation, "string");
205
- assert.equal(await run("() => document.readyState"), "complete");
206
- });
207
-
208
- test("reload({ ignoreCache }) is reported as unsupported by this Firefox", OPTIONS, async () => {
209
- await assert.rejects(page.reload({ ignoreCache: true }), UnsupportedOperationError);
210
- });
211
-
212
- test("close closes a tab; the page then refuses further use", OPTIONS, async () => {
213
- const tab = await browser.newPage({ type: "tab" });
214
- await tab.navigateTo(`${base}/tab`);
215
- assert.equal(await tab.title(), "Title /tab");
216
- await tab.close();
217
- assert.equal(tab.closed, true);
218
- await assert.rejects(tab.url(), /page closed/);
219
- await assert.rejects(tab.screenshot(), /page closed/);
220
- await tab.close(); // idempotent
221
- });
222
-
223
- test("close({ runBeforeUnload }) works on a page without handlers", OPTIONS, async () => {
224
- const tab = await browser.newPage({ type: "tab" });
225
- await tab.navigateTo(`${base}/tab2`);
226
- await tab.close({ runBeforeUnload: true });
227
- assert.equal(tab.closed, true);
228
- });
@@ -1,119 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { after, before, test } from "node:test";
3
- import { judgePageLogs } from "../runner/page-logs-report.js";
4
- import { callFunction } from "../script/call-function.js";
5
- import type { BiDiConnector } from "../transport/bidi-connection.js";
6
- import { Browser } from "./browser.js";
7
- import type { ConsoleMessage, PageError } from "./page-logs.js";
8
- import type Page from "./page.js";
9
-
10
- let browser: Browser;
11
- let page: Page;
12
-
13
- before(async () => {
14
- ({ browser, page } = await Browser.launch("firefox", { port: 9240, headless: true }));
15
- await page.navigateTo("about:blank");
16
- });
17
-
18
- after(async () => {
19
- await page.dispose();
20
- await browser?.close();
21
- });
22
-
23
- const OPTIONS = { timeout: 30000 };
24
- const run = (source: string) =>
25
- callFunction((page as unknown as { biDiConnector: BiDiConnector }).biDiConnector, page.contextId, source, [], {
26
- awaitPromise: false,
27
- });
28
-
29
- test("console calls arrive with method, text and plain arguments", OPTIONS, async () => {
30
- page.clearLogs();
31
- const seen: ConsoleMessage[] = [];
32
- const onConsole = (message: ConsoleMessage) => seen.push(message);
33
- page.on("console", onConsole);
34
- await page.startLogging();
35
- await run(`() => {
36
- console.log("hello", 42, { a: [1, 2], b: "x" });
37
- console.warn("careful");
38
- console.error("bad but handled");
39
- console.info("fyi");
40
- console.debug("details");
41
- }`);
42
- await page.syncLogs();
43
- page.off("console", onConsole);
44
-
45
- assert.deepEqual(seen.map((message) => message.type()), ["log", "warn", "error", "info", "debug"]);
46
- assert.match(seen[0]!.text(), /^hello 42 Object/, "the browser's own rendering of the call");
47
- assert.deepEqual(seen[0]!.args, ["hello", 42, { a: [1, 2], b: "x" }]);
48
- assert.equal(seen[1]!.level, "warn");
49
- assert.equal(seen[2]!.level, "error");
50
- assert.ok(seen[0]!.location(), "a location is reported");
51
- assert.deepEqual(page.getLogs().map((log) => [log.type, log.method]), [
52
- ["console", "log"], ["console", "warn"], ["console", "error"], ["console", "info"], ["console", "debug"],
53
- ]);
54
- assert.equal(page.pageErrors().length, 0, "console.error is not an uncaught exception");
55
- });
56
-
57
- test("an uncaught exception is a page error with a stack", OPTIONS, async () => {
58
- page.clearLogs();
59
- const errors: PageError[] = [];
60
- const onError = (error: PageError) => errors.push(error);
61
- page.on("pageerror", onError);
62
- await run(`() => { setTimeout(function explode() { throw new TypeError("boom from the page"); }, 0); }`);
63
- await new Promise((resolve) => setTimeout(resolve, 200));
64
- await page.syncLogs();
65
- page.off("pageerror", onError);
66
-
67
- assert.equal(errors.length, 1);
68
- assert.match(errors[0]!.message, /TypeError: boom from the page/);
69
- assert.match(errors[0]!.stack ?? "", /explode/);
70
- assert.equal(page.pageErrors().length, 1);
71
- assert.deepEqual(page.getLogs().map((log) => [log.type, log.level]), [["javascript", "error"]]);
72
- });
73
-
74
- test("an unhandled promise rejection is reported as a page error too (Firefox)", OPTIONS, async () => {
75
- page.clearLogs();
76
- await run(`() => { Promise.reject(new Error("nobody catches me")); }`);
77
- await new Promise((resolve) => setTimeout(resolve, 200));
78
- await page.syncLogs();
79
- assert.deepEqual(page.pageErrors().map((error) => error.message), ["Error: nobody catches me"]);
80
- });
81
-
82
- test("logs of an iframe belong to the page", OPTIONS, async () => {
83
- page.clearLogs();
84
- await run(`() => {
85
- const frame = document.body.appendChild(document.createElement("iframe"));
86
- frame.src = "about:blank";
87
- frame.addEventListener("load", () => frame.contentWindow.console.log("from the frame"), { once: true });
88
- }`);
89
- await new Promise((resolve) => setTimeout(resolve, 300));
90
- await page.syncLogs();
91
- assert.deepEqual(page.getLogs().map((log) => log.text), ["from the frame"]);
92
- });
93
-
94
- test("the first log of a new document is not lost", OPTIONS, async () => {
95
- page.clearLogs();
96
- await page.navigateTo(`data:text/html,<script>console.log("very early")</script>`);
97
- await page.syncLogs();
98
- assert.ok(page.getLogs().some((log) => log.text === "very early"), JSON.stringify(page.getLogs()));
99
- });
100
-
101
- test("what the runner would do with these logs", OPTIONS, async () => {
102
- page.clearLogs();
103
- await run(`() => { console.log("context"); setTimeout(() => { throw new Error("uncaught in test"); }, 0); }`);
104
- await new Promise((resolve) => setTimeout(resolve, 200));
105
- await page.syncLogs();
106
-
107
- const input = {
108
- entries: page.getLogs(),
109
- errors: page.pageErrors(),
110
- dropped: page.logsDropped,
111
- testFailed: false,
112
- allowPageErrors: page.pageErrorsAllowed,
113
- };
114
- const verdict = judgePageLogs({ ...input, config: { failOnPageError: true } });
115
- assert.match(verdict.error!.message, /^1 uncaught page error\(s\): .*uncaught in test/);
116
- assert.deepEqual(verdict.logs!.map((log) => log.text.replace(/^.*(context|uncaught in test).*$/, "$1")), ["context", "uncaught in test"]);
117
- assert.equal(judgePageLogs({ ...input, config: { failOnPageError: true, ignoreErrors: ["uncaught in test"] } }).error, undefined);
118
- assert.equal(judgePageLogs({ ...input, config: {} }).error, undefined);
119
- });