@vitest/browser 2.0.0-beta.1 → 2.0.0-beta.11
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 +244 -0
- package/context.js +2 -0
- package/dist/client/.vite/manifest.json +24 -0
- package/dist/client/__vitest__/assets/index-BfnqOMHY.js +51 -0
- package/dist/client/__vitest__/assets/index-qZYZB8Y3.css +1 -0
- package/dist/client/__vitest__/index.html +16 -11
- package/dist/client/__vitest_browser__/orchestrator-DQ4hbmZ_.js +338 -0
- package/dist/client/__vitest_browser__/{rpc-By4jD8av.js → rpc-D6HtJ5Rb.js} +310 -399
- package/dist/client/__vitest_browser__/tester-IF8AbWCS.js +837 -0
- package/dist/client/esm-client-injector.js +24 -37
- package/dist/client/{index.html → orchestrator.html} +8 -7
- package/dist/client/{tester.html → tester/tester.html} +13 -3
- package/dist/context.js +175 -0
- package/dist/index.d.ts +103 -4
- package/dist/index.js +1944 -332
- package/dist/providers.js +20 -116
- package/dist/state.js +1 -0
- package/dist/webdriver-BRud6NtS.js +180 -0
- package/package.json +23 -11
- package/providers/playwright.d.ts +36 -2
- package/providers/webdriverio.d.ts +4 -0
- package/providers.d.ts +5 -5
- package/dist/client/__vitest__/assets/index-BMbN3lBu.js +0 -51
- package/dist/client/__vitest__/assets/index-D0Wp7rbG.css +0 -1
- package/dist/client/__vitest_browser__/main-DmAU-Uff.js +0 -102
- package/dist/client/__vitest_browser__/tester-CrKhlp5g.js +0 -473
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import { createDebugger, isFileServingAllowed as isFileServingAllowed$1, getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir, createServer } from 'vitest/node';
|
|
2
|
+
import fs, { existsSync, readdirSync, promises, readFileSync } from 'node:fs';
|
|
3
|
+
import { WebSocketServer } from 'ws';
|
|
4
|
+
import { isFileServingAllowed } from 'vite';
|
|
5
|
+
import { builtinModules, createRequire } from 'node:module';
|
|
6
|
+
import { readFile as readFile$1, mkdir } from 'node:fs/promises';
|
|
1
7
|
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import {
|
|
8
|
+
import { createDefer, slash, toArray } from '@vitest/utils';
|
|
3
9
|
import sirv from 'sirv';
|
|
4
|
-
import { coverageConfigDefaults } from 'vitest/config';
|
|
5
|
-
import {
|
|
10
|
+
import { defaultBrowserPort, coverageConfigDefaults } from 'vitest/config';
|
|
11
|
+
import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-BRud6NtS.js';
|
|
12
|
+
import { resolve as resolve$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
|
|
6
13
|
import MagicString from 'magic-string';
|
|
7
14
|
import { esmWalker } from '@vitest/utils/ast';
|
|
15
|
+
import * as nodeos from 'node:os';
|
|
16
|
+
import crypto from 'node:crypto';
|
|
8
17
|
|
|
9
18
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
10
19
|
function normalizeWindowsPath(input = "") {
|
|
@@ -17,6 +26,7 @@ function normalizeWindowsPath(input = "") {
|
|
|
17
26
|
const _UNC_REGEX = /^[/\\]{2}/;
|
|
18
27
|
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
|
|
19
28
|
const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
|
|
29
|
+
const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
|
|
20
30
|
const normalize = function(path) {
|
|
21
31
|
if (path.length === 0) {
|
|
22
32
|
return ".";
|
|
@@ -151,246 +161,1567 @@ function normalizeString(path, allowAboveRoot) {
|
|
|
151
161
|
const isAbsolute = function(p) {
|
|
152
162
|
return _IS_ABSOLUTE_RE.test(p);
|
|
153
163
|
};
|
|
164
|
+
const _EXTNAME_RE = /.(\.[^./]+)$/;
|
|
165
|
+
const extname = function(p) {
|
|
166
|
+
const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));
|
|
167
|
+
return match && match[1] || "";
|
|
168
|
+
};
|
|
169
|
+
const relative = function(from, to) {
|
|
170
|
+
const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
171
|
+
const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
172
|
+
if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
|
|
173
|
+
return _to.join("/");
|
|
174
|
+
}
|
|
175
|
+
const _fromCopy = [..._from];
|
|
176
|
+
for (const segment of _fromCopy) {
|
|
177
|
+
if (_to[0] !== segment) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
_from.shift();
|
|
181
|
+
_to.shift();
|
|
182
|
+
}
|
|
183
|
+
return [..._from.map(() => ".."), ..._to].join("/");
|
|
184
|
+
};
|
|
185
|
+
const dirname = function(p) {
|
|
186
|
+
const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
|
|
187
|
+
if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
|
|
188
|
+
segments[0] += "/";
|
|
189
|
+
}
|
|
190
|
+
return segments.join("/") || (isAbsolute(p) ? "/" : ".");
|
|
191
|
+
};
|
|
154
192
|
const basename = function(p, extension) {
|
|
155
193
|
const lastSegment = normalizeWindowsPath(p).split("/").pop();
|
|
156
194
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
157
195
|
};
|
|
158
196
|
|
|
197
|
+
const DEFAULT_TIMEOUT = 6e4;
|
|
198
|
+
function defaultSerialize(i) {
|
|
199
|
+
return i;
|
|
200
|
+
}
|
|
201
|
+
const defaultDeserialize = defaultSerialize;
|
|
202
|
+
const { clearTimeout, setTimeout } = globalThis;
|
|
203
|
+
const random = Math.random.bind(Math);
|
|
204
|
+
function createBirpc(functions, options) {
|
|
205
|
+
const {
|
|
206
|
+
post,
|
|
207
|
+
on,
|
|
208
|
+
eventNames = [],
|
|
209
|
+
serialize = defaultSerialize,
|
|
210
|
+
deserialize = defaultDeserialize,
|
|
211
|
+
resolver,
|
|
212
|
+
timeout = DEFAULT_TIMEOUT
|
|
213
|
+
} = options;
|
|
214
|
+
const rpcPromiseMap = /* @__PURE__ */ new Map();
|
|
215
|
+
let _promise;
|
|
216
|
+
const rpc = new Proxy({}, {
|
|
217
|
+
get(_, method) {
|
|
218
|
+
if (method === "$functions")
|
|
219
|
+
return functions;
|
|
220
|
+
if (method === "then" && !eventNames.includes("then") && !("then" in functions))
|
|
221
|
+
return void 0;
|
|
222
|
+
const sendEvent = (...args) => {
|
|
223
|
+
post(serialize({ m: method, a: args, t: "q" }));
|
|
224
|
+
};
|
|
225
|
+
if (eventNames.includes(method)) {
|
|
226
|
+
sendEvent.asEvent = sendEvent;
|
|
227
|
+
return sendEvent;
|
|
228
|
+
}
|
|
229
|
+
const sendCall = async (...args) => {
|
|
230
|
+
await _promise;
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const id = nanoid();
|
|
233
|
+
let timeoutId;
|
|
234
|
+
if (timeout >= 0) {
|
|
235
|
+
timeoutId = setTimeout(() => {
|
|
236
|
+
try {
|
|
237
|
+
options.onTimeoutError?.(method, args);
|
|
238
|
+
throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
reject(e);
|
|
241
|
+
}
|
|
242
|
+
rpcPromiseMap.delete(id);
|
|
243
|
+
}, timeout);
|
|
244
|
+
if (typeof timeoutId === "object")
|
|
245
|
+
timeoutId = timeoutId.unref?.();
|
|
246
|
+
}
|
|
247
|
+
rpcPromiseMap.set(id, { resolve, reject, timeoutId });
|
|
248
|
+
post(serialize({ m: method, a: args, i: id, t: "q" }));
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
sendCall.asEvent = sendEvent;
|
|
252
|
+
return sendCall;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
_promise = on(async (data, ...extra) => {
|
|
256
|
+
const msg = deserialize(data);
|
|
257
|
+
if (msg.t === "q") {
|
|
258
|
+
const { m: method, a: args } = msg;
|
|
259
|
+
let result, error;
|
|
260
|
+
const fn = resolver ? resolver(method, functions[method]) : functions[method];
|
|
261
|
+
if (!fn) {
|
|
262
|
+
error = new Error(`[birpc] function "${method}" not found`);
|
|
263
|
+
} else {
|
|
264
|
+
try {
|
|
265
|
+
result = await fn.apply(rpc, args);
|
|
266
|
+
} catch (e) {
|
|
267
|
+
error = e;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (msg.i) {
|
|
271
|
+
if (error && options.onError)
|
|
272
|
+
options.onError(error, method, args);
|
|
273
|
+
post(serialize({ t: "s", i: msg.i, r: result, e: error }), ...extra);
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
const { i: ack, r: result, e: error } = msg;
|
|
277
|
+
const promise = rpcPromiseMap.get(ack);
|
|
278
|
+
if (promise) {
|
|
279
|
+
clearTimeout(promise.timeoutId);
|
|
280
|
+
if (error)
|
|
281
|
+
promise.reject(error);
|
|
282
|
+
else
|
|
283
|
+
promise.resolve(result);
|
|
284
|
+
}
|
|
285
|
+
rpcPromiseMap.delete(ack);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return rpc;
|
|
289
|
+
}
|
|
290
|
+
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
291
|
+
function nanoid(size = 21) {
|
|
292
|
+
let id = "";
|
|
293
|
+
let i = size;
|
|
294
|
+
while (i--)
|
|
295
|
+
id += urlAlphabet[random() * 64 | 0];
|
|
296
|
+
return id;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// <reference types="../types/index.d.ts" />
|
|
300
|
+
|
|
301
|
+
// (c) 2020-present Andrea Giammarchi
|
|
302
|
+
|
|
303
|
+
const {parse: $parse, stringify: $stringify} = JSON;
|
|
304
|
+
const {keys} = Object;
|
|
305
|
+
|
|
306
|
+
const Primitive = String; // it could be Number
|
|
307
|
+
const primitive = 'string'; // it could be 'number'
|
|
308
|
+
|
|
309
|
+
const ignore = {};
|
|
310
|
+
const object = 'object';
|
|
311
|
+
|
|
312
|
+
const noop = (_, value) => value;
|
|
313
|
+
|
|
314
|
+
const primitives = value => (
|
|
315
|
+
value instanceof Primitive ? Primitive(value) : value
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const Primitives = (_, value) => (
|
|
319
|
+
typeof value === primitive ? new Primitive(value) : value
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const revive = (input, parsed, output, $) => {
|
|
323
|
+
const lazy = [];
|
|
324
|
+
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
325
|
+
const k = ke[y];
|
|
326
|
+
const value = output[k];
|
|
327
|
+
if (value instanceof Primitive) {
|
|
328
|
+
const tmp = input[value];
|
|
329
|
+
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
330
|
+
parsed.add(tmp);
|
|
331
|
+
output[k] = ignore;
|
|
332
|
+
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
333
|
+
}
|
|
334
|
+
else
|
|
335
|
+
output[k] = $.call(output, k, tmp);
|
|
336
|
+
}
|
|
337
|
+
else if (output[k] !== ignore)
|
|
338
|
+
output[k] = $.call(output, k, value);
|
|
339
|
+
}
|
|
340
|
+
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
341
|
+
const {k, a} = lazy[i];
|
|
342
|
+
output[k] = $.call(output, k, revive.apply(null, a));
|
|
343
|
+
}
|
|
344
|
+
return output;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const set = (known, input, value) => {
|
|
348
|
+
const index = Primitive(input.push(value) - 1);
|
|
349
|
+
known.set(value, index);
|
|
350
|
+
return index;
|
|
351
|
+
};
|
|
352
|
+
|
|
159
353
|
/**
|
|
160
|
-
*
|
|
161
|
-
* @
|
|
354
|
+
* Converts a specialized flatted string into a JS value.
|
|
355
|
+
* @param {string} text
|
|
356
|
+
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
357
|
+
* @returns {any}
|
|
162
358
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
359
|
+
const parse = (text, reviver) => {
|
|
360
|
+
const input = $parse(text, Primitives).map(primitives);
|
|
361
|
+
const value = input[0];
|
|
362
|
+
const $ = reviver || noop;
|
|
363
|
+
const tmp = typeof value === object && value ?
|
|
364
|
+
revive(input, new Set, value, $) :
|
|
365
|
+
value;
|
|
366
|
+
return $.call({'': tmp}, '', tmp);
|
|
367
|
+
};
|
|
166
368
|
|
|
167
369
|
/**
|
|
168
|
-
*
|
|
169
|
-
* @param {
|
|
170
|
-
* @
|
|
370
|
+
* Converts a JS value into a specialized flatted string.
|
|
371
|
+
* @param {any} value
|
|
372
|
+
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
373
|
+
* @param {string | number | undefined} [space]
|
|
374
|
+
* @returns {string}
|
|
171
375
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
break;
|
|
185
|
-
|
|
186
|
-
case 'ObjectPattern':
|
|
187
|
-
for (const prop of param.properties) {
|
|
188
|
-
if (prop.type === 'RestElement') {
|
|
189
|
-
extract_identifiers(prop.argument, nodes);
|
|
190
|
-
} else {
|
|
191
|
-
extract_identifiers(prop.value, nodes);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
break;
|
|
196
|
-
|
|
197
|
-
case 'ArrayPattern':
|
|
198
|
-
for (const element of param.elements) {
|
|
199
|
-
if (element) extract_identifiers(element, nodes);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
break;
|
|
203
|
-
|
|
204
|
-
case 'RestElement':
|
|
205
|
-
extract_identifiers(param.argument, nodes);
|
|
206
|
-
break;
|
|
207
|
-
|
|
208
|
-
case 'AssignmentPattern':
|
|
209
|
-
extract_identifiers(param.left, nodes);
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return nodes;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const viInjectedKey = "__vi_inject__";
|
|
217
|
-
const viExportAllHelper = "__vitest_browser_runner__.exportAll";
|
|
218
|
-
const skipHijack = [
|
|
219
|
-
"/@vite/client",
|
|
220
|
-
"/@vite/env",
|
|
221
|
-
/vite\/dist\/client/
|
|
222
|
-
];
|
|
223
|
-
function injectVitestModule(code, id, parse) {
|
|
224
|
-
if (skipHijack.some((skip) => id.match(skip)))
|
|
225
|
-
return;
|
|
226
|
-
const s = new MagicString(code);
|
|
227
|
-
let ast;
|
|
228
|
-
try {
|
|
229
|
-
ast = parse(code);
|
|
230
|
-
} catch (err) {
|
|
231
|
-
console.error(`Cannot parse ${id}:
|
|
232
|
-
${err.message}`);
|
|
233
|
-
return;
|
|
376
|
+
const stringify = (value, replacer, space) => {
|
|
377
|
+
const $ = replacer && typeof replacer === object ?
|
|
378
|
+
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
379
|
+
(replacer || noop);
|
|
380
|
+
const known = new Map;
|
|
381
|
+
const input = [];
|
|
382
|
+
const output = [];
|
|
383
|
+
let i = +set(known, input, $.call({'': value}, '', value));
|
|
384
|
+
let firstRun = !i;
|
|
385
|
+
while (i < input.length) {
|
|
386
|
+
firstRun = true;
|
|
387
|
+
output[i] = $stringify(input[i++], replace, space);
|
|
234
388
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
389
|
+
return '[' + output.join(',') + ']';
|
|
390
|
+
function replace(key, value) {
|
|
391
|
+
if (firstRun) {
|
|
392
|
+
firstRun = !firstRun;
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
const after = $.call(this, key, value);
|
|
396
|
+
switch (typeof after) {
|
|
397
|
+
case object:
|
|
398
|
+
if (after === null) return after;
|
|
399
|
+
case primitive:
|
|
400
|
+
return known.get(after) || set(known, input, after);
|
|
401
|
+
}
|
|
402
|
+
return after;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
async function resolveMock(project, rawId, importer, hasFactory) {
|
|
407
|
+
const { id, fsPath, external } = await resolveId(project, rawId, importer);
|
|
408
|
+
if (hasFactory) {
|
|
409
|
+
return { type: "factory", resolvedId: id };
|
|
410
|
+
}
|
|
411
|
+
const mockPath = resolveMockPath(project.config.root, fsPath, external);
|
|
412
|
+
return {
|
|
413
|
+
type: mockPath === null ? "automock" : "redirect",
|
|
414
|
+
mockPath,
|
|
415
|
+
resolvedId: id
|
|
252
416
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
417
|
+
}
|
|
418
|
+
async function resolveId(project, rawId, importer) {
|
|
419
|
+
const resolved = await project.browser.vite.pluginContainer.resolveId(
|
|
420
|
+
rawId,
|
|
421
|
+
importer,
|
|
422
|
+
{
|
|
423
|
+
ssr: false
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
return resolveModule(project, rawId, resolved);
|
|
427
|
+
}
|
|
428
|
+
async function resolveModule(project, rawId, resolved) {
|
|
429
|
+
const id = resolved?.id || rawId;
|
|
430
|
+
const external = !isAbsolute(id) || isModuleDirectory(project.config, id) ? rawId : null;
|
|
431
|
+
return {
|
|
432
|
+
id,
|
|
433
|
+
fsPath: cleanUrl(id),
|
|
434
|
+
external
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function isModuleDirectory(config, path) {
|
|
438
|
+
const moduleDirectories = config.server.deps?.moduleDirectories || [
|
|
439
|
+
"/node_modules/"
|
|
440
|
+
];
|
|
441
|
+
return moduleDirectories.some((dir) => path.includes(dir));
|
|
442
|
+
}
|
|
443
|
+
function resolveMockPath(root, mockPath, external) {
|
|
444
|
+
const path = external || mockPath;
|
|
445
|
+
if (external || isNodeBuiltin(mockPath) || !existsSync(mockPath)) {
|
|
446
|
+
const mockDirname = dirname(path);
|
|
447
|
+
const mockFolder = join(
|
|
448
|
+
root,
|
|
449
|
+
"__mocks__",
|
|
450
|
+
mockDirname
|
|
271
451
|
);
|
|
452
|
+
if (!existsSync(mockFolder)) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
const files = readdirSync(mockFolder);
|
|
456
|
+
const baseOriginal = basename(path);
|
|
457
|
+
for (const file of files) {
|
|
458
|
+
const baseFile = basename(file, extname(file));
|
|
459
|
+
if (baseFile === baseOriginal) {
|
|
460
|
+
return resolve(mockFolder, file);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
const dir = dirname(path);
|
|
466
|
+
const baseId = basename(path);
|
|
467
|
+
const fullPath = resolve(dir, "__mocks__", baseId);
|
|
468
|
+
return existsSync(fullPath) ? fullPath : null;
|
|
469
|
+
}
|
|
470
|
+
const prefixedBuiltins = /* @__PURE__ */ new Set(["node:test"]);
|
|
471
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
472
|
+
...builtinModules,
|
|
473
|
+
"assert/strict",
|
|
474
|
+
"diagnostics_channel",
|
|
475
|
+
"dns/promises",
|
|
476
|
+
"fs/promises",
|
|
477
|
+
"path/posix",
|
|
478
|
+
"path/win32",
|
|
479
|
+
"readline/promises",
|
|
480
|
+
"stream/consumers",
|
|
481
|
+
"stream/promises",
|
|
482
|
+
"stream/web",
|
|
483
|
+
"timers/promises",
|
|
484
|
+
"util/types",
|
|
485
|
+
"wasi"
|
|
486
|
+
]);
|
|
487
|
+
const NODE_BUILTIN_NAMESPACE = "node:";
|
|
488
|
+
function isNodeBuiltin(id) {
|
|
489
|
+
if (prefixedBuiltins.has(id)) {
|
|
490
|
+
return true;
|
|
272
491
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
492
|
+
return builtins.has(
|
|
493
|
+
id.startsWith(NODE_BUILTIN_NAMESPACE) ? id.slice(NODE_BUILTIN_NAMESPACE.length) : id
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
const postfixRE = /[?#].*$/;
|
|
497
|
+
function cleanUrl(url) {
|
|
498
|
+
return url.replace(postfixRE, "");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const debug$1 = createDebugger("vitest:browser:api");
|
|
502
|
+
const BROWSER_API_PATH = "/__vitest_browser_api__";
|
|
503
|
+
function setupBrowserRpc(server) {
|
|
504
|
+
const project = server.project;
|
|
505
|
+
const vite = server.vite;
|
|
506
|
+
const ctx = project.ctx;
|
|
507
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
508
|
+
vite.httpServer?.on("upgrade", (request, socket, head) => {
|
|
509
|
+
if (!request.url) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const { pathname, searchParams } = new URL(request.url, "http://localhost");
|
|
513
|
+
if (pathname !== BROWSER_API_PATH) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const type = searchParams.get("type") ?? "tester";
|
|
517
|
+
const sessionId = searchParams.get("sessionId") ?? "0";
|
|
518
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
519
|
+
wss.emit("connection", ws, request);
|
|
520
|
+
const rpc = setupClient(ws);
|
|
521
|
+
const state = server.state;
|
|
522
|
+
const clients = type === "tester" ? state.testers : state.orchestrators;
|
|
523
|
+
clients.set(sessionId, rpc);
|
|
524
|
+
debug$1?.("[%s] Browser API connected to %s", sessionId, type);
|
|
525
|
+
ws.on("close", () => {
|
|
526
|
+
debug$1?.("[%s] Browser API disconnected from %s", sessionId, type);
|
|
527
|
+
clients.delete(sessionId);
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
function checkFileAccess(path) {
|
|
532
|
+
if (!isFileServingAllowed(path, vite)) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function setupClient(ws) {
|
|
539
|
+
const rpc = createBirpc(
|
|
540
|
+
{
|
|
541
|
+
async onUnhandledError(error, type) {
|
|
542
|
+
ctx.state.catchError(error, type);
|
|
543
|
+
},
|
|
544
|
+
async onCollected(files) {
|
|
545
|
+
ctx.state.collectFiles(files);
|
|
546
|
+
await ctx.report("onCollected", files);
|
|
547
|
+
},
|
|
548
|
+
async onTaskUpdate(packs) {
|
|
549
|
+
ctx.state.updateTasks(packs);
|
|
550
|
+
await ctx.report("onTaskUpdate", packs);
|
|
551
|
+
},
|
|
552
|
+
onAfterSuiteRun(meta) {
|
|
553
|
+
ctx.coverageProvider?.onAfterSuiteRun(meta);
|
|
554
|
+
},
|
|
555
|
+
sendLog(log) {
|
|
556
|
+
return ctx.report("onUserConsoleLog", log);
|
|
557
|
+
},
|
|
558
|
+
resolveSnapshotPath(testPath) {
|
|
559
|
+
return ctx.snapshot.resolvePath(testPath);
|
|
560
|
+
},
|
|
561
|
+
resolveSnapshotRawPath(testPath, rawPath) {
|
|
562
|
+
return ctx.snapshot.resolveRawPath(testPath, rawPath);
|
|
563
|
+
},
|
|
564
|
+
snapshotSaved(snapshot) {
|
|
565
|
+
ctx.snapshot.add(snapshot);
|
|
566
|
+
},
|
|
567
|
+
async readSnapshotFile(snapshotPath) {
|
|
568
|
+
checkFileAccess(snapshotPath);
|
|
569
|
+
if (!existsSync(snapshotPath)) {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
return promises.readFile(snapshotPath, "utf-8");
|
|
573
|
+
},
|
|
574
|
+
async saveSnapshotFile(id, content) {
|
|
575
|
+
checkFileAccess(id);
|
|
576
|
+
await promises.mkdir(dirname(id), { recursive: true });
|
|
577
|
+
return promises.writeFile(id, content, "utf-8");
|
|
578
|
+
},
|
|
579
|
+
async removeSnapshotFile(id) {
|
|
580
|
+
checkFileAccess(id);
|
|
581
|
+
if (!existsSync(id)) {
|
|
582
|
+
throw new Error(`Snapshot file "${id}" does not exist.`);
|
|
583
|
+
}
|
|
584
|
+
return promises.unlink(id);
|
|
585
|
+
},
|
|
586
|
+
getBrowserFileSourceMap(id) {
|
|
587
|
+
const mod = server.vite.moduleGraph.getModuleById(id);
|
|
588
|
+
return mod?.transformResult?.map;
|
|
589
|
+
},
|
|
590
|
+
onCancel(reason) {
|
|
591
|
+
ctx.cancelCurrentRun(reason);
|
|
592
|
+
},
|
|
593
|
+
async resolveId(id, importer) {
|
|
594
|
+
const result = await project.server.pluginContainer.resolveId(
|
|
595
|
+
id,
|
|
596
|
+
importer,
|
|
597
|
+
{
|
|
598
|
+
ssr: false
|
|
599
|
+
}
|
|
284
600
|
);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
601
|
+
return result;
|
|
602
|
+
},
|
|
603
|
+
debug(...args) {
|
|
604
|
+
ctx.logger.console.debug(...args);
|
|
605
|
+
},
|
|
606
|
+
getCountOfFailedTests() {
|
|
607
|
+
return ctx.state.getCountOfFailedTests();
|
|
608
|
+
},
|
|
609
|
+
async triggerCommand(contextId, command, testPath, payload) {
|
|
610
|
+
debug$1?.('[%s] Triggering command "%s"', contextId, command);
|
|
611
|
+
const provider = server.provider;
|
|
612
|
+
if (!provider) {
|
|
613
|
+
throw new Error("Commands are only available for browser tests.");
|
|
614
|
+
}
|
|
615
|
+
const commands = project.config.browser?.commands;
|
|
616
|
+
if (!commands || !commands[command]) {
|
|
617
|
+
throw new Error(`Unknown command "${command}".`);
|
|
618
|
+
}
|
|
619
|
+
if (provider.beforeCommand) {
|
|
620
|
+
await provider.beforeCommand(command, payload);
|
|
621
|
+
}
|
|
622
|
+
const context = Object.assign(
|
|
623
|
+
{
|
|
624
|
+
testPath,
|
|
625
|
+
project,
|
|
626
|
+
provider,
|
|
627
|
+
contextId
|
|
628
|
+
},
|
|
629
|
+
provider.getCommandsContext(contextId)
|
|
630
|
+
);
|
|
631
|
+
let result;
|
|
632
|
+
try {
|
|
633
|
+
result = await commands[command](context, ...payload);
|
|
634
|
+
} finally {
|
|
635
|
+
if (provider.afterCommand) {
|
|
636
|
+
await provider.afterCommand(command, payload);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
},
|
|
641
|
+
finishBrowserTests(contextId) {
|
|
642
|
+
debug$1?.("[%s] Finishing browser tests for context", contextId);
|
|
643
|
+
return server.state.getContext(contextId)?.resolve();
|
|
644
|
+
},
|
|
645
|
+
resolveMock(rawId, importer, hasFactory) {
|
|
646
|
+
return resolveMock(project, rawId, importer, hasFactory);
|
|
647
|
+
},
|
|
648
|
+
invalidate(ids) {
|
|
649
|
+
ids.forEach((id) => {
|
|
650
|
+
const moduleGraph = server.vite.moduleGraph;
|
|
651
|
+
const module = moduleGraph.getModuleById(id);
|
|
652
|
+
if (module) {
|
|
653
|
+
moduleGraph.invalidateModule(module, /* @__PURE__ */ new Set(), Date.now(), true);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
post: (msg) => ws.send(msg),
|
|
660
|
+
on: (fn) => ws.on("message", fn),
|
|
661
|
+
eventNames: ["onCancel"],
|
|
662
|
+
serialize: (data) => stringify(data, stringifyReplace),
|
|
663
|
+
deserialize: parse,
|
|
664
|
+
onTimeoutError(functionName) {
|
|
665
|
+
throw new Error(`[vitest-api]: Timeout calling "${functionName}"`);
|
|
289
666
|
}
|
|
290
667
|
}
|
|
668
|
+
);
|
|
669
|
+
ctx.onCancel((reason) => rpc.onCancel(reason));
|
|
670
|
+
return rpc;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function cloneByOwnProperties(value) {
|
|
674
|
+
return Object.getOwnPropertyNames(value).reduce(
|
|
675
|
+
(clone, prop) => ({
|
|
676
|
+
...clone,
|
|
677
|
+
[prop]: value[prop]
|
|
678
|
+
}),
|
|
679
|
+
{}
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
function stringifyReplace(key, value) {
|
|
683
|
+
if (value instanceof Error) {
|
|
684
|
+
const cloned = cloneByOwnProperties(value);
|
|
685
|
+
return {
|
|
686
|
+
name: value.name,
|
|
687
|
+
message: value.message,
|
|
688
|
+
stack: value.stack,
|
|
689
|
+
...cloned
|
|
690
|
+
};
|
|
691
|
+
} else {
|
|
692
|
+
return value;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
class BrowserServerState {
|
|
697
|
+
orchestrators = /* @__PURE__ */ new Map();
|
|
698
|
+
testers = /* @__PURE__ */ new Map();
|
|
699
|
+
contexts = /* @__PURE__ */ new Map();
|
|
700
|
+
getContext(contextId) {
|
|
701
|
+
return this.contexts.get(contextId);
|
|
702
|
+
}
|
|
703
|
+
createAsyncContext(contextId, files) {
|
|
704
|
+
const defer = createDefer();
|
|
705
|
+
this.contexts.set(contextId, {
|
|
706
|
+
files,
|
|
707
|
+
resolve: () => {
|
|
708
|
+
defer.resolve();
|
|
709
|
+
this.contexts.delete(contextId);
|
|
710
|
+
},
|
|
711
|
+
reject: defer.reject
|
|
712
|
+
});
|
|
713
|
+
return defer;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function replacer(code, values) {
|
|
718
|
+
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? "");
|
|
719
|
+
}
|
|
720
|
+
const builtinProviders = ["webdriverio", "playwright", "preview"];
|
|
721
|
+
async function getBrowserProvider(options, project) {
|
|
722
|
+
if (options.provider == null || builtinProviders.includes(options.provider)) {
|
|
723
|
+
const providers = await import('./providers.js');
|
|
724
|
+
const provider = options.provider || "preview";
|
|
725
|
+
return providers[provider];
|
|
726
|
+
}
|
|
727
|
+
let customProviderModule;
|
|
728
|
+
try {
|
|
729
|
+
customProviderModule = await project.runner.executeId(
|
|
730
|
+
options.provider
|
|
731
|
+
);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
throw new Error(
|
|
734
|
+
`Failed to load custom BrowserProvider from ${options.provider}`,
|
|
735
|
+
{ cause: error }
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
if (customProviderModule.default == null) {
|
|
739
|
+
throw new Error(
|
|
740
|
+
`Custom BrowserProvider loaded from ${options.provider} was not the default export`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
return customProviderModule.default;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
class BrowserServer {
|
|
747
|
+
constructor(project, base) {
|
|
748
|
+
this.project = project;
|
|
749
|
+
this.base = base;
|
|
750
|
+
this.state = new BrowserServerState();
|
|
751
|
+
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
752
|
+
const distRoot = resolve(pkgRoot, "dist");
|
|
753
|
+
this.prefixTesterUrl = `${base}__vitest_test__/__test__/`;
|
|
754
|
+
this.faviconUrl = `${base}__vitest__/favicon.svg`;
|
|
755
|
+
this.manifest = (async () => {
|
|
756
|
+
return JSON.parse(
|
|
757
|
+
await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8")
|
|
758
|
+
);
|
|
759
|
+
})().then((manifest) => this.manifest = manifest);
|
|
760
|
+
this.testerHtml = readFile$1(
|
|
761
|
+
resolve(distRoot, "client/tester/tester.html"),
|
|
762
|
+
"utf8"
|
|
763
|
+
).then((html) => this.testerHtml = html);
|
|
764
|
+
this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
|
|
765
|
+
this.injectorJs = readFile$1(
|
|
766
|
+
resolve(distRoot, "client/esm-client-injector.js"),
|
|
767
|
+
"utf8"
|
|
768
|
+
).then((js) => this.injectorJs = js);
|
|
769
|
+
this.stateJs = readFile$1(
|
|
770
|
+
resolve(distRoot, "state.js"),
|
|
771
|
+
"utf-8"
|
|
772
|
+
).then((js) => this.stateJs = js);
|
|
773
|
+
}
|
|
774
|
+
faviconUrl;
|
|
775
|
+
prefixTesterUrl;
|
|
776
|
+
orchestratorScripts;
|
|
777
|
+
testerScripts;
|
|
778
|
+
manifest;
|
|
779
|
+
testerHtml;
|
|
780
|
+
orchestratorHtml;
|
|
781
|
+
injectorJs;
|
|
782
|
+
stateJs;
|
|
783
|
+
state;
|
|
784
|
+
provider;
|
|
785
|
+
vite;
|
|
786
|
+
setServer(server) {
|
|
787
|
+
this.vite = server;
|
|
788
|
+
}
|
|
789
|
+
getSerializableConfig() {
|
|
790
|
+
const config = wrapConfig(this.project.getSerializableConfig());
|
|
791
|
+
config.env ??= {};
|
|
792
|
+
config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
|
|
793
|
+
return config;
|
|
794
|
+
}
|
|
795
|
+
resolveTesterUrl(pathname) {
|
|
796
|
+
const [contextId, testFile] = pathname.slice(this.prefixTesterUrl.length).split("/");
|
|
797
|
+
const decodedTestFile = decodeURIComponent(testFile);
|
|
798
|
+
return { contextId, testFile: decodedTestFile };
|
|
799
|
+
}
|
|
800
|
+
async formatScripts(scripts) {
|
|
801
|
+
if (!scripts?.length) {
|
|
802
|
+
return "";
|
|
291
803
|
}
|
|
804
|
+
const server = this.vite;
|
|
805
|
+
const promises = scripts.map(
|
|
806
|
+
async ({ content, src, async, id, type = "module" }, index) => {
|
|
807
|
+
const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : void 0) || src;
|
|
808
|
+
const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`);
|
|
809
|
+
await server.moduleGraph.ensureEntryFromUrl(transformId);
|
|
810
|
+
const contentProcessed = content && type === "module" ? (await server.pluginContainer.transform(content, transformId)).code : content;
|
|
811
|
+
return `<script type="${type}"${async ? " async" : ""}${srcLink ? ` src="${slash(`/@fs/${srcLink}`)}"` : ""}>${contentProcessed || ""}<\/script>`;
|
|
812
|
+
}
|
|
813
|
+
);
|
|
814
|
+
return (await Promise.all(promises)).join("\n");
|
|
292
815
|
}
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
816
|
+
async initBrowserProvider() {
|
|
817
|
+
if (this.provider) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const Provider = await getBrowserProvider(this.project.config.browser, this.project);
|
|
821
|
+
this.provider = new Provider();
|
|
822
|
+
const browser = this.project.config.browser.name;
|
|
823
|
+
if (!browser) {
|
|
824
|
+
throw new Error(
|
|
825
|
+
`[${this.project.getName()}] Browser name is required. Please, set \`test.browser.name\` option manually.`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
const supportedBrowsers = this.provider.getSupportedBrowsers();
|
|
829
|
+
if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
|
|
830
|
+
throw new Error(
|
|
831
|
+
`[${this.project.getName()}] Browser "${browser}" is not supported by the browser provider "${this.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
const providerOptions = this.project.config.browser.providerOptions;
|
|
835
|
+
await this.provider.initialize(this.project, {
|
|
836
|
+
browser,
|
|
837
|
+
options: providerOptions
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
async close() {
|
|
841
|
+
await this.vite.close();
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function wrapConfig(config) {
|
|
845
|
+
return {
|
|
846
|
+
...config,
|
|
847
|
+
// workaround RegExp serialization
|
|
848
|
+
testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : void 0
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const click = async (context, xpath, options = {}) => {
|
|
853
|
+
const provider = context.provider;
|
|
854
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
855
|
+
const tester = context.frame;
|
|
856
|
+
await tester.locator(`xpath=${xpath}`).click({
|
|
857
|
+
timeout: 1e3,
|
|
858
|
+
...options
|
|
859
|
+
});
|
|
860
|
+
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
861
|
+
const browser = context.browser;
|
|
862
|
+
const markedXpath = `//${xpath}`;
|
|
863
|
+
await browser.$(markedXpath).click(options);
|
|
864
|
+
} else {
|
|
865
|
+
throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
const dblClick = async (context, xpath, options = {}) => {
|
|
869
|
+
const provider = context.provider;
|
|
870
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
871
|
+
const tester = context.frame;
|
|
872
|
+
await tester.locator(`xpath=${xpath}`).dblclick(options);
|
|
873
|
+
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
874
|
+
const browser = context.browser;
|
|
875
|
+
const markedXpath = `//${xpath}`;
|
|
876
|
+
await browser.$(markedXpath).doubleClick();
|
|
877
|
+
} else {
|
|
878
|
+
throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
var clickableInputTypes;
|
|
883
|
+
(function(clickableInputTypes) {
|
|
884
|
+
clickableInputTypes["button"] = "button";
|
|
885
|
+
clickableInputTypes["color"] = "color";
|
|
886
|
+
clickableInputTypes["file"] = "file";
|
|
887
|
+
clickableInputTypes["image"] = "image";
|
|
888
|
+
clickableInputTypes["reset"] = "reset";
|
|
889
|
+
clickableInputTypes["submit"] = "submit";
|
|
890
|
+
clickableInputTypes["checkbox"] = "checkbox";
|
|
891
|
+
clickableInputTypes["radio"] = "radio";
|
|
892
|
+
})(clickableInputTypes || (clickableInputTypes = {}));
|
|
893
|
+
|
|
894
|
+
const ClipboardStubControl = Symbol('Manage ClipboardSub');
|
|
895
|
+
function isClipboardStub(clipboard) {
|
|
896
|
+
return !!(clipboard === null || clipboard === void 0 ? void 0 : clipboard[ClipboardStubControl]);
|
|
897
|
+
}
|
|
898
|
+
function resetClipboardStubOnView(window) {
|
|
899
|
+
if (isClipboardStub(window.navigator.clipboard)) {
|
|
900
|
+
window.navigator.clipboard[ClipboardStubControl].resetClipboardStub();
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function detachClipboardStubFromView(window) {
|
|
904
|
+
if (isClipboardStub(window.navigator.clipboard)) {
|
|
905
|
+
window.navigator.clipboard[ClipboardStubControl].detachClipboardStub();
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const g = globalThis;
|
|
909
|
+
/* istanbul ignore else */ if (typeof g.afterEach === 'function') {
|
|
910
|
+
g.afterEach(()=>resetClipboardStubOnView(globalThis.window));
|
|
911
|
+
}
|
|
912
|
+
/* istanbul ignore else */ if (typeof g.afterAll === 'function') {
|
|
913
|
+
g.afterAll(()=>detachClipboardStubFromView(globalThis.window));
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
var editableInputTypes;
|
|
917
|
+
(function(editableInputTypes) {
|
|
918
|
+
editableInputTypes["text"] = "text";
|
|
919
|
+
editableInputTypes["date"] = "date";
|
|
920
|
+
editableInputTypes["datetime-local"] = "datetime-local";
|
|
921
|
+
editableInputTypes["email"] = "email";
|
|
922
|
+
editableInputTypes["month"] = "month";
|
|
923
|
+
editableInputTypes["number"] = "number";
|
|
924
|
+
editableInputTypes["password"] = "password";
|
|
925
|
+
editableInputTypes["search"] = "search";
|
|
926
|
+
editableInputTypes["tel"] = "tel";
|
|
927
|
+
editableInputTypes["time"] = "time";
|
|
928
|
+
editableInputTypes["url"] = "url";
|
|
929
|
+
editableInputTypes["week"] = "week";
|
|
930
|
+
})(editableInputTypes || (editableInputTypes = {}));
|
|
931
|
+
|
|
932
|
+
var maxLengthSupportedTypes;
|
|
933
|
+
(function(maxLengthSupportedTypes) {
|
|
934
|
+
maxLengthSupportedTypes["email"] = "email";
|
|
935
|
+
maxLengthSupportedTypes["password"] = "password";
|
|
936
|
+
maxLengthSupportedTypes["search"] = "search";
|
|
937
|
+
maxLengthSupportedTypes["telephone"] = "telephone";
|
|
938
|
+
maxLengthSupportedTypes["text"] = "text";
|
|
939
|
+
maxLengthSupportedTypes["url"] = "url";
|
|
940
|
+
})(maxLengthSupportedTypes || (maxLengthSupportedTypes = {}));
|
|
941
|
+
|
|
942
|
+
var bracketDict;
|
|
943
|
+
(function(bracketDict) {
|
|
944
|
+
bracketDict["{"] = "}";
|
|
945
|
+
bracketDict["["] = "]";
|
|
946
|
+
})(bracketDict || (bracketDict = {}));
|
|
947
|
+
/**
|
|
948
|
+
* Read the next key definition from user input
|
|
949
|
+
*
|
|
950
|
+
* Describe key per `{descriptor}` or `[descriptor]`.
|
|
951
|
+
* Everything else will be interpreted as a single character as descriptor - e.g. `a`.
|
|
952
|
+
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
953
|
+
* A previously pressed key can be released per `{/descriptor}`.
|
|
954
|
+
* Keeping the key pressed can be written as `{descriptor>}`.
|
|
955
|
+
* When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
|
|
956
|
+
* You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
|
|
957
|
+
*/ function readNextDescriptor(text, context) {
|
|
958
|
+
let pos = 0;
|
|
959
|
+
const startBracket = text[pos] in bracketDict ? text[pos] : '';
|
|
960
|
+
pos += startBracket.length;
|
|
961
|
+
const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
|
|
962
|
+
const type = isEscapedChar ? '' : startBracket;
|
|
963
|
+
return {
|
|
964
|
+
type,
|
|
965
|
+
...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function readPrintableChar(text, pos, context) {
|
|
969
|
+
const descriptor = text[pos];
|
|
970
|
+
assertDescriptor(descriptor, text, pos);
|
|
971
|
+
pos += descriptor.length;
|
|
972
|
+
return {
|
|
973
|
+
consumedLength: pos,
|
|
974
|
+
descriptor,
|
|
975
|
+
releasePrevious: false,
|
|
976
|
+
releaseSelf: true,
|
|
977
|
+
repeat: 1
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function readTag(text, pos, startBracket, context) {
|
|
981
|
+
var _text_slice_match, _text_slice_match1;
|
|
982
|
+
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
|
|
983
|
+
pos += releasePreviousModifier.length;
|
|
984
|
+
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
|
|
985
|
+
pos += Number(escapedDescriptor);
|
|
986
|
+
const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === void 0 ? void 0 : _text_slice_match[0];
|
|
987
|
+
assertDescriptor(descriptor, text, pos);
|
|
988
|
+
pos += descriptor.length;
|
|
989
|
+
var _text_slice_match_;
|
|
990
|
+
const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === void 0 ? void 0 : _text_slice_match1[0]) !== null && _text_slice_match_ !== void 0 ? _text_slice_match_ : '';
|
|
991
|
+
pos += repeatModifier.length;
|
|
992
|
+
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
|
|
993
|
+
pos += releaseSelfModifier.length;
|
|
994
|
+
const expectedEndBracket = bracketDict[startBracket];
|
|
995
|
+
const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
|
|
996
|
+
if (!endBracket) {
|
|
997
|
+
throw new Error(getErrorMessage([
|
|
998
|
+
!repeatModifier && 'repeat modifier',
|
|
999
|
+
!releaseSelfModifier && 'release modifier',
|
|
1000
|
+
`"${expectedEndBracket}"`
|
|
1001
|
+
].filter(Boolean).join(' or '), text[pos], text));
|
|
1002
|
+
}
|
|
1003
|
+
pos += endBracket.length;
|
|
1004
|
+
return {
|
|
1005
|
+
consumedLength: pos,
|
|
1006
|
+
descriptor,
|
|
1007
|
+
releasePrevious: !!releasePreviousModifier,
|
|
1008
|
+
repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
|
|
1009
|
+
releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
function assertDescriptor(descriptor, text, pos, context) {
|
|
1013
|
+
if (!descriptor) {
|
|
1014
|
+
throw new Error(getErrorMessage('key descriptor', text[pos], text));
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
|
|
1018
|
+
if (releaseSelfModifier) {
|
|
1019
|
+
return releaseSelfModifier === '/';
|
|
1020
|
+
}
|
|
1021
|
+
if (repeatModifier) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function getErrorMessage(expected, found, text, context) {
|
|
1026
|
+
return `Expected ${expected} but found "${found !== null && found !== void 0 ? found : ''}" in "${text}"
|
|
1027
|
+
See ${`https://testing-library.com/docs/user-event/keyboard`}
|
|
1028
|
+
for more information about how userEvent parses your input.`;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
var ApiLevel;
|
|
1032
|
+
(function(ApiLevel) {
|
|
1033
|
+
ApiLevel[ApiLevel["Trigger"] = 2] = "Trigger";
|
|
1034
|
+
ApiLevel[ApiLevel["Call"] = 1] = "Call";
|
|
1035
|
+
})(ApiLevel || (ApiLevel = {}));
|
|
1036
|
+
|
|
1037
|
+
var PointerEventsCheckLevel;
|
|
1038
|
+
(function(PointerEventsCheckLevel) {
|
|
1039
|
+
/**
|
|
1040
|
+
* Check pointer events on every user interaction that triggers a bunch of events.
|
|
1041
|
+
* E.g. once for releasing a mouse button even though this triggers `pointerup`, `mouseup`, `click`, etc...
|
|
1042
|
+
*/ PointerEventsCheckLevel[PointerEventsCheckLevel["EachTrigger"] = 4] = "EachTrigger";
|
|
1043
|
+
/** Check each target once per call to pointer (related) API */ PointerEventsCheckLevel[PointerEventsCheckLevel["EachApiCall"] = 2] = "EachApiCall";
|
|
1044
|
+
/** Check each event target once */ PointerEventsCheckLevel[PointerEventsCheckLevel["EachTarget"] = 1] = "EachTarget";
|
|
1045
|
+
/** No pointer events check */ PointerEventsCheckLevel[PointerEventsCheckLevel["Never"] = 0] = "Never";
|
|
1046
|
+
})(PointerEventsCheckLevel || (PointerEventsCheckLevel = {}));
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Parse key defintions per `keyboardMap`
|
|
1050
|
+
*
|
|
1051
|
+
* Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
|
|
1052
|
+
* Everything else will be interpreted as a typed character - e.g. `a`.
|
|
1053
|
+
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
1054
|
+
* Keeping the key pressed can be written as `{key>}`.
|
|
1055
|
+
* When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
|
|
1056
|
+
* You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
|
|
1057
|
+
*/ function parseKeyDef(keyboardMap, text) {
|
|
1058
|
+
const defs = [];
|
|
1059
|
+
do {
|
|
1060
|
+
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
1061
|
+
var _keyboardMap_find;
|
|
1062
|
+
const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
|
|
1063
|
+
if (type === '[') {
|
|
1064
|
+
var _def_code;
|
|
1065
|
+
return ((_def_code = def.code) === null || _def_code === void 0 ? void 0 : _def_code.toLowerCase()) === descriptor.toLowerCase();
|
|
1066
|
+
} else if (type === '{') {
|
|
1067
|
+
var _def_key;
|
|
1068
|
+
return ((_def_key = def.key) === null || _def_key === void 0 ? void 0 : _def_key.toLowerCase()) === descriptor.toLowerCase();
|
|
1069
|
+
}
|
|
1070
|
+
return def.key === descriptor;
|
|
1071
|
+
})) !== null && _keyboardMap_find !== void 0 ? _keyboardMap_find : {
|
|
1072
|
+
key: 'Unknown',
|
|
1073
|
+
code: 'Unknown',
|
|
1074
|
+
[type === '[' ? 'code' : 'key']: descriptor
|
|
1075
|
+
};
|
|
1076
|
+
defs.push({
|
|
1077
|
+
keyDef,
|
|
1078
|
+
releasePrevious,
|
|
1079
|
+
releaseSelf,
|
|
1080
|
+
repeat
|
|
1081
|
+
});
|
|
1082
|
+
text = text.slice(consumedLength);
|
|
1083
|
+
}while (text)
|
|
1084
|
+
return defs;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
var DOM_KEY_LOCATION;
|
|
1088
|
+
(function(DOM_KEY_LOCATION) {
|
|
1089
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
|
|
1090
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
|
|
1091
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
|
|
1092
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
|
|
1093
|
+
})(DOM_KEY_LOCATION || (DOM_KEY_LOCATION = {}));
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Mapping for a default US-104-QWERTY keyboard
|
|
1097
|
+
*/ const defaultKeyMap = [
|
|
1098
|
+
// alphanumeric keys
|
|
1099
|
+
...'0123456789'.split('').map((c)=>({
|
|
1100
|
+
code: `Digit${c}`,
|
|
1101
|
+
key: c
|
|
1102
|
+
})),
|
|
1103
|
+
...')!@#$%^&*('.split('').map((c, i)=>({
|
|
1104
|
+
code: `Digit${i}`,
|
|
1105
|
+
key: c,
|
|
1106
|
+
shiftKey: true
|
|
1107
|
+
})),
|
|
1108
|
+
...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
|
|
1109
|
+
code: `Key${c.toUpperCase()}`,
|
|
1110
|
+
key: c
|
|
1111
|
+
})),
|
|
1112
|
+
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
|
|
1113
|
+
code: `Key${c}`,
|
|
1114
|
+
key: c,
|
|
1115
|
+
shiftKey: true
|
|
1116
|
+
})),
|
|
1117
|
+
// alphanumeric block - functional
|
|
1118
|
+
{
|
|
1119
|
+
code: 'Space',
|
|
1120
|
+
key: ' '
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
code: 'AltLeft',
|
|
1124
|
+
key: 'Alt',
|
|
1125
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
code: 'AltRight',
|
|
1129
|
+
key: 'Alt',
|
|
1130
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
code: 'ShiftLeft',
|
|
1134
|
+
key: 'Shift',
|
|
1135
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
code: 'ShiftRight',
|
|
1139
|
+
key: 'Shift',
|
|
1140
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
code: 'ControlLeft',
|
|
1144
|
+
key: 'Control',
|
|
1145
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
code: 'ControlRight',
|
|
1149
|
+
key: 'Control',
|
|
1150
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
code: 'MetaLeft',
|
|
1154
|
+
key: 'Meta',
|
|
1155
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
code: 'MetaRight',
|
|
1159
|
+
key: 'Meta',
|
|
1160
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
code: 'OSLeft',
|
|
1164
|
+
key: 'OS',
|
|
1165
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
code: 'OSRight',
|
|
1169
|
+
key: 'OS',
|
|
1170
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
code: 'Tab',
|
|
1174
|
+
key: 'Tab'
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
code: 'CapsLock',
|
|
1178
|
+
key: 'CapsLock'
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
code: 'Backspace',
|
|
1182
|
+
key: 'Backspace'
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
code: 'Enter',
|
|
1186
|
+
key: 'Enter'
|
|
1187
|
+
},
|
|
1188
|
+
// function
|
|
1189
|
+
{
|
|
1190
|
+
code: 'Escape',
|
|
1191
|
+
key: 'Escape'
|
|
1192
|
+
},
|
|
1193
|
+
// arrows
|
|
1194
|
+
{
|
|
1195
|
+
code: 'ArrowUp',
|
|
1196
|
+
key: 'ArrowUp'
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
code: 'ArrowDown',
|
|
1200
|
+
key: 'ArrowDown'
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
code: 'ArrowLeft',
|
|
1204
|
+
key: 'ArrowLeft'
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
code: 'ArrowRight',
|
|
1208
|
+
key: 'ArrowRight'
|
|
1209
|
+
},
|
|
1210
|
+
// control pad
|
|
1211
|
+
{
|
|
1212
|
+
code: 'Home',
|
|
1213
|
+
key: 'Home'
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
code: 'End',
|
|
1217
|
+
key: 'End'
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
code: 'Delete',
|
|
1221
|
+
key: 'Delete'
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
code: 'PageUp',
|
|
1225
|
+
key: 'PageUp'
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
code: 'PageDown',
|
|
1229
|
+
key: 'PageDown'
|
|
1230
|
+
},
|
|
1231
|
+
// Special keys that are not part of a default US-layout but included for specific behavior
|
|
1232
|
+
{
|
|
1233
|
+
code: 'Fn',
|
|
1234
|
+
key: 'Fn'
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
code: 'Symbol',
|
|
1238
|
+
key: 'Symbol'
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
code: 'AltRight',
|
|
1242
|
+
key: 'AltGraph'
|
|
1243
|
+
}
|
|
1244
|
+
];
|
|
1245
|
+
|
|
1246
|
+
const keyboard = async (context, text) => {
|
|
1247
|
+
function focusIframe() {
|
|
1248
|
+
if (!document.activeElement || document.activeElement.ownerDocument !== document || document.activeElement === document.body) {
|
|
1249
|
+
window.focus();
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1253
|
+
await context.frame.evaluate(focusIframe);
|
|
1254
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1255
|
+
await context.browser.execute(focusIframe);
|
|
1256
|
+
}
|
|
1257
|
+
await keyboardImplementation(
|
|
1258
|
+
context.provider,
|
|
1259
|
+
context.contextId,
|
|
1260
|
+
text,
|
|
1261
|
+
async () => {
|
|
1262
|
+
function selectAll() {
|
|
1263
|
+
const element = document.activeElement;
|
|
1264
|
+
if (element && element.select) {
|
|
1265
|
+
element.select();
|
|
304
1266
|
}
|
|
305
|
-
|
|
1267
|
+
}
|
|
1268
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1269
|
+
await context.frame.evaluate(selectAll);
|
|
1270
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1271
|
+
await context.browser.execute(selectAll);
|
|
306
1272
|
} else {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1273
|
+
throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
false
|
|
1277
|
+
);
|
|
1278
|
+
};
|
|
1279
|
+
async function keyboardImplementation(provider, contextId, text, selectAll, skipRelease) {
|
|
1280
|
+
const pressed = /* @__PURE__ */ new Set();
|
|
1281
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1282
|
+
const page = provider.getPage(contextId);
|
|
1283
|
+
const actions = parseKeyDef(defaultKeyMap, text);
|
|
1284
|
+
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
|
|
1285
|
+
const key = keyDef.key;
|
|
1286
|
+
if (pressed.has(key)) {
|
|
1287
|
+
await page.keyboard.up(key);
|
|
1288
|
+
pressed.delete(key);
|
|
1289
|
+
}
|
|
1290
|
+
if (!releasePrevious) {
|
|
1291
|
+
if (key === "selectall") {
|
|
1292
|
+
await selectAll();
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
for (let i = 1; i <= repeat; i++) {
|
|
1296
|
+
await page.keyboard.down(key);
|
|
1297
|
+
}
|
|
1298
|
+
if (releaseSelf) {
|
|
1299
|
+
await page.keyboard.up(key);
|
|
317
1300
|
} else {
|
|
318
|
-
|
|
319
|
-
const local = spec.local.name;
|
|
320
|
-
const binding = idToImportMap.get(local);
|
|
321
|
-
defineExport(node.end, spec.exported.name, binding || local);
|
|
322
|
-
}
|
|
1301
|
+
pressed.add(key);
|
|
323
1302
|
}
|
|
324
1303
|
}
|
|
325
1304
|
}
|
|
326
|
-
if (
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
const { name } = node.declaration.id;
|
|
330
|
-
s.remove(
|
|
331
|
-
node.start,
|
|
332
|
-
node.start + 15
|
|
333
|
-
/* 'export default '.length */
|
|
334
|
-
);
|
|
335
|
-
s.append(
|
|
336
|
-
`
|
|
337
|
-
Object.defineProperty(${viInjectedKey}, "default", { enumerable: true, configurable: true, value: ${name} });`
|
|
338
|
-
);
|
|
339
|
-
} else {
|
|
340
|
-
s.update(
|
|
341
|
-
node.start,
|
|
342
|
-
node.start + 14,
|
|
343
|
-
`${viInjectedKey}.default =`
|
|
344
|
-
);
|
|
345
|
-
s.append(`
|
|
346
|
-
export default { ${viInjectedKey}: ${viInjectedKey}.default };
|
|
347
|
-
`);
|
|
1305
|
+
if (!skipRelease && pressed.size) {
|
|
1306
|
+
for (const key of pressed) {
|
|
1307
|
+
await page.keyboard.up(key);
|
|
348
1308
|
}
|
|
349
1309
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
1310
|
+
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1311
|
+
const { Key } = await import('webdriverio');
|
|
1312
|
+
const browser = provider.browser;
|
|
1313
|
+
const actions = parseKeyDef(defaultKeyMap, text);
|
|
1314
|
+
let keyboard2 = browser.action("key");
|
|
1315
|
+
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
|
|
1316
|
+
let key = keyDef.key;
|
|
1317
|
+
const code = "location" in keyDef ? keyDef.key : keyDef.code;
|
|
1318
|
+
const special = Key[code];
|
|
1319
|
+
if (special) {
|
|
1320
|
+
key = special;
|
|
1321
|
+
}
|
|
1322
|
+
if (pressed.has(key)) {
|
|
1323
|
+
keyboard2.up(key);
|
|
1324
|
+
pressed.delete(key);
|
|
1325
|
+
}
|
|
1326
|
+
if (!releasePrevious) {
|
|
1327
|
+
if (key === "selectall") {
|
|
1328
|
+
await keyboard2.perform();
|
|
1329
|
+
keyboard2 = browser.action("key");
|
|
1330
|
+
await selectAll();
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
for (let i = 1; i <= repeat; i++) {
|
|
1334
|
+
keyboard2.down(key);
|
|
1335
|
+
}
|
|
1336
|
+
if (releaseSelf) {
|
|
1337
|
+
keyboard2.up(key);
|
|
1338
|
+
} else {
|
|
1339
|
+
pressed.add(key);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
358
1342
|
}
|
|
1343
|
+
await keyboard2.perform(skipRelease);
|
|
359
1344
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
1345
|
+
return {
|
|
1346
|
+
pressed
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const type = async (context, xpath, text, options = {}) => {
|
|
1351
|
+
const { skipClick = false, skipAutoClose = false } = options;
|
|
1352
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1353
|
+
const { frame } = context;
|
|
1354
|
+
const element = frame.locator(`xpath=${xpath}`);
|
|
1355
|
+
if (!skipClick) {
|
|
1356
|
+
await element.focus();
|
|
1357
|
+
}
|
|
1358
|
+
await keyboardImplementation(
|
|
1359
|
+
context.provider,
|
|
1360
|
+
context.contextId,
|
|
1361
|
+
text,
|
|
1362
|
+
() => element.selectText(),
|
|
1363
|
+
skipAutoClose
|
|
1364
|
+
);
|
|
1365
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1366
|
+
const browser = context.browser;
|
|
1367
|
+
const markedXpath = `//${xpath}`;
|
|
1368
|
+
const element = browser.$(markedXpath);
|
|
1369
|
+
if (!skipClick && !await element.isFocused()) {
|
|
1370
|
+
await element.click();
|
|
1371
|
+
}
|
|
1372
|
+
await keyboardImplementation(
|
|
1373
|
+
context.provider,
|
|
1374
|
+
context.contextId,
|
|
1375
|
+
text,
|
|
1376
|
+
() => browser.execute(() => {
|
|
1377
|
+
const element2 = document.activeElement;
|
|
1378
|
+
if (element2) {
|
|
1379
|
+
element2.select();
|
|
373
1380
|
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
1381
|
+
}),
|
|
1382
|
+
skipAutoClose
|
|
1383
|
+
);
|
|
1384
|
+
} else {
|
|
1385
|
+
throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
const clear = async (context, xpath) => {
|
|
1390
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1391
|
+
const { frame } = context;
|
|
1392
|
+
const element = frame.locator(`xpath=${xpath}`);
|
|
1393
|
+
await element.clear({
|
|
1394
|
+
timeout: 1e3
|
|
1395
|
+
});
|
|
1396
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1397
|
+
const browser = context.browser;
|
|
1398
|
+
const markedXpath = `//${xpath}`;
|
|
1399
|
+
const element = await browser.$(markedXpath);
|
|
1400
|
+
await element.clearValue();
|
|
1401
|
+
} else {
|
|
1402
|
+
throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
const fill = async (context, xpath, text, options = {}) => {
|
|
1407
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1408
|
+
const { frame } = context;
|
|
1409
|
+
const element = frame.locator(`xpath=${xpath}`);
|
|
1410
|
+
await element.fill(text, { timeout: 1e3, ...options });
|
|
1411
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1412
|
+
const browser = context.browser;
|
|
1413
|
+
const markedXpath = `//${xpath}`;
|
|
1414
|
+
await browser.$(markedXpath).setValue(text);
|
|
1415
|
+
} else {
|
|
1416
|
+
throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
const selectOptions = async (context, xpath, userValues, options = {}) => {
|
|
1421
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1422
|
+
const value = userValues;
|
|
1423
|
+
const { frame } = context;
|
|
1424
|
+
const selectElement = frame.locator(`xpath=${xpath}`);
|
|
1425
|
+
const values = await Promise.all(value.map(async (v) => {
|
|
1426
|
+
if (typeof v === "string") {
|
|
1427
|
+
return v;
|
|
1428
|
+
}
|
|
1429
|
+
const elementHandler = await frame.locator(`xpath=${v.element}`).elementHandle();
|
|
1430
|
+
if (!elementHandler) {
|
|
1431
|
+
throw new Error(`Element not found: ${v.element}`);
|
|
1432
|
+
}
|
|
1433
|
+
return elementHandler;
|
|
1434
|
+
}));
|
|
1435
|
+
await selectElement.selectOption(values, {
|
|
1436
|
+
timeout: 1e3,
|
|
1437
|
+
...options
|
|
1438
|
+
});
|
|
1439
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1440
|
+
const values = userValues;
|
|
1441
|
+
if (!values.length) {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const markedXpath = `//${xpath}`;
|
|
1445
|
+
const browser = context.browser;
|
|
1446
|
+
if (values.length === 1 && "index" in values[0]) {
|
|
1447
|
+
const selectElement = browser.$(markedXpath);
|
|
1448
|
+
await selectElement.selectByIndex(values[0].index);
|
|
1449
|
+
} else {
|
|
1450
|
+
throw new Error(`Provider "webdriverio" doesn't support selecting multiple values at once`);
|
|
1451
|
+
}
|
|
1452
|
+
} else {
|
|
1453
|
+
throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
const tab = async (context, options = {}) => {
|
|
1458
|
+
const provider = context.provider;
|
|
1459
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1460
|
+
const page = context.page;
|
|
1461
|
+
await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (provider instanceof WebdriverBrowserProvider) {
|
|
1465
|
+
const { Key } = await import('webdriverio');
|
|
1466
|
+
const browser = context.browser;
|
|
1467
|
+
await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
throw new Error(`Provider "${provider.name}" doesn't support tab command`);
|
|
1471
|
+
};
|
|
1472
|
+
|
|
1473
|
+
const dragAndDrop = async (context, source, target, options) => {
|
|
1474
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1475
|
+
await context.frame.dragAndDrop(
|
|
1476
|
+
`xpath=${source}`,
|
|
1477
|
+
`xpath=${target}`,
|
|
1478
|
+
{
|
|
1479
|
+
timeout: 1e3,
|
|
1480
|
+
...options
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1484
|
+
const sourceXpath = `//${source}`;
|
|
1485
|
+
const targetXpath = `//${target}`;
|
|
1486
|
+
const $source = context.browser.$(sourceXpath);
|
|
1487
|
+
const $target = context.browser.$(targetXpath);
|
|
1488
|
+
const duration = options?.duration ?? 10;
|
|
1489
|
+
await context.browser.action("pointer").move({ duration: 0, origin: $source, x: 0, y: 0 }).down({ button: 0 }).move({ duration: 0, origin: "pointer", x: 0, y: 0 }).pause(duration).move({ duration: 0, origin: $target, x: 0, y: 0 }).move({ duration: 0, origin: "pointer", x: 1, y: 0 }).move({ duration: 0, origin: "pointer", x: -1, y: 0 }).up({ button: 0 }).perform();
|
|
1490
|
+
} else {
|
|
1491
|
+
throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
const hover = async (context, xpath, options = {}) => {
|
|
1496
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1497
|
+
await context.frame.locator(`xpath=${xpath}`).hover({
|
|
1498
|
+
timeout: 1e3,
|
|
1499
|
+
...options
|
|
1500
|
+
});
|
|
1501
|
+
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1502
|
+
const browser = context.browser;
|
|
1503
|
+
const markedXpath = `//${xpath}`;
|
|
1504
|
+
await browser.$(markedXpath).moveTo(options);
|
|
1505
|
+
} else {
|
|
1506
|
+
throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
function assertFileAccess(path, project) {
|
|
1511
|
+
if (!isFileServingAllowed$1(path, project.server) && !isFileServingAllowed$1(path, project.ctx.server)) {
|
|
1512
|
+
throw new Error(
|
|
1513
|
+
`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => {
|
|
1518
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
1519
|
+
assertFileAccess(filepath, project);
|
|
1520
|
+
if (typeof options === "object" && !options.encoding) {
|
|
1521
|
+
options.encoding = "utf-8";
|
|
1522
|
+
}
|
|
1523
|
+
return promises.readFile(filepath, options);
|
|
1524
|
+
};
|
|
1525
|
+
const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => {
|
|
1526
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
1527
|
+
assertFileAccess(filepath, project);
|
|
1528
|
+
const dir = dirname$1(filepath);
|
|
1529
|
+
if (!fs.existsSync(dir)) {
|
|
1530
|
+
await promises.mkdir(dir, { recursive: true });
|
|
1531
|
+
}
|
|
1532
|
+
await promises.writeFile(filepath, data, options);
|
|
1533
|
+
};
|
|
1534
|
+
const removeFile = async ({ project, testPath = process.cwd() }, path) => {
|
|
1535
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
1536
|
+
assertFileAccess(filepath, project);
|
|
1537
|
+
await promises.rm(filepath);
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
const screenshot = async (context, name, options = {}) => {
|
|
1541
|
+
if (!context.testPath) {
|
|
1542
|
+
throw new Error(`Cannot take a screenshot without a test path`);
|
|
1543
|
+
}
|
|
1544
|
+
const path = resolveScreenshotPath(
|
|
1545
|
+
context.testPath,
|
|
1546
|
+
name,
|
|
1547
|
+
context.project.config
|
|
1548
|
+
);
|
|
1549
|
+
const savePath = normalize$1(path);
|
|
1550
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1551
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1552
|
+
if (options.element) {
|
|
1553
|
+
const { element: elementXpath, ...config } = options;
|
|
1554
|
+
const iframe = context.frame;
|
|
1555
|
+
const element = iframe.locator(`xpath=${elementXpath}`);
|
|
1556
|
+
await element.screenshot({ ...config, path: savePath });
|
|
1557
|
+
} else {
|
|
1558
|
+
await context.frame.locator("body").screenshot({ ...options, path: savePath });
|
|
1559
|
+
}
|
|
1560
|
+
return path;
|
|
1561
|
+
}
|
|
1562
|
+
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1563
|
+
const page = context.provider.browser;
|
|
1564
|
+
if (!options.element) {
|
|
1565
|
+
const body = await page.$("body");
|
|
1566
|
+
await body.saveScreenshot(savePath);
|
|
1567
|
+
return path;
|
|
1568
|
+
}
|
|
1569
|
+
const xpath = `//${options.element}`;
|
|
1570
|
+
const element = await page.$(xpath);
|
|
1571
|
+
await element.saveScreenshot(savePath);
|
|
1572
|
+
return path;
|
|
1573
|
+
}
|
|
1574
|
+
throw new Error(
|
|
1575
|
+
`Provider "${context.provider.name}" does not support screenshots`
|
|
1576
|
+
);
|
|
1577
|
+
};
|
|
1578
|
+
function resolveScreenshotPath(testPath, name, config) {
|
|
1579
|
+
const dir = dirname(testPath);
|
|
1580
|
+
const base = basename(testPath);
|
|
1581
|
+
if (config.browser.screenshotDirectory) {
|
|
1582
|
+
return resolve(
|
|
1583
|
+
config.browser.screenshotDirectory,
|
|
1584
|
+
relative(config.root, dir),
|
|
1585
|
+
base,
|
|
1586
|
+
name
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
return resolve(dir, "__screenshots__", base, name);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
var builtinCommands = {
|
|
1593
|
+
readFile,
|
|
1594
|
+
removeFile,
|
|
1595
|
+
writeFile,
|
|
1596
|
+
__vitest_click: click,
|
|
1597
|
+
__vitest_dblClick: dblClick,
|
|
1598
|
+
__vitest_screenshot: screenshot,
|
|
1599
|
+
__vitest_type: type,
|
|
1600
|
+
__vitest_clear: clear,
|
|
1601
|
+
__vitest_fill: fill,
|
|
1602
|
+
__vitest_tab: tab,
|
|
1603
|
+
__vitest_keyboard: keyboard,
|
|
1604
|
+
__vitest_selectOptions: selectOptions,
|
|
1605
|
+
__vitest_dragAndDrop: dragAndDrop,
|
|
1606
|
+
__vitest_hover: hover
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
|
|
1610
|
+
const ID_CONTEXT = "@vitest/browser/context";
|
|
1611
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1612
|
+
function BrowserContext(server) {
|
|
1613
|
+
const project = server.project;
|
|
1614
|
+
project.config.browser.commands ??= {};
|
|
1615
|
+
for (const [name, command] of Object.entries(builtinCommands)) {
|
|
1616
|
+
project.config.browser.commands[name] ??= command;
|
|
1617
|
+
}
|
|
1618
|
+
for (const command in project.config.browser.commands) {
|
|
1619
|
+
if (!/^[a-z_$][\w$]*$/i.test(command)) {
|
|
1620
|
+
throw new Error(
|
|
1621
|
+
`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
name: "vitest:browser:virtual-module:context",
|
|
1627
|
+
enforce: "pre",
|
|
1628
|
+
resolveId(id) {
|
|
1629
|
+
if (id === ID_CONTEXT) {
|
|
1630
|
+
return VIRTUAL_ID_CONTEXT;
|
|
379
1631
|
}
|
|
380
1632
|
},
|
|
1633
|
+
load(id) {
|
|
1634
|
+
if (id === VIRTUAL_ID_CONTEXT) {
|
|
1635
|
+
return generateContextFile.call(this, server);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
async function generateContextFile(server) {
|
|
1641
|
+
const commands = Object.keys(server.project.config.browser.commands ?? {});
|
|
1642
|
+
const filepathCode = "__vitest_worker__.filepath || __vitest_worker__.current?.file?.filepath || undefined";
|
|
1643
|
+
const provider = server.provider;
|
|
1644
|
+
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
1645
|
+
return ` ["${command}"]: (...args) => rpc().triggerCommand(contextId, "${command}", filepath(), args),`;
|
|
1646
|
+
}).join("\n");
|
|
1647
|
+
const userEventNonProviderImport = await getUserEventImport(
|
|
1648
|
+
provider,
|
|
1649
|
+
this.resolve.bind(this)
|
|
1650
|
+
);
|
|
1651
|
+
const distContextPath = slash(`/@fs/${resolve(__dirname, "context.js")}`);
|
|
1652
|
+
return `
|
|
1653
|
+
import { page, userEvent as __userEvent_CDP__ } from '${distContextPath}'
|
|
1654
|
+
${userEventNonProviderImport}
|
|
1655
|
+
const filepath = () => ${filepathCode}
|
|
1656
|
+
const rpc = () => __vitest_worker__.rpc
|
|
1657
|
+
const contextId = __vitest_browser_runner__.contextId
|
|
1658
|
+
|
|
1659
|
+
export const server = {
|
|
1660
|
+
platform: ${JSON.stringify(process.platform)},
|
|
1661
|
+
version: ${JSON.stringify(process.version)},
|
|
1662
|
+
provider: ${JSON.stringify(provider.name)},
|
|
1663
|
+
browser: ${JSON.stringify(server.project.config.browser.name)},
|
|
1664
|
+
commands: {
|
|
1665
|
+
${commandsCode}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
export const commands = server.commands
|
|
1669
|
+
export const userEvent = ${getUserEvent(provider)}
|
|
1670
|
+
export { page }
|
|
1671
|
+
`;
|
|
1672
|
+
}
|
|
1673
|
+
function getUserEvent(provider) {
|
|
1674
|
+
if (provider.name !== "preview") {
|
|
1675
|
+
return "__userEvent_CDP__";
|
|
1676
|
+
}
|
|
1677
|
+
return `{
|
|
1678
|
+
...__vitest_user_event__,
|
|
1679
|
+
fill: async (element, text) => {
|
|
1680
|
+
await __vitest_user_event__.clear(element)
|
|
1681
|
+
await __vitest_user_event__.type(element, text)
|
|
1682
|
+
},
|
|
1683
|
+
dragAndDrop: async () => {
|
|
1684
|
+
throw new Error('Provider "preview" does not support dragging elements')
|
|
1685
|
+
}
|
|
1686
|
+
}`;
|
|
1687
|
+
}
|
|
1688
|
+
async function getUserEventImport(provider, resolve2) {
|
|
1689
|
+
if (provider.name !== "preview") {
|
|
1690
|
+
return "";
|
|
1691
|
+
}
|
|
1692
|
+
const resolved = await resolve2("@testing-library/user-event", __dirname);
|
|
1693
|
+
if (!resolved) {
|
|
1694
|
+
throw new Error(`Failed to resolve user-event package from ${__dirname}`);
|
|
1695
|
+
}
|
|
1696
|
+
return `import { userEvent as __vitest_user_event__ } from '${slash(
|
|
1697
|
+
`/@fs/${resolved.id}`
|
|
1698
|
+
)}'`;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function injectDynamicImport(code, id, parse) {
|
|
1702
|
+
const s = new MagicString(code);
|
|
1703
|
+
let ast;
|
|
1704
|
+
try {
|
|
1705
|
+
ast = parse(code);
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
console.error(`Cannot parse ${id}:
|
|
1708
|
+
${err.message}`);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
esmWalker(ast, {
|
|
381
1712
|
// TODO: make env updatable
|
|
382
1713
|
onImportMeta() {
|
|
383
1714
|
},
|
|
384
1715
|
onDynamicImport(node) {
|
|
385
|
-
const replace = "__vitest_browser_runner__.wrapModule(import(";
|
|
386
|
-
s.overwrite(
|
|
1716
|
+
const replace = "__vitest_browser_runner__.wrapModule(() => import(";
|
|
1717
|
+
s.overwrite(
|
|
1718
|
+
node.start,
|
|
1719
|
+
node.source.start,
|
|
1720
|
+
replace
|
|
1721
|
+
);
|
|
387
1722
|
s.overwrite(node.end - 1, node.end, "))");
|
|
388
1723
|
}
|
|
389
1724
|
});
|
|
390
|
-
s.prepend(`const ${viInjectedKey} = { [Symbol.toStringTag]: "Module" };
|
|
391
|
-
`);
|
|
392
|
-
s.append(`
|
|
393
|
-
export { ${viInjectedKey} }`);
|
|
394
1725
|
return {
|
|
395
1726
|
ast,
|
|
396
1727
|
code: s.toString(),
|
|
@@ -398,127 +1729,212 @@ export { ${viInjectedKey} }`);
|
|
|
398
1729
|
};
|
|
399
1730
|
}
|
|
400
1731
|
|
|
401
|
-
|
|
1732
|
+
const regexDynamicImport = /import\s*\(/;
|
|
1733
|
+
var DynamicImport = () => {
|
|
1734
|
+
return {
|
|
1735
|
+
name: "vitest:browser:esm-injector",
|
|
1736
|
+
enforce: "post",
|
|
1737
|
+
transform(source, id) {
|
|
1738
|
+
if (!regexDynamicImport.test(source)) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
return injectDynamicImport(source, id, this.parse);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
async function resolveOrchestrator(server, url, res) {
|
|
1747
|
+
const project = server.project;
|
|
1748
|
+
let contextId = url.searchParams.get("contextId");
|
|
1749
|
+
if (!contextId) {
|
|
1750
|
+
const contexts = [...server.state.orchestrators.keys()];
|
|
1751
|
+
contextId = contexts[contexts.length - 1] ?? "none";
|
|
1752
|
+
}
|
|
1753
|
+
const files = server.state.getContext(contextId)?.files ?? [];
|
|
1754
|
+
const config = server.getSerializableConfig();
|
|
1755
|
+
const injectorJs = typeof server.injectorJs === "string" ? server.injectorJs : await server.injectorJs;
|
|
1756
|
+
const injector = replacer(injectorJs, {
|
|
1757
|
+
__VITEST_PROVIDER__: JSON.stringify(server.provider.name),
|
|
1758
|
+
__VITEST_CONFIG__: JSON.stringify(config),
|
|
1759
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({
|
|
1760
|
+
root: server.vite.config.root
|
|
1761
|
+
}),
|
|
1762
|
+
__VITEST_FILES__: JSON.stringify(files),
|
|
1763
|
+
__VITEST_TYPE__: '"orchestrator"',
|
|
1764
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId),
|
|
1765
|
+
__VITEST_PROVIDED_CONTEXT__: "{}"
|
|
1766
|
+
});
|
|
1767
|
+
res.removeHeader("Content-Security-Policy");
|
|
1768
|
+
if (!server.orchestratorScripts) {
|
|
1769
|
+
server.orchestratorScripts = await server.formatScripts(
|
|
1770
|
+
project.config.browser.orchestratorScripts
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
let baseHtml = typeof server.orchestratorHtml === "string" ? server.orchestratorHtml : await server.orchestratorHtml;
|
|
1774
|
+
if (project.config.browser.ui) {
|
|
1775
|
+
const manifestContent = server.manifest instanceof Promise ? await server.manifest : server.manifest;
|
|
1776
|
+
const jsEntry = manifestContent["orchestrator.html"].file;
|
|
1777
|
+
const base = server.vite.config.base || "/";
|
|
1778
|
+
baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace(
|
|
1779
|
+
"<!-- !LOAD_METADATA! -->",
|
|
1780
|
+
[
|
|
1781
|
+
"<script>{__VITEST_INJECTOR__}<\/script>",
|
|
1782
|
+
"{__VITEST_SCRIPTS__}",
|
|
1783
|
+
`<script type="module" crossorigin src="${base}${jsEntry}"><\/script>`
|
|
1784
|
+
].join("\n")
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
return replacer(baseHtml, {
|
|
1788
|
+
__VITEST_FAVICON__: server.faviconUrl,
|
|
1789
|
+
__VITEST_TITLE__: "Vitest Browser Runner",
|
|
1790
|
+
__VITEST_SCRIPTS__: server.orchestratorScripts,
|
|
1791
|
+
__VITEST_INJECTOR__: injector,
|
|
1792
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId)
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
async function resolveTester(server, url, res) {
|
|
1797
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
1798
|
+
if (typeof csp === "string") {
|
|
1799
|
+
res.setHeader(
|
|
1800
|
+
"Content-Security-Policy",
|
|
1801
|
+
csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *")
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
const { contextId, testFile } = server.resolveTesterUrl(url.pathname);
|
|
1805
|
+
const project = server.project;
|
|
1806
|
+
const state = server.state;
|
|
1807
|
+
const testFiles = await project.globTestFiles();
|
|
1808
|
+
const tests = testFile === "__vitest_all__" || !testFiles.includes(testFile) ? "__vitest_browser_runner__.files" : JSON.stringify([testFile]);
|
|
1809
|
+
const iframeId = JSON.stringify(testFile);
|
|
1810
|
+
const files = state.getContext(contextId)?.files ?? [];
|
|
1811
|
+
const injectorJs = typeof server.injectorJs === "string" ? server.injectorJs : await server.injectorJs;
|
|
1812
|
+
const config = server.getSerializableConfig();
|
|
1813
|
+
const injector = replacer(injectorJs, {
|
|
1814
|
+
__VITEST_PROVIDER__: JSON.stringify(server.provider.name),
|
|
1815
|
+
__VITEST_CONFIG__: JSON.stringify(config),
|
|
1816
|
+
__VITEST_FILES__: JSON.stringify(files),
|
|
1817
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({
|
|
1818
|
+
root: server.vite.config.root
|
|
1819
|
+
}),
|
|
1820
|
+
__VITEST_TYPE__: '"tester"',
|
|
1821
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId),
|
|
1822
|
+
__VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(project.getProvidedContext()))
|
|
1823
|
+
});
|
|
1824
|
+
if (!server.testerScripts) {
|
|
1825
|
+
const testerScripts = await server.formatScripts(
|
|
1826
|
+
project.config.browser.testerScripts
|
|
1827
|
+
);
|
|
1828
|
+
const clientScript = `<script type="module" src="${config.base || "/"}@vite/client"><\/script>`;
|
|
1829
|
+
const stateJs = typeof server.stateJs === "string" ? server.stateJs : await server.stateJs;
|
|
1830
|
+
const stateScript = `<script type="module">${stateJs}<\/script>`;
|
|
1831
|
+
server.testerScripts = `${stateScript}${clientScript}${testerScripts}`;
|
|
1832
|
+
}
|
|
1833
|
+
const testerHtml = typeof server.testerHtml === "string" ? server.testerHtml : await server.testerHtml;
|
|
1834
|
+
return replacer(testerHtml, {
|
|
1835
|
+
__VITEST_FAVICON__: server.faviconUrl,
|
|
1836
|
+
__VITEST_TITLE__: "Vitest Browser Tester",
|
|
1837
|
+
__VITEST_SCRIPTS__: server.testerScripts,
|
|
1838
|
+
__VITEST_INJECTOR__: injector,
|
|
1839
|
+
__VITEST_APPEND__: `<script type="module">
|
|
1840
|
+
__vitest_browser_runner__.runningFiles = ${tests}
|
|
1841
|
+
__vitest_browser_runner__.iframeId = ${iframeId}
|
|
1842
|
+
__vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
|
|
1843
|
+
<\/script>`
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
var BrowserPlugin = (browserServer, base = "/") => {
|
|
402
1848
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
403
1849
|
const distRoot = resolve(pkgRoot, "dist");
|
|
1850
|
+
const project = browserServer.project;
|
|
404
1851
|
return [
|
|
405
1852
|
{
|
|
406
1853
|
enforce: "pre",
|
|
407
1854
|
name: "vitest:browser",
|
|
408
|
-
async config(viteConfig) {
|
|
409
|
-
// Enables using ignore hint for coverage providers with @preserve keyword
|
|
410
|
-
if (viteConfig.esbuild !== false) {
|
|
411
|
-
viteConfig.esbuild ||= {};
|
|
412
|
-
viteConfig.esbuild.legalComments = "inline";
|
|
413
|
-
}
|
|
414
|
-
},
|
|
415
1855
|
async configureServer(server) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const injectorJs = readFile(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
|
|
419
|
-
const favicon = `${base}favicon.svg`;
|
|
420
|
-
const testerPrefix = `${base}__vitest_test__/__test__/`;
|
|
421
|
-
server.middlewares.use((_req, res, next) => {
|
|
1856
|
+
browserServer.setServer(server);
|
|
1857
|
+
server.middlewares.use(function vitestHeaders(_req, res, next) {
|
|
422
1858
|
const headers = server.config.server.headers;
|
|
423
1859
|
if (headers) {
|
|
424
|
-
for (const name in headers)
|
|
1860
|
+
for (const name in headers) {
|
|
425
1861
|
res.setHeader(name, headers[name]);
|
|
1862
|
+
}
|
|
426
1863
|
}
|
|
427
1864
|
next();
|
|
428
1865
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
server.middlewares.use(async (req, res, next) => {
|
|
432
|
-
if (!req.url)
|
|
1866
|
+
server.middlewares.use(async function vitestBrowserMode(req, res, next) {
|
|
1867
|
+
if (!req.url) {
|
|
433
1868
|
return next();
|
|
1869
|
+
}
|
|
434
1870
|
const url = new URL(req.url, "http://localhost");
|
|
435
|
-
if (!url.pathname.startsWith(
|
|
1871
|
+
if (!url.pathname.startsWith(browserServer.prefixTesterUrl) && url.pathname !== base) {
|
|
436
1872
|
return next();
|
|
437
|
-
|
|
1873
|
+
}
|
|
1874
|
+
res.setHeader(
|
|
1875
|
+
"Cache-Control",
|
|
1876
|
+
"no-cache, max-age=0, must-revalidate"
|
|
1877
|
+
);
|
|
438
1878
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
439
|
-
|
|
440
|
-
const config = wrapConfig(project.getSerializableConfig());
|
|
441
|
-
config.env ??= {};
|
|
442
|
-
config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
|
|
443
|
-
const injector = replacer(await injectorJs, {
|
|
444
|
-
__VITEST_CONFIG__: JSON.stringify(config),
|
|
445
|
-
__VITEST_FILES__: JSON.stringify(files)
|
|
446
|
-
});
|
|
1879
|
+
res.removeHeader("X-Frame-Options");
|
|
447
1880
|
if (url.pathname === base) {
|
|
448
|
-
|
|
449
|
-
indexScripts = await formatScripts(project.config.browser.indexScripts, server);
|
|
450
|
-
const html2 = replacer(await runnerHtml, {
|
|
451
|
-
__VITEST_FAVICON__: favicon,
|
|
452
|
-
__VITEST_TITLE__: "Vitest Browser Runner",
|
|
453
|
-
__VITEST_SCRIPTS__: indexScripts,
|
|
454
|
-
__VITEST_INJECTOR__: injector
|
|
455
|
-
});
|
|
1881
|
+
const html2 = await resolveOrchestrator(browserServer, url, res);
|
|
456
1882
|
res.write(html2, "utf-8");
|
|
457
1883
|
res.end();
|
|
458
1884
|
return;
|
|
459
1885
|
}
|
|
460
|
-
const
|
|
461
|
-
const tests = decodedTestFile === "__vitest_all__" || !files.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
|
|
462
|
-
if (!testerScripts)
|
|
463
|
-
testerScripts = await formatScripts(project.config.browser.testerScripts, server);
|
|
464
|
-
const html = replacer(await testerHtml, {
|
|
465
|
-
__VITEST_FAVICON__: favicon,
|
|
466
|
-
__VITEST_TITLE__: "Vitest Browser Tester",
|
|
467
|
-
__VITEST_SCRIPTS__: testerScripts,
|
|
468
|
-
__VITEST_INJECTOR__: injector,
|
|
469
|
-
__VITEST_APPEND__: (
|
|
470
|
-
// TODO: have only a single global variable to not pollute the global scope
|
|
471
|
-
`<script type="module">
|
|
472
|
-
__vitest_browser_runner__.runningFiles = ${tests}
|
|
473
|
-
__vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
|
|
474
|
-
<\/script>`
|
|
475
|
-
)
|
|
476
|
-
});
|
|
1886
|
+
const html = await resolveTester(browserServer, url, res);
|
|
477
1887
|
res.write(html, "utf-8");
|
|
478
1888
|
res.end();
|
|
479
1889
|
});
|
|
480
1890
|
server.middlewares.use(
|
|
481
|
-
base
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
1891
|
+
`${base}favicon.svg`,
|
|
1892
|
+
(_, res) => {
|
|
1893
|
+
const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
|
|
1894
|
+
res.write(content, "utf-8");
|
|
1895
|
+
res.end();
|
|
1896
|
+
}
|
|
486
1897
|
);
|
|
487
1898
|
const coverageFolder = resolveCoverageFolder(project);
|
|
488
1899
|
const coveragePath = coverageFolder ? coverageFolder[1] : void 0;
|
|
489
|
-
if (coveragePath && base === coveragePath)
|
|
490
|
-
throw new Error(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1900
|
+
if (coveragePath && base === coveragePath) {
|
|
1901
|
+
throw new Error(
|
|
1902
|
+
`The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
coverageFolder && server.middlewares.use(
|
|
1906
|
+
coveragePath,
|
|
1907
|
+
sirv(coverageFolder[0], {
|
|
1908
|
+
single: true,
|
|
1909
|
+
dev: true,
|
|
1910
|
+
setHeaders: (res) => {
|
|
1911
|
+
res.setHeader(
|
|
1912
|
+
"Cache-Control",
|
|
1913
|
+
"public,max-age=0,must-revalidate"
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
})
|
|
1917
|
+
);
|
|
498
1918
|
}
|
|
499
1919
|
},
|
|
500
1920
|
{
|
|
501
1921
|
name: "vitest:browser:tests",
|
|
502
1922
|
enforce: "pre",
|
|
503
1923
|
async config() {
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
root
|
|
510
|
-
} = project.config;
|
|
511
|
-
const projectRoot = dir || root;
|
|
512
|
-
const entries = await project.globAllTestFiles(include, exclude, includeSource, projectRoot);
|
|
1924
|
+
const allTestFiles = await project.globTestFiles();
|
|
1925
|
+
const browserTestFiles = allTestFiles.filter(
|
|
1926
|
+
(file) => getFilePoolName(project, file) === "browser"
|
|
1927
|
+
);
|
|
1928
|
+
const setupFiles = toArray(project.config.setupFiles);
|
|
513
1929
|
return {
|
|
514
1930
|
optimizeDeps: {
|
|
515
1931
|
entries: [
|
|
516
|
-
...
|
|
517
|
-
|
|
518
|
-
"
|
|
519
|
-
"
|
|
520
|
-
"
|
|
521
|
-
"
|
|
1932
|
+
...browserTestFiles,
|
|
1933
|
+
...setupFiles,
|
|
1934
|
+
resolve(distDir, "index.js"),
|
|
1935
|
+
resolve(distDir, "browser.js"),
|
|
1936
|
+
resolve(distDir, "runners.js"),
|
|
1937
|
+
resolve(distDir, "utils.js")
|
|
522
1938
|
],
|
|
523
1939
|
exclude: [
|
|
524
1940
|
"vitest",
|
|
@@ -526,93 +1942,289 @@ var index = (project, base = "/") => {
|
|
|
526
1942
|
"vitest/browser",
|
|
527
1943
|
"vitest/runners",
|
|
528
1944
|
"@vitest/utils",
|
|
529
|
-
|
|
530
|
-
"
|
|
1945
|
+
"@vitest/utils/source-map",
|
|
1946
|
+
"@vitest/runner",
|
|
1947
|
+
"@vitest/spy",
|
|
1948
|
+
"@vitest/utils/error",
|
|
1949
|
+
"@vitest/snapshot",
|
|
1950
|
+
"@vitest/expect",
|
|
1951
|
+
"std-env",
|
|
1952
|
+
"tinybench",
|
|
1953
|
+
"tinyspy",
|
|
1954
|
+
"pathe",
|
|
1955
|
+
"msw",
|
|
1956
|
+
"msw/browser"
|
|
531
1957
|
],
|
|
532
1958
|
include: [
|
|
533
1959
|
"vitest > @vitest/utils > pretty-format",
|
|
534
1960
|
"vitest > @vitest/snapshot > pretty-format",
|
|
535
1961
|
"vitest > @vitest/snapshot > magic-string",
|
|
536
|
-
"vitest > diff-sequences",
|
|
537
1962
|
"vitest > pretty-format",
|
|
538
1963
|
"vitest > pretty-format > ansi-styles",
|
|
539
1964
|
"vitest > pretty-format > ansi-regex",
|
|
540
|
-
"vitest > chai"
|
|
1965
|
+
"vitest > chai",
|
|
1966
|
+
"vitest > chai > loupe",
|
|
1967
|
+
"vitest > @vitest/runner > p-limit",
|
|
1968
|
+
"vitest > @vitest/utils > diff-sequences",
|
|
1969
|
+
"@vitest/browser > @testing-library/user-event",
|
|
1970
|
+
"@vitest/browser > @testing-library/dom"
|
|
541
1971
|
]
|
|
542
1972
|
}
|
|
543
1973
|
};
|
|
544
1974
|
},
|
|
545
|
-
transform(code, id) {
|
|
546
|
-
if (id.includes("loupe/loupe.js")) {
|
|
547
|
-
const exportsList = ["custom", "inspect", "registerConstructor", "registerStringTag"];
|
|
548
|
-
const codeAppend = exportsList.map((i) => `export const ${i} = globalThis.loupe.${i}`).join("\n");
|
|
549
|
-
return `${code}
|
|
550
|
-
${codeAppend}
|
|
551
|
-
export default globalThis.loupe`;
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
1975
|
async resolveId(id) {
|
|
555
|
-
if (!/\?browserv=\w+$/.test(id))
|
|
1976
|
+
if (!/\?browserv=\w+$/.test(id)) {
|
|
556
1977
|
return;
|
|
1978
|
+
}
|
|
557
1979
|
let useId = id.slice(0, id.lastIndexOf("?"));
|
|
558
|
-
if (useId.startsWith("/@fs/"))
|
|
1980
|
+
if (useId.startsWith("/@fs/")) {
|
|
559
1981
|
useId = useId.slice(5);
|
|
560
|
-
|
|
1982
|
+
}
|
|
1983
|
+
if (/^\w:/.test(useId)) {
|
|
561
1984
|
useId = useId.replace(/\\/g, "/");
|
|
1985
|
+
}
|
|
562
1986
|
return useId;
|
|
563
1987
|
}
|
|
564
1988
|
},
|
|
565
1989
|
{
|
|
566
|
-
name: "vitest:browser:
|
|
1990
|
+
name: "vitest:browser:resolve-virtual",
|
|
1991
|
+
async resolveId(rawId) {
|
|
1992
|
+
if (rawId.startsWith("/__virtual_vitest__")) {
|
|
1993
|
+
const url = new URL(rawId, "http://localhost");
|
|
1994
|
+
if (!url.searchParams.has("id")) {
|
|
1995
|
+
throw new TypeError(`Invalid virtual module id: ${rawId}, requires "id" query.`);
|
|
1996
|
+
}
|
|
1997
|
+
const id = decodeURIComponent(url.searchParams.get("id"));
|
|
1998
|
+
const resolved = await this.resolve(id, distRoot, {
|
|
1999
|
+
skipSelf: true
|
|
2000
|
+
});
|
|
2001
|
+
return resolved;
|
|
2002
|
+
}
|
|
2003
|
+
if (rawId === "/__vitest_msw__") {
|
|
2004
|
+
return this.resolve("msw/mockServiceWorker.js", distRoot, {
|
|
2005
|
+
skipSelf: true
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
name: "vitest:browser:assets",
|
|
2012
|
+
resolveId(id) {
|
|
2013
|
+
if (id.startsWith("/__vitest_browser__/") || id.startsWith("/__vitest__/")) {
|
|
2014
|
+
return resolve(distRoot, "client", id.slice(1));
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
},
|
|
2018
|
+
BrowserContext(browserServer),
|
|
2019
|
+
DynamicImport(),
|
|
2020
|
+
{
|
|
2021
|
+
name: "vitest:browser:config",
|
|
567
2022
|
enforce: "post",
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (
|
|
571
|
-
|
|
572
|
-
|
|
2023
|
+
async config(viteConfig) {
|
|
2024
|
+
// Enables using ignore hint for coverage providers with @preserve keyword
|
|
2025
|
+
if (viteConfig.esbuild !== false) {
|
|
2026
|
+
viteConfig.esbuild ||= {};
|
|
2027
|
+
viteConfig.esbuild.legalComments = "inline";
|
|
2028
|
+
}
|
|
2029
|
+
const server = resolveApiServerConfig(
|
|
2030
|
+
viteConfig.test?.browser || {},
|
|
2031
|
+
defaultBrowserPort
|
|
2032
|
+
) || {
|
|
2033
|
+
port: defaultBrowserPort
|
|
2034
|
+
};
|
|
2035
|
+
server.middlewareMode = false;
|
|
2036
|
+
viteConfig.server = {
|
|
2037
|
+
...viteConfig.server,
|
|
2038
|
+
...server,
|
|
2039
|
+
open: false
|
|
2040
|
+
};
|
|
2041
|
+
viteConfig.server.fs ??= {};
|
|
2042
|
+
viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
|
|
2043
|
+
viteConfig.server.fs.allow.push(
|
|
2044
|
+
...resolveFsAllow(
|
|
2045
|
+
project.ctx.config.root,
|
|
2046
|
+
project.ctx.server.config.configFile
|
|
2047
|
+
)
|
|
2048
|
+
);
|
|
2049
|
+
return {
|
|
2050
|
+
resolve: {
|
|
2051
|
+
alias: viteConfig.test?.alias
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
},
|
|
2056
|
+
// TODO: remove this when @testing-library/vue supports ESM
|
|
2057
|
+
{
|
|
2058
|
+
name: "vitest:browser:support-vue-testing-library",
|
|
2059
|
+
config() {
|
|
2060
|
+
return {
|
|
2061
|
+
optimizeDeps: {
|
|
2062
|
+
esbuildOptions: {
|
|
2063
|
+
plugins: [
|
|
2064
|
+
{
|
|
2065
|
+
name: "test-utils-rewrite",
|
|
2066
|
+
setup(build) {
|
|
2067
|
+
const _require = createRequire(import.meta.url);
|
|
2068
|
+
build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
|
|
2069
|
+
const resolved = _require.resolve(args.path, {
|
|
2070
|
+
paths: [args.importer]
|
|
2071
|
+
});
|
|
2072
|
+
return { path: resolved };
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
]
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
573
2080
|
}
|
|
574
2081
|
}
|
|
575
2082
|
];
|
|
576
2083
|
};
|
|
577
2084
|
function resolveCoverageFolder(project) {
|
|
578
2085
|
const options = project.ctx.config;
|
|
579
|
-
const htmlReporter = options.coverage?.enabled ? options.coverage.reporter.find((reporter) => {
|
|
580
|
-
if (typeof reporter === "string")
|
|
2086
|
+
const htmlReporter = options.coverage?.enabled ? toArray(options.coverage.reporter).find((reporter) => {
|
|
2087
|
+
if (typeof reporter === "string") {
|
|
581
2088
|
return reporter === "html";
|
|
2089
|
+
}
|
|
582
2090
|
return reporter[0] === "html";
|
|
583
2091
|
}) : void 0;
|
|
584
|
-
if (!htmlReporter)
|
|
2092
|
+
if (!htmlReporter) {
|
|
585
2093
|
return void 0;
|
|
2094
|
+
}
|
|
586
2095
|
const root = resolve(
|
|
587
|
-
options.root ||
|
|
2096
|
+
options.root || process.cwd(),
|
|
588
2097
|
options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory
|
|
589
2098
|
);
|
|
590
2099
|
const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : void 0;
|
|
591
|
-
if (!subdir || typeof subdir !== "string")
|
|
2100
|
+
if (!subdir || typeof subdir !== "string") {
|
|
592
2101
|
return [root, `/${basename(root)}/`];
|
|
2102
|
+
}
|
|
593
2103
|
return [resolve(root, subdir), `/${basename(root)}/${subdir}/`];
|
|
594
2104
|
}
|
|
595
|
-
|
|
2105
|
+
|
|
2106
|
+
const debug = createDebugger("vitest:browser:pool");
|
|
2107
|
+
function createBrowserPool(ctx) {
|
|
2108
|
+
const providers = /* @__PURE__ */ new Set();
|
|
2109
|
+
const waitForTests = async (contextId, project, files) => {
|
|
2110
|
+
const context = project.browser.state.createAsyncContext(contextId, files);
|
|
2111
|
+
return await context;
|
|
2112
|
+
};
|
|
2113
|
+
const runTests = async (project, files) => {
|
|
2114
|
+
ctx.state.clearFiles(project, files);
|
|
2115
|
+
const browser = project.browser;
|
|
2116
|
+
const threadsCount = getThreadsCount(project);
|
|
2117
|
+
const provider = browser.provider;
|
|
2118
|
+
providers.add(provider);
|
|
2119
|
+
const resolvedUrls = browser.vite.resolvedUrls;
|
|
2120
|
+
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
2121
|
+
if (!origin) {
|
|
2122
|
+
throw new Error(
|
|
2123
|
+
`Can't find browser origin URL for project "${project.getName()}" when running tests for files "${files.join('", "')}"`
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
const filesPerThread = Math.ceil(files.length / threadsCount);
|
|
2127
|
+
const chunks = [];
|
|
2128
|
+
for (let i = 0; i < files.length; i += filesPerThread) {
|
|
2129
|
+
const chunk = files.slice(i, i + filesPerThread);
|
|
2130
|
+
chunks.push(chunk);
|
|
2131
|
+
}
|
|
2132
|
+
debug?.(
|
|
2133
|
+
`[%s] Running %s tests in %s chunks (%s threads)`,
|
|
2134
|
+
project.getName() || "core",
|
|
2135
|
+
files.length,
|
|
2136
|
+
chunks.length,
|
|
2137
|
+
threadsCount
|
|
2138
|
+
);
|
|
2139
|
+
const orchestrators = [...browser.state.orchestrators.entries()];
|
|
2140
|
+
const promises = [];
|
|
2141
|
+
chunks.forEach((files2, index) => {
|
|
2142
|
+
if (orchestrators[index]) {
|
|
2143
|
+
const [contextId, orchestrator] = orchestrators[index];
|
|
2144
|
+
debug?.(
|
|
2145
|
+
"Reusing orchestrator (context %s) for files: %s",
|
|
2146
|
+
contextId,
|
|
2147
|
+
[...files2.map((f) => relative(project.config.root, f))].join(", ")
|
|
2148
|
+
);
|
|
2149
|
+
const promise = waitForTests(contextId, project, files2);
|
|
2150
|
+
promises.push(promise);
|
|
2151
|
+
orchestrator.createTesters(files2);
|
|
2152
|
+
} else {
|
|
2153
|
+
const contextId = crypto.randomUUID();
|
|
2154
|
+
const waitPromise = waitForTests(contextId, project, files2);
|
|
2155
|
+
debug?.(
|
|
2156
|
+
"Opening a new context %s for files: %s",
|
|
2157
|
+
contextId,
|
|
2158
|
+
[...files2.map((f) => relative(project.config.root, f))].join(", ")
|
|
2159
|
+
);
|
|
2160
|
+
const url = new URL("/", origin);
|
|
2161
|
+
url.searchParams.set("contextId", contextId);
|
|
2162
|
+
const page = provider.openPage(contextId, url.toString()).then(() => waitPromise);
|
|
2163
|
+
promises.push(page);
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
await Promise.all(promises);
|
|
2167
|
+
};
|
|
2168
|
+
const runWorkspaceTests = async (specs) => {
|
|
2169
|
+
const groupedFiles = /* @__PURE__ */ new Map();
|
|
2170
|
+
for (const [project, file] of specs) {
|
|
2171
|
+
const files = groupedFiles.get(project) || [];
|
|
2172
|
+
files.push(file);
|
|
2173
|
+
groupedFiles.set(project, files);
|
|
2174
|
+
}
|
|
2175
|
+
for (const [project, files] of groupedFiles.entries()) {
|
|
2176
|
+
await runTests(project, files);
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
2180
|
+
function getThreadsCount(project) {
|
|
2181
|
+
const config = project.config.browser;
|
|
2182
|
+
if (!config.headless || !project.browser.provider.supportsParallelism) {
|
|
2183
|
+
return 1;
|
|
2184
|
+
}
|
|
2185
|
+
if (!config.fileParallelism) {
|
|
2186
|
+
return 1;
|
|
2187
|
+
}
|
|
2188
|
+
return ctx.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
|
|
2189
|
+
}
|
|
596
2190
|
return {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
2191
|
+
name: "browser",
|
|
2192
|
+
async close() {
|
|
2193
|
+
await Promise.all([...providers].map((provider) => provider.close()));
|
|
2194
|
+
providers.clear();
|
|
2195
|
+
},
|
|
2196
|
+
runTests: runWorkspaceTests
|
|
600
2197
|
};
|
|
601
2198
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
2199
|
+
|
|
2200
|
+
async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
|
|
2201
|
+
const server = new BrowserServer(project, "/");
|
|
2202
|
+
const root = project.config.root;
|
|
2203
|
+
await project.ctx.packageInstaller.ensureInstalled("@vitest/browser", root);
|
|
2204
|
+
const configPath = typeof configFile === "string" ? configFile : false;
|
|
2205
|
+
const vite = await createServer({
|
|
2206
|
+
...project.options,
|
|
2207
|
+
// spread project config inlined in root workspace config
|
|
2208
|
+
base: "/",
|
|
2209
|
+
logLevel: "error",
|
|
2210
|
+
mode: project.config.mode,
|
|
2211
|
+
configFile: configPath,
|
|
2212
|
+
// watch is handled by Vitest
|
|
2213
|
+
server: {
|
|
2214
|
+
hmr: false,
|
|
2215
|
+
watch: null,
|
|
2216
|
+
preTransformRequests: false
|
|
2217
|
+
},
|
|
2218
|
+
plugins: [
|
|
2219
|
+
...prePlugins,
|
|
2220
|
+
...project.options?.plugins || [],
|
|
2221
|
+
BrowserPlugin(server),
|
|
2222
|
+
...postPlugins
|
|
2223
|
+
]
|
|
614
2224
|
});
|
|
615
|
-
|
|
2225
|
+
await vite.listen();
|
|
2226
|
+
setupBrowserRpc(server);
|
|
2227
|
+
return server;
|
|
616
2228
|
}
|
|
617
2229
|
|
|
618
|
-
export {
|
|
2230
|
+
export { createBrowserPool, createBrowserServer };
|