@allwright.dev/vitest 0.0.52 → 0.0.53
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 +16 -12
- package/dist/index.d.ts +11 -4
- package/dist/index.js +294 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,13 +12,15 @@ bun add -d vitest @allwright.dev/vitest
|
|
|
12
12
|
import { expect, test } from "@allwright.dev/vitest";
|
|
13
13
|
|
|
14
14
|
test("opens a page", async ({ page }) => {
|
|
15
|
-
await page.goto("https://
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
await page.goto("https://themoderninternet.vercel.app");
|
|
16
|
+
await page.click(
|
|
17
|
+
"xpath=//div[contains(@class,'card')][.//h2[normalize-space()='Form Inputs']]//button[normalize-space()='Visit page']",
|
|
18
|
+
);
|
|
19
|
+
await expect(page.locator('xpath=//h1[text()="Form Inputs"]')).toHaveText("Form Inputs");
|
|
18
20
|
});
|
|
19
21
|
|
|
20
|
-
test("opens an Android app", async ({
|
|
21
|
-
await
|
|
22
|
+
test("opens an Android app", async ({ androidApp }) => {
|
|
23
|
+
await androidApp.click('Id=com.example.airticket:id/bottom_nav_account');
|
|
22
24
|
});
|
|
23
25
|
```
|
|
24
26
|
|
|
@@ -77,21 +79,23 @@ test.use({
|
|
|
77
79
|
},
|
|
78
80
|
});
|
|
79
81
|
|
|
80
|
-
test("android only", async ({
|
|
81
|
-
await
|
|
82
|
+
test("android only", async ({ androidApp }) => {
|
|
83
|
+
await androidApp.fill('xpath=//*[@text="Email"]', "user@example.com");
|
|
82
84
|
});
|
|
83
85
|
|
|
84
|
-
test("hybrid web and android", async ({ page,
|
|
85
|
-
await page.goto("https://
|
|
86
|
-
await
|
|
86
|
+
test("hybrid web and android", async ({ page, androidApp }) => {
|
|
87
|
+
await page.goto("https://themoderninternet.vercel.app");
|
|
88
|
+
await androidApp.click('Id=com.example.airticket:id/bottom_nav_account');
|
|
87
89
|
});
|
|
88
90
|
```
|
|
89
91
|
|
|
92
|
+
Fixtures are lazy on first use. Injecting `browser`, `page`, `android`, or `androidApp` does not launch or connect immediately; the underlying session is created only when the test first performs an action through that fixture. Sync metadata properties like `browser.sessionId` or `android.sessionId` are only available after the lazy fixture has been realized by a prior awaited call.
|
|
93
|
+
|
|
90
94
|
Available fixtures:
|
|
91
95
|
|
|
92
96
|
- `browser`: launched web browser
|
|
93
97
|
- `page`: initial web page
|
|
94
98
|
- `android`: connected Android device session
|
|
95
|
-
- `
|
|
99
|
+
- `androidApp`: launched Android app page
|
|
96
100
|
|
|
97
|
-
`
|
|
101
|
+
`androidApp` launches using `allwright.android.launchOptions` first, then falls back to `config.mobile.android`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MobileAndroidConnectOptions, type MobileAndroidDevice, type MobileAndroidLaunchOptions, type
|
|
1
|
+
import { type MobileAndroidConnectOptions, type MobileAndroidDevice, type MobileAndroidLaunchOptions, type MobileAndroidApp, type Browser, type BrowserKind, type LaunchOptions, type Locator, type Page, type WaitForSelectorOptions } from "@allwright.dev/core";
|
|
2
2
|
import { expect as vitestExpect } from "vitest";
|
|
3
3
|
export interface AllwrightVitestOptions {
|
|
4
4
|
launchOptions?: LaunchOptions;
|
|
@@ -16,7 +16,7 @@ export interface AllwrightVitestFixtures {
|
|
|
16
16
|
browser: Browser;
|
|
17
17
|
page: Page;
|
|
18
18
|
android: MobileAndroidDevice;
|
|
19
|
-
|
|
19
|
+
androidApp: MobileAndroidApp;
|
|
20
20
|
}
|
|
21
21
|
export interface RetryExpectationOptions {
|
|
22
22
|
timeoutMs?: number;
|
|
@@ -42,12 +42,19 @@ export interface LocatorExpectMatchers {
|
|
|
42
42
|
toHaveCount(expected: number, options?: RetryExpectationOptions): Promise<void>;
|
|
43
43
|
toBeVisible(options?: VisibleExpectationOptions): Promise<void>;
|
|
44
44
|
}
|
|
45
|
+
interface LazyResource<T> {
|
|
46
|
+
peek(): T | null;
|
|
47
|
+
get(): Promise<T>;
|
|
48
|
+
realized(): boolean;
|
|
49
|
+
}
|
|
45
50
|
export declare const test: import("vitest").TestAPI<{
|
|
46
|
-
browser: Browser;
|
|
47
51
|
page: Page;
|
|
52
|
+
browser: Browser;
|
|
48
53
|
android: MobileAndroidDevice;
|
|
49
|
-
|
|
54
|
+
androidApp: MobileAndroidApp;
|
|
50
55
|
allwright: AllwrightVitestOptions;
|
|
56
|
+
_browserResource: LazyResource<Browser>;
|
|
57
|
+
_androidResource: LazyResource<MobileAndroidDevice>;
|
|
51
58
|
}>;
|
|
52
59
|
type VitestExpect = typeof vitestExpect;
|
|
53
60
|
type VitestMatcherReturn = ReturnType<VitestExpect>;
|
package/dist/index.js
CHANGED
|
@@ -2,39 +2,319 @@ import { launchConfiguredBrowser, mobile, resolveConfig, setServerAddr, shutdown
|
|
|
2
2
|
import { expect as vitestExpect, test as base } from "vitest";
|
|
3
3
|
const DEFAULT_ANDROID_CONNECT_TIMEOUT_MS = 30_000;
|
|
4
4
|
const DEFAULT_ANDROID_LAUNCH_TIMEOUT_MS = 60_000;
|
|
5
|
+
function createLazyResource(factory) {
|
|
6
|
+
let value = null;
|
|
7
|
+
let promise = null;
|
|
8
|
+
return {
|
|
9
|
+
peek() {
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
get() {
|
|
13
|
+
if (value) {
|
|
14
|
+
return Promise.resolve(value);
|
|
15
|
+
}
|
|
16
|
+
if (!promise) {
|
|
17
|
+
promise = factory().then((resolved) => {
|
|
18
|
+
value = resolved;
|
|
19
|
+
return resolved;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return promise;
|
|
23
|
+
},
|
|
24
|
+
realized() {
|
|
25
|
+
return value !== null;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function getLazySyncProperty(resource, property) {
|
|
30
|
+
const value = resource.peek();
|
|
31
|
+
if (value) {
|
|
32
|
+
return value[property];
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`fixture property ${String(property)} is not available before the lazy resource is initialized; use an async method first`);
|
|
35
|
+
}
|
|
36
|
+
function createLazyLocator(pageResource, selectorFactory) {
|
|
37
|
+
const lazyLocator = {
|
|
38
|
+
get page() {
|
|
39
|
+
return createLazyPage(pageResource);
|
|
40
|
+
},
|
|
41
|
+
get selector() {
|
|
42
|
+
throw new Error("lazy locator selector is not available before the page fixture is initialized");
|
|
43
|
+
},
|
|
44
|
+
async click(options) {
|
|
45
|
+
return (await pageResource.get()).locator(await selectorFactory()).click(options);
|
|
46
|
+
},
|
|
47
|
+
async count(options) {
|
|
48
|
+
return (await pageResource.get()).locator(await selectorFactory()).count(options);
|
|
49
|
+
},
|
|
50
|
+
async highlight(options) {
|
|
51
|
+
return (await pageResource.get()).locator(await selectorFactory()).highlight(options);
|
|
52
|
+
},
|
|
53
|
+
async focus(options) {
|
|
54
|
+
return (await pageResource.get()).locator(await selectorFactory()).focus(options);
|
|
55
|
+
},
|
|
56
|
+
async fill(value, options) {
|
|
57
|
+
return (await pageResource.get()).locator(await selectorFactory()).fill(value, options);
|
|
58
|
+
},
|
|
59
|
+
async hover(options) {
|
|
60
|
+
return (await pageResource.get()).locator(await selectorFactory()).hover(options);
|
|
61
|
+
},
|
|
62
|
+
async press(key, options) {
|
|
63
|
+
return (await pageResource.get()).locator(await selectorFactory()).press(key, options);
|
|
64
|
+
},
|
|
65
|
+
async textContent(options) {
|
|
66
|
+
return (await pageResource.get()).locator(await selectorFactory()).textContent(options);
|
|
67
|
+
},
|
|
68
|
+
async innerText(options) {
|
|
69
|
+
return (await pageResource.get()).locator(await selectorFactory()).innerText(options);
|
|
70
|
+
},
|
|
71
|
+
async waitFor(options) {
|
|
72
|
+
return (await pageResource.get()).locator(await selectorFactory()).waitFor(options);
|
|
73
|
+
},
|
|
74
|
+
locator(selector) {
|
|
75
|
+
return createLazyLocator(pageResource, async () => (await pageResource.get()).locator(await selectorFactory()).locator(selector).selector);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
return lazyLocator;
|
|
79
|
+
}
|
|
80
|
+
function createLazyPage(pageResource) {
|
|
81
|
+
const lazyPage = {
|
|
82
|
+
get sessionId() {
|
|
83
|
+
return getLazySyncProperty(pageResource, "sessionId");
|
|
84
|
+
},
|
|
85
|
+
get browserSessionId() {
|
|
86
|
+
return getLazySyncProperty(pageResource, "browserSessionId");
|
|
87
|
+
},
|
|
88
|
+
locator(selector) {
|
|
89
|
+
return createLazyLocator(pageResource, async () => selector);
|
|
90
|
+
},
|
|
91
|
+
async goto(url, options) {
|
|
92
|
+
return (await pageResource.get()).goto(url, options);
|
|
93
|
+
},
|
|
94
|
+
async navigate(url, options) {
|
|
95
|
+
return (await pageResource.get()).navigate(url, options);
|
|
96
|
+
},
|
|
97
|
+
async click(selector, options) {
|
|
98
|
+
return (await pageResource.get()).click(selector, options);
|
|
99
|
+
},
|
|
100
|
+
async count(selector, options) {
|
|
101
|
+
return (await pageResource.get()).count(selector, options);
|
|
102
|
+
},
|
|
103
|
+
async highlight(selector, options) {
|
|
104
|
+
return (await pageResource.get()).highlight(selector, options);
|
|
105
|
+
},
|
|
106
|
+
async focus(selector, options) {
|
|
107
|
+
return (await pageResource.get()).focus(selector, options);
|
|
108
|
+
},
|
|
109
|
+
async fill(selector, value, options) {
|
|
110
|
+
return (await pageResource.get()).fill(selector, value, options);
|
|
111
|
+
},
|
|
112
|
+
async hover(selector, options) {
|
|
113
|
+
return (await pageResource.get()).hover(selector, options);
|
|
114
|
+
},
|
|
115
|
+
async press(selector, key, options) {
|
|
116
|
+
return (await pageResource.get()).press(selector, key, options);
|
|
117
|
+
},
|
|
118
|
+
async textContent(selector, options) {
|
|
119
|
+
return (await pageResource.get()).textContent(selector, options);
|
|
120
|
+
},
|
|
121
|
+
async innerText(selector, options) {
|
|
122
|
+
return (await pageResource.get()).innerText(selector, options);
|
|
123
|
+
},
|
|
124
|
+
async waitForSelector(selector, options) {
|
|
125
|
+
return (await pageResource.get()).waitForSelector(selector, options);
|
|
126
|
+
},
|
|
127
|
+
async close() {
|
|
128
|
+
await (await pageResource.get()).close();
|
|
129
|
+
},
|
|
130
|
+
async ping(message) {
|
|
131
|
+
return (await pageResource.get()).ping(message);
|
|
132
|
+
},
|
|
133
|
+
pageInfo() {
|
|
134
|
+
const page = pageResource.peek();
|
|
135
|
+
if (page) {
|
|
136
|
+
return page.pageInfo();
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
sessionId: getLazySyncProperty(pageResource, "sessionId"),
|
|
140
|
+
browserSessionId: getLazySyncProperty(pageResource, "browserSessionId"),
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
return lazyPage;
|
|
145
|
+
}
|
|
146
|
+
function createLazyBrowser(browserResource) {
|
|
147
|
+
const lazyBrowser = {
|
|
148
|
+
get sessionId() {
|
|
149
|
+
return getLazySyncProperty(browserResource, "sessionId");
|
|
150
|
+
},
|
|
151
|
+
get browserName() {
|
|
152
|
+
return getLazySyncProperty(browserResource, "browserName");
|
|
153
|
+
},
|
|
154
|
+
get launchNote() {
|
|
155
|
+
return getLazySyncProperty(browserResource, "launchNote");
|
|
156
|
+
},
|
|
157
|
+
get cdpWebSocketURL() {
|
|
158
|
+
return getLazySyncProperty(browserResource, "cdpWebSocketURL");
|
|
159
|
+
},
|
|
160
|
+
get userDataDir() {
|
|
161
|
+
return getLazySyncProperty(browserResource, "userDataDir");
|
|
162
|
+
},
|
|
163
|
+
page() {
|
|
164
|
+
return createLazyPage(createLazyResource(async () => (await browserResource.get()).page()));
|
|
165
|
+
},
|
|
166
|
+
initialPage() {
|
|
167
|
+
return createLazyPage(createLazyResource(async () => (await browserResource.get()).initialPage()));
|
|
168
|
+
},
|
|
169
|
+
initialTab() {
|
|
170
|
+
return createLazyPage(createLazyResource(async () => (await browserResource.get()).initialTab()));
|
|
171
|
+
},
|
|
172
|
+
pages() {
|
|
173
|
+
const browser = browserResource.peek();
|
|
174
|
+
if (!browser) {
|
|
175
|
+
return [this.page()];
|
|
176
|
+
}
|
|
177
|
+
return browser.pages();
|
|
178
|
+
},
|
|
179
|
+
async newPage(options) {
|
|
180
|
+
return (await browserResource.get()).newPage(options);
|
|
181
|
+
},
|
|
182
|
+
async newTab(options) {
|
|
183
|
+
return (await browserResource.get()).newTab(options);
|
|
184
|
+
},
|
|
185
|
+
async close() {
|
|
186
|
+
await (await browserResource.get()).close();
|
|
187
|
+
},
|
|
188
|
+
async ping(message) {
|
|
189
|
+
return (await browserResource.get()).ping(message);
|
|
190
|
+
},
|
|
191
|
+
browserInfo() {
|
|
192
|
+
const browser = browserResource.peek();
|
|
193
|
+
if (browser) {
|
|
194
|
+
return browser.browserInfo();
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
sessionId: getLazySyncProperty(browserResource, "sessionId"),
|
|
198
|
+
browserName: getLazySyncProperty(browserResource, "browserName"),
|
|
199
|
+
launchNote: getLazySyncProperty(browserResource, "launchNote"),
|
|
200
|
+
cdpWebSocketURL: getLazySyncProperty(browserResource, "cdpWebSocketURL"),
|
|
201
|
+
userDataDir: getLazySyncProperty(browserResource, "userDataDir"),
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
return lazyBrowser;
|
|
206
|
+
}
|
|
207
|
+
function createLazyAndroidApp(appResource) {
|
|
208
|
+
const lazyApp = {
|
|
209
|
+
get sessionId() {
|
|
210
|
+
return getLazySyncProperty(appResource, "sessionId");
|
|
211
|
+
},
|
|
212
|
+
locator(selector) {
|
|
213
|
+
return createLazyAndroidLocator(appResource, async () => selector);
|
|
214
|
+
},
|
|
215
|
+
async click(selector, options) {
|
|
216
|
+
return (await appResource.get()).click(selector, options);
|
|
217
|
+
},
|
|
218
|
+
async fill(selector, value, options) {
|
|
219
|
+
return (await appResource.get()).fill(selector, value, options);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
return lazyApp;
|
|
223
|
+
}
|
|
224
|
+
function createLazyAndroidLocator(appResource, selectorFactory) {
|
|
225
|
+
const lazyLocator = {
|
|
226
|
+
get page() {
|
|
227
|
+
return createLazyAndroidApp(appResource);
|
|
228
|
+
},
|
|
229
|
+
get selector() {
|
|
230
|
+
throw new Error("lazy Android locator selector is not available before the app fixture is initialized");
|
|
231
|
+
},
|
|
232
|
+
async click(options) {
|
|
233
|
+
return (await appResource.get()).locator(await selectorFactory()).click(options);
|
|
234
|
+
},
|
|
235
|
+
async fill(value, options) {
|
|
236
|
+
return (await appResource.get()).locator(await selectorFactory()).fill(value, options);
|
|
237
|
+
},
|
|
238
|
+
locator(selector) {
|
|
239
|
+
return createLazyAndroidLocator(appResource, async () => (await appResource.get()).locator(await selectorFactory()).locator(selector).selector);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
return lazyLocator;
|
|
243
|
+
}
|
|
244
|
+
function createLazyAndroidDevice(deviceResource) {
|
|
245
|
+
const initialAppResource = createLazyResource(async () => (await deviceResource.get()).app());
|
|
246
|
+
const lazyDevice = {
|
|
247
|
+
get sessionId() {
|
|
248
|
+
return getLazySyncProperty(deviceResource, "sessionId");
|
|
249
|
+
},
|
|
250
|
+
app() {
|
|
251
|
+
return createLazyAndroidApp(initialAppResource);
|
|
252
|
+
},
|
|
253
|
+
initialApp() {
|
|
254
|
+
return createLazyAndroidApp(initialAppResource);
|
|
255
|
+
},
|
|
256
|
+
async launch(options) {
|
|
257
|
+
return (await deviceResource.get()).launch(options);
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
return lazyDevice;
|
|
261
|
+
}
|
|
5
262
|
export const test = base.extend({
|
|
6
263
|
allwright: async ({}, use) => {
|
|
7
264
|
await use({});
|
|
8
265
|
},
|
|
9
|
-
|
|
266
|
+
_browserResource: async ({ allwright }, use) => {
|
|
10
267
|
const config = resolveVitestConfig(allwright);
|
|
11
268
|
if (config.serverAddr) {
|
|
12
269
|
setServerAddr(config.serverAddr);
|
|
13
270
|
}
|
|
14
271
|
activeExpectDefaults = config.expect;
|
|
15
|
-
const
|
|
272
|
+
const browserResource = createLazyResource(async () => launchConfiguredBrowser(config));
|
|
16
273
|
try {
|
|
17
|
-
await use(
|
|
274
|
+
await use(browserResource);
|
|
18
275
|
}
|
|
19
276
|
finally {
|
|
20
277
|
activeExpectDefaults = {};
|
|
21
|
-
|
|
278
|
+
if (browserResource.realized()) {
|
|
279
|
+
await browserResource.get().then((resolved) => resolved.close()).catch(() => undefined);
|
|
280
|
+
}
|
|
22
281
|
await shutdown();
|
|
23
282
|
}
|
|
24
283
|
},
|
|
284
|
+
_androidResource: async ({ allwright }, use) => {
|
|
285
|
+
const config = resolveVitestConfig(allwright);
|
|
286
|
+
if (config.serverAddr) {
|
|
287
|
+
setServerAddr(config.serverAddr);
|
|
288
|
+
}
|
|
289
|
+
const androidResource = createLazyResource(async () => mobile.android.connect(resolveAndroidConnectOptions(config, allwright)));
|
|
290
|
+
try {
|
|
291
|
+
await use(androidResource);
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await shutdown();
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
browser: async ({ _browserResource }, use) => {
|
|
298
|
+
try {
|
|
299
|
+
await use(createLazyBrowser(_browserResource));
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
// resource lifecycle is owned by _browserResource
|
|
303
|
+
}
|
|
304
|
+
},
|
|
25
305
|
page: async ({ browser }, use) => {
|
|
26
306
|
await use(browser.page());
|
|
27
307
|
},
|
|
28
|
-
android: async ({
|
|
29
|
-
|
|
30
|
-
const android = await mobile.android.connect(resolveAndroidConnectOptions(config, allwright));
|
|
31
|
-
await use(android);
|
|
308
|
+
android: async ({ _androidResource }, use) => {
|
|
309
|
+
await use(createLazyAndroidDevice(_androidResource));
|
|
32
310
|
},
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
311
|
+
androidApp: async ({ _androidResource, allwright }, use) => {
|
|
312
|
+
const appResource = createLazyResource(async () => {
|
|
313
|
+
const config = resolveVitestConfig(allwright);
|
|
314
|
+
const launchOptions = resolveAndroidLaunchOptions(config, allwright);
|
|
315
|
+
return (await _androidResource.get()).launch(launchOptions);
|
|
316
|
+
});
|
|
317
|
+
await use(createLazyAndroidApp(appResource));
|
|
38
318
|
},
|
|
39
319
|
});
|
|
40
320
|
const DEFAULT_EXPECT_TIMEOUT_MS = 5_000;
|
|
@@ -88,7 +368,7 @@ function resolveAndroidLaunchOptions(config, options) {
|
|
|
88
368
|
DEFAULT_ANDROID_LAUNCH_TIMEOUT_MS,
|
|
89
369
|
};
|
|
90
370
|
if (!launchOptions.apkPath && !launchOptions.appId) {
|
|
91
|
-
throw new Error("
|
|
371
|
+
throw new Error("androidApp fixture requires android launch options with `apkPath` or `appId`, or config.mobile.android.app configured");
|
|
92
372
|
}
|
|
93
373
|
return launchOptions;
|
|
94
374
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allwright.dev/vitest",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
4
4
|
"description": "Vitest fixtures for the allwright TypeScript client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"prepublishOnly": "bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@allwright.dev/core": "0.0.
|
|
42
|
+
"@allwright.dev/core": "0.0.53"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.9.2",
|