@itmetsam/samurai-framework 0.21.3 → 0.21.5
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/dist/api.d.ts +3 -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 +26 -49
- package/dist/assert/locator-assertions.js +35 -78
- 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 +2 -0
- package/dist/assert/text-match.js +8 -0
- 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/browser.d.ts +9 -0
- package/dist/browser/browser.js +25 -5
- package/dist/browser/cdp.d.ts +11 -0
- package/dist/browser/cdp.js +28 -0
- package/dist/browser/chrome-launch.d.ts +4 -0
- package/dist/browser/chrome-launch.js +2 -2
- package/dist/browser/index.d.ts +5 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/launch-firefox.js +1 -1
- package/dist/browser/launch-flags.d.ts +4 -1
- package/dist/browser/launch-flags.js +18 -5
- 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 +1 -1
- package/dist/browser/page-locators.js +1 -1
- package/dist/browser/page-navigation.d.ts +18 -0
- package/dist/browser/page-navigation.js +52 -2
- package/dist/browser/page-settings.d.ts +1 -1
- package/dist/browser/page.d.ts +52 -1
- package/dist/browser/page.js +66 -2
- package/dist/browser/session-capabilities.d.ts +10 -0
- package/dist/browser/session-capabilities.js +11 -0
- package/dist/browser/session.d.ts +2 -1
- package/dist/browser/session.js +3 -3
- package/dist/browser/storage-state.d.ts +6 -0
- package/dist/browser/storage-state.js +37 -0
- package/dist/browser/use-applier.d.ts +27 -0
- package/dist/browser/use-applier.js +94 -0
- package/dist/browser/viewport.d.ts +3 -2
- package/dist/browser/viewport.js +15 -2
- package/dist/config/config.js +2 -0
- package/dist/config/use-options.d.ts +16 -0
- package/dist/config/use-options.js +128 -0
- package/dist/locator/element-access.d.ts +2 -0
- package/dist/locator/element-reads.d.ts +17 -0
- package/dist/locator/element-reads.js +31 -0
- package/dist/locator/evaluate.d.ts +12 -0
- package/dist/locator/evaluate.js +36 -0
- package/dist/locator/locator.d.ts +23 -0
- package/dist/locator/locator.js +36 -1
- package/dist/logger/warn-once.d.ts +4 -0
- package/dist/logger/warn-once.js +13 -0
- package/dist/runner/attachments.d.ts +16 -0
- package/dist/runner/attachments.js +60 -0
- package/dist/runner/fixture-args.d.ts +15 -0
- package/dist/runner/fixture-args.js +95 -0
- package/dist/runner/fixture-defs.d.ts +22 -0
- package/dist/runner/fixture-defs.js +103 -0
- package/dist/runner/fixture-scope.d.ts +33 -0
- package/dist/runner/fixture-scope.js +156 -0
- package/dist/runner/fixtures.d.ts +34 -0
- package/dist/runner/fixtures.js +39 -0
- package/dist/runner/hooks.d.ts +6 -1
- package/dist/runner/hooks.js +15 -3
- package/dist/runner/registry.d.ts +3 -39
- package/dist/runner/registry.js +93 -19
- package/dist/runner/reporter.d.ts +7 -1
- package/dist/runner/reporter.js +3 -0
- package/dist/runner/run-test.d.ts +1 -3
- package/dist/runner/run-test.js +92 -34
- package/dist/runner/steps.d.ts +13 -0
- package/dist/runner/steps.js +67 -0
- package/dist/runner/suite.d.ts +5 -0
- package/dist/runner/suite.js +8 -0
- package/dist/runner/test-annotations.d.ts +16 -0
- package/dist/runner/test-annotations.js +47 -0
- package/dist/runner/test-function.d.ts +103 -0
- package/dist/runner/test-function.js +1 -0
- package/dist/runner/test-info.d.ts +104 -0
- package/dist/runner/test-info.js +169 -0
- package/dist/runner/test-runner.js +2 -1
- package/dist/runner/test-timeout.d.ts +22 -0
- package/dist/runner/test-timeout.js +45 -0
- package/dist/runner/use-options.d.ts +7 -0
- package/dist/runner/use-options.js +18 -0
- package/dist/runner/worker-session.js +2 -1
- package/dist/script/call-function.d.ts +7 -0
- package/dist/script/call-function.js +21 -3
- package/dist/script/element-handle.d.ts +1 -1
- package/dist/script/element-handle.js +1 -1
- package/dist/types/config.d.ts +3 -2
- package/dist/types/extend.d.ts +30 -0
- package/dist/types/test.d.ts +120 -0
- package/dist/types/use.d.ts +47 -0
- package/dist/wait/wait-until.d.ts +3 -1
- package/dist/wait/wait-until.js +7 -2
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ export { test, describe, beforeEach, afterEach, beforeAll, afterAll, } from "./r
|
|
|
2
2
|
export { expect } from "./assert/expect.js";
|
|
3
3
|
export { defineConfig } from "./config/config.js";
|
|
4
4
|
export { defineStep, type StepDefinition, type StepResult, type StepRunInput, } from "./flows/steps/index.js";
|
|
5
|
-
export type { AllHookFixtures, TestDetails, TestFixtures, TestOptions, TestOutputs, } from "./types/test.js";
|
|
5
|
+
export type { AllHookFixtures, TestAnnotation, TestAttachment, TestDetails, TestFixtures, TestInfo, TestOptions, TestOutputs, TestStepEntry, } from "./types/test.js";
|
|
6
|
+
export type { FixtureOptions } from "./types/extend.js";
|
|
7
|
+
export type { ColorScheme, StorageState, StorageStateCookie, UseOptions, } from "./types/use.js";
|
|
6
8
|
export { generatedFile, type GeneratedFile, type FileInput, } from "./browser/generated-files.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { inspect } from "node:util";
|
|
2
|
+
/**
|
|
3
|
+
* A placeholder inside an expected value that matches a whole family of values, as in Jest and Playwright:
|
|
4
|
+
* `expect(x).toEqual({ id: expect.any(Number) })`
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class AsymmetricMatcher {
|
|
7
|
+
abstract asymmetricMatch(actual: unknown): boolean;
|
|
8
|
+
abstract toString(): string;
|
|
9
|
+
[inspect.custom](): string;
|
|
10
|
+
}
|
|
11
|
+
/** Matches an instance of `type` (primitives for `String`, `Number`, `Boolean`, `BigInt`, `Symbol`; any non-null object for `Object`; a function for `Function`) */
|
|
12
|
+
export declare const any: (type: unknown) => AsymmetricMatcher;
|
|
13
|
+
/** Matches anything but `null` and `undefined` */
|
|
14
|
+
export declare const anything: () => AsymmetricMatcher;
|
|
15
|
+
/** Matches a string that contains `part` */
|
|
16
|
+
export declare const stringContaining: (part: string) => AsymmetricMatcher;
|
|
17
|
+
/** Matches a string that matches the RegExp (a string pattern is compiled to one) */
|
|
18
|
+
export declare const stringMatching: (pattern: string | RegExp) => AsymmetricMatcher;
|
|
19
|
+
/** Matches an array that has an equal item for each of `items`, in any order and with others besides */
|
|
20
|
+
export declare const arrayContaining: (items: unknown[]) => AsymmetricMatcher;
|
|
21
|
+
/** Matches an object that has each key of `shape` with an equal value, and may have others */
|
|
22
|
+
export declare const objectContaining: (shape: object) => AsymmetricMatcher;
|
|
23
|
+
/**
|
|
24
|
+
* The meaning of `toEqual`: `isDeepStrictEqual` (so an `undefined` property differs from a missing one),
|
|
25
|
+
* except that asymmetric matchers inside arrays and plain objects of `expected` match by their own rule
|
|
26
|
+
*/
|
|
27
|
+
export declare function equalsWith(actual: unknown, expected: unknown): boolean;
|
|
28
|
+
/** `toMatchObject`: plain objects in `expected` need only the keys they name; arrays still need the same length */
|
|
29
|
+
export declare function subsetMatches(actual: unknown, expected: unknown): boolean;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { inspect, isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { format } from "./assertion-error.js";
|
|
3
|
+
/**
|
|
4
|
+
* A placeholder inside an expected value that matches a whole family of values, as in Jest and Playwright:
|
|
5
|
+
* `expect(x).toEqual({ id: expect.any(Number) })`
|
|
6
|
+
*/
|
|
7
|
+
export class AsymmetricMatcher {
|
|
8
|
+
[inspect.custom]() {
|
|
9
|
+
return this.toString();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
class Any extends AsymmetricMatcher {
|
|
13
|
+
constructor(type) {
|
|
14
|
+
super();
|
|
15
|
+
this.type = type;
|
|
16
|
+
if (typeof type !== "function") {
|
|
17
|
+
throw new TypeError("expect.any() expects a class or constructor");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
asymmetricMatch(actual) {
|
|
21
|
+
switch (this.type) {
|
|
22
|
+
case String:
|
|
23
|
+
return typeof actual === "string" || actual instanceof String;
|
|
24
|
+
case Number:
|
|
25
|
+
return typeof actual === "number" || actual instanceof Number;
|
|
26
|
+
case Boolean:
|
|
27
|
+
return typeof actual === "boolean" || actual instanceof Boolean;
|
|
28
|
+
case BigInt:
|
|
29
|
+
return typeof actual === "bigint";
|
|
30
|
+
case Symbol:
|
|
31
|
+
return typeof actual === "symbol";
|
|
32
|
+
case Function:
|
|
33
|
+
return typeof actual === "function";
|
|
34
|
+
case Object:
|
|
35
|
+
return typeof actual === "object" && actual !== null;
|
|
36
|
+
}
|
|
37
|
+
return actual instanceof this.type;
|
|
38
|
+
}
|
|
39
|
+
toString() {
|
|
40
|
+
return `Any<${this.type.name}>`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
class Anything extends AsymmetricMatcher {
|
|
44
|
+
asymmetricMatch(actual) {
|
|
45
|
+
return actual !== null && actual !== undefined;
|
|
46
|
+
}
|
|
47
|
+
toString() {
|
|
48
|
+
return "Anything";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
class StringContaining extends AsymmetricMatcher {
|
|
52
|
+
constructor(part) {
|
|
53
|
+
super();
|
|
54
|
+
this.part = part;
|
|
55
|
+
}
|
|
56
|
+
asymmetricMatch(actual) {
|
|
57
|
+
return typeof actual === "string" && actual.includes(this.part);
|
|
58
|
+
}
|
|
59
|
+
toString() {
|
|
60
|
+
return `StringContaining ${format(this.part)}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
class StringMatching extends AsymmetricMatcher {
|
|
64
|
+
constructor(pattern) {
|
|
65
|
+
super();
|
|
66
|
+
this.pattern = typeof pattern === "string" ? new RegExp(pattern) : pattern;
|
|
67
|
+
}
|
|
68
|
+
asymmetricMatch(actual) {
|
|
69
|
+
// search() ignores lastIndex, so a global RegExp gives the same answer on every call
|
|
70
|
+
return typeof actual === "string" && actual.search(this.pattern) !== -1;
|
|
71
|
+
}
|
|
72
|
+
toString() {
|
|
73
|
+
return `StringMatching ${format(this.pattern)}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
class ArrayContaining extends AsymmetricMatcher {
|
|
77
|
+
constructor(items) {
|
|
78
|
+
super();
|
|
79
|
+
this.items = items;
|
|
80
|
+
}
|
|
81
|
+
asymmetricMatch(actual) {
|
|
82
|
+
return (Array.isArray(actual) &&
|
|
83
|
+
this.items.every((item) => actual.some((o) => equalsWith(o, item))));
|
|
84
|
+
}
|
|
85
|
+
toString() {
|
|
86
|
+
return `ArrayContaining ${format(this.items)}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
class ObjectContaining extends AsymmetricMatcher {
|
|
90
|
+
constructor(shape) {
|
|
91
|
+
super();
|
|
92
|
+
this.shape = shape;
|
|
93
|
+
}
|
|
94
|
+
asymmetricMatch(actual) {
|
|
95
|
+
if (typeof actual !== "object" || actual === null)
|
|
96
|
+
return false;
|
|
97
|
+
return Object.entries(this.shape).every(([key, item]) => key in actual && equalsWith(actual[key], item));
|
|
98
|
+
}
|
|
99
|
+
toString() {
|
|
100
|
+
return `ObjectContaining ${format(this.shape)}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Matches an instance of `type` (primitives for `String`, `Number`, `Boolean`, `BigInt`, `Symbol`; any non-null object for `Object`; a function for `Function`) */
|
|
104
|
+
export const any = (type) => new Any(type);
|
|
105
|
+
/** Matches anything but `null` and `undefined` */
|
|
106
|
+
export const anything = () => new Anything();
|
|
107
|
+
/** Matches a string that contains `part` */
|
|
108
|
+
export const stringContaining = (part) => new StringContaining(part);
|
|
109
|
+
/** Matches a string that matches the RegExp (a string pattern is compiled to one) */
|
|
110
|
+
export const stringMatching = (pattern) => new StringMatching(pattern);
|
|
111
|
+
/** Matches an array that has an equal item for each of `items`, in any order and with others besides */
|
|
112
|
+
export const arrayContaining = (items) => new ArrayContaining(items);
|
|
113
|
+
/** Matches an object that has each key of `shape` with an equal value, and may have others */
|
|
114
|
+
export const objectContaining = (shape) => new ObjectContaining(shape);
|
|
115
|
+
function isPlainObject(value) {
|
|
116
|
+
if (typeof value !== "object" || value === null)
|
|
117
|
+
return false;
|
|
118
|
+
const prototype = Object.getPrototypeOf(value);
|
|
119
|
+
return prototype === Object.prototype || prototype === null;
|
|
120
|
+
}
|
|
121
|
+
/** Whether `value` is, or holds in arrays and plain objects, an asymmetric matcher */
|
|
122
|
+
function holdsMatcher(value, seen = new Set()) {
|
|
123
|
+
if (value instanceof AsymmetricMatcher)
|
|
124
|
+
return true;
|
|
125
|
+
if (typeof value !== "object" || value === null || seen.has(value))
|
|
126
|
+
return false;
|
|
127
|
+
if (!Array.isArray(value) && !isPlainObject(value))
|
|
128
|
+
return false;
|
|
129
|
+
seen.add(value);
|
|
130
|
+
return Object.values(value).some((item) => holdsMatcher(item, seen));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The meaning of `toEqual`: `isDeepStrictEqual` (so an `undefined` property differs from a missing one),
|
|
134
|
+
* except that asymmetric matchers inside arrays and plain objects of `expected` match by their own rule
|
|
135
|
+
*/
|
|
136
|
+
export function equalsWith(actual, expected) {
|
|
137
|
+
if (!holdsMatcher(expected))
|
|
138
|
+
return isDeepStrictEqual(actual, expected);
|
|
139
|
+
return walk(actual, expected, equalsWith, false);
|
|
140
|
+
}
|
|
141
|
+
/** `toMatchObject`: plain objects in `expected` need only the keys they name; arrays still need the same length */
|
|
142
|
+
export function subsetMatches(actual, expected) {
|
|
143
|
+
return walk(actual, expected, subsetMatches, true);
|
|
144
|
+
}
|
|
145
|
+
function walk(actual, expected, recurse, subset) {
|
|
146
|
+
if (expected instanceof AsymmetricMatcher)
|
|
147
|
+
return expected.asymmetricMatch(actual);
|
|
148
|
+
if (Array.isArray(expected)) {
|
|
149
|
+
return (Array.isArray(actual) &&
|
|
150
|
+
actual.length === expected.length &&
|
|
151
|
+
expected.every((item, i) => recurse(actual[i], item)));
|
|
152
|
+
}
|
|
153
|
+
if (isPlainObject(expected)) {
|
|
154
|
+
if (typeof actual !== "object" || actual === null)
|
|
155
|
+
return false;
|
|
156
|
+
const keys = Object.keys(expected);
|
|
157
|
+
if (subset) {
|
|
158
|
+
return keys.every((key) => key in actual && recurse(actual[key], expected[key]));
|
|
159
|
+
}
|
|
160
|
+
return (Object.getPrototypeOf(actual) === Object.getPrototypeOf(expected) &&
|
|
161
|
+
Object.keys(actual).length === keys.length &&
|
|
162
|
+
keys.every((key) => Object.prototype.hasOwnProperty.call(actual, key) &&
|
|
163
|
+
recurse(actual[key], expected[key])));
|
|
164
|
+
}
|
|
165
|
+
return isDeepStrictEqual(actual, expected);
|
|
166
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type Locator from "../locator/locator.js";
|
|
2
|
+
/** The state a checkbox-like element is in */
|
|
3
|
+
export type CheckedState = boolean | "mixed";
|
|
4
|
+
/** Whether the element is the active element of its document or shadow root */
|
|
5
|
+
export declare function readFocused(locator: Locator): Promise<boolean | null>;
|
|
6
|
+
/** The element's JS property `name`, wrapped as `{ value }`; null when nothing matches */
|
|
7
|
+
export declare function readJSProperty(locator: Locator, name: string): Promise<{
|
|
8
|
+
value: unknown;
|
|
9
|
+
} | null>;
|
|
10
|
+
/** The share (0 to 1) of the element inside the viewport, as an IntersectionObserver reports it; null when nothing matches */
|
|
11
|
+
export declare function readViewportRatio(locator: Locator): Promise<number | null>;
|
|
12
|
+
/** The `id` property ("" when it has none), or null when nothing matches */
|
|
13
|
+
export declare function readId(locator: Locator): Promise<string | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Input and textarea: the value is empty. Anything else: no element children and no text besides whitespace.
|
|
16
|
+
* Null when nothing matches.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readEmpty(locator: Locator): Promise<boolean | null>;
|
|
19
|
+
/** true, false or "mixed" (an indeterminate checkbox, or `aria-checked="mixed"`); null when nothing matches */
|
|
20
|
+
export declare function readCheckedState(locator: Locator): Promise<CheckedState | null>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `body` in the page with the first match as `el` (and the optional `arg`) and answers its result, or
|
|
3
|
+
* null when nothing matches. Reads never wait and do not enforce strict mode: they use the first match.
|
|
4
|
+
*/
|
|
5
|
+
function readFirst(locator, body, arg) {
|
|
6
|
+
return locator.evaluateAll(`(els, arg) => { const el = els[0]; if (!el) return null; ${body} }`, arg);
|
|
7
|
+
}
|
|
8
|
+
/** Whether the element is the active element of its document or shadow root */
|
|
9
|
+
export function readFocused(locator) {
|
|
10
|
+
return readFirst(locator, `return el.getRootNode().activeElement === el;`);
|
|
11
|
+
}
|
|
12
|
+
/** The element's JS property `name`, wrapped as `{ value }`; null when nothing matches */
|
|
13
|
+
export function readJSProperty(locator, name) {
|
|
14
|
+
return readFirst(locator, `return { value: el[arg] };`, name);
|
|
15
|
+
}
|
|
16
|
+
/** The share (0 to 1) of the element inside the viewport, as an IntersectionObserver reports it; null when nothing matches */
|
|
17
|
+
export function readViewportRatio(locator) {
|
|
18
|
+
return readFirst(locator, `return new Promise((resolve) => {
|
|
19
|
+
const observer = new IntersectionObserver((entries) => {
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
resolve(entries[entries.length - 1].intersectionRatio);
|
|
22
|
+
});
|
|
23
|
+
observer.observe(el);
|
|
24
|
+
});`);
|
|
25
|
+
}
|
|
26
|
+
/** The `id` property ("" when it has none), or null when nothing matches */
|
|
27
|
+
export function readId(locator) {
|
|
28
|
+
return readFirst(locator, `return el.id;`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Input and textarea: the value is empty. Anything else: no element children and no text besides whitespace.
|
|
32
|
+
* Null when nothing matches.
|
|
33
|
+
*/
|
|
34
|
+
export function readEmpty(locator) {
|
|
35
|
+
return readFirst(locator, `if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") return el.value === "";
|
|
36
|
+
return el.children.length === 0 && (el.textContent || "").trim() === "";`);
|
|
37
|
+
}
|
|
38
|
+
/** true, false or "mixed" (an indeterminate checkbox, or `aria-checked="mixed"`); null when nothing matches */
|
|
39
|
+
export function readCheckedState(locator) {
|
|
40
|
+
return readFirst(locator, `const type = (el.getAttribute("type") || "").toLowerCase();
|
|
41
|
+
const input = el.tagName === "INPUT" && (type === "checkbox" || type === "radio");
|
|
42
|
+
if (input && el.indeterminate) return "mixed";
|
|
43
|
+
if ((input && el.checked) || el.getAttribute("aria-checked") === "true") return true;
|
|
44
|
+
return el.getAttribute("aria-checked") === "mixed" ? "mixed" : false;`);
|
|
45
|
+
}
|
package/dist/assert/expect.d.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import Page from "../browser/page.js";
|
|
2
2
|
import Locator from "../locator/locator.js";
|
|
3
|
+
import { any, anything, arrayContaining, objectContaining, stringContaining, stringMatching } from "./asymmetric.js";
|
|
4
|
+
import { poll } from "./retry-assertions.js";
|
|
3
5
|
import { LocatorAssertions } from "./locator-assertions.js";
|
|
4
6
|
import { PageAssertions } from "./page-assertions.js";
|
|
5
7
|
import { ValueAssertions } from "./value-assertions.js";
|
|
6
8
|
export { takePendingAssertions } from "./pending-assertions.js";
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
/** `expect`: the assertions for a locator, a page or a plain value, plus the asymmetric matchers */
|
|
10
|
+
export interface Expect {
|
|
11
|
+
/** Auto-retrying assertions on a page element */
|
|
12
|
+
(locator: Locator): LocatorAssertions;
|
|
13
|
+
/** Auto-retrying assertions on the page URL and title */
|
|
14
|
+
(page: Page): PageAssertions;
|
|
15
|
+
/** Synchronous assertions on a plain value */
|
|
16
|
+
<T>(value: T): ValueAssertions<T>;
|
|
17
|
+
/** The value matchers, retried until they pass: `await expect.poll(() => getStatus()).toBe("done")` */
|
|
18
|
+
poll: typeof poll;
|
|
19
|
+
any: typeof any;
|
|
20
|
+
anything: typeof anything;
|
|
21
|
+
stringContaining: typeof stringContaining;
|
|
22
|
+
stringMatching: typeof stringMatching;
|
|
23
|
+
arrayContaining: typeof arrayContaining;
|
|
24
|
+
objectContaining: typeof objectContaining;
|
|
25
|
+
}
|
|
26
|
+
export declare const expect: Expect;
|
package/dist/assert/expect.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import Page from "../browser/page.js";
|
|
2
2
|
import Locator from "../locator/locator.js";
|
|
3
|
+
import { any, anything, arrayContaining, objectContaining, stringContaining, stringMatching, } from "./asymmetric.js";
|
|
4
|
+
import { poll } from "./retry-assertions.js";
|
|
3
5
|
import { LocatorAssertions } from "./locator-assertions.js";
|
|
4
6
|
import { PageAssertions } from "./page-assertions.js";
|
|
5
7
|
import { ValueAssertions } from "./value-assertions.js";
|
|
6
8
|
export { takePendingAssertions } from "./pending-assertions.js";
|
|
7
|
-
|
|
8
|
-
export { PageAssertions } from "./page-assertions.js";
|
|
9
|
-
export { ValueAssertions } from "./value-assertions.js";
|
|
10
|
-
export function expect(value) {
|
|
9
|
+
function expectValue(value) {
|
|
11
10
|
if (value instanceof Locator)
|
|
12
11
|
return new LocatorAssertions(value);
|
|
13
12
|
if (value instanceof Page)
|
|
14
13
|
return new PageAssertions(value);
|
|
15
14
|
return new ValueAssertions(value);
|
|
16
15
|
}
|
|
16
|
+
export const expect = Object.assign(expectValue, {
|
|
17
|
+
poll,
|
|
18
|
+
any,
|
|
19
|
+
anything,
|
|
20
|
+
stringContaining,
|
|
21
|
+
stringMatching,
|
|
22
|
+
arrayContaining,
|
|
23
|
+
objectContaining,
|
|
24
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type Locator from "../locator/locator.js";
|
|
2
|
+
export interface LocatorAssertionOptions {
|
|
3
|
+
/** Time (ms) to keep retrying. Defaults to config `expect.timeout`, then 5000 */
|
|
4
|
+
timeout?: number;
|
|
5
|
+
}
|
|
6
|
+
/** The options of the checks that can keep the text they found */
|
|
7
|
+
export interface TextAssertionOptions extends LocatorAssertionOptions {
|
|
8
|
+
/** Saves the text that passed (a RegExp's first group, else its match) as this output of the running test */
|
|
9
|
+
output?: string;
|
|
10
|
+
}
|
|
11
|
+
/** What `readEventually` reads, what it waits for and how its failure is named */
|
|
12
|
+
export interface EventualRead<V> {
|
|
13
|
+
/** The matcher, with `not.` in front when negated */
|
|
14
|
+
name: string;
|
|
15
|
+
locator: Locator;
|
|
16
|
+
expected: unknown;
|
|
17
|
+
read: () => Promise<V>;
|
|
18
|
+
/** True for the read that ends the wait */
|
|
19
|
+
check: (actual: V) => boolean;
|
|
20
|
+
/** Time (ms) to keep retrying. Defaults to the expect timeout */
|
|
21
|
+
timeout?: number | undefined;
|
|
22
|
+
/** A read of `null` (no element) never passes, and fails as "could not read" */
|
|
23
|
+
mustExist?: boolean;
|
|
24
|
+
/** The first line of a failure, instead of `expect(locator).<name>` */
|
|
25
|
+
call?: string;
|
|
26
|
+
/** What a failure shows as the received value, when the read is a wrapper around it */
|
|
27
|
+
display?: (actual: V) => unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Re-reads the element until `check` passes or the timeout expires, and resolves with the read that passed. A
|
|
31
|
+
* timeout fails as an `AssertionError` with the last read, or as "could not read" when no read completed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readEventually<V>({ name, locator, expected, read, check, timeout: perCall, mustExist, call, display, }: EventualRead<V>): Promise<V>;
|
|
34
|
+
export declare class LocatorAssertionBase {
|
|
35
|
+
protected readonly locator: Locator;
|
|
36
|
+
protected readonly negated: boolean;
|
|
37
|
+
constructor(locator: Locator, negated?: boolean);
|
|
38
|
+
/** The matcher name as failures show it: `not.` in front when negated */
|
|
39
|
+
protected matcherName(matcher: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Re-reads the element until `check` passes or the timeout expires. With `mustExist`, a read of `null`
|
|
42
|
+
* (no element) never passes, with or without `.not`, and fails as "could not read"
|
|
43
|
+
*/
|
|
44
|
+
protected assertEventually<V>(matcher: string, expected: unknown, read: () => Promise<V>, check: (actual: V) => boolean, options?: LocatorAssertionOptions, mustExist?: boolean, save?: (actual: V) => void): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* A text check that can keep what it read: with `options.output` the read that passed goes to that output, as
|
|
47
|
+
* `expected` captures it. The sink is taken now, when the call starts.
|
|
48
|
+
*/
|
|
49
|
+
protected assertText(matcher: string, expected: string | RegExp, read: () => Promise<string | null>, mode: "exact" | "contains", options?: TextAssertionOptions, normalize?: boolean): Promise<void>;
|
|
50
|
+
/** For matchers that need an element: `read` answers null when there is none, which never passes */
|
|
51
|
+
protected assertExisting<V>(matcher: string, expected: unknown, read: () => Promise<V | null>, check: (actual: V) => boolean, options?: LocatorAssertionOptions): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { resolveTimeout } from "../config/timeouts.js";
|
|
2
|
+
import { waitUntil, WaitTimeoutError } from "../wait/wait-until.js";
|
|
3
|
+
import { AssertionError } from "./assertion-error.js";
|
|
4
|
+
import { track } from "./pending-assertions.js";
|
|
5
|
+
import { bindOutput } from "../wait/output-sink.js";
|
|
6
|
+
import { captured, normalizedTextCheck, textCheck } from "./text-match.js";
|
|
7
|
+
/**
|
|
8
|
+
* Re-reads the element until `check` passes or the timeout expires, and resolves with the read that passed. A
|
|
9
|
+
* timeout fails as an `AssertionError` with the last read, or as "could not read" when no read completed.
|
|
10
|
+
*/
|
|
11
|
+
export async function readEventually({ name, locator, expected, read, check, timeout: perCall, mustExist = false, call, display, }) {
|
|
12
|
+
const timeout = await resolveTimeout(perCall);
|
|
13
|
+
try {
|
|
14
|
+
return await waitUntil(read, (actual) => (mustExist && actual === null ? false : check(actual)), { timeout });
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
if (!(err instanceof WaitTimeoutError))
|
|
18
|
+
throw err;
|
|
19
|
+
// Reads never resolve to undefined, so an undefined `last` means no read completed
|
|
20
|
+
if (err.last === undefined || (mustExist && err.last === null)) {
|
|
21
|
+
throw new Error(`${call ?? `expect(locator).${name}`}: could not read ${locator.selector} within ${timeout}ms`);
|
|
22
|
+
}
|
|
23
|
+
throw new AssertionError({
|
|
24
|
+
matcher: name,
|
|
25
|
+
expected,
|
|
26
|
+
actual: display ? display(err.last) : err.last,
|
|
27
|
+
locator: locator.selector,
|
|
28
|
+
call,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class LocatorAssertionBase {
|
|
33
|
+
constructor(locator, negated = false) {
|
|
34
|
+
this.locator = locator;
|
|
35
|
+
this.negated = negated;
|
|
36
|
+
}
|
|
37
|
+
/** The matcher name as failures show it: `not.` in front when negated */
|
|
38
|
+
matcherName(matcher) {
|
|
39
|
+
return this.negated ? `not.${matcher}` : matcher;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Re-reads the element until `check` passes or the timeout expires. With `mustExist`, a read of `null`
|
|
43
|
+
* (no element) never passes, with or without `.not`, and fails as "could not read"
|
|
44
|
+
*/
|
|
45
|
+
assertEventually(matcher, expected, read, check, options, mustExist = false, save) {
|
|
46
|
+
const name = this.negated ? `not.${matcher}` : matcher;
|
|
47
|
+
return track(`expect(locator).${name}()`, readEventually({
|
|
48
|
+
name,
|
|
49
|
+
locator: this.locator,
|
|
50
|
+
expected,
|
|
51
|
+
read,
|
|
52
|
+
check: (actual) => check(actual) !== this.negated,
|
|
53
|
+
timeout: options?.timeout,
|
|
54
|
+
mustExist,
|
|
55
|
+
}).then((actual) => save?.(actual)));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A text check that can keep what it read: with `options.output` the read that passed goes to that output, as
|
|
59
|
+
* `expected` captures it. The sink is taken now, when the call starts.
|
|
60
|
+
*/
|
|
61
|
+
assertText(matcher, expected, read, mode, options, normalize = false) {
|
|
62
|
+
let save;
|
|
63
|
+
if (options?.output !== undefined) {
|
|
64
|
+
const call = `expect(locator).${this.negated ? "not." : ""}${matcher}`;
|
|
65
|
+
if (this.negated)
|
|
66
|
+
throw new Error(`${call}: output can't be saved from a negated check`);
|
|
67
|
+
const keep = bindOutput(call, options.output);
|
|
68
|
+
save = (actual) => keep(captured(expected, actual));
|
|
69
|
+
}
|
|
70
|
+
return this.assertEventually(matcher, expected, read, normalize
|
|
71
|
+
? normalizedTextCheck(expected, mode)
|
|
72
|
+
: textCheck(expected, mode), options, false, save);
|
|
73
|
+
}
|
|
74
|
+
/** For matchers that need an element: `read` answers null when there is none, which never passes */
|
|
75
|
+
assertExisting(matcher, expected, read, check, options) {
|
|
76
|
+
return this.assertEventually(matcher, expected, read, (actual) => actual !== null && check(actual), options, true);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,66 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { MoreLocatorAssertions } from "./more-locator-assertions.js";
|
|
2
|
+
import type { LocatorAssertionOptions, TextAssertionOptions } from "./locator-assertion-base.js";
|
|
3
|
+
export { readEventually } from "./locator-assertion-base.js";
|
|
4
|
+
export type { LocatorAssertionOptions, TextAssertionOptions, } from "./locator-assertion-base.js";
|
|
5
|
+
export interface CheckedOptions extends LocatorAssertionOptions {
|
|
6
|
+
/** The state to expect: `true` (default), `false`, or `"mixed"` (an indeterminate checkbox, or `aria-checked="mixed"`) */
|
|
7
|
+
checked?: boolean | "mixed";
|
|
8
|
+
/** Same as `checked: "mixed"` */
|
|
9
|
+
indeterminate?: boolean;
|
|
5
10
|
}
|
|
6
|
-
|
|
7
|
-
export interface TextAssertionOptions extends LocatorAssertionOptions {
|
|
8
|
-
/** Saves the text that passed (a RegExp's first group, else its match) as this output of the running test */
|
|
9
|
-
output?: string;
|
|
10
|
-
}
|
|
11
|
-
/** What `readEventually` reads, what it waits for and how its failure is named */
|
|
12
|
-
export interface EventualRead<V> {
|
|
13
|
-
/** The matcher, with `not.` in front when negated */
|
|
14
|
-
name: string;
|
|
15
|
-
locator: Locator;
|
|
16
|
-
expected: unknown;
|
|
17
|
-
read: () => Promise<V>;
|
|
18
|
-
/** True for the read that ends the wait */
|
|
19
|
-
check: (actual: V) => boolean;
|
|
20
|
-
/** Time (ms) to keep retrying. Defaults to the expect timeout */
|
|
21
|
-
timeout?: number | undefined;
|
|
22
|
-
/** A read of `null` (no element) never passes, and fails as "could not read" */
|
|
23
|
-
mustExist?: boolean;
|
|
24
|
-
/** The first line of a failure, instead of `expect(locator).<name>` */
|
|
25
|
-
call?: string;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Re-reads the element until `check` passes or the timeout expires, and resolves with the read that passed. A
|
|
29
|
-
* timeout fails as an `AssertionError` with the last read, or as "could not read" when no read completed.
|
|
30
|
-
*/
|
|
31
|
-
export declare function readEventually<V>({ name, locator, expected, read, check, timeout: perCall, mustExist, call, }: EventualRead<V>): Promise<V>;
|
|
32
|
-
export declare class LocatorAssertions {
|
|
33
|
-
private readonly locator;
|
|
34
|
-
private readonly negated;
|
|
35
|
-
constructor(locator: Locator, negated?: boolean);
|
|
11
|
+
export declare class LocatorAssertions extends MoreLocatorAssertions {
|
|
36
12
|
/** Inverts the next matcher: it retries until the condition is false */
|
|
37
13
|
get not(): LocatorAssertions;
|
|
14
|
+
/** The element exists, has a box and isn't hidden by CSS; `visible: false` expects the opposite, as `toBeHidden` */
|
|
15
|
+
toBeVisible(options?: LocatorAssertionOptions & {
|
|
16
|
+
visible?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
38
18
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
19
|
+
* Trimmed text content equals `expected` (a string, compared with whitespace collapsed on both sides) or matches it (RegExp).
|
|
20
|
+
* An array expects one entry per matched element, in order, and as many entries as elements
|
|
41
21
|
*/
|
|
42
|
-
|
|
22
|
+
toHaveText(expected: string | RegExp | Array<string | RegExp>, options?: TextAssertionOptions): Promise<void>;
|
|
43
23
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
24
|
+
* Text content contains `expected` (a string, compared with whitespace collapsed on both sides) or matches it (RegExp).
|
|
25
|
+
* An array expects its entries, in order, in some of the matched elements (one entry per element, others may sit between)
|
|
46
26
|
*/
|
|
47
|
-
|
|
48
|
-
/** For matchers that need an element: `read` answers null when there is none, which never passes */
|
|
49
|
-
private assertExisting;
|
|
50
|
-
toBeVisible(options?: LocatorAssertionOptions): Promise<void>;
|
|
51
|
-
/** Trimmed text content equals `expected` (string) or matches it (RegExp) */
|
|
52
|
-
toHaveText(expected: string | RegExp, options?: TextAssertionOptions): Promise<void>;
|
|
53
|
-
/** Text content contains `expected` (string) or matches it (RegExp) */
|
|
54
|
-
toContainText(expected: string | RegExp, options?: TextAssertionOptions): Promise<void>;
|
|
27
|
+
toContainText(expected: string | RegExp | Array<string | RegExp>, options?: TextAssertionOptions): Promise<void>;
|
|
55
28
|
toHaveValue(expected: string | RegExp, options?: TextAssertionOptions): Promise<void>;
|
|
29
|
+
/** The attribute equals `expected` (string) or matches it (RegExp); with only a name (and options), that the attribute exists */
|
|
30
|
+
toHaveAttribute(name: string, options?: LocatorAssertionOptions): Promise<void>;
|
|
56
31
|
toHaveAttribute(name: string, expected: string | RegExp, options?: TextAssertionOptions): Promise<void>;
|
|
57
32
|
toHaveCount(expected: number, options?: LocatorAssertionOptions): Promise<void>;
|
|
58
33
|
/** The element is missing, or has no box or is hidden by CSS (the opposite of `toBeVisible`) */
|
|
59
34
|
toBeHidden(options?: LocatorAssertionOptions): Promise<void>;
|
|
60
35
|
/** The element exists and is neither `:disabled` nor `aria-disabled="true"`; a missing element never passes, not even with `.not` */
|
|
61
|
-
toBeEnabled(options?: LocatorAssertionOptions
|
|
36
|
+
toBeEnabled(options?: LocatorAssertionOptions & {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
}): Promise<void>;
|
|
62
39
|
/** A checked checkbox or radio input, or an element with `aria-checked="true"`; a missing element never passes, not even with `.not` */
|
|
63
|
-
toBeChecked(options?:
|
|
40
|
+
toBeChecked(options?: CheckedOptions): Promise<void>;
|
|
64
41
|
/** The class attribute equals `expected` (string, whitespace collapsed and trimmed on both sides) or matches it (RegExp, tested on the attribute as written) */
|
|
65
42
|
toHaveClass(expected: string | RegExp, options?: LocatorAssertionOptions): Promise<void>;
|
|
66
43
|
/** The computed value of a CSS property equals `expected` (string) or matches it (RegExp); `name` may be camelCase (`backgroundColor`) or kebab-case, `--custom` is kept */
|