@itmetsam/samurai-framework 0.21.2 → 0.21.4
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 +1 -1
- package/dist/assert/asymmetric.d.ts +29 -0
- package/dist/assert/asymmetric.js +166 -0
- package/dist/assert/element-probes.d.ts +20 -0
- package/dist/assert/element-probes.js +45 -0
- package/dist/assert/expect.d.ts +20 -9
- package/dist/assert/expect.js +12 -4
- package/dist/assert/locator-assertion-base.d.ts +52 -0
- package/dist/assert/locator-assertion-base.js +78 -0
- package/dist/assert/locator-assertions.d.ts +30 -49
- package/dist/assert/locator-assertions.js +44 -79
- package/dist/assert/more-locator-assertions.d.ts +30 -0
- package/dist/assert/more-locator-assertions.js +86 -0
- package/dist/assert/retry-assertions.d.ts +28 -0
- package/dist/assert/retry-assertions.js +113 -0
- package/dist/assert/text-match.d.ts +4 -2
- package/dist/assert/text-match.js +10 -2
- package/dist/assert/value-assertions.d.ts +29 -1
- package/dist/assert/value-assertions.js +84 -5
- package/dist/assert/value-matchers.d.ts +19 -0
- package/dist/assert/value-matchers.js +53 -0
- package/dist/browser/dialog.js +4 -1
- package/dist/browser/init-script.d.ts +11 -0
- package/dist/browser/init-script.js +29 -0
- package/dist/browser/navigation-error.d.ts +5 -1
- package/dist/browser/navigation-error.js +10 -2
- package/dist/browser/page-content.d.ts +5 -0
- package/dist/browser/page-content.js +43 -0
- package/dist/browser/page-evaluate.d.ts +9 -0
- package/dist/browser/page-evaluate.js +20 -0
- package/dist/browser/page-locators.d.ts +18 -0
- package/dist/browser/page-locators.js +19 -0
- package/dist/browser/page-navigation.d.ts +18 -0
- package/dist/browser/page-navigation.js +52 -2
- package/dist/browser/page-permissions.d.ts +6 -0
- package/dist/browser/page-permissions.js +15 -0
- package/dist/browser/page-settings.d.ts +3 -0
- package/dist/browser/page-settings.js +4 -0
- package/dist/browser/page.d.ts +76 -13
- package/dist/browser/page.js +103 -45
- package/dist/config/variables.js +2 -1
- package/dist/locator/accessible-name.d.ts +22 -0
- package/dist/locator/accessible-name.js +128 -0
- package/dist/locator/action-timeout-error.js +5 -1
- package/dist/locator/actionability.d.ts +3 -2
- package/dist/locator/actionability.js +4 -4
- package/dist/locator/attribute-locator.d.ts +13 -0
- package/dist/locator/attribute-locator.js +27 -0
- package/dist/locator/chain-resolver.d.ts +12 -3
- package/dist/locator/chain-resolver.js +85 -15
- package/dist/locator/check-actions.d.ts +15 -0
- package/dist/locator/check-actions.js +44 -0
- package/dist/locator/combine-step.d.ts +5 -0
- package/dist/locator/combine-step.js +15 -0
- package/dist/locator/control-probe.d.ts +10 -0
- package/dist/locator/control-probe.js +50 -0
- package/dist/locator/element-access.d.ts +26 -0
- package/dist/locator/element-access.js +53 -0
- package/dist/locator/element-actions.d.ts +17 -0
- package/dist/locator/element-actions.js +51 -0
- package/dist/locator/element-reads.d.ts +46 -4
- package/dist/locator/element-reads.js +114 -2
- package/dist/locator/element-state.d.ts +5 -0
- package/dist/locator/element-state.js +2 -0
- package/dist/locator/evaluate.d.ts +12 -0
- package/dist/locator/evaluate.js +36 -0
- package/dist/locator/file-actions.d.ts +2 -2
- package/dist/locator/filter-step.d.ts +24 -0
- package/dist/locator/filter-step.js +72 -0
- package/dist/locator/label-locator.d.ts +1 -1
- package/dist/locator/locator.d.ts +118 -30
- package/dist/locator/locator.js +218 -151
- package/dist/locator/page-scripts.d.ts +2 -0
- package/dist/locator/page-scripts.js +6 -0
- package/dist/locator/page-search.d.ts +11 -2
- package/dist/locator/page-search.js +15 -3
- package/dist/locator/page-step.d.ts +16 -0
- package/dist/locator/page-step.js +35 -0
- package/dist/locator/role-locator.d.ts +65 -0
- package/dist/locator/role-locator.js +125 -0
- package/dist/locator/role-table.d.ts +18 -0
- package/dist/locator/role-table.js +190 -0
- package/dist/locator/select-actions.d.ts +13 -0
- package/dist/locator/select-actions.js +73 -0
- package/dist/locator/selector-string.d.ts +7 -0
- package/dist/locator/selector-string.js +27 -0
- package/dist/locator/selector.d.ts +47 -15
- package/dist/locator/selector.js +83 -12
- package/dist/locator/strict-mode.d.ts +6 -0
- package/dist/locator/strict-mode.js +6 -0
- package/dist/locator/text-locator.d.ts +1 -1
- package/dist/network/url-match.js +3 -1
- package/dist/script/call-function.d.ts +7 -0
- package/dist/script/call-function.js +21 -3
- package/dist/types/config.d.ts +7 -0
- package/dist/wait/wait-until.d.ts +3 -1
- package/dist/wait/wait-until.js +7 -2
- package/package.json +1 -1
|
@@ -22,6 +22,11 @@ export interface ProbeOptions {
|
|
|
22
22
|
scroll: boolean;
|
|
23
23
|
/** Check which element receives a click at the element's centre */
|
|
24
24
|
hitTest: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Probe a checkbox, radio or switch the way a user reaches it: opacity 0 does not hide it, and a click on
|
|
27
|
+
* one of its labels counts as hitting it (`control-probe.ts`). Only the check actions set this
|
|
28
|
+
*/
|
|
29
|
+
control?: boolean;
|
|
25
30
|
}
|
|
26
31
|
export type WaitForState = "attached" | "detached" | "visible" | "hidden";
|
|
27
32
|
export type CheckName = "attached" | "visible" | "stable" | "enabled" | "editable" | "hit target";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CONTROL_PROBE_BRANCH } from "./control-probe.js";
|
|
1
2
|
const TEXT_INPUT_TYPES = [
|
|
2
3
|
"text",
|
|
3
4
|
"search",
|
|
@@ -26,6 +27,7 @@ export const PROBE_ELEMENT = `(el, options) => {
|
|
|
26
27
|
if (!el || !el.isConnected) {
|
|
27
28
|
return ${JSON.stringify(DETACHED_STATE)};
|
|
28
29
|
}
|
|
30
|
+
${CONTROL_PROBE_BRANCH}
|
|
29
31
|
if (options.scroll) {
|
|
30
32
|
const before = el.getBoundingClientRect();
|
|
31
33
|
const inViewport = before.top >= 0 && before.left >= 0 &&
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PageFunction } from "../script/call-function.js";
|
|
2
|
+
import type { ElementAccess } from "./element-access.js";
|
|
3
|
+
/**
|
|
4
|
+
* Waits until the element is attached (hidden is fine), then calls `fn(element, arg)` in the page.
|
|
5
|
+
* A missing element ends in the usual timeout error; a strict locator throws when several match.
|
|
6
|
+
* What the page function throws reaches the caller unchanged (a `ScriptError`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function evaluateOnElement<R, A>(access: ElementAccess, fn: PageFunction<[any, A], R>, arg: A | undefined, options?: {
|
|
9
|
+
timeout?: number;
|
|
10
|
+
}): Promise<R>;
|
|
11
|
+
/** Calls `fn(elements, arg)` in the page with every element the locator matches now (an empty array when none); does not wait */
|
|
12
|
+
export declare function evaluateOnAll<R, A>(access: ElementAccess, fn: PageFunction<[any[], A], R>, arg: A | undefined): Promise<R>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BiDiError } from "../transport/bidi-error.js";
|
|
2
|
+
import { callFunction } from "../script/call-function.js";
|
|
3
|
+
import { ATTACHED_ONLY } from "./page-scripts.js";
|
|
4
|
+
/**
|
|
5
|
+
* Waits until the element is attached (hidden is fine), then calls `fn(element, arg)` in the page.
|
|
6
|
+
* A missing element ends in the usual timeout error; a strict locator throws when several match.
|
|
7
|
+
* What the page function throws reaches the caller unchanged (a `ScriptError`).
|
|
8
|
+
*/
|
|
9
|
+
export async function evaluateOnElement(access, fn, arg, options) {
|
|
10
|
+
await access.waitForActionable("evaluate", ATTACHED_ONLY, { scroll: false, hitTest: false }, options?.timeout === undefined ? undefined : { timeout: options.timeout });
|
|
11
|
+
const el = await access.requireElement();
|
|
12
|
+
try {
|
|
13
|
+
return await callFunction(access.connector, access.contextId, fn, arg === undefined ? [el] : [el, arg]);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
// The element went away between the search and the call
|
|
17
|
+
if (err instanceof BiDiError && err.code === "no such node")
|
|
18
|
+
throw access.noElementError();
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Calls `fn(elements, arg)` in the page with every element the locator matches now (an empty array when none); does not wait */
|
|
23
|
+
export async function evaluateOnAll(access, fn, arg) {
|
|
24
|
+
// One more search when an element was removed between the search and the call
|
|
25
|
+
for (let attempt = 1;; attempt++) {
|
|
26
|
+
const elements = await access.resolveAll();
|
|
27
|
+
try {
|
|
28
|
+
return await callFunction(access.connector, access.contextId, fn, arg === undefined ? [elements] : [elements, arg]);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (attempt === 2 ||
|
|
32
|
+
!(err instanceof BiDiError && err.code === "no such node"))
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js"
|
|
|
3
3
|
import type { ElementHandle } from "../script/element-handle.js";
|
|
4
4
|
import type { Arg } from "../script/serialize.js";
|
|
5
5
|
import { type FileInput } from "../browser/generated-files.js";
|
|
6
|
-
import type { CheckName, ProbeOptions } from "./element-state.js";
|
|
6
|
+
import type { CheckName, ElementState, ProbeOptions } from "./element-state.js";
|
|
7
7
|
import type { ActionOptions } from "./actionability.js";
|
|
8
8
|
/** What the file actions need of a locator */
|
|
9
9
|
export interface ElementContext {
|
|
@@ -12,7 +12,7 @@ export interface ElementContext {
|
|
|
12
12
|
/** The locator's description for error messages */
|
|
13
13
|
readonly selector: string;
|
|
14
14
|
call<T>(fn: string, args: Arg[], sandbox?: string): Promise<T>;
|
|
15
|
-
waitForActionable(action: string, checks: readonly CheckName[], probe: ProbeOptions, options: ActionOptions | undefined): Promise<
|
|
15
|
+
waitForActionable(action: string, checks: readonly CheckName[], probe: ProbeOptions, options: ActionOptions | undefined): Promise<ElementState>;
|
|
16
16
|
/** The element the locator matches now; throws when none does */
|
|
17
17
|
requireElement(): Promise<ElementHandle>;
|
|
18
18
|
/** The error for an operation that needs an element when none matches */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ElementHandle } from "../script/element-handle.js";
|
|
2
|
+
import type { CallInPage } from "./chain-resolver.js";
|
|
3
|
+
import type { FilterTests, Selector, TextCriterion } from "./selector.js";
|
|
4
|
+
/** What `filter()` and the options of `locator(selector, options)` take; `has` and `hasNot` are locators searched inside each candidate */
|
|
5
|
+
export interface FilterOptions<L = unknown> {
|
|
6
|
+
/** Keeps elements whose text contains this string (case-insensitive, whitespace collapsed), or matches this RegExp (tested on the text as it is) */
|
|
7
|
+
hasText?: TextCriterion;
|
|
8
|
+
/** Keeps elements whose text does not contain / match it */
|
|
9
|
+
hasNotText?: TextCriterion;
|
|
10
|
+
/** Keeps elements that contain a match of this locator, searched inside the element */
|
|
11
|
+
has?: L;
|
|
12
|
+
/** Keeps elements that contain no match of this locator */
|
|
13
|
+
hasNot?: L;
|
|
14
|
+
}
|
|
15
|
+
/** The `filter` step for the options; `chainOf` reads the chain of a `has` / `hasNot` locator (and rejects one with fallbacks) */
|
|
16
|
+
export declare function filterSelector<L>(options: FilterOptions<L>, chainOf: (locator: L, option: string) => readonly Selector[]): Selector;
|
|
17
|
+
/** What applying a filter needs: the page, and a search of a chain inside one element */
|
|
18
|
+
export interface FilterContext {
|
|
19
|
+
call: CallInPage;
|
|
20
|
+
/** The first match of `chain` inside `start` (its first step is scoped to it), or none */
|
|
21
|
+
findInside(chain: readonly Selector[], start: ElementHandle): Promise<ElementHandle[]>;
|
|
22
|
+
}
|
|
23
|
+
/** The candidates that pass every test of the filter, in their order */
|
|
24
|
+
export declare function applyFilter(candidates: readonly ElementHandle[], tests: FilterTests, { call, findInside }: FilterContext): Promise<ElementHandle[]>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const OPTION_NAMES = ["hasText", "hasNotText", "has", "hasNot"];
|
|
2
|
+
function checkText(name, value) {
|
|
3
|
+
if (value !== undefined &&
|
|
4
|
+
typeof value !== "string" &&
|
|
5
|
+
!(value instanceof RegExp)) {
|
|
6
|
+
throw new TypeError(`filter(): ${name} must be a string or a RegExp`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** The `filter` step for the options; `chainOf` reads the chain of a `has` / `hasNot` locator (and rejects one with fallbacks) */
|
|
10
|
+
export function filterSelector(options, chainOf) {
|
|
11
|
+
if (typeof options !== "object" || options === null) {
|
|
12
|
+
throw new TypeError("filter(): options must be an object");
|
|
13
|
+
}
|
|
14
|
+
const given = OPTION_NAMES.filter((name) => options[name] !== undefined);
|
|
15
|
+
if (given.length === 0) {
|
|
16
|
+
throw new TypeError("filter(): give at least one of hasText, hasNotText, has or hasNot");
|
|
17
|
+
}
|
|
18
|
+
checkText("hasText", options.hasText);
|
|
19
|
+
checkText("hasNotText", options.hasNotText);
|
|
20
|
+
const tests = {};
|
|
21
|
+
if (options.hasText !== undefined)
|
|
22
|
+
tests.hasText = options.hasText;
|
|
23
|
+
if (options.hasNotText !== undefined)
|
|
24
|
+
tests.hasNotText = options.hasNotText;
|
|
25
|
+
if (options.has !== undefined)
|
|
26
|
+
tests.has = chainOf(options.has, "has");
|
|
27
|
+
if (options.hasNot !== undefined)
|
|
28
|
+
tests.hasNot = chainOf(options.hasNot, "hasNot");
|
|
29
|
+
return { kind: "filter", ...tests };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Function declaration run in the page as `(elements, hasText, hasNotText)`: the elements whose
|
|
33
|
+
* `textContent` passes both tests. A string test is a substring match with whitespace collapsed and case
|
|
34
|
+
* ignored (Playwright's `hasText`); a RegExp is tested on the text as it is, without its `g` and `y` flags.
|
|
35
|
+
*/
|
|
36
|
+
const TEXT_FILTER = `(elements, hasText, hasNotText) => {
|
|
37
|
+
const collapse = (text) => String(text).replace(/\\s+/g, " ").trim().toLowerCase();
|
|
38
|
+
const found = (el, wanted) => {
|
|
39
|
+
const text = el.textContent ?? "";
|
|
40
|
+
return typeof wanted === "string"
|
|
41
|
+
? collapse(text).includes(collapse(wanted))
|
|
42
|
+
: new RegExp(wanted.source, wanted.flags.replace(/[gy]/g, "")).test(text);
|
|
43
|
+
};
|
|
44
|
+
return elements.filter((el) =>
|
|
45
|
+
(hasText === undefined || found(el, hasText)) &&
|
|
46
|
+
(hasNotText === undefined || !found(el, hasNotText)));
|
|
47
|
+
}`;
|
|
48
|
+
/** The candidates that pass every test of the filter, in their order */
|
|
49
|
+
export async function applyFilter(candidates, tests, { call, findInside }) {
|
|
50
|
+
let kept = [...candidates];
|
|
51
|
+
if (tests.hasText !== undefined || tests.hasNotText !== undefined) {
|
|
52
|
+
kept = await call(TEXT_FILTER, [
|
|
53
|
+
kept,
|
|
54
|
+
tests.hasText,
|
|
55
|
+
tests.hasNotText,
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
for (const [chain, wanted] of [
|
|
59
|
+
[tests.has, true],
|
|
60
|
+
[tests.hasNot, false],
|
|
61
|
+
]) {
|
|
62
|
+
if (!chain)
|
|
63
|
+
continue;
|
|
64
|
+
const passing = [];
|
|
65
|
+
for (const candidate of kept) {
|
|
66
|
+
if ((await findInside(chain, candidate)).length > 0 === wanted)
|
|
67
|
+
passing.push(candidate);
|
|
68
|
+
}
|
|
69
|
+
kept = passing;
|
|
70
|
+
}
|
|
71
|
+
return kept;
|
|
72
|
+
}
|
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
*
|
|
11
11
|
* A string, so the transpiler cannot inject helpers that do not exist in the page.
|
|
12
12
|
*/
|
|
13
|
-
export declare const LABEL_LOCATE = "(starts, value, match, ignoreCase) => {\n \n const
|
|
13
|
+
export declare const LABEL_LOCATE = "(starts, value, match, ignoreCase) => {\n \n const collapse = (text) => String(text).replace(/\\s+/g, \" \").trim();\n const normalise = (text) => {\n const collapsed = collapse(text);\n return ignoreCase ? collapsed.toLowerCase() : collapsed;\n };\n const pattern = typeof value === \"string\" ? null : new RegExp(value.source, value.flags.replace(/[gy]/g, \"\"));\n const wanted = pattern ? \"\" : normalise(value);\n const textMatches = (text) => {\n if (pattern) return pattern.test(collapse(text));\n const normal = normalise(text);\n return match === \"full\" ? normal === wanted : normal.includes(wanted);\n };\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 if (labelsOf(el).some(textMatches)) matches.push(el);\n }\n }\n return matches.sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));\n}";
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
2
|
import type { BrowsingContext, ElementRectangle } from "../types/bidi-modules/browsing-context.js";
|
|
3
3
|
import { ElementHandle } from "../script/element-handle.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { PageFunction } from "../script/call-function.js";
|
|
5
|
+
import type { ElementScreenshotOptions } from "../browser/screenshot.js";
|
|
5
6
|
import type { FileInput } from "../browser/generated-files.js";
|
|
6
7
|
import { type ActionOptions } from "./actionability.js";
|
|
7
|
-
import { type
|
|
8
|
+
import { type CheckOptions } from "./check-actions.js";
|
|
9
|
+
import { type BoundingBox } from "./element-reads.js";
|
|
10
|
+
import type { WaitForState } from "./element-state.js";
|
|
8
11
|
import type { HelperInstaller } from "./helper-realm.js";
|
|
9
|
-
import { type
|
|
12
|
+
import { type StrictSource } from "./strict-mode.js";
|
|
13
|
+
import type { RoleOptions } from "./role-locator.js";
|
|
14
|
+
import { type Selector, type ExactOption, type TextOptions } from "./selector.js";
|
|
15
|
+
import { type SelectOptionValue } from "./select-actions.js";
|
|
16
|
+
import { type FilterOptions } from "./filter-step.js";
|
|
10
17
|
export type { ActionOptions } from "./actionability.js";
|
|
11
18
|
export default class Locator {
|
|
12
19
|
private biDiConnector;
|
|
@@ -14,19 +21,23 @@ export default class Locator {
|
|
|
14
21
|
private chain;
|
|
15
22
|
private helpers;
|
|
16
23
|
private alternatives;
|
|
24
|
+
private strictness;
|
|
25
|
+
/** Asked once per locator: the answer comes from the config, which a wait would otherwise read on every poll */
|
|
26
|
+
private strictAnswer;
|
|
17
27
|
private matched;
|
|
18
28
|
private resolver;
|
|
19
29
|
/** What the actionability waits use of this locator */
|
|
20
30
|
private actionability;
|
|
21
|
-
/** What the
|
|
22
|
-
private
|
|
31
|
+
/** What the reads and actions use of this locator */
|
|
32
|
+
private access;
|
|
23
33
|
/**
|
|
24
34
|
* `selector` is an xpath, or a chain of selector steps that each search inside the previous step's matches.
|
|
25
35
|
* `helpers` is the page's helper realm. With it the actionability probe is installed once per
|
|
26
36
|
* document and called by name; without it (a locator built by hand) the probe source is sent on every poll.
|
|
27
37
|
* `alternatives` are fallback locators tried in order whenever this chain matches nothing (see `withFallbacks`).
|
|
38
|
+
* `strictness` says whether actions and reads throw when the locator matches several elements (see `strict`); default no.
|
|
28
39
|
*/
|
|
29
|
-
constructor(selector: string | readonly Selector[], biDiConnector: BiDiConnector, contextId: BrowsingContext, helpers?: HelperInstaller, alternatives?: readonly Locator[]);
|
|
40
|
+
constructor(selector: string | readonly Selector[], biDiConnector: BiDiConnector, contextId: BrowsingContext, helpers?: HelperInstaller, alternatives?: readonly Locator[], strictness?: StrictSource);
|
|
30
41
|
/**
|
|
31
42
|
* The description of this locator used in error messages, e.g. `//form >> role=button[name="Save"]`;
|
|
32
43
|
* fallbacks follow as `or …`
|
|
@@ -43,6 +54,14 @@ export default class Locator {
|
|
|
43
54
|
* page.getByTestId("save").withFallbacks(page.getByRole("button", { name: "Save" }), page.getByText("Save"));
|
|
44
55
|
*/
|
|
45
56
|
withFallbacks(...fallbacks: Locator[]): Locator;
|
|
57
|
+
/**
|
|
58
|
+
* This locator, strict or not whatever the page and the config say: a strict locator makes actions and reads
|
|
59
|
+
* (`click`, `fill`, `textContent`, ...) throw `name(): strict mode violation, N elements match ...` when it
|
|
60
|
+
* matches several elements, instead of using the first. Steps added to it stay strict. See config `use.strictSelectors`
|
|
61
|
+
* @example
|
|
62
|
+
* await page.locator("li").strict().click(); // throws when there is more than one li
|
|
63
|
+
*/
|
|
64
|
+
strict(enabled?: boolean): Locator;
|
|
46
65
|
/**
|
|
47
66
|
* Which locator found elements the last time this one searched: index 0 is the primary, 1 the first
|
|
48
67
|
* fallback, and so on. `undefined` before any search or when the last search found nothing.
|
|
@@ -51,20 +70,49 @@ export default class Locator {
|
|
|
51
70
|
index: number;
|
|
52
71
|
selector: string;
|
|
53
72
|
} | undefined;
|
|
54
|
-
/** Elements matching the label text (`<label>`, `aria-labelledby`, `aria-label`), inside this locator's matches */
|
|
55
|
-
getByLabel(text: string, options?: TextOptions): Locator;
|
|
73
|
+
/** Elements matching the label text (`<label>`, `aria-labelledby`, `aria-label`), inside this locator's matches. See `TextOptions` for `exact` and RegExp */
|
|
74
|
+
getByLabel(text: string | RegExp, options?: TextOptions): Locator;
|
|
75
|
+
/** Elements whose `placeholder` matches, inside this locator's matches. See `TextOptions` for `exact` and RegExp */
|
|
76
|
+
getByPlaceholder(text: string | RegExp, options?: ExactOption): Locator;
|
|
77
|
+
/** Elements whose `alt` text matches (images, areas, image inputs), inside this locator's matches. See `TextOptions` */
|
|
78
|
+
getByAltText(text: string | RegExp, options?: ExactOption): Locator;
|
|
79
|
+
/** Elements whose `title` attribute matches, inside this locator's matches. See `TextOptions` */
|
|
80
|
+
getByTitle(text: string | RegExp, options?: ExactOption): Locator;
|
|
56
81
|
/** Elements whose `data-testid` attribute equals `testId`, inside this locator's matches */
|
|
57
82
|
getByTestId(testId: string): Locator;
|
|
58
|
-
/**
|
|
59
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Elements matching `selector` inside this locator's matches. An xpath (`//x`, `./x`, `xpath=…`, or an older
|
|
85
|
+
* relative one such as `a[@id='x']`) searches inside them; anything else is css (`css=` is accepted).
|
|
86
|
+
* `options` filter the matches, as `filter()` does
|
|
87
|
+
*/
|
|
88
|
+
locator(selector: string, options?: FilterOptions<Locator>): Locator;
|
|
60
89
|
/** Elements matching the CSS selector inside this locator's matches */
|
|
61
90
|
getByCss(css: string): Locator;
|
|
62
|
-
/** Elements whose text matches, inside this locator's matches. Exact match unless `match: "partial"` */
|
|
63
|
-
getByText(text: string, options?: TextOptions): Locator;
|
|
64
|
-
/** Elements with the ARIA role (and accessible name, when given), inside this locator's matches */
|
|
65
|
-
getByRole(role: string, options?:
|
|
66
|
-
|
|
67
|
-
|
|
91
|
+
/** Elements whose text matches, inside this locator's matches. Exact match unless `match: "partial"` or `exact: false`; a RegExp is tested as written */
|
|
92
|
+
getByText(text: string | RegExp, options?: TextOptions): Locator;
|
|
93
|
+
/** Elements with the ARIA role (and accessible name and state, when given), inside this locator's matches. See `RoleOptions` */
|
|
94
|
+
getByRole(role: string, options?: RoleOptions): Locator;
|
|
95
|
+
/** The n-th (0-based) match, in document order; a negative index counts from the end (`-1` is the last). Matches nothing when out of range */
|
|
96
|
+
nth(index: number): Locator;
|
|
97
|
+
/**
|
|
98
|
+
* The matches that pass the tests, in their order. `hasText` keeps elements whose text contains the string
|
|
99
|
+
* (case-insensitive, whitespace collapsed; this is Playwright's rule, the `getBy*` text matching is unchanged)
|
|
100
|
+
* or matches the RegExp; `has` keeps elements containing a match of the locator, searched inside them.
|
|
101
|
+
* @example
|
|
102
|
+
* await page.locator("li").filter({ hasText: "paid", has: page.getByRole("button") }).first().click();
|
|
103
|
+
*/
|
|
104
|
+
filter(options: FilterOptions<Locator>): Locator;
|
|
105
|
+
/** The elements both this locator and `other` match, in this locator's order */
|
|
106
|
+
and(other: Locator): Locator;
|
|
107
|
+
/** The elements either this locator or `other` matches, in document order, each once */
|
|
108
|
+
or(other: Locator): Locator;
|
|
109
|
+
private combined;
|
|
110
|
+
/** The chain of a locator used inside another (`has`); a locator with fallbacks cannot be */
|
|
111
|
+
private chainFor;
|
|
112
|
+
/** The first match, in document order */
|
|
113
|
+
first(): Locator;
|
|
114
|
+
/** The last match, in document order */
|
|
115
|
+
last(): Locator;
|
|
68
116
|
/** One locator per element currently matching, in document order */
|
|
69
117
|
all(): Promise<Locator[]>;
|
|
70
118
|
/**
|
|
@@ -76,24 +124,35 @@ export default class Locator {
|
|
|
76
124
|
/** The primary or the first fallback that currently matches, without its own fallbacks */
|
|
77
125
|
private firstMatching;
|
|
78
126
|
private withoutFallbacks;
|
|
127
|
+
/**
|
|
128
|
+
* The element the locator matches first. With a `method` name and strict selectors on, several matches
|
|
129
|
+
* throw instead (`method` is the public method that asked, for the message)
|
|
130
|
+
*/
|
|
79
131
|
private resolveOne;
|
|
80
|
-
/** Calls `fn` in the page; a throwing function becomes a "Failed to locate element" error */
|
|
81
|
-
private call;
|
|
82
|
-
/**
|
|
83
|
-
* Calls a function on the first matching element: `body` sees it as `el`, followed by `params`.
|
|
84
|
-
* Answers `whenMissing` when nothing matches, or the element disappeared meanwhile.
|
|
85
|
-
*/
|
|
86
|
-
private callOnElement;
|
|
87
|
-
/** Like `callOnElement`, for operations that cannot go on without the element */
|
|
88
|
-
private callOnExistingElement;
|
|
89
|
-
/** The error for an operation that needs an element when none matches */
|
|
90
|
-
private noElementError;
|
|
91
|
-
/** The element the locator matches now; throws when none does */
|
|
92
|
-
private requireElement;
|
|
93
|
-
private probeState;
|
|
94
132
|
private waitForActionable;
|
|
95
133
|
/** Waits until the element is attached, visible, stable, enabled and not covered, then clicks its centre with real pointer input */
|
|
96
134
|
click(options?: ActionOptions): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Checks a checkbox, radio or `role=checkbox|radio|switch` element; does nothing when it is already checked.
|
|
137
|
+
* Waits like `click`, but a hidden input counts as reachable through its `<label>`, which is what gets clicked.
|
|
138
|
+
* `trial` waits without clicking. Throws when the click did not change the state.
|
|
139
|
+
* @example
|
|
140
|
+
* await page.getByLabel("I accept the terms").check();
|
|
141
|
+
*/
|
|
142
|
+
check(options?: CheckOptions): Promise<void>;
|
|
143
|
+
/** Unchecks a checkbox or `role=checkbox|switch` element; does nothing when it is already unchecked. A radio button cannot be unchecked: throws */
|
|
144
|
+
uncheck(options?: CheckOptions): Promise<void>;
|
|
145
|
+
/** `check()` or `uncheck()`, whichever brings the element to `checked` */
|
|
146
|
+
setChecked(checked: boolean, options?: CheckOptions): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Selects options of a `<select>` or `<select multiple>` (which replaces its selection) and fires `input` and
|
|
149
|
+
* `change`. A string matches an option's value or its label; `{ value, label, index }` must match every field
|
|
150
|
+
* given. Waits until the select is visible and enabled and every option exists. Returns the values now selected.
|
|
151
|
+
* @example
|
|
152
|
+
* await page.getByLabel("Country").selectOption("Netherlands");
|
|
153
|
+
* await page.locator("select").selectOption([{ index: 0 }, { value: "b" }]);
|
|
154
|
+
*/
|
|
155
|
+
selectOption(values: SelectOptionValue | SelectOptionValue[], options?: ActionOptions): Promise<string[]>;
|
|
97
156
|
/**
|
|
98
157
|
* Sets the files of an `<input type=file>`; `[]` clears it. Paths are relative to the project folder (absolute
|
|
99
158
|
* ones are used as they are); `generatedFile(…)` writes a file of a given size when the step runs.
|
|
@@ -136,8 +195,31 @@ export default class Locator {
|
|
|
136
195
|
getBoundingClientRect(): Promise<ElementRectangle>;
|
|
137
196
|
/** Whether the element exists, has a non-empty box and is not hidden by CSS */
|
|
138
197
|
isVisible(): Promise<boolean>;
|
|
198
|
+
/** The opposite of `isVisible`: true for a hidden element and for one that is not there */
|
|
199
|
+
isHidden(): Promise<boolean>;
|
|
139
200
|
/** Trimmed text content, or null when the element is missing */
|
|
140
201
|
textContent(): Promise<string | null>;
|
|
202
|
+
/** Rendered text of the element (as a person would copy it, hidden text left out), untrimmed, or null when the element is missing */
|
|
203
|
+
innerText(): Promise<string | null>;
|
|
204
|
+
/** The markup inside the element, or null when the element is missing */
|
|
205
|
+
innerHTML(): Promise<string | null>;
|
|
206
|
+
/** `textContent` of every match, as the page has it (untrimmed, unlike `textContent()`); `[]` when none match. Does not wait */
|
|
207
|
+
allTextContents(): Promise<string[]>;
|
|
208
|
+
/** `innerText` of every match; `[]` when none match. Does not wait */
|
|
209
|
+
allInnerTexts(): Promise<string[]>;
|
|
210
|
+
/** The element's box in CSS pixels from the viewport's top left, or null when it is missing or not rendered (`display: none`). Does not wait or scroll */
|
|
211
|
+
boundingBox(): Promise<BoundingBox | null>;
|
|
212
|
+
/**
|
|
213
|
+
* Waits until the element is attached (hidden is fine), then calls `fn(element, arg)` in the page and returns the result
|
|
214
|
+
* (see `page.evaluate` for what comes back). `fn` is a function or the source of one; a strict locator throws when several match.
|
|
215
|
+
* @example
|
|
216
|
+
* const id = await page.getByTestId("row").evaluate((el) => el.dataset.id);
|
|
217
|
+
*/
|
|
218
|
+
evaluate<R = unknown, A = unknown>(fn: PageFunction<[any, A], R>, arg?: A, options?: {
|
|
219
|
+
timeout?: number;
|
|
220
|
+
}): Promise<R>;
|
|
221
|
+
/** Calls `fn(elements, arg)` in the page with an array of every match (empty when none; does not wait) and returns the result */
|
|
222
|
+
evaluateAll<R = unknown, A = unknown>(fn: PageFunction<[any[], A], R>, arg?: A): Promise<R>;
|
|
141
223
|
/** Value of an input/textarea/select, or null when missing */
|
|
142
224
|
inputValue(): Promise<string | null>;
|
|
143
225
|
/** Attribute value, or null when the element or attribute is missing */
|
|
@@ -148,10 +230,16 @@ export default class Locator {
|
|
|
148
230
|
count(): Promise<number>;
|
|
149
231
|
/** Whether the element exists and is not disabled; does not wait */
|
|
150
232
|
isEnabled(): Promise<boolean>;
|
|
233
|
+
/** The opposite of `isEnabled`: true for a disabled element and for one that is not there; does not wait */
|
|
234
|
+
isDisabled(): Promise<boolean>;
|
|
151
235
|
/** Like `isEnabled`, but null when the element is missing, so callers can tell "disabled" from "not there"; does not wait */
|
|
152
236
|
enabledState(): Promise<boolean | null>;
|
|
153
237
|
/** Whether a checkbox or radio input is checked or the element has `aria-checked="true"`; null when the element is missing */
|
|
154
238
|
isChecked(): Promise<boolean | null>;
|
|
239
|
+
/** The accessible name (what `getByRole`'s `name` is matched against), "" when there is none, or null when the element is missing */
|
|
240
|
+
accessibleName(): Promise<string | null>;
|
|
241
|
+
/** The accessible description (`aria-describedby`, `aria-description`, else `title` when it is not the name), or null when the element is missing */
|
|
242
|
+
accessibleDescription(): Promise<string | null>;
|
|
155
243
|
/** The class attribute as written ("" when the element has none), or null when the element is missing */
|
|
156
244
|
classAttribute(): Promise<string | null>;
|
|
157
245
|
/** Computed value of a CSS property (camelCase names become kebab-case, `--custom` is kept), or null when the element is missing */
|