@vitest/browser 3.1.0-beta.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context.d.ts +24 -1
- package/dist/client/.vite/manifest.json +1 -1
- package/dist/client/__vitest__/assets/index-B0KEk_KY.css +1 -0
- package/dist/client/__vitest__/assets/index-BLZJq7cG.js +52 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/tester-DiLSqOx4.js +3431 -0
- package/dist/client/tester/tester.html +1 -1
- package/dist/client.js +109 -105
- package/dist/context.js +374 -390
- package/dist/expect-element.js +25 -0
- package/dist/index-DjDyxzt8.js +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +1991 -1887
- package/dist/locators/index.d.ts +64 -0
- package/dist/locators/index.js +1 -4
- package/dist/locators/playwright.js +1 -123
- package/dist/locators/preview.js +1 -85
- package/dist/locators/webdriverio.js +1 -157
- package/dist/providers.js +35 -37
- package/dist/public-utils-xf4CCUzp.js +6 -0
- package/dist/state.js +170 -1
- package/dist/utils.js +1 -3
- package/dist/webdriver-2iYWIzBv.js +403 -0
- package/jest-dom.d.ts +623 -712
- package/matchers.d.ts +4 -4
- package/package.json +14 -16
- package/dist/client/__vitest__/assets/index-Bne9c1R6.css +0 -1
- package/dist/client/__vitest__/assets/index-CsZqQx26.js +0 -52
- package/dist/client/__vitest_browser__/tester-lo_P6U-u.js +0 -15577
- package/dist/index-DrTP5i7N.js +0 -195
- package/dist/public-utils-J4vwTaki.js +0 -5561
- package/dist/utils-VCysLhWp.js +0 -115
- package/dist/webdriver-C5-VI7VH.js +0 -275
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { createManualModuleSource } from '@vitest/mocker/node';
|
|
2
|
+
|
|
3
|
+
const playwrightBrowsers = [
|
|
4
|
+
"firefox",
|
|
5
|
+
"webkit",
|
|
6
|
+
"chromium"
|
|
7
|
+
];
|
|
8
|
+
class PlaywrightBrowserProvider {
|
|
9
|
+
name = "playwright";
|
|
10
|
+
supportsParallelism = true;
|
|
11
|
+
browser = null;
|
|
12
|
+
browserName;
|
|
13
|
+
project;
|
|
14
|
+
options;
|
|
15
|
+
contexts = new Map();
|
|
16
|
+
pages = new Map();
|
|
17
|
+
browserPromise = null;
|
|
18
|
+
mocker;
|
|
19
|
+
getSupportedBrowsers() {
|
|
20
|
+
return playwrightBrowsers;
|
|
21
|
+
}
|
|
22
|
+
initialize(project, { browser, options }) {
|
|
23
|
+
this.project = project;
|
|
24
|
+
this.browserName = browser;
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.mocker = this.createMocker();
|
|
27
|
+
}
|
|
28
|
+
async openBrowser() {
|
|
29
|
+
if (this.browserPromise) {
|
|
30
|
+
return this.browserPromise;
|
|
31
|
+
}
|
|
32
|
+
if (this.browser) {
|
|
33
|
+
return this.browser;
|
|
34
|
+
}
|
|
35
|
+
this.browserPromise = (async () => {
|
|
36
|
+
const options = this.project.config.browser;
|
|
37
|
+
const playwright = await import('playwright');
|
|
38
|
+
const launchOptions = {
|
|
39
|
+
...this.options?.launch,
|
|
40
|
+
headless: options.headless
|
|
41
|
+
};
|
|
42
|
+
if (this.project.config.inspector.enabled) {
|
|
43
|
+
const port = this.project.config.inspector.port || 9229;
|
|
44
|
+
const host = this.project.config.inspector.host || "127.0.0.1";
|
|
45
|
+
launchOptions.args ||= [];
|
|
46
|
+
launchOptions.args.push(`--remote-debugging-port=${port}`);
|
|
47
|
+
launchOptions.args.push(`--remote-debugging-address=${host}`);
|
|
48
|
+
this.project.vitest.logger.log(`Debugger listening on ws://${host}:${port}`);
|
|
49
|
+
}
|
|
50
|
+
if (this.project.config.browser.ui && this.browserName === "chromium") {
|
|
51
|
+
if (!launchOptions.args) {
|
|
52
|
+
launchOptions.args = [];
|
|
53
|
+
}
|
|
54
|
+
if (!launchOptions.args.includes("--start-maximized") && !launchOptions.args.includes("--start-fullscreen")) {
|
|
55
|
+
launchOptions.args.push("--start-maximized");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const browser = await playwright[this.browserName].launch(launchOptions);
|
|
59
|
+
this.browser = browser;
|
|
60
|
+
this.browserPromise = null;
|
|
61
|
+
return this.browser;
|
|
62
|
+
})();
|
|
63
|
+
return this.browserPromise;
|
|
64
|
+
}
|
|
65
|
+
createMocker() {
|
|
66
|
+
const idPreficates = new Map();
|
|
67
|
+
const sessionIds = new Map();
|
|
68
|
+
function createPredicate(sessionId, url) {
|
|
69
|
+
const moduleUrl = new URL(url, "http://localhost");
|
|
70
|
+
const predicate = (url) => {
|
|
71
|
+
if (url.searchParams.has("_vitest_original")) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (url.pathname !== moduleUrl.pathname) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
url.searchParams.delete("t");
|
|
78
|
+
url.searchParams.delete("v");
|
|
79
|
+
url.searchParams.delete("import");
|
|
80
|
+
if (url.searchParams.size !== moduleUrl.searchParams.size) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
for (const [param, value] of url.searchParams.entries()) {
|
|
84
|
+
if (moduleUrl.searchParams.get(param) !== value) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
const ids = sessionIds.get(sessionId) || [];
|
|
91
|
+
ids.push(moduleUrl.href);
|
|
92
|
+
sessionIds.set(sessionId, ids);
|
|
93
|
+
idPreficates.set(moduleUrl.href, predicate);
|
|
94
|
+
return predicate;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
register: async (sessionId, module) => {
|
|
98
|
+
const page = this.getPage(sessionId);
|
|
99
|
+
await page.route(createPredicate(sessionId, module.url), async (route) => {
|
|
100
|
+
if (module.type === "manual") {
|
|
101
|
+
const exports = Object.keys(await module.resolve());
|
|
102
|
+
const body = createManualModuleSource(module.url, exports);
|
|
103
|
+
return route.fulfill({
|
|
104
|
+
body,
|
|
105
|
+
headers: getHeaders(this.project.browser.vite.config)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const isWebkit = this.browserName === "webkit";
|
|
109
|
+
if (isWebkit) {
|
|
110
|
+
const url = module.type === "redirect" ? (() => {
|
|
111
|
+
const url = new URL(module.redirect);
|
|
112
|
+
return url.href.slice(url.origin.length);
|
|
113
|
+
})() : (() => {
|
|
114
|
+
const url = new URL(route.request().url());
|
|
115
|
+
url.searchParams.set("mock", module.type);
|
|
116
|
+
return url.href.slice(url.origin.length);
|
|
117
|
+
})();
|
|
118
|
+
const result = await this.project.browser.vite.transformRequest(url).catch(() => null);
|
|
119
|
+
if (!result) {
|
|
120
|
+
return route.continue();
|
|
121
|
+
}
|
|
122
|
+
let content = result.code;
|
|
123
|
+
if (result.map && "version" in result.map && result.map.mappings) {
|
|
124
|
+
const type = isDirectCSSRequest(url) ? "css" : "js";
|
|
125
|
+
content = getCodeWithSourcemap(type, content.toString(), result.map);
|
|
126
|
+
}
|
|
127
|
+
return route.fulfill({
|
|
128
|
+
body: content,
|
|
129
|
+
headers: getHeaders(this.project.browser.vite.config)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (module.type === "redirect") {
|
|
133
|
+
return route.fulfill({
|
|
134
|
+
status: 302,
|
|
135
|
+
headers: { Location: module.redirect }
|
|
136
|
+
});
|
|
137
|
+
} else if (module.type === "automock" || module.type === "autospy") {
|
|
138
|
+
const url = new URL(route.request().url());
|
|
139
|
+
url.searchParams.set("mock", module.type);
|
|
140
|
+
return route.fulfill({
|
|
141
|
+
status: 302,
|
|
142
|
+
headers: { Location: url.href }
|
|
143
|
+
});
|
|
144
|
+
} else ;
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
delete: async (sessionId, id) => {
|
|
148
|
+
const page = this.getPage(sessionId);
|
|
149
|
+
const predicate = idPreficates.get(id);
|
|
150
|
+
if (predicate) {
|
|
151
|
+
await page.unroute(predicate).finally(() => idPreficates.delete(id));
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
clear: async (sessionId) => {
|
|
155
|
+
const page = this.getPage(sessionId);
|
|
156
|
+
const ids = sessionIds.get(sessionId) || [];
|
|
157
|
+
const promises = ids.map((id) => {
|
|
158
|
+
const predicate = idPreficates.get(id);
|
|
159
|
+
if (predicate) {
|
|
160
|
+
return page.unroute(predicate).finally(() => idPreficates.delete(id));
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
});
|
|
164
|
+
await Promise.all(promises).finally(() => sessionIds.delete(sessionId));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async createContext(sessionId) {
|
|
169
|
+
if (this.contexts.has(sessionId)) {
|
|
170
|
+
return this.contexts.get(sessionId);
|
|
171
|
+
}
|
|
172
|
+
const browser = await this.openBrowser();
|
|
173
|
+
const { actionTimeout,...contextOptions } = this.options?.context ?? {};
|
|
174
|
+
const options = {
|
|
175
|
+
...contextOptions,
|
|
176
|
+
ignoreHTTPSErrors: true
|
|
177
|
+
};
|
|
178
|
+
if (this.project.config.browser.ui) {
|
|
179
|
+
options.viewport = null;
|
|
180
|
+
}
|
|
181
|
+
const context = await browser.newContext(options);
|
|
182
|
+
if (actionTimeout) {
|
|
183
|
+
context.setDefaultTimeout(actionTimeout);
|
|
184
|
+
}
|
|
185
|
+
this.contexts.set(sessionId, context);
|
|
186
|
+
return context;
|
|
187
|
+
}
|
|
188
|
+
getPage(sessionId) {
|
|
189
|
+
const page = this.pages.get(sessionId);
|
|
190
|
+
if (!page) {
|
|
191
|
+
throw new Error(`Page "${sessionId}" not found in ${this.browserName} browser.`);
|
|
192
|
+
}
|
|
193
|
+
return page;
|
|
194
|
+
}
|
|
195
|
+
getCommandsContext(sessionId) {
|
|
196
|
+
const page = this.getPage(sessionId);
|
|
197
|
+
return {
|
|
198
|
+
page,
|
|
199
|
+
context: this.contexts.get(sessionId),
|
|
200
|
+
frame() {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
const frame = page.frame("vitest-iframe");
|
|
203
|
+
if (frame) {
|
|
204
|
+
return resolve(frame);
|
|
205
|
+
}
|
|
206
|
+
const timeout = setTimeout(() => {
|
|
207
|
+
const err = new Error(`Cannot find "vitest-iframe" on the page. This is a bug in Vitest, please report it.`);
|
|
208
|
+
reject(err);
|
|
209
|
+
}, 1e3).unref();
|
|
210
|
+
page.on("frameattached", (frame) => {
|
|
211
|
+
clearTimeout(timeout);
|
|
212
|
+
resolve(frame);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
get iframe() {
|
|
217
|
+
return page.frameLocator("[data-vitest=\"true\"]");
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async openBrowserPage(sessionId) {
|
|
222
|
+
if (this.pages.has(sessionId)) {
|
|
223
|
+
const page = this.pages.get(sessionId);
|
|
224
|
+
await page.close();
|
|
225
|
+
this.pages.delete(sessionId);
|
|
226
|
+
}
|
|
227
|
+
const context = await this.createContext(sessionId);
|
|
228
|
+
const page = await context.newPage();
|
|
229
|
+
this.pages.set(sessionId, page);
|
|
230
|
+
if (process.env.VITEST_PW_DEBUG) {
|
|
231
|
+
page.on("requestfailed", (request) => {
|
|
232
|
+
console.error("[PW Error]", request.resourceType(), "request failed for", request.url(), "url:", request.failure()?.errorText);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
page.on("crash", () => {
|
|
236
|
+
const session = this.project.vitest._browserSessions.getSession(sessionId);
|
|
237
|
+
session?.reject(new Error("Page crashed when executing tests"));
|
|
238
|
+
});
|
|
239
|
+
return page;
|
|
240
|
+
}
|
|
241
|
+
async openPage(sessionId, url, beforeNavigate) {
|
|
242
|
+
const browserPage = await this.openBrowserPage(sessionId);
|
|
243
|
+
await beforeNavigate?.();
|
|
244
|
+
await browserPage.goto(url, { timeout: 0 });
|
|
245
|
+
}
|
|
246
|
+
async getCDPSession(sessionid) {
|
|
247
|
+
const page = this.getPage(sessionid);
|
|
248
|
+
const cdp = await page.context().newCDPSession(page);
|
|
249
|
+
return {
|
|
250
|
+
async send(method, params) {
|
|
251
|
+
const result = await cdp.send(method, params);
|
|
252
|
+
return result;
|
|
253
|
+
},
|
|
254
|
+
on(event, listener) {
|
|
255
|
+
cdp.on(event, listener);
|
|
256
|
+
},
|
|
257
|
+
off(event, listener) {
|
|
258
|
+
cdp.off(event, listener);
|
|
259
|
+
},
|
|
260
|
+
once(event, listener) {
|
|
261
|
+
cdp.once(event, listener);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async close() {
|
|
266
|
+
const browser = this.browser;
|
|
267
|
+
this.browser = null;
|
|
268
|
+
await Promise.all([...this.pages.values()].map((p) => p.close()));
|
|
269
|
+
this.pages.clear();
|
|
270
|
+
await Promise.all([...this.contexts.values()].map((c) => c.close()));
|
|
271
|
+
this.contexts.clear();
|
|
272
|
+
await browser?.close();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function getHeaders(config) {
|
|
276
|
+
const headers = { "Content-Type": "application/javascript" };
|
|
277
|
+
for (const name in config.server.headers) {
|
|
278
|
+
headers[name] = String(config.server.headers[name]);
|
|
279
|
+
}
|
|
280
|
+
return headers;
|
|
281
|
+
}
|
|
282
|
+
function getCodeWithSourcemap(type, code, map) {
|
|
283
|
+
if (type === "js") {
|
|
284
|
+
code += `\n//# sourceMappingURL=${genSourceMapUrl(map)}`;
|
|
285
|
+
} else if (type === "css") {
|
|
286
|
+
code += `\n/*# sourceMappingURL=${genSourceMapUrl(map)} */`;
|
|
287
|
+
}
|
|
288
|
+
return code;
|
|
289
|
+
}
|
|
290
|
+
function genSourceMapUrl(map) {
|
|
291
|
+
if (typeof map !== "string") {
|
|
292
|
+
map = JSON.stringify(map);
|
|
293
|
+
}
|
|
294
|
+
return `data:application/json;base64,${Buffer.from(map).toString("base64")}`;
|
|
295
|
+
}
|
|
296
|
+
const CSS_LANGS_RE = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
|
|
297
|
+
const directRequestRE = /[?&]direct\b/;
|
|
298
|
+
function isDirectCSSRequest(request) {
|
|
299
|
+
return CSS_LANGS_RE.test(request) && directRequestRE.test(request);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const webdriverBrowsers = [
|
|
303
|
+
"firefox",
|
|
304
|
+
"chrome",
|
|
305
|
+
"edge",
|
|
306
|
+
"safari"
|
|
307
|
+
];
|
|
308
|
+
class WebdriverBrowserProvider {
|
|
309
|
+
name = "webdriverio";
|
|
310
|
+
supportsParallelism = false;
|
|
311
|
+
browser = null;
|
|
312
|
+
browserName;
|
|
313
|
+
project;
|
|
314
|
+
options;
|
|
315
|
+
getSupportedBrowsers() {
|
|
316
|
+
return webdriverBrowsers;
|
|
317
|
+
}
|
|
318
|
+
async initialize(ctx, { browser, options }) {
|
|
319
|
+
this.project = ctx;
|
|
320
|
+
this.browserName = browser;
|
|
321
|
+
this.options = options;
|
|
322
|
+
}
|
|
323
|
+
async switchToTestFrame() {
|
|
324
|
+
const page = this.browser;
|
|
325
|
+
if (page.switchFrame) {
|
|
326
|
+
await page.switchFrame(page.$("iframe[data-vitest]"));
|
|
327
|
+
} else {
|
|
328
|
+
const iframe = await page.findElement("css selector", "iframe[data-vitest]");
|
|
329
|
+
await page.switchToFrame(iframe);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async switchToMainFrame() {
|
|
333
|
+
const page = this.browser;
|
|
334
|
+
if (page.switchFrame) {
|
|
335
|
+
await page.switchFrame(null);
|
|
336
|
+
} else {
|
|
337
|
+
await page.switchToParentFrame();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
getCommandsContext() {
|
|
341
|
+
return { browser: this.browser };
|
|
342
|
+
}
|
|
343
|
+
async openBrowser() {
|
|
344
|
+
if (this.browser) {
|
|
345
|
+
return this.browser;
|
|
346
|
+
}
|
|
347
|
+
const options = this.project.config.browser;
|
|
348
|
+
if (this.browserName === "safari") {
|
|
349
|
+
if (options.headless) {
|
|
350
|
+
throw new Error("You've enabled headless mode for Safari but it doesn't currently support it.");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const { remote } = await import('webdriverio');
|
|
354
|
+
this.browser = await remote({
|
|
355
|
+
...this.options,
|
|
356
|
+
logLevel: "error",
|
|
357
|
+
capabilities: this.buildCapabilities()
|
|
358
|
+
});
|
|
359
|
+
return this.browser;
|
|
360
|
+
}
|
|
361
|
+
buildCapabilities() {
|
|
362
|
+
const capabilities = {
|
|
363
|
+
...this.options?.capabilities,
|
|
364
|
+
browserName: this.browserName
|
|
365
|
+
};
|
|
366
|
+
const headlessMap = {
|
|
367
|
+
chrome: ["goog:chromeOptions", ["headless", "disable-gpu"]],
|
|
368
|
+
firefox: ["moz:firefoxOptions", ["-headless"]],
|
|
369
|
+
edge: ["ms:edgeOptions", ["--headless"]]
|
|
370
|
+
};
|
|
371
|
+
const options = this.project.config.browser;
|
|
372
|
+
const browser = this.browserName;
|
|
373
|
+
if (browser !== "safari" && options.headless) {
|
|
374
|
+
const [key, args] = headlessMap[browser];
|
|
375
|
+
const currentValues = (this.options?.capabilities)?.[key] || {};
|
|
376
|
+
const newArgs = [...currentValues.args || [], ...args];
|
|
377
|
+
capabilities[key] = {
|
|
378
|
+
...currentValues,
|
|
379
|
+
args: newArgs
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (options.ui && (browser === "chrome" || browser === "edge")) {
|
|
383
|
+
const key = browser === "chrome" ? "goog:chromeOptions" : "ms:edgeOptions";
|
|
384
|
+
const args = capabilities[key]?.args || [];
|
|
385
|
+
if (!args.includes("--start-maximized") && !args.includes("--start-fullscreen")) {
|
|
386
|
+
args.push("--start-maximized");
|
|
387
|
+
}
|
|
388
|
+
capabilities[key] ??= {};
|
|
389
|
+
capabilities[key].args = args;
|
|
390
|
+
}
|
|
391
|
+
return capabilities;
|
|
392
|
+
}
|
|
393
|
+
async openPage(_sessionId, url) {
|
|
394
|
+
const browserInstance = await this.openBrowser();
|
|
395
|
+
await browserInstance.url(url);
|
|
396
|
+
}
|
|
397
|
+
async close() {
|
|
398
|
+
await Promise.all([this.browser?.sessionId ? this.browser?.deleteSession?.() : null]);
|
|
399
|
+
process.exit();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export { PlaywrightBrowserProvider as P, WebdriverBrowserProvider as W };
|