@allwright.dev/vitest 0.1.1 → 0.1.3
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 +47 -0
- package/dist/expectations.d.ts +30 -0
- package/dist/expectations.js +85 -0
- package/dist/index.d.ts +5 -28
- package/dist/index.js +75 -106
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -117,3 +117,50 @@ Available fixtures:
|
|
|
117
117
|
`androidApp` launches using `allwright.android.launchOptions` first, then falls back to `config.mobile.android`.
|
|
118
118
|
|
|
119
119
|
Android apps and locators support the Android-applicable action and expectation subset: `click`, `count`, `focus`, `fill`, `press`, `textContent`, `innerText`, `waitForSelector`, and `screenshot`. `expect(androidApp)` and `expect(androidApp.locator(...))` provide `toHaveText`, `toContainText`, `toHaveCount`, and `toBeVisible` with the same retry controls as web fixtures. Hover and highlight remain web-only.
|
|
120
|
+
|
|
121
|
+
The opt-in accessibility integration test exercises the public page fixture,
|
|
122
|
+
bundled protobuf, running engine and dynamically loaded web plugin. From this
|
|
123
|
+
repository, after building the packages, run:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
ALLWRIGHT_TEST_BROWSER=chromium bun run --cwd typescript/vitest vitest run tests/accessibility.spec.ts
|
|
127
|
+
ALLWRIGHT_TEST_BROWSER=firefox bun run --cwd typescript/vitest vitest run tests/accessibility.spec.ts
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Set `ALLWRIGHT_TEST_LIVE_SITE=1` to reproduce the Form Inputs navigation on the
|
|
131
|
+
public example site. The default fixture is a local data URL. Use
|
|
132
|
+
`ALLWRIGHT_CLI_PATH` and `ALLWRIGHT_SERVER_ADDR` to select the runtime under test;
|
|
133
|
+
client, engine and installed web plugin versions must match.
|
|
134
|
+
|
|
135
|
+
### Negated assertions and locator exclusion
|
|
136
|
+
|
|
137
|
+
Every Allwright page/app and locator matcher supports both Vitest's `.not` form
|
|
138
|
+
and the callable `.not()` alias. Negation retries the opposite condition using
|
|
139
|
+
the same timeout and interval options:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
await expect(page.getByRole('status')).not.toHaveText('Loading');
|
|
143
|
+
await expect(page.getByTestId('spinner')).not().toBeVisible();
|
|
144
|
+
await expect(page).not.toHaveCount('.error', 1);
|
|
145
|
+
await expect(androidApp.locator('text="Loading"')).not().toBeVisible();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Negative visibility succeeds when the element is hidden or absent. Selector,
|
|
149
|
+
transport, and session errors still fail; they are not evidence of invisibility.
|
|
150
|
+
Negative text assertions require a successful text read. Matcher objects are
|
|
151
|
+
immutable, and a second `.not` toggles back to positive assertions. Ordinary
|
|
152
|
+
Vitest value assertions retain their native `.not` syntax.
|
|
153
|
+
|
|
154
|
+
Web locators also support exclusion by element identity, with further chaining:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const enabledActions = page.getByRole('button')
|
|
158
|
+
.not(page.getByRole('button', { disabled: true }));
|
|
159
|
+
await expect(enabledActions).not().toHaveCount(0);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Both locators must belong to the same page. `.not(other)` removes matching nodes;
|
|
163
|
+
`filter({ hasNot: other })` removes candidates containing matching descendants.
|
|
164
|
+
The same exclusion API is available in the core web clients as TypeScript/Java
|
|
165
|
+
`.not(other)`, Go `.Not(other)`, Rust `.not(&other)`, and Python `.not_(other)`.
|
|
166
|
+
Native Android locator exclusion is not exposed by this web selector API.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CommandOptions, Locator, MobileAndroidApp, MobileAndroidLocator, Page, WaitForSelectorOptions } from "@allwright.dev/core";
|
|
2
|
+
export interface RetryExpectationOptions {
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
intervalMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TextExpectationOptions extends RetryExpectationOptions {
|
|
7
|
+
command?: CommandOptions;
|
|
8
|
+
}
|
|
9
|
+
export interface VisibleExpectationOptions extends RetryExpectationOptions {
|
|
10
|
+
command?: WaitForSelectorOptions;
|
|
11
|
+
}
|
|
12
|
+
export interface PageExpectMatchers {
|
|
13
|
+
readonly not: PageExpectMatchers & (() => PageExpectMatchers);
|
|
14
|
+
toHaveText(selector: string, expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
15
|
+
toContainText(selector: string, expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
16
|
+
toHaveCount(selector: string, expected: number, options?: RetryExpectationOptions): Promise<void>;
|
|
17
|
+
toBeVisible(selector: string, options?: VisibleExpectationOptions): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface LocatorExpectMatchers {
|
|
20
|
+
readonly not: LocatorExpectMatchers & (() => LocatorExpectMatchers);
|
|
21
|
+
toHaveText(expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
22
|
+
toContainText(expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
23
|
+
toHaveCount(expected: number, options?: RetryExpectationOptions): Promise<void>;
|
|
24
|
+
toBeVisible(options?: VisibleExpectationOptions): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
type Defaults = () => RetryExpectationOptions;
|
|
27
|
+
type AnyLocator = Locator | MobileAndroidLocator;
|
|
28
|
+
export declare function createLocatorExpect(locator: AnyLocator, defaults: Defaults, negated?: boolean): LocatorExpectMatchers;
|
|
29
|
+
export declare function createPageExpect(page: Page | MobileAndroidApp, defaults: Defaults, negated?: boolean): PageExpectMatchers;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { expect as vitestExpect } from "vitest";
|
|
2
|
+
function callableMatchers(matchers) {
|
|
3
|
+
return Object.defineProperties(() => matchers, Object.getOwnPropertyDescriptors(matchers));
|
|
4
|
+
}
|
|
5
|
+
async function retryExpectation(callback, options, defaults) {
|
|
6
|
+
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs ?? 5_000;
|
|
7
|
+
const intervalMs = options.intervalMs ?? defaults.intervalMs ?? 100;
|
|
8
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || !Number.isFinite(intervalMs) || intervalMs < 0) {
|
|
9
|
+
throw new Error("Expectation timeoutMs and intervalMs must be finite non-negative numbers");
|
|
10
|
+
}
|
|
11
|
+
const deadline = Date.now() + timeoutMs;
|
|
12
|
+
while (true) {
|
|
13
|
+
try {
|
|
14
|
+
// The assertion owns retrying; do not let a nested command use the server's full default timeout.
|
|
15
|
+
await callback({ timeoutMs: Math.max(1, Math.min(options.command?.timeoutMs ?? Math.max(1, intervalMs), deadline - Date.now())) });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (Date.now() + intervalMs >= deadline)
|
|
20
|
+
throw error;
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function isVisible(locator, command) {
|
|
26
|
+
if ("filter" in locator) {
|
|
27
|
+
return (await locator.filter({ visible: true }).count(command)).count > 0;
|
|
28
|
+
}
|
|
29
|
+
// Android has no visibility-filter query. An absent node is not visible; only
|
|
30
|
+
// its explicit native visibility failure is a negative observation.
|
|
31
|
+
if ((await locator.count(command)).count === 0)
|
|
32
|
+
return false;
|
|
33
|
+
try {
|
|
34
|
+
return (await locator.waitFor({ ...command, visible: true })).visible;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof Error && error.message.startsWith("selector found but not visible: "))
|
|
38
|
+
return false;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function createLocatorExpect(locator, defaults, negated = false) {
|
|
43
|
+
const assertion = (value) => negated ? vitestExpect(value).not : vitestExpect(value);
|
|
44
|
+
return {
|
|
45
|
+
get not() { return callableMatchers(createLocatorExpect(locator, defaults, !negated)); },
|
|
46
|
+
async toHaveText(expected, options = {}) {
|
|
47
|
+
await retryExpectation(async (command) => {
|
|
48
|
+
const { text } = await locator.textContent(command);
|
|
49
|
+
if (expected instanceof RegExp)
|
|
50
|
+
assertion(text).toMatch(new RegExp(expected.source, expected.flags));
|
|
51
|
+
else
|
|
52
|
+
assertion(text).toBe(expected);
|
|
53
|
+
}, options, defaults());
|
|
54
|
+
},
|
|
55
|
+
async toContainText(expected, options = {}) {
|
|
56
|
+
await retryExpectation(async (command) => {
|
|
57
|
+
const { text } = await locator.textContent(command);
|
|
58
|
+
if (expected instanceof RegExp)
|
|
59
|
+
assertion(text).toMatch(new RegExp(expected.source, expected.flags));
|
|
60
|
+
else
|
|
61
|
+
assertion(text).toContain(expected);
|
|
62
|
+
}, options, defaults());
|
|
63
|
+
},
|
|
64
|
+
async toHaveCount(expected, options = {}) {
|
|
65
|
+
await retryExpectation(async (command) => {
|
|
66
|
+
assertion((await locator.count(command)).count).toBe(expected);
|
|
67
|
+
}, options, defaults());
|
|
68
|
+
},
|
|
69
|
+
async toBeVisible(options = {}) {
|
|
70
|
+
await retryExpectation(async (command) => {
|
|
71
|
+
assertion(await isVisible(locator, command)).toBe(true);
|
|
72
|
+
}, options, defaults());
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function createPageExpect(page, defaults, negated = false) {
|
|
77
|
+
const matcher = (selector) => createLocatorExpect(page.locator(selector), defaults, negated);
|
|
78
|
+
return {
|
|
79
|
+
get not() { return callableMatchers(createPageExpect(page, defaults, !negated)); },
|
|
80
|
+
toHaveText: (selector, expected, options) => matcher(selector).toHaveText(expected, options),
|
|
81
|
+
toContainText: (selector, expected, options) => matcher(selector).toContainText(expected, options),
|
|
82
|
+
toHaveCount: (selector, expected, options) => matcher(selector).toHaveCount(expected, options),
|
|
83
|
+
toBeVisible: (selector, options) => matcher(selector).toBeVisible(options),
|
|
84
|
+
};
|
|
85
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import type { PageExpectMatchers, LocatorExpectMatchers } from "./expectations.js";
|
|
2
|
+
export type { RetryExpectationOptions, TextExpectationOptions, VisibleExpectationOptions, PageExpectMatchers, LocatorExpectMatchers } from "./expectations.js";
|
|
3
|
+
import { type MobileAndroidConnectOptions, type MobileAndroidDevice, type MobileAndroidLaunchOptions, type MobileAndroidApp, type Browser, type BrowserKind, type LaunchOptions, type Locator, type MobileAndroidLocator, type Page } from "@allwright.dev/core";
|
|
4
|
+
import { expect as vitestExpect, type Assertion } from "vitest";
|
|
3
5
|
export interface AllwrightVitestOptions {
|
|
4
6
|
launchOptions?: LaunchOptions;
|
|
5
7
|
serverAddr?: string;
|
|
@@ -18,30 +20,6 @@ export interface AllwrightVitestFixtures {
|
|
|
18
20
|
android: MobileAndroidDevice;
|
|
19
21
|
androidApp: MobileAndroidApp;
|
|
20
22
|
}
|
|
21
|
-
export interface RetryExpectationOptions {
|
|
22
|
-
timeoutMs?: number;
|
|
23
|
-
intervalMs?: number;
|
|
24
|
-
}
|
|
25
|
-
export interface TextExpectationOptions extends RetryExpectationOptions {
|
|
26
|
-
command?: {
|
|
27
|
-
timeoutMs?: number;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export interface VisibleExpectationOptions extends RetryExpectationOptions {
|
|
31
|
-
command?: WaitForSelectorOptions;
|
|
32
|
-
}
|
|
33
|
-
export interface PageExpectMatchers {
|
|
34
|
-
toHaveText(selector: string, expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
35
|
-
toContainText(selector: string, expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
36
|
-
toHaveCount(selector: string, expected: number, options?: RetryExpectationOptions): Promise<void>;
|
|
37
|
-
toBeVisible(selector: string, options?: VisibleExpectationOptions): Promise<void>;
|
|
38
|
-
}
|
|
39
|
-
export interface LocatorExpectMatchers {
|
|
40
|
-
toHaveText(expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
41
|
-
toContainText(expected: string | RegExp, options?: TextExpectationOptions): Promise<void>;
|
|
42
|
-
toHaveCount(expected: number, options?: RetryExpectationOptions): Promise<void>;
|
|
43
|
-
toBeVisible(options?: VisibleExpectationOptions): Promise<void>;
|
|
44
|
-
}
|
|
45
23
|
interface LazyResource<T> {
|
|
46
24
|
peek(): T | null;
|
|
47
25
|
get(): Promise<T>;
|
|
@@ -57,13 +35,12 @@ export declare const test: import("vitest").TestAPI<{
|
|
|
57
35
|
_androidResource: LazyResource<MobileAndroidDevice>;
|
|
58
36
|
}>;
|
|
59
37
|
type VitestExpect = typeof vitestExpect;
|
|
60
|
-
type VitestMatcherReturn = ReturnType<VitestExpect>;
|
|
61
38
|
interface AllwrightExpect extends VitestExpect {
|
|
62
39
|
(actual: Page): PageExpectMatchers;
|
|
63
40
|
(actual: Locator): LocatorExpectMatchers;
|
|
64
41
|
(actual: MobileAndroidApp): PageExpectMatchers;
|
|
65
42
|
(actual: MobileAndroidLocator): LocatorExpectMatchers;
|
|
66
|
-
<T>(actual: T):
|
|
43
|
+
<T>(actual: T): Assertion<T>;
|
|
67
44
|
}
|
|
68
45
|
export declare const expect: AllwrightExpect;
|
|
69
46
|
export { vitestExpect };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLocatorExpect, createPageExpect } from "./expectations.js";
|
|
1
2
|
import { launchConfiguredBrowser, mobile, resolveConfig, setServerAddr, } from "@allwright.dev/core";
|
|
2
3
|
import { expect as vitestExpect, test as base } from "vitest";
|
|
3
4
|
const DEFAULT_ANDROID_CONNECT_TIMEOUT_MS = 30_000;
|
|
@@ -33,6 +34,17 @@ function getLazySyncProperty(resource, property) {
|
|
|
33
34
|
}
|
|
34
35
|
throw new Error(`fixture property ${String(property)} is not available before the lazy resource is initialized; use an async method first`);
|
|
35
36
|
}
|
|
37
|
+
const lazyLocatorSources = new WeakMap();
|
|
38
|
+
async function resolveFilterLocator(locator, page) {
|
|
39
|
+
if (!locator)
|
|
40
|
+
return undefined;
|
|
41
|
+
const source = lazyLocatorSources.get(locator);
|
|
42
|
+
if (!source)
|
|
43
|
+
return locator;
|
|
44
|
+
if (source.page !== page)
|
|
45
|
+
throw new Error('Filter locators must belong to the same page');
|
|
46
|
+
return (await page.get()).locator(await source.selector());
|
|
47
|
+
}
|
|
36
48
|
function createLazyLocator(pageResource, selectorFactory) {
|
|
37
49
|
const lazyLocator = {
|
|
38
50
|
get page() {
|
|
@@ -71,10 +83,45 @@ function createLazyLocator(pageResource, selectorFactory) {
|
|
|
71
83
|
async waitFor(options) {
|
|
72
84
|
return (await pageResource.get()).locator(await selectorFactory()).waitFor(options);
|
|
73
85
|
},
|
|
74
|
-
locator(selector) {
|
|
75
|
-
|
|
86
|
+
locator(selector, options) {
|
|
87
|
+
const result = createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).locator(selector).selector);
|
|
88
|
+
return options ? result.filter(options) : result;
|
|
89
|
+
},
|
|
90
|
+
getByRole(...args) {
|
|
91
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByRole(...args).selector);
|
|
92
|
+
},
|
|
93
|
+
getByText(...args) {
|
|
94
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByText(...args).selector);
|
|
95
|
+
},
|
|
96
|
+
getByLabel(...args) {
|
|
97
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByLabel(...args).selector);
|
|
98
|
+
},
|
|
99
|
+
getByPlaceholder(...args) {
|
|
100
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByPlaceholder(...args).selector);
|
|
101
|
+
},
|
|
102
|
+
getByAltText(...args) {
|
|
103
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByAltText(...args).selector);
|
|
104
|
+
},
|
|
105
|
+
getByTitle(...args) {
|
|
106
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByTitle(...args).selector);
|
|
107
|
+
},
|
|
108
|
+
getByTestId(...args) {
|
|
109
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).getByTestId(...args).selector);
|
|
76
110
|
},
|
|
111
|
+
not(other) {
|
|
112
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).not((await resolveFilterLocator(other, pageResource))).selector);
|
|
113
|
+
},
|
|
114
|
+
filter(options = {}) {
|
|
115
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).filter({ ...options,
|
|
116
|
+
has: await resolveFilterLocator(options.has, pageResource),
|
|
117
|
+
hasNot: await resolveFilterLocator(options.hasNot, pageResource),
|
|
118
|
+
}).selector);
|
|
119
|
+
},
|
|
120
|
+
nth(index) { return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).nth(index).selector); },
|
|
121
|
+
first() { return this.nth(0); },
|
|
122
|
+
last() { return this.nth(-1); },
|
|
77
123
|
};
|
|
124
|
+
lazyLocatorSources.set(lazyLocator, { page: pageResource, selector: selectorFactory });
|
|
78
125
|
return lazyLocator;
|
|
79
126
|
}
|
|
80
127
|
function createLazyPage(pageResource) {
|
|
@@ -85,8 +132,9 @@ function createLazyPage(pageResource) {
|
|
|
85
132
|
get browserSessionId() {
|
|
86
133
|
return getLazySyncProperty(pageResource, "browserSessionId");
|
|
87
134
|
},
|
|
88
|
-
locator(selector) {
|
|
89
|
-
|
|
135
|
+
locator(selector, options) {
|
|
136
|
+
const result = createLazyLocator(pageResource, async () => selector);
|
|
137
|
+
return options ? result.filter(options) : result;
|
|
90
138
|
},
|
|
91
139
|
async goto(url, options) {
|
|
92
140
|
return (await pageResource.get()).goto(url, options);
|
|
@@ -146,6 +194,27 @@ function createLazyPage(pageResource) {
|
|
|
146
194
|
browserSessionId: getLazySyncProperty(pageResource, "browserSessionId"),
|
|
147
195
|
};
|
|
148
196
|
},
|
|
197
|
+
getByRole(...args) {
|
|
198
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByRole(...args).selector);
|
|
199
|
+
},
|
|
200
|
+
getByText(...args) {
|
|
201
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByText(...args).selector);
|
|
202
|
+
},
|
|
203
|
+
getByLabel(...args) {
|
|
204
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByLabel(...args).selector);
|
|
205
|
+
},
|
|
206
|
+
getByPlaceholder(...args) {
|
|
207
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByPlaceholder(...args).selector);
|
|
208
|
+
},
|
|
209
|
+
getByAltText(...args) {
|
|
210
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByAltText(...args).selector);
|
|
211
|
+
},
|
|
212
|
+
getByTitle(...args) {
|
|
213
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByTitle(...args).selector);
|
|
214
|
+
},
|
|
215
|
+
getByTestId(...args) {
|
|
216
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).getByTestId(...args).selector);
|
|
217
|
+
},
|
|
149
218
|
};
|
|
150
219
|
return lazyPage;
|
|
151
220
|
}
|
|
@@ -356,8 +425,6 @@ export const test = base.extend({
|
|
|
356
425
|
await use(createLazyAndroidApp(appResource));
|
|
357
426
|
},
|
|
358
427
|
});
|
|
359
|
-
const DEFAULT_EXPECT_TIMEOUT_MS = 5_000;
|
|
360
|
-
const DEFAULT_EXPECT_INTERVAL_MS = 100;
|
|
361
428
|
function isPage(value) {
|
|
362
429
|
return !!value && typeof value === "object" && typeof value.locator === "function";
|
|
363
430
|
}
|
|
@@ -411,114 +478,16 @@ function resolveAndroidLaunchOptions(config, options) {
|
|
|
411
478
|
}
|
|
412
479
|
return launchOptions;
|
|
413
480
|
}
|
|
414
|
-
async function retryExpectation(callback, options = {}, defaults = {}) {
|
|
415
|
-
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs ?? DEFAULT_EXPECT_TIMEOUT_MS;
|
|
416
|
-
const intervalMs = options.intervalMs ?? defaults.intervalMs ?? DEFAULT_EXPECT_INTERVAL_MS;
|
|
417
|
-
const deadline = Date.now() + timeoutMs;
|
|
418
|
-
let lastError;
|
|
419
|
-
while (Date.now() <= deadline) {
|
|
420
|
-
try {
|
|
421
|
-
await callback();
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
catch (error) {
|
|
425
|
-
lastError = error;
|
|
426
|
-
if (Date.now() + intervalMs > deadline) {
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
433
|
-
}
|
|
434
|
-
function createPageExpect(page) {
|
|
435
|
-
return {
|
|
436
|
-
async toHaveText(selector, expected, options = {}) {
|
|
437
|
-
await retryExpectation(async () => {
|
|
438
|
-
const result = await page.textContent(selector, options.command ?? {});
|
|
439
|
-
if (expected instanceof RegExp) {
|
|
440
|
-
vitestExpect(result.text).toMatch(expected);
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
vitestExpect(result.text).toBe(expected);
|
|
444
|
-
}, options, currentExpectDefaults());
|
|
445
|
-
},
|
|
446
|
-
async toContainText(selector, expected, options = {}) {
|
|
447
|
-
await retryExpectation(async () => {
|
|
448
|
-
const result = await page.textContent(selector, options.command ?? {});
|
|
449
|
-
if (expected instanceof RegExp) {
|
|
450
|
-
vitestExpect(result.text).toMatch(expected);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
vitestExpect(result.text).toContain(expected);
|
|
454
|
-
}, options, currentExpectDefaults());
|
|
455
|
-
},
|
|
456
|
-
async toHaveCount(selector, expected, options = {}) {
|
|
457
|
-
await retryExpectation(async () => {
|
|
458
|
-
const result = await page.count(selector, {});
|
|
459
|
-
vitestExpect(result.count).toBe(expected);
|
|
460
|
-
}, options, currentExpectDefaults());
|
|
461
|
-
},
|
|
462
|
-
async toBeVisible(selector, options = {}) {
|
|
463
|
-
await retryExpectation(async () => {
|
|
464
|
-
const result = await page.waitForSelector(selector, {
|
|
465
|
-
visible: true,
|
|
466
|
-
...(options.command ?? {}),
|
|
467
|
-
});
|
|
468
|
-
vitestExpect(result.visible).toBe(true);
|
|
469
|
-
}, options, currentExpectDefaults());
|
|
470
|
-
},
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
function createLocatorExpect(locator) {
|
|
474
|
-
return {
|
|
475
|
-
async toHaveText(expected, options = {}) {
|
|
476
|
-
await retryExpectation(async () => {
|
|
477
|
-
const result = await locator.textContent(options.command ?? {});
|
|
478
|
-
if (expected instanceof RegExp) {
|
|
479
|
-
vitestExpect(result.text).toMatch(expected);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
vitestExpect(result.text).toBe(expected);
|
|
483
|
-
}, options, currentExpectDefaults());
|
|
484
|
-
},
|
|
485
|
-
async toContainText(expected, options = {}) {
|
|
486
|
-
await retryExpectation(async () => {
|
|
487
|
-
const result = await locator.textContent(options.command ?? {});
|
|
488
|
-
if (expected instanceof RegExp) {
|
|
489
|
-
vitestExpect(result.text).toMatch(expected);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
vitestExpect(result.text).toContain(expected);
|
|
493
|
-
}, options, currentExpectDefaults());
|
|
494
|
-
},
|
|
495
|
-
async toHaveCount(expected, options = {}) {
|
|
496
|
-
await retryExpectation(async () => {
|
|
497
|
-
const result = await locator.count({});
|
|
498
|
-
vitestExpect(result.count).toBe(expected);
|
|
499
|
-
}, options, currentExpectDefaults());
|
|
500
|
-
},
|
|
501
|
-
async toBeVisible(options = {}) {
|
|
502
|
-
await retryExpectation(async () => {
|
|
503
|
-
const result = await locator.waitFor({
|
|
504
|
-
visible: true,
|
|
505
|
-
...(options.command ?? {}),
|
|
506
|
-
});
|
|
507
|
-
vitestExpect(result.visible).toBe(true);
|
|
508
|
-
}, options, currentExpectDefaults());
|
|
509
|
-
},
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
481
|
let activeExpectDefaults = {};
|
|
513
482
|
function currentExpectDefaults() {
|
|
514
483
|
return activeExpectDefaults;
|
|
515
484
|
}
|
|
516
485
|
const expectImpl = ((actual) => {
|
|
517
486
|
if (isLocator(actual)) {
|
|
518
|
-
return createLocatorExpect(actual);
|
|
487
|
+
return createLocatorExpect(actual, currentExpectDefaults);
|
|
519
488
|
}
|
|
520
489
|
if (isPage(actual)) {
|
|
521
|
-
return createPageExpect(actual);
|
|
490
|
+
return createPageExpect(actual, currentExpectDefaults);
|
|
522
491
|
}
|
|
523
492
|
return vitestExpect(actual);
|
|
524
493
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allwright.dev/vitest",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Vitest fixtures for the allwright TypeScript client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"prepublishOnly": "bun run build"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@allwright.dev/core": "0.1.
|
|
50
|
+
"@allwright.dev/core": "0.1.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"typescript": "^5.9.2",
|