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