@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.
- package/README.md +129 -0
- package/bin/samurai.mjs +31 -0
- package/dist/api.js +3 -0
- package/dist/assert/assertion-error.d.ts +23 -0
- package/dist/assert/assertion-error.js +45 -0
- package/dist/assert/expect.d.ts +55 -0
- package/dist/assert/expect.js +188 -0
- package/dist/browser/browser-context.d.ts +91 -0
- package/dist/browser/browser-context.js +146 -0
- package/dist/browser/browser-finder.d.ts +2 -0
- package/dist/browser/browser-finder.js +95 -0
- package/dist/browser/browser.d.ts +95 -0
- package/dist/browser/browser.js +372 -0
- package/dist/browser/context-tree.d.ts +23 -0
- package/dist/browser/context-tree.js +60 -0
- package/dist/browser/dialog.d.ts +37 -0
- package/dist/browser/dialog.js +61 -0
- package/dist/browser/download.d.ts +71 -0
- package/dist/browser/download.js +137 -0
- package/dist/browser/downloads-dir.d.ts +8 -0
- package/dist/browser/downloads-dir.js +38 -0
- package/dist/browser/emulation.d.ts +61 -0
- package/dist/browser/emulation.js +144 -0
- package/dist/browser/file-chooser.d.ts +39 -0
- package/dist/browser/file-chooser.js +86 -0
- package/dist/browser/file-paths.d.ts +4 -0
- package/dist/browser/file-paths.js +18 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/navigation-error.d.ts +13 -0
- package/dist/browser/navigation-error.js +19 -0
- package/dist/browser/page-logs.d.ts +86 -0
- package/dist/browser/page-logs.js +149 -0
- package/dist/browser/page-wait.d.ts +9 -0
- package/dist/browser/page-wait.js +17 -0
- package/dist/browser/page.d.ts +370 -0
- package/dist/browser/page.js +1038 -0
- package/dist/browser/pdf.d.ts +72 -0
- package/dist/browser/pdf.js +37 -0
- package/dist/browser/screenshot.d.ts +29 -0
- package/dist/browser/screenshot.js +42 -0
- package/dist/cli/args.d.ts +35 -0
- package/dist/cli/args.js +69 -0
- package/dist/cli/cli.cli-test.d.ts +1 -0
- package/dist/cli/cli.cli-test.js +170 -0
- package/dist/cli/console-reporter.d.ts +15 -0
- package/dist/cli/console-reporter.js +52 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +61 -0
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.js +156 -0
- package/dist/config/config.d.ts +24 -0
- package/dist/config/config.js +42 -0
- package/dist/config/default.d.ts +2 -0
- package/dist/config/default.js +3 -0
- package/dist/config/mask.d.ts +11 -0
- package/dist/config/mask.js +85 -0
- package/dist/config/run-settings.d.ts +28 -0
- package/dist/config/run-settings.js +61 -0
- package/dist/config/secrets.d.ts +10 -0
- package/dist/config/secrets.js +67 -0
- package/dist/config/variables.d.ts +11 -0
- package/dist/config/variables.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/locator/action-timeout-error.d.ts +25 -0
- package/dist/locator/action-timeout-error.js +35 -0
- package/dist/locator/element-state.d.ts +44 -0
- package/dist/locator/element-state.js +109 -0
- package/dist/locator/label-locator.d.ts +13 -0
- package/dist/locator/label-locator.js +49 -0
- package/dist/locator/locator.d.ts +168 -0
- package/dist/locator/locator.js +591 -0
- package/dist/locator/selector-errors.d.ts +9 -0
- package/dist/locator/selector-errors.js +15 -0
- package/dist/locator/selector.d.ts +60 -0
- package/dist/locator/selector.js +113 -0
- package/dist/locator/text-locator.d.ts +13 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +30 -0
- package/dist/network/data-collector.d.ts +26 -0
- package/dist/network/data-collector.js +77 -0
- package/dist/network/network-tracker.d.ts +94 -0
- package/dist/network/network-tracker.js +198 -0
- package/dist/network/network-wait.d.ts +16 -0
- package/dist/network/network-wait.js +24 -0
- package/dist/network/response.d.ts +22 -0
- package/dist/network/response.js +42 -0
- package/dist/network/router.d.ts +120 -0
- package/dist/network/router.js +343 -0
- package/dist/network/url-match.d.ts +8 -0
- package/dist/network/url-match.js +38 -0
- package/dist/recorder/apply.d.ts +3 -0
- package/dist/recorder/apply.js +10 -0
- package/dist/recorder/capture.d.ts +18 -0
- package/dist/recorder/capture.js +198 -0
- package/dist/recorder/index.d.ts +3 -0
- package/dist/recorder/index.js +3 -0
- package/dist/recorder/normalise.d.ts +68 -0
- package/dist/recorder/normalise.js +104 -0
- package/dist/recorder/ranking.d.ts +9 -0
- package/dist/recorder/ranking.js +21 -0
- package/dist/recorder/recorder.d.ts +52 -0
- package/dist/recorder/recorder.js +143 -0
- package/dist/recorder/session.d.ts +49 -0
- package/dist/recorder/session.js +113 -0
- package/dist/runner/module-type.d.ts +6 -0
- package/dist/runner/module-type.js +29 -0
- package/dist/runner/page-logs-report.d.ts +40 -0
- package/dist/runner/page-logs-report.js +44 -0
- package/dist/runner/prepare-run.d.ts +14 -0
- package/dist/runner/prepare-run.js +20 -0
- package/dist/runner/project.d.ts +17 -0
- package/dist/runner/project.js +33 -0
- package/dist/runner/registry.d.ts +7 -0
- package/dist/runner/registry.js +40 -0
- package/dist/runner/reporter.d.ts +43 -0
- package/dist/runner/reporter.js +83 -0
- package/dist/runner/run.d.ts +27 -0
- package/dist/runner/run.js +34 -0
- package/dist/runner/test-error.d.ts +3 -0
- package/dist/runner/test-error.js +18 -0
- package/dist/runner/test-runner.d.ts +44 -0
- package/dist/runner/test-runner.js +205 -0
- package/dist/script/call-function.d.ts +34 -0
- package/dist/script/call-function.js +71 -0
- package/dist/script/element-handle.d.ts +10 -0
- package/{src/script/element-handle.ts → dist/script/element-handle.js} +4 -4
- package/dist/script/helpers.d.ts +28 -0
- package/dist/script/helpers.js +50 -0
- package/dist/script/preload.d.ts +31 -0
- package/dist/script/preload.js +71 -0
- package/dist/script/serialize.d.ts +29 -0
- package/dist/script/serialize.js +207 -0
- package/dist/steps/build.d.ts +5 -0
- package/dist/steps/build.js +38 -0
- package/dist/steps/edit.d.ts +30 -0
- package/dist/steps/edit.js +80 -0
- package/dist/steps/emit.d.ts +6 -0
- package/dist/steps/emit.js +73 -0
- package/dist/steps/index.d.ts +5 -0
- package/dist/steps/index.js +4 -0
- package/dist/steps/model.d.ts +103 -0
- package/dist/steps/model.js +1 -0
- package/dist/steps/parse.d.ts +3 -0
- package/dist/steps/parse.js +272 -0
- package/dist/transport/bidi-connection.d.ts +46 -0
- package/dist/transport/bidi-connection.js +176 -0
- package/dist/transport/bidi-error.d.ts +10 -0
- package/dist/transport/bidi-error.js +10 -0
- package/dist/transport/subscriptions.d.ts +36 -0
- package/dist/transport/subscriptions.js +81 -0
- package/{src → dist}/types/test.d.ts +10 -4
- package/dist/wait/wait-until.d.ts +20 -0
- package/dist/wait/wait-until.js +63 -0
- package/package.json +44 -7
- package/.claude/settings.local.json +0 -7
- package/.env.example +0 -3
- package/.github/actions/setup/action.yml +0 -16
- package/.github/workflows/ci.yml +0 -71
- package/.trunk/configs/.markdownlint.yaml +0 -2
- package/.trunk/configs/.yamllint.yaml +0 -7
- package/.trunk/configs/svgo.config.mjs +0 -14
- package/.trunk/trunk.yaml +0 -34
- package/docs/superpowers/bidi-roadmap.md +0 -60
- package/docs/superpowers/plans/2026-09-29-auto-wait.md +0 -1827
- package/docs/superpowers/plans/2026-09-29-expect.md +0 -1350
- package/docs/superpowers/plans/2026-10-01-ci.md +0 -350
- package/docs/superpowers/plans/2026-10-01-test-api-alignment.md +0 -1821
- package/docs/superpowers/recorder-roadmap.md +0 -52
- package/docs/superpowers/specs/2026-09-29-auto-wait-design.md +0 -185
- package/docs/superpowers/specs/2026-09-29-bidi-foundation-design.md +0 -130
- package/docs/superpowers/specs/2026-09-29-contexts-and-storage-design.md +0 -88
- package/docs/superpowers/specs/2026-09-29-downloads-and-file-dialogs-design.md +0 -94
- package/docs/superpowers/specs/2026-09-29-emulation-design.md +0 -88
- package/docs/superpowers/specs/2026-09-29-expect-design.md +0 -130
- package/docs/superpowers/specs/2026-09-29-locate-nodes-design.md +0 -90
- package/docs/superpowers/specs/2026-09-29-logs-and-errors-design.md +0 -87
- package/docs/superpowers/specs/2026-09-29-navigation-waits-design.md +0 -83
- package/docs/superpowers/specs/2026-09-29-network-data-design.md +0 -78
- package/docs/superpowers/specs/2026-09-29-network-interception-design.md +0 -91
- package/docs/superpowers/specs/2026-09-29-network-tracking-design.md +0 -86
- package/docs/superpowers/specs/2026-09-29-page-api-design.md +0 -90
- package/docs/superpowers/specs/2026-09-29-preload-scripts-design.md +0 -82
- package/docs/superpowers/specs/2026-09-29-script-call-function-design.md +0 -107
- package/docs/superpowers/specs/2026-09-29-set-files-design.md +0 -62
- package/docs/superpowers/specs/2026-09-29-user-prompts-design.md +0 -75
- package/docs/superpowers/specs/2026-10-01-ci-design.md +0 -106
- package/docs/superpowers/specs/2026-10-01-test-api-alignment-design.md +0 -241
- package/public/logo-dark.svg +0 -236
- package/public/logo-light.svg +0 -262
- package/samurai.config.ts +0 -7
- package/src/assert/assertion-error.test.ts +0 -55
- package/src/assert/assertion-error.ts +0 -62
- package/src/assert/expect-locator.test.ts +0 -217
- package/src/assert/expect.test.ts +0 -73
- package/src/assert/expect.ts +0 -299
- package/src/browser/browser-context.browser-test.ts +0 -201
- package/src/browser/browser-context.test.ts +0 -407
- package/src/browser/browser-context.ts +0 -208
- package/src/browser/browser-finder.ts +0 -110
- package/src/browser/browser.test.ts +0 -88
- package/src/browser/browser.ts +0 -515
- package/src/browser/context-tree.test.ts +0 -86
- package/src/browser/context-tree.ts +0 -70
- package/src/browser/dialog.browser-test.ts +0 -144
- package/src/browser/dialog.test.ts +0 -322
- package/src/browser/dialog.ts +0 -83
- package/src/browser/download.test.ts +0 -161
- package/src/browser/download.ts +0 -172
- package/src/browser/downloads-dir.test.ts +0 -75
- package/src/browser/downloads-dir.ts +0 -39
- package/src/browser/emulation.browser-test.ts +0 -186
- package/src/browser/emulation.test.ts +0 -200
- package/src/browser/emulation.ts +0 -204
- package/src/browser/file-chooser.test.ts +0 -142
- package/src/browser/file-chooser.ts +0 -99
- package/src/browser/file-paths.ts +0 -18
- package/src/browser/goto.browser-test.ts +0 -44
- package/src/browser/navigation-error.ts +0 -26
- package/src/browser/navigation.browser-test.ts +0 -172
- package/src/browser/page-api.browser-test.ts +0 -228
- package/src/browser/page-logs.browser-test.ts +0 -119
- package/src/browser/page-logs.test.ts +0 -212
- package/src/browser/page-logs.ts +0 -208
- package/src/browser/page-wait.ts +0 -19
- package/src/browser/page.test.ts +0 -1567
- package/src/browser/page.ts +0 -1176
- package/src/browser/pdf.ts +0 -62
- package/src/browser/screenshot.ts +0 -65
- package/src/browser/transfers.browser-test.ts +0 -180
- package/src/config/config.ts +0 -33
- package/src/config/default.ts +0 -5
- package/src/config/fixtures.test.ts +0 -137
- package/src/config/mask.test.ts +0 -143
- package/src/config/mask.ts +0 -87
- package/src/config/run-settings.test.ts +0 -163
- package/src/config/run-settings.ts +0 -114
- package/src/config/secrets.ts +0 -70
- package/src/config/variables.ts +0 -31
- package/src/index.ts +0 -10
- package/src/locator/action-timeout-error.test.ts +0 -83
- package/src/locator/action-timeout-error.ts +0 -72
- package/src/locator/element-state.test.ts +0 -181
- package/src/locator/element-state.ts +0 -159
- package/src/locator/locate-nodes.browser-test.ts +0 -169
- package/src/locator/locator-actions.test.ts +0 -355
- package/src/locator/locator-chain.test.ts +0 -292
- package/src/locator/locator.browser-test.ts +0 -137
- package/src/locator/locator.test.ts +0 -179
- package/src/locator/locator.ts +0 -656
- package/src/locator/selector-errors.ts +0 -18
- package/src/locator/selector.test.ts +0 -124
- package/src/locator/selector.ts +0 -107
- package/src/locator/set-files.browser-test.ts +0 -141
- package/src/locator/set-files.test.ts +0 -191
- package/src/locator/text-locator.test.ts +0 -118
- package/src/logger/index.test.ts +0 -28
- package/src/logger/index.ts +0 -40
- package/src/network/data-collector.test.ts +0 -111
- package/src/network/data-collector.ts +0 -92
- package/src/network/network-data.browser-test.ts +0 -192
- package/src/network/network-tracker.browser-test.ts +0 -151
- package/src/network/network-tracker.test.ts +0 -307
- package/src/network/network-tracker.ts +0 -286
- package/src/network/network-wait.ts +0 -28
- package/src/network/response.test.ts +0 -93
- package/src/network/response.ts +0 -58
- package/src/network/router.browser-test.ts +0 -260
- package/src/network/router.test.ts +0 -528
- package/src/network/router.ts +0 -420
- package/src/network/url-match.test.ts +0 -69
- package/src/network/url-match.ts +0 -41
- package/src/runner/page-logs-report.test.ts +0 -144
- package/src/runner/page-logs-report.ts +0 -82
- package/src/runner/prepare-run.test.ts +0 -33
- package/src/runner/prepare-run.ts +0 -36
- package/src/runner/registry.test.ts +0 -59
- package/src/runner/registry.ts +0 -46
- package/src/runner/reporter.test.ts +0 -73
- package/src/runner/reporter.ts +0 -109
- package/src/runner/test-error.test.ts +0 -39
- package/src/runner/test-error.ts +0 -20
- package/src/runner/test-runner.ts +0 -184
- package/src/script/call-function.browser-test.ts +0 -112
- package/src/script/call-function.test.ts +0 -174
- package/src/script/call-function.ts +0 -113
- package/src/script/helpers.test.ts +0 -116
- package/src/script/helpers.ts +0 -68
- package/src/script/preload.browser-test.ts +0 -144
- package/src/script/preload.test.ts +0 -180
- package/src/script/preload.ts +0 -102
- package/src/script/serialize.test.ts +0 -182
- package/src/script/serialize.ts +0 -203
- package/src/testing/browser-fixture.ts +0 -18
- package/src/testing/fake-websocket.ts +0 -72
- package/src/testing/stub-connector.ts +0 -154
- package/src/tests/index.spec.ts +0 -30
- package/src/transport/bidi-connection.test.ts +0 -155
- package/src/transport/bidi-connection.ts +0 -256
- package/src/transport/bidi-error.ts +0 -23
- package/src/transport/foundation.browser-test.ts +0 -73
- package/src/transport/subscriptions.test.ts +0 -140
- package/src/transport/subscriptions.ts +0 -110
- package/src/types/bidi-commands.test-d.ts +0 -55
- package/src/wait/wait-until.test.ts +0 -117
- package/src/wait/wait-until.ts +0 -86
- package/tsconfig.json +0 -44
- /package/{src/api.ts → dist/api.d.ts} +0 -0
- /package/{src/locator/text-locator.ts → dist/locator/text-locator.js} +0 -0
- /package/{src → dist}/types/bidi-modules/browser.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/browsing-context.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/emulation.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/input.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/log.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/network.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/permissions.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/script.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/session.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/storage.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/web-extensions.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/command.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/error.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/event.d.ts +0 -0
- /package/{src → dist}/types/bidi.d.ts +0 -0
- /package/{src → dist}/types/browser.d.ts +0 -0
- /package/{src → dist}/types/config.d.ts +0 -0
- /package/{src → dist}/types/env.d.ts +0 -0
package/src/script/serialize.ts
DELETED
|
@@ -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
|
-
}
|
package/src/tests/index.spec.ts
DELETED
|
@@ -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
|
-
});
|