@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface ElementBox {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ElementState {
|
|
8
|
+
attached: boolean;
|
|
9
|
+
visible: boolean;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
editable: boolean;
|
|
12
|
+
/** Viewport coordinates after any scroll; null when not attached */
|
|
13
|
+
box: ElementBox | null;
|
|
14
|
+
/**
|
|
15
|
+
* "self" when a click at the element's centre reaches it, otherwise a description of
|
|
16
|
+
* the covering element ("nothing" when no element is there); null when not checked
|
|
17
|
+
*/
|
|
18
|
+
hitTarget: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface ProbeOptions {
|
|
21
|
+
/** Scroll the element into view first when it is not fully inside the viewport */
|
|
22
|
+
scroll: boolean;
|
|
23
|
+
/** Check which element receives a click at the element's centre */
|
|
24
|
+
hitTest: boolean;
|
|
25
|
+
}
|
|
26
|
+
export type WaitForState = "attached" | "detached" | "visible" | "hidden";
|
|
27
|
+
export type CheckName = "attached" | "visible" | "stable" | "enabled" | "editable" | "hit target";
|
|
28
|
+
export type CheckResult = "pass" | "fail" | "pending";
|
|
29
|
+
/** Results keyed by check name, in the order the checks were evaluated */
|
|
30
|
+
export type CheckResults = Partial<Record<CheckName, CheckResult>>;
|
|
31
|
+
/** State of an element that is not in the document (never matched, or gone) */
|
|
32
|
+
export declare const DETACHED_STATE: ElementState;
|
|
33
|
+
/**
|
|
34
|
+
* Function declaration run in the page through `script.callFunction` as `(el, options)`, where `el`
|
|
35
|
+
* is the located element. Returns its state as a plain object in one round trip. Kept as a string so
|
|
36
|
+
* the transpiler cannot inject helpers (`__name`) that do not exist in the page.
|
|
37
|
+
* A node that was removed from the document stays resolvable in the browser, hence `isConnected`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PROBE_ELEMENT: string;
|
|
40
|
+
/** Checks the probe result is a complete state; throws on anything else */
|
|
41
|
+
export declare function parseElementState(value: unknown): ElementState;
|
|
42
|
+
/** Evaluates the required checks in order; after the first failure the rest are pending */
|
|
43
|
+
export declare function evaluateChecks(required: readonly CheckName[], current: ElementState, previous: ElementState | undefined): CheckResults;
|
|
44
|
+
export declare function allPass(results: CheckResults): boolean;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const TEXT_INPUT_TYPES = ["text", "search", "email", "url", "tel", "password", "number"];
|
|
2
|
+
/** State of an element that is not in the document (never matched, or gone) */
|
|
3
|
+
export const DETACHED_STATE = {
|
|
4
|
+
attached: false,
|
|
5
|
+
visible: false,
|
|
6
|
+
enabled: false,
|
|
7
|
+
editable: false,
|
|
8
|
+
box: null,
|
|
9
|
+
hitTarget: null,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Function declaration run in the page through `script.callFunction` as `(el, options)`, where `el`
|
|
13
|
+
* is the located element. Returns its state as a plain object in one round trip. Kept as a string so
|
|
14
|
+
* the transpiler cannot inject helpers (`__name`) that do not exist in the page.
|
|
15
|
+
* A node that was removed from the document stays resolvable in the browser, hence `isConnected`.
|
|
16
|
+
*/
|
|
17
|
+
export const PROBE_ELEMENT = `(el, options) => {
|
|
18
|
+
if (!el || !el.isConnected) {
|
|
19
|
+
return { attached: false, visible: false, enabled: false, editable: false, box: null, hitTarget: null };
|
|
20
|
+
}
|
|
21
|
+
if (options.scroll) {
|
|
22
|
+
const before = el.getBoundingClientRect();
|
|
23
|
+
const inViewport = before.top >= 0 && before.left >= 0 &&
|
|
24
|
+
before.bottom <= window.innerHeight && before.right <= window.innerWidth;
|
|
25
|
+
if (!inViewport) el.scrollIntoView({ block: "center", inline: "center" });
|
|
26
|
+
}
|
|
27
|
+
const rect = el.getBoundingClientRect();
|
|
28
|
+
const style = getComputedStyle(el);
|
|
29
|
+
const visible = rect.width > 0 && rect.height > 0 &&
|
|
30
|
+
style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
|
|
31
|
+
const enabled = !el.matches(":disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
32
|
+
const tag = el.tagName.toLowerCase();
|
|
33
|
+
const typeable = Boolean(
|
|
34
|
+
(tag === "input" && ${JSON.stringify(TEXT_INPUT_TYPES)}.includes((el.getAttribute("type") || "text").toLowerCase())) ||
|
|
35
|
+
tag === "textarea" || el.isContentEditable);
|
|
36
|
+
const editable = Boolean(enabled && !el.readOnly && typeable);
|
|
37
|
+
let hitTarget = null;
|
|
38
|
+
if (options.hitTest) {
|
|
39
|
+
const top = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
40
|
+
hitTarget = !top ? "nothing"
|
|
41
|
+
: top === el || el.contains(top) ? "self"
|
|
42
|
+
: top.tagName.toLowerCase() + (top.id ? "#" + top.id : "") +
|
|
43
|
+
Array.from(top.classList, (name) => "." + name).join("");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
attached: true, visible, enabled, editable,
|
|
47
|
+
box: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
|
|
48
|
+
hitTarget,
|
|
49
|
+
};
|
|
50
|
+
}`;
|
|
51
|
+
/** Checks the probe result is a complete state; throws on anything else */
|
|
52
|
+
export function parseElementState(value) {
|
|
53
|
+
if (typeof value !== "object" || value === null) {
|
|
54
|
+
throw new Error(`Expected an element state object but received "${describeType(value)}"`);
|
|
55
|
+
}
|
|
56
|
+
const state = value;
|
|
57
|
+
const flags = ["attached", "visible", "enabled", "editable"];
|
|
58
|
+
if (flags.some((flag) => typeof state[flag] !== "boolean") ||
|
|
59
|
+
!("box" in state) ||
|
|
60
|
+
!("hitTarget" in state)) {
|
|
61
|
+
throw new Error(`Malformed element state: ${JSON.stringify(value)}`);
|
|
62
|
+
}
|
|
63
|
+
return state;
|
|
64
|
+
}
|
|
65
|
+
function describeType(value) {
|
|
66
|
+
return value === null ? "null" : typeof value;
|
|
67
|
+
}
|
|
68
|
+
function sameBox(a, b) {
|
|
69
|
+
return (a !== null &&
|
|
70
|
+
b !== null &&
|
|
71
|
+
a.x === b.x &&
|
|
72
|
+
a.y === b.y &&
|
|
73
|
+
a.width === b.width &&
|
|
74
|
+
a.height === b.height);
|
|
75
|
+
}
|
|
76
|
+
function checkPasses(name, current, previous) {
|
|
77
|
+
switch (name) {
|
|
78
|
+
case "attached":
|
|
79
|
+
return current.attached;
|
|
80
|
+
case "visible":
|
|
81
|
+
return current.visible;
|
|
82
|
+
case "stable":
|
|
83
|
+
return sameBox(current.box, previous?.box ?? null);
|
|
84
|
+
case "enabled":
|
|
85
|
+
return current.enabled;
|
|
86
|
+
case "editable":
|
|
87
|
+
return current.editable;
|
|
88
|
+
case "hit target":
|
|
89
|
+
return current.hitTarget === "self";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Evaluates the required checks in order; after the first failure the rest are pending */
|
|
93
|
+
export function evaluateChecks(required, current, previous) {
|
|
94
|
+
const results = {};
|
|
95
|
+
let failed = false;
|
|
96
|
+
for (const name of required) {
|
|
97
|
+
if (failed) {
|
|
98
|
+
results[name] = "pending";
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const pass = checkPasses(name, current, previous);
|
|
102
|
+
results[name] = pass ? "pass" : "fail";
|
|
103
|
+
failed = !pass;
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
export function allPass(results) {
|
|
108
|
+
return Object.values(results).every((result) => result === "pass");
|
|
109
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function declaration run in the page as `(starts, value, match, ignoreCase)`; returns the elements
|
|
3
|
+
* named by a label whose text matches. BiDi has no label locator, so the framework does it.
|
|
4
|
+
*
|
|
5
|
+
* - An element's labels are its `<label>`s (`el.labels`: `for=`, nesting), the elements its
|
|
6
|
+
* `aria-labelledby` points to, and its `aria-label`.
|
|
7
|
+
* - Searches the descendants of every start node (the whole document when there are none).
|
|
8
|
+
* - Text is whitespace collapsed and trimmed; `full` needs equality, `partial` a substring.
|
|
9
|
+
* - Results are unique and in document order.
|
|
10
|
+
*
|
|
11
|
+
* A string, so the transpiler cannot inject helpers that do not exist in the page.
|
|
12
|
+
*/
|
|
13
|
+
export declare const LABEL_LOCATE = "(starts, value, match, ignoreCase) => {\n const normalise = (text) => {\n const collapsed = String(text).replace(/\\s+/g, \" \").trim();\n return ignoreCase ? collapsed.toLowerCase() : collapsed;\n };\n const wanted = normalise(value);\n const textOf = (node) => node.innerText ?? node.textContent ?? \"\";\n const labelsOf = (el) => {\n const texts = [];\n if (el.labels) for (const label of el.labels) texts.push(textOf(label));\n const by = el.getAttribute(\"aria-labelledby\");\n if (by) {\n for (const id of by.split(/\\s+/)) {\n const named = id && document.getElementById(id);\n if (named) texts.push(textOf(named));\n }\n }\n const own = el.getAttribute(\"aria-label\");\n if (own) texts.push(own);\n return texts;\n };\n const roots = starts.length > 0 ? starts : [document];\n const seen = new Set();\n const matches = [];\n for (const root of roots) {\n for (const el of root.querySelectorAll(\"*\")) {\n if (seen.has(el)) continue;\n seen.add(el);\n const found = labelsOf(el).some((text) => {\n const normal = normalise(text);\n return match === \"full\" ? normal === wanted : normal.includes(wanted);\n });\n if (found) matches.push(el);\n }\n }\n return matches.sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));\n}";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function declaration run in the page as `(starts, value, match, ignoreCase)`; returns the elements
|
|
3
|
+
* named by a label whose text matches. BiDi has no label locator, so the framework does it.
|
|
4
|
+
*
|
|
5
|
+
* - An element's labels are its `<label>`s (`el.labels`: `for=`, nesting), the elements its
|
|
6
|
+
* `aria-labelledby` points to, and its `aria-label`.
|
|
7
|
+
* - Searches the descendants of every start node (the whole document when there are none).
|
|
8
|
+
* - Text is whitespace collapsed and trimmed; `full` needs equality, `partial` a substring.
|
|
9
|
+
* - Results are unique and in document order.
|
|
10
|
+
*
|
|
11
|
+
* A string, so the transpiler cannot inject helpers that do not exist in the page.
|
|
12
|
+
*/
|
|
13
|
+
export const LABEL_LOCATE = `(starts, value, match, ignoreCase) => {
|
|
14
|
+
const normalise = (text) => {
|
|
15
|
+
const collapsed = String(text).replace(/\\s+/g, " ").trim();
|
|
16
|
+
return ignoreCase ? collapsed.toLowerCase() : collapsed;
|
|
17
|
+
};
|
|
18
|
+
const wanted = normalise(value);
|
|
19
|
+
const textOf = (node) => node.innerText ?? node.textContent ?? "";
|
|
20
|
+
const labelsOf = (el) => {
|
|
21
|
+
const texts = [];
|
|
22
|
+
if (el.labels) for (const label of el.labels) texts.push(textOf(label));
|
|
23
|
+
const by = el.getAttribute("aria-labelledby");
|
|
24
|
+
if (by) {
|
|
25
|
+
for (const id of by.split(/\\s+/)) {
|
|
26
|
+
const named = id && document.getElementById(id);
|
|
27
|
+
if (named) texts.push(textOf(named));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const own = el.getAttribute("aria-label");
|
|
31
|
+
if (own) texts.push(own);
|
|
32
|
+
return texts;
|
|
33
|
+
};
|
|
34
|
+
const roots = starts.length > 0 ? starts : [document];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const matches = [];
|
|
37
|
+
for (const root of roots) {
|
|
38
|
+
for (const el of root.querySelectorAll("*")) {
|
|
39
|
+
if (seen.has(el)) continue;
|
|
40
|
+
seen.add(el);
|
|
41
|
+
const found = labelsOf(el).some((text) => {
|
|
42
|
+
const normal = normalise(text);
|
|
43
|
+
return match === "full" ? normal === wanted : normal.includes(wanted);
|
|
44
|
+
});
|
|
45
|
+
if (found) matches.push(el);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return matches.sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));
|
|
49
|
+
}`;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
+
import type { BrowsingContext, ElementRectangle } from "../types/bidi-modules/browsing-context.js";
|
|
3
|
+
import { type HelperInstaller } from "../script/helpers.js";
|
|
4
|
+
import { ElementHandle } from "../script/element-handle.js";
|
|
5
|
+
import { type ElementScreenshotOptions } from "../browser/screenshot.js";
|
|
6
|
+
import { type WaitForState } from "./element-state.js";
|
|
7
|
+
import { type Selector, type TextOptions } from "./selector.js";
|
|
8
|
+
export type { WaitForState } from "./element-state.js";
|
|
9
|
+
export { InvalidSelectorError, UnsupportedOperationError, } from "./selector-errors.js";
|
|
10
|
+
export type { TextOptions } from "./selector.js";
|
|
11
|
+
export interface ActionOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Time (ms) to wait for the element to become actionable. Defaults to config `expect.timeout`, then 5000.
|
|
14
|
+
* 0 checks once without waiting; the stability check is skipped because it needs two probes
|
|
15
|
+
*/
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/** Skip every actionability check except "attached" */
|
|
18
|
+
force?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export default class Locator {
|
|
21
|
+
private biDiConnector;
|
|
22
|
+
private contextId;
|
|
23
|
+
private chain;
|
|
24
|
+
private helpers;
|
|
25
|
+
private alternatives;
|
|
26
|
+
private matched;
|
|
27
|
+
/**
|
|
28
|
+
* `selector` is an xpath, or a chain of selector steps that each search inside the previous step's matches.
|
|
29
|
+
* `helpers` is the page's helper realm. With it the actionability probe is installed once per
|
|
30
|
+
* document and called by name; without it (a locator built by hand) the probe source is sent on every poll.
|
|
31
|
+
* `alternatives` are fallback locators tried in order whenever this chain matches nothing (see `withFallbacks`).
|
|
32
|
+
*/
|
|
33
|
+
constructor(selector: string | readonly Selector[], biDiConnector: BiDiConnector, contextId: BrowsingContext, helpers?: HelperInstaller, alternatives?: readonly Locator[]);
|
|
34
|
+
/**
|
|
35
|
+
* The description of this locator used in error messages, e.g. `//form >> role=button[name="Save"]`;
|
|
36
|
+
* fallbacks follow as `or …`
|
|
37
|
+
*/
|
|
38
|
+
get selector(): string;
|
|
39
|
+
/** Steps that keep the fallbacks: each fallback gets the same step, so "inside A or B" stays meaningful */
|
|
40
|
+
private child;
|
|
41
|
+
/**
|
|
42
|
+
* A locator that tries this one first and, whenever it matches nothing, each of `fallbacks` in order;
|
|
43
|
+
* the first one with a match wins. For steps with several ways to find the same element (a test id,
|
|
44
|
+
* then role and name, then text), most stable first. `matchedBy` says which one matched last, so a
|
|
45
|
+
* self-healing layer can tell the primary locator has gone stale.
|
|
46
|
+
* @example
|
|
47
|
+
* page.getByTestId("save").withFallbacks(page.getByRole("button", { name: "Save" }), page.getByText("Save"));
|
|
48
|
+
*/
|
|
49
|
+
withFallbacks(...fallbacks: Locator[]): Locator;
|
|
50
|
+
/**
|
|
51
|
+
* Which locator found elements the last time this one searched: index 0 is the primary, 1 the first
|
|
52
|
+
* fallback, and so on. `undefined` before any search or when the last search found nothing.
|
|
53
|
+
*/
|
|
54
|
+
get matchedBy(): {
|
|
55
|
+
index: number;
|
|
56
|
+
selector: string;
|
|
57
|
+
} | undefined;
|
|
58
|
+
/** Elements matching the label text (`<label>`, `aria-labelledby`, `aria-label`), inside this locator's matches */
|
|
59
|
+
getByLabel(text: string, options?: TextOptions): Locator;
|
|
60
|
+
/** Elements whose `data-testid` attribute equals `testId`, inside this locator's matches */
|
|
61
|
+
getByTestId(testId: string): Locator;
|
|
62
|
+
/** Elements matching `xpath` inside this locator's matches (relative and `//` paths search inside them) */
|
|
63
|
+
locator(xpath: string): Locator;
|
|
64
|
+
/** Elements matching the CSS selector inside this locator's matches */
|
|
65
|
+
getByCss(css: string): Locator;
|
|
66
|
+
/** Elements whose text matches, inside this locator's matches. Exact match unless `match: "partial"` */
|
|
67
|
+
getByText(text: string, options?: TextOptions): Locator;
|
|
68
|
+
/** Elements with the ARIA role (and accessible name, when given), inside this locator's matches */
|
|
69
|
+
getByRole(role: string, options?: {
|
|
70
|
+
name?: string;
|
|
71
|
+
}): Locator;
|
|
72
|
+
/** One locator per element currently matching, in document order */
|
|
73
|
+
all(): Promise<Locator[]>;
|
|
74
|
+
/**
|
|
75
|
+
* Finds the elements the chain matches, in document order. Every step searches inside the previous
|
|
76
|
+
* step's matches; `max` limits the final result. Nothing is cached: the element that matches "first"
|
|
77
|
+
* can change between polls, and a removed node stays resolvable in the browser.
|
|
78
|
+
*/
|
|
79
|
+
private resolve;
|
|
80
|
+
/** The primary or the first fallback that currently matches, without its own fallbacks */
|
|
81
|
+
private firstMatching;
|
|
82
|
+
private withoutFallbacks;
|
|
83
|
+
private resolveChain;
|
|
84
|
+
private resolveOne;
|
|
85
|
+
private locateStep;
|
|
86
|
+
private locateNatively;
|
|
87
|
+
/** Text search done in the page, for browsers without BiDi's innerText locator */
|
|
88
|
+
private locateByText;
|
|
89
|
+
/** Runs a page-side search `(starts, value, match, ignoreCase) => elements` */
|
|
90
|
+
private locateInPage;
|
|
91
|
+
/** Calls `fn` in the page; a throwing function becomes a "Failed to locate element" error */
|
|
92
|
+
private call;
|
|
93
|
+
/**
|
|
94
|
+
* Calls a function on the first matching element: `body` sees it as `el`, followed by `params`.
|
|
95
|
+
* Answers `whenMissing` when nothing matches, or the element disappeared meanwhile.
|
|
96
|
+
*/
|
|
97
|
+
private callOnElement;
|
|
98
|
+
/** Like `callOnElement`, for operations that cannot go on without the element */
|
|
99
|
+
private callOnExistingElement;
|
|
100
|
+
/** Reads the element's actionability state: one search, one probe */
|
|
101
|
+
private probeState;
|
|
102
|
+
private callProbe;
|
|
103
|
+
/**
|
|
104
|
+
* Probes until every required check passes and returns that state.
|
|
105
|
+
* Throws ActionTimeoutError describing the last probe on timeout.
|
|
106
|
+
*/
|
|
107
|
+
private waitForActionable;
|
|
108
|
+
/** Waits until the element is attached, visible, stable, enabled and not covered, then clicks its centre with real pointer input */
|
|
109
|
+
click(options?: ActionOptions): Promise<void>;
|
|
110
|
+
/** Simulate a click event on the center off a page element.
|
|
111
|
+
* This is mostly used internally to focus input elements when using the `fill()` method
|
|
112
|
+
* @example
|
|
113
|
+
* const container = page.locator("div[@id=['clickable-element']");
|
|
114
|
+
* const rect = await container.getBoundingClientRect();
|
|
115
|
+
* await container.clickRect(rect);
|
|
116
|
+
*/
|
|
117
|
+
clickRect(rect: Pick<ElementRectangle, "x" | "y" | "width" | "height">): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Sets the files of an `<input type=file>` (an empty list clears them); the browser fires `input` and `change`.
|
|
120
|
+
* Paths are resolved against the working directory and must exist on the machine the browser runs on.
|
|
121
|
+
* Waits for the input to be attached and enabled; it need not be visible, file inputs are often hidden behind a label.
|
|
122
|
+
* @example
|
|
123
|
+
* await page.locator("input[@type='file']").setInputFiles("./fixtures/avatar.png");
|
|
124
|
+
* await page.locator("input[@type='file']").setInputFiles([]);
|
|
125
|
+
*/
|
|
126
|
+
setInputFiles(files: string | string[], options?: {
|
|
127
|
+
timeout?: number;
|
|
128
|
+
}): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Takes a screenshot of just this element, after scrolling it into view and waiting until it is attached and
|
|
131
|
+
* visible. Returns the image (also written to `options.path`).
|
|
132
|
+
* @example
|
|
133
|
+
* await page.locator("div[@id='chart']").screenshot({ path: "out/chart.png" });
|
|
134
|
+
*/
|
|
135
|
+
screenshot(options?: ElementScreenshotOptions & {
|
|
136
|
+
timeout?: number;
|
|
137
|
+
}): Promise<Buffer>;
|
|
138
|
+
/** Waits until the element is attached, then focuses it */
|
|
139
|
+
focus(options?: ActionOptions): Promise<void>;
|
|
140
|
+
/** Waits until the element reaches `state` (default "visible"); does not scroll */
|
|
141
|
+
waitFor({ state, timeout, }?: {
|
|
142
|
+
state?: WaitForState;
|
|
143
|
+
timeout?: number;
|
|
144
|
+
}): Promise<void>;
|
|
145
|
+
/** Returns the browser's getBoundingClientRect result for the given element */
|
|
146
|
+
getBoundingClientRect(): Promise<ElementRectangle>;
|
|
147
|
+
private stringOrNull;
|
|
148
|
+
/** Whether the element exists, has a non-empty box and is not hidden by CSS */
|
|
149
|
+
isVisible(): Promise<boolean>;
|
|
150
|
+
/** Trimmed text content, or null when the element is missing */
|
|
151
|
+
textContent(): Promise<string | null>;
|
|
152
|
+
/** Value of an input/textarea/select, or null when missing */
|
|
153
|
+
inputValue(): Promise<string | null>;
|
|
154
|
+
/** Attribute value, or null when the element or attribute is missing */
|
|
155
|
+
getAttribute(name: string): Promise<string | null>;
|
|
156
|
+
/** References to the elements currently matching, in document order. Internal: for tooling that identifies elements */
|
|
157
|
+
elements(): Promise<ElementHandle[]>;
|
|
158
|
+
/** Number of elements currently matching */
|
|
159
|
+
count(): Promise<number>;
|
|
160
|
+
/** Whether the element exists and is not disabled; does not wait */
|
|
161
|
+
isEnabled(): Promise<boolean>;
|
|
162
|
+
/** Whether the element exists and accepts typing (enabled, not readonly, a text field); does not wait */
|
|
163
|
+
isEditable(): Promise<boolean>;
|
|
164
|
+
/** Wait for certain time (ms). Default: 300ms */
|
|
165
|
+
wait(duration?: number): Promise<void>;
|
|
166
|
+
/** Waits until the field is editable and not covered, then replaces its value by typing `value`; an empty string clears it */
|
|
167
|
+
fill(value: string, options?: ActionOptions): Promise<void>;
|
|
168
|
+
}
|