@vitest/browser 3.1.1 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/.vite/manifest.json +6 -6
- package/dist/client/__vitest__/assets/index-Cv3XDLXs.js +52 -0
- package/dist/client/__vitest__/assets/{index-B0KEk_KY.css → index-D6BhetW8.css} +1 -1
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/orchestrator-CuTjqoE1.js +287 -0
- package/dist/client/__vitest_browser__/{tester-DiLSqOx4.js → tester-D8qCxA_3.js} +3172 -3084
- package/dist/client/__vitest_browser__/{utils-CNTxSNQV.js → utils-Owv5OOOf.js} +2 -2
- package/dist/client/error-catcher.js +1 -14
- package/dist/client/esm-client-injector.js +0 -1
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/tester.html +2 -2
- package/dist/client.js +63 -23
- package/dist/context.js +4 -4
- package/dist/expect-element.js +1 -1
- package/dist/index-C3ICQ6zz.js +1 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.js +364 -244
- package/dist/locators/index.js +1 -1
- package/dist/locators/playwright.js +1 -1
- package/dist/locators/preview.js +1 -1
- package/dist/locators/webdriverio.js +1 -1
- package/dist/providers.js +2 -1
- package/dist/{public-utils-xf4CCUzp.js → public-utils-DUr23h1p.js} +2 -2
- package/dist/state.js +2 -67
- package/dist/utils.js +1 -1
- package/dist/{webdriver-2iYWIzBv.js → webdriver-BH7t2pDp.js} +63 -9
- package/package.json +16 -16
- package/dist/client/__vitest__/assets/index-BLZJq7cG.js +0 -52
- package/dist/client/__vitest_browser__/orchestrator-CqPXjvQE.js +0 -241
- package/dist/index-DjDyxzt8.js +0 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import c from 'tinyrainbow';
|
|
|
4
4
|
import { getFilePoolName, distDir, resolveApiServerConfig, resolveFsAllow, isFileServingAllowed, createDebugger, isValidApiRequest, createViteLogger, createViteServer } from 'vitest/node';
|
|
5
5
|
import fs, { readFileSync, lstatSync, promises, existsSync } from 'node:fs';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
-
import { slash as slash$1, toArray } from '@vitest/utils';
|
|
7
|
+
import { slash as slash$1, toArray, createDefer } from '@vitest/utils';
|
|
8
8
|
import MagicString from 'magic-string';
|
|
9
9
|
import sirv from 'sirv';
|
|
10
10
|
import * as vite from 'vite';
|
|
@@ -13,12 +13,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import crypto from 'node:crypto';
|
|
14
14
|
import { mkdir, readFile as readFile$1 } from 'node:fs/promises';
|
|
15
15
|
import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
16
|
-
import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-
|
|
16
|
+
import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-BH7t2pDp.js';
|
|
17
17
|
import { resolve as resolve$1, dirname as dirname$1, basename as basename$1, normalize as normalize$1 } from 'node:path';
|
|
18
18
|
import { WebSocketServer } from 'ws';
|
|
19
19
|
import * as nodeos from 'node:os';
|
|
20
20
|
|
|
21
|
-
var version = "3.1.
|
|
21
|
+
var version = "3.1.3";
|
|
22
22
|
|
|
23
23
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
24
24
|
function normalizeWindowsPath(input = "") {
|
|
@@ -213,6 +213,113 @@ const basename = function(p, extension) {
|
|
|
213
213
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
214
214
|
const distRoot = resolve(pkgRoot, "dist");
|
|
215
215
|
|
|
216
|
+
/// <reference types="../types/index.d.ts" />
|
|
217
|
+
|
|
218
|
+
// (c) 2020-present Andrea Giammarchi
|
|
219
|
+
|
|
220
|
+
const {parse: $parse, stringify: $stringify} = JSON;
|
|
221
|
+
const {keys} = Object;
|
|
222
|
+
|
|
223
|
+
const Primitive = String; // it could be Number
|
|
224
|
+
const primitive = 'string'; // it could be 'number'
|
|
225
|
+
|
|
226
|
+
const ignore = {};
|
|
227
|
+
const object = 'object';
|
|
228
|
+
|
|
229
|
+
const noop = (_, value) => value;
|
|
230
|
+
|
|
231
|
+
const primitives = value => (
|
|
232
|
+
value instanceof Primitive ? Primitive(value) : value
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const Primitives = (_, value) => (
|
|
236
|
+
typeof value === primitive ? new Primitive(value) : value
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const revive = (input, parsed, output, $) => {
|
|
240
|
+
const lazy = [];
|
|
241
|
+
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
242
|
+
const k = ke[y];
|
|
243
|
+
const value = output[k];
|
|
244
|
+
if (value instanceof Primitive) {
|
|
245
|
+
const tmp = input[value];
|
|
246
|
+
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
247
|
+
parsed.add(tmp);
|
|
248
|
+
output[k] = ignore;
|
|
249
|
+
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
250
|
+
}
|
|
251
|
+
else
|
|
252
|
+
output[k] = $.call(output, k, tmp);
|
|
253
|
+
}
|
|
254
|
+
else if (output[k] !== ignore)
|
|
255
|
+
output[k] = $.call(output, k, value);
|
|
256
|
+
}
|
|
257
|
+
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
258
|
+
const {k, a} = lazy[i];
|
|
259
|
+
output[k] = $.call(output, k, revive.apply(null, a));
|
|
260
|
+
}
|
|
261
|
+
return output;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const set = (known, input, value) => {
|
|
265
|
+
const index = Primitive(input.push(value) - 1);
|
|
266
|
+
known.set(value, index);
|
|
267
|
+
return index;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Converts a specialized flatted string into a JS value.
|
|
272
|
+
* @param {string} text
|
|
273
|
+
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
274
|
+
* @returns {any}
|
|
275
|
+
*/
|
|
276
|
+
const parse = (text, reviver) => {
|
|
277
|
+
const input = $parse(text, Primitives).map(primitives);
|
|
278
|
+
const value = input[0];
|
|
279
|
+
const $ = reviver || noop;
|
|
280
|
+
const tmp = typeof value === object && value ?
|
|
281
|
+
revive(input, new Set, value, $) :
|
|
282
|
+
value;
|
|
283
|
+
return $.call({'': tmp}, '', tmp);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Converts a JS value into a specialized flatted string.
|
|
288
|
+
* @param {any} value
|
|
289
|
+
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
290
|
+
* @param {string | number | undefined} [space]
|
|
291
|
+
* @returns {string}
|
|
292
|
+
*/
|
|
293
|
+
const stringify = (value, replacer, space) => {
|
|
294
|
+
const $ = replacer && typeof replacer === object ?
|
|
295
|
+
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
296
|
+
(replacer || noop);
|
|
297
|
+
const known = new Map;
|
|
298
|
+
const input = [];
|
|
299
|
+
const output = [];
|
|
300
|
+
let i = +set(known, input, $.call({'': value}, '', value));
|
|
301
|
+
let firstRun = !i;
|
|
302
|
+
while (i < input.length) {
|
|
303
|
+
firstRun = true;
|
|
304
|
+
output[i] = $stringify(input[i++], replace, space);
|
|
305
|
+
}
|
|
306
|
+
return '[' + output.join(',') + ']';
|
|
307
|
+
function replace(key, value) {
|
|
308
|
+
if (firstRun) {
|
|
309
|
+
firstRun = !firstRun;
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
const after = $.call(this, key, value);
|
|
313
|
+
switch (typeof after) {
|
|
314
|
+
case object:
|
|
315
|
+
if (after === null) return after;
|
|
316
|
+
case primitive:
|
|
317
|
+
return known.get(after) || set(known, input, after);
|
|
318
|
+
}
|
|
319
|
+
return after;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
216
323
|
function replacer(code, values) {
|
|
217
324
|
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
|
|
218
325
|
}
|
|
@@ -249,22 +356,23 @@ async function resolveOrchestrator(globalServer, url, res) {
|
|
|
249
356
|
sessionId = contexts[contexts.length - 1] ?? "none";
|
|
250
357
|
}
|
|
251
358
|
const session = globalServer.vitest._browserSessions.getSession(sessionId);
|
|
252
|
-
const files = session?.files ?? [];
|
|
253
359
|
const browserProject = session?.project.browser || [...globalServer.children][0];
|
|
254
360
|
if (!browserProject) {
|
|
255
361
|
return;
|
|
256
362
|
}
|
|
363
|
+
if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
257
366
|
const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
|
|
258
367
|
const injector = replacer(injectorJs, {
|
|
259
368
|
__VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider || "preview"),
|
|
260
369
|
__VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
|
|
261
370
|
__VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
|
|
262
|
-
__VITEST_METHOD__: JSON.stringify(
|
|
263
|
-
__VITEST_FILES__: JSON.stringify(files),
|
|
371
|
+
__VITEST_METHOD__: JSON.stringify("orchestrate"),
|
|
264
372
|
__VITEST_TYPE__: "\"orchestrator\"",
|
|
265
373
|
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
266
374
|
__VITEST_TESTER_ID__: "\"none\"",
|
|
267
|
-
__VITEST_PROVIDED_CONTEXT__:
|
|
375
|
+
__VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
|
|
268
376
|
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
269
377
|
});
|
|
270
378
|
res.removeHeader("Content-Security-Policy");
|
|
@@ -327,119 +435,12 @@ function createOrchestratorMiddleware(parentServer) {
|
|
|
327
435
|
};
|
|
328
436
|
}
|
|
329
437
|
|
|
330
|
-
/// <reference types="../types/index.d.ts" />
|
|
331
|
-
|
|
332
|
-
// (c) 2020-present Andrea Giammarchi
|
|
333
|
-
|
|
334
|
-
const {parse: $parse, stringify: $stringify} = JSON;
|
|
335
|
-
const {keys} = Object;
|
|
336
|
-
|
|
337
|
-
const Primitive = String; // it could be Number
|
|
338
|
-
const primitive = 'string'; // it could be 'number'
|
|
339
|
-
|
|
340
|
-
const ignore = {};
|
|
341
|
-
const object = 'object';
|
|
342
|
-
|
|
343
|
-
const noop = (_, value) => value;
|
|
344
|
-
|
|
345
|
-
const primitives = value => (
|
|
346
|
-
value instanceof Primitive ? Primitive(value) : value
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
const Primitives = (_, value) => (
|
|
350
|
-
typeof value === primitive ? new Primitive(value) : value
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
const revive = (input, parsed, output, $) => {
|
|
354
|
-
const lazy = [];
|
|
355
|
-
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
356
|
-
const k = ke[y];
|
|
357
|
-
const value = output[k];
|
|
358
|
-
if (value instanceof Primitive) {
|
|
359
|
-
const tmp = input[value];
|
|
360
|
-
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
361
|
-
parsed.add(tmp);
|
|
362
|
-
output[k] = ignore;
|
|
363
|
-
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
364
|
-
}
|
|
365
|
-
else
|
|
366
|
-
output[k] = $.call(output, k, tmp);
|
|
367
|
-
}
|
|
368
|
-
else if (output[k] !== ignore)
|
|
369
|
-
output[k] = $.call(output, k, value);
|
|
370
|
-
}
|
|
371
|
-
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
372
|
-
const {k, a} = lazy[i];
|
|
373
|
-
output[k] = $.call(output, k, revive.apply(null, a));
|
|
374
|
-
}
|
|
375
|
-
return output;
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const set = (known, input, value) => {
|
|
379
|
-
const index = Primitive(input.push(value) - 1);
|
|
380
|
-
known.set(value, index);
|
|
381
|
-
return index;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Converts a specialized flatted string into a JS value.
|
|
386
|
-
* @param {string} text
|
|
387
|
-
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
388
|
-
* @returns {any}
|
|
389
|
-
*/
|
|
390
|
-
const parse = (text, reviver) => {
|
|
391
|
-
const input = $parse(text, Primitives).map(primitives);
|
|
392
|
-
const value = input[0];
|
|
393
|
-
const $ = reviver || noop;
|
|
394
|
-
const tmp = typeof value === object && value ?
|
|
395
|
-
revive(input, new Set, value, $) :
|
|
396
|
-
value;
|
|
397
|
-
return $.call({'': tmp}, '', tmp);
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Converts a JS value into a specialized flatted string.
|
|
402
|
-
* @param {any} value
|
|
403
|
-
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
404
|
-
* @param {string | number | undefined} [space]
|
|
405
|
-
* @returns {string}
|
|
406
|
-
*/
|
|
407
|
-
const stringify = (value, replacer, space) => {
|
|
408
|
-
const $ = replacer && typeof replacer === object ?
|
|
409
|
-
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
410
|
-
(replacer || noop);
|
|
411
|
-
const known = new Map;
|
|
412
|
-
const input = [];
|
|
413
|
-
const output = [];
|
|
414
|
-
let i = +set(known, input, $.call({'': value}, '', value));
|
|
415
|
-
let firstRun = !i;
|
|
416
|
-
while (i < input.length) {
|
|
417
|
-
firstRun = true;
|
|
418
|
-
output[i] = $stringify(input[i++], replace, space);
|
|
419
|
-
}
|
|
420
|
-
return '[' + output.join(',') + ']';
|
|
421
|
-
function replace(key, value) {
|
|
422
|
-
if (firstRun) {
|
|
423
|
-
firstRun = !firstRun;
|
|
424
|
-
return value;
|
|
425
|
-
}
|
|
426
|
-
const after = $.call(this, key, value);
|
|
427
|
-
switch (typeof after) {
|
|
428
|
-
case object:
|
|
429
|
-
if (after === null) return after;
|
|
430
|
-
case primitive:
|
|
431
|
-
return known.get(after) || set(known, input, after);
|
|
432
|
-
}
|
|
433
|
-
return after;
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
|
|
437
438
|
async function resolveTester(globalServer, url, res, next) {
|
|
438
439
|
const csp = res.getHeader("Content-Security-Policy");
|
|
439
440
|
if (typeof csp === "string") {
|
|
440
441
|
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
441
442
|
}
|
|
442
|
-
const
|
|
443
|
+
const sessionId = url.searchParams.get("sessionId") || "none";
|
|
443
444
|
const session = globalServer.vitest._browserSessions.getSession(sessionId);
|
|
444
445
|
if (!session) {
|
|
445
446
|
res.statusCode = 400;
|
|
@@ -447,11 +448,6 @@ async function resolveTester(globalServer, url, res, next) {
|
|
|
447
448
|
return;
|
|
448
449
|
}
|
|
449
450
|
const project = globalServer.vitest.getProjectByName(session.project.name || "");
|
|
450
|
-
const { testFiles } = await project.globTestFiles();
|
|
451
|
-
const tests = testFile === "__vitest_all__" || !testFiles.includes(testFile) ? "__vitest_browser_runner__.files" : JSON.stringify([testFile]);
|
|
452
|
-
const iframeId = JSON.stringify(testFile);
|
|
453
|
-
const files = session.files ?? [];
|
|
454
|
-
const method = session.method ?? "run";
|
|
455
451
|
const browserProject = project.browser || [...globalServer.children][0];
|
|
456
452
|
if (!browserProject) {
|
|
457
453
|
res.statusCode = 400;
|
|
@@ -462,13 +458,12 @@ async function resolveTester(globalServer, url, res, next) {
|
|
|
462
458
|
const injector = replacer(injectorJs, {
|
|
463
459
|
__VITEST_PROVIDER__: JSON.stringify(project.browser.provider.name),
|
|
464
460
|
__VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
|
|
465
|
-
__VITEST_FILES__: JSON.stringify(files),
|
|
466
461
|
__VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
|
|
467
462
|
__VITEST_TYPE__: "\"tester\"",
|
|
468
|
-
__VITEST_METHOD__: JSON.stringify(
|
|
463
|
+
__VITEST_METHOD__: JSON.stringify("none"),
|
|
469
464
|
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
470
465
|
__VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()),
|
|
471
|
-
__VITEST_PROVIDED_CONTEXT__:
|
|
466
|
+
__VITEST_PROVIDED_CONTEXT__: "{}",
|
|
472
467
|
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
473
468
|
});
|
|
474
469
|
const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
|
|
@@ -477,17 +472,11 @@ async function resolveTester(globalServer, url, res, next) {
|
|
|
477
472
|
const indexhtml = await browserProject.vite.transformIndexHtml(url, testerHtml);
|
|
478
473
|
const html = replacer(indexhtml, {
|
|
479
474
|
__VITEST_FAVICON__: globalServer.faviconUrl,
|
|
480
|
-
__VITEST_INJECTOR__: injector
|
|
481
|
-
__VITEST_APPEND__: `
|
|
482
|
-
__vitest_browser_runner__.runningFiles = ${tests}
|
|
483
|
-
__vitest_browser_runner__.iframeId = ${iframeId}
|
|
484
|
-
__vitest_browser_runner__.${method === "run" ? "runTests" : "collectTests"}(__vitest_browser_runner__.runningFiles)
|
|
485
|
-
document.querySelector('script[data-vitest-append]').remove()
|
|
486
|
-
`
|
|
475
|
+
__VITEST_INJECTOR__: injector
|
|
487
476
|
});
|
|
488
477
|
return html;
|
|
489
478
|
} catch (err) {
|
|
490
|
-
session.
|
|
479
|
+
session.fail(err);
|
|
491
480
|
next(err);
|
|
492
481
|
}
|
|
493
482
|
}
|
|
@@ -498,7 +487,7 @@ function createTesterMiddleware(browserServer) {
|
|
|
498
487
|
return next();
|
|
499
488
|
}
|
|
500
489
|
const url = new URL(req.url, "http://localhost");
|
|
501
|
-
if (!url.pathname.startsWith(browserServer.prefixTesterUrl)) {
|
|
490
|
+
if (!url.pathname.startsWith(browserServer.prefixTesterUrl) || !url.searchParams.has("sessionId")) {
|
|
502
491
|
return next();
|
|
503
492
|
}
|
|
504
493
|
const html = await resolveTester(browserServer, url, res, next);
|
|
@@ -961,16 +950,7 @@ body {
|
|
|
961
950
|
injectTo: "head"
|
|
962
951
|
} : null,
|
|
963
952
|
...parentServer.testerScripts,
|
|
964
|
-
...testerTags
|
|
965
|
-
{
|
|
966
|
-
tag: "script",
|
|
967
|
-
attrs: {
|
|
968
|
-
"type": "module",
|
|
969
|
-
"data-vitest-append": ""
|
|
970
|
-
},
|
|
971
|
-
children: "{__VITEST_APPEND__}",
|
|
972
|
-
injectTo: "body"
|
|
973
|
-
}
|
|
953
|
+
...testerTags
|
|
974
954
|
].filter((s) => s != null);
|
|
975
955
|
}
|
|
976
956
|
},
|
|
@@ -1216,6 +1196,7 @@ const types = {
|
|
|
1216
1196
|
'application/dash+xml': ['mpd'],
|
|
1217
1197
|
'application/dash-patch+xml': ['mpp'],
|
|
1218
1198
|
'application/davmount+xml': ['davmount'],
|
|
1199
|
+
'application/dicom': ['dcm'],
|
|
1219
1200
|
'application/docbook+xml': ['dbk'],
|
|
1220
1201
|
'application/dssc+der': ['dssc'],
|
|
1221
1202
|
'application/dssc+xml': ['xdssc'],
|
|
@@ -1302,7 +1283,14 @@ const types = {
|
|
|
1302
1283
|
'application/oebps-package+xml': ['opf'],
|
|
1303
1284
|
'application/ogg': ['ogx'],
|
|
1304
1285
|
'application/omdoc+xml': ['omdoc'],
|
|
1305
|
-
'application/onenote': [
|
|
1286
|
+
'application/onenote': [
|
|
1287
|
+
'onetoc',
|
|
1288
|
+
'onetoc2',
|
|
1289
|
+
'onetmp',
|
|
1290
|
+
'onepkg',
|
|
1291
|
+
'one',
|
|
1292
|
+
'onea',
|
|
1293
|
+
],
|
|
1306
1294
|
'application/oxps': ['oxps'],
|
|
1307
1295
|
'application/p2p-overlay+xml': ['relo'],
|
|
1308
1296
|
'application/patch-ops-error+xml': ['xer'],
|
|
@@ -1398,6 +1386,7 @@ const types = {
|
|
|
1398
1386
|
'application/yang': ['yang'],
|
|
1399
1387
|
'application/yin+xml': ['yin'],
|
|
1400
1388
|
'application/zip': ['zip'],
|
|
1389
|
+
'application/zip+dotlottie': ['lottie'],
|
|
1401
1390
|
'audio/3gpp': ['*3gpp'],
|
|
1402
1391
|
'audio/aac': ['adts', 'aac'],
|
|
1403
1392
|
'audio/adpcm': ['adp'],
|
|
@@ -1406,7 +1395,7 @@ const types = {
|
|
|
1406
1395
|
'audio/midi': ['mid', 'midi', 'kar', 'rmi'],
|
|
1407
1396
|
'audio/mobile-xmf': ['mxmf'],
|
|
1408
1397
|
'audio/mp3': ['*mp3'],
|
|
1409
|
-
'audio/mp4': ['m4a', 'mp4a'],
|
|
1398
|
+
'audio/mp4': ['m4a', 'mp4a', 'm4b'],
|
|
1410
1399
|
'audio/mpeg': ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
|
|
1411
1400
|
'audio/ogg': ['oga', 'ogg', 'spx', 'opus'],
|
|
1412
1401
|
'audio/s3m': ['s3m'],
|
|
@@ -1438,11 +1427,12 @@ const types = {
|
|
|
1438
1427
|
'image/heif': ['heif'],
|
|
1439
1428
|
'image/heif-sequence': ['heifs'],
|
|
1440
1429
|
'image/hej2k': ['hej2'],
|
|
1441
|
-
'image/hsj2': ['hsj2'],
|
|
1442
1430
|
'image/ief': ['ief'],
|
|
1431
|
+
'image/jaii': ['jaii'],
|
|
1432
|
+
'image/jais': ['jais'],
|
|
1443
1433
|
'image/jls': ['jls'],
|
|
1444
1434
|
'image/jp2': ['jp2', 'jpg2'],
|
|
1445
|
-
'image/jpeg': ['
|
|
1435
|
+
'image/jpeg': ['jpg', 'jpeg', 'jpe'],
|
|
1446
1436
|
'image/jph': ['jph'],
|
|
1447
1437
|
'image/jphc': ['jhc'],
|
|
1448
1438
|
'image/jpm': ['jpm', 'jpgm'],
|
|
@@ -1457,6 +1447,7 @@ const types = {
|
|
|
1457
1447
|
'image/jxss': ['jxss'],
|
|
1458
1448
|
'image/ktx': ['ktx'],
|
|
1459
1449
|
'image/ktx2': ['ktx2'],
|
|
1450
|
+
'image/pjpeg': ['jfif'],
|
|
1460
1451
|
'image/png': ['png'],
|
|
1461
1452
|
'image/sgi': ['sgi'],
|
|
1462
1453
|
'image/svg+xml': ['svg', 'svgz'],
|
|
@@ -1470,7 +1461,7 @@ const types = {
|
|
|
1470
1461
|
'message/global-delivery-status': ['u8dsn'],
|
|
1471
1462
|
'message/global-disposition-notification': ['u8mdn'],
|
|
1472
1463
|
'message/global-headers': ['u8hdr'],
|
|
1473
|
-
'message/rfc822': ['eml', 'mime'],
|
|
1464
|
+
'message/rfc822': ['eml', 'mime', 'mht', 'mhtml'],
|
|
1474
1465
|
'model/3mf': ['3mf'],
|
|
1475
1466
|
'model/gltf+json': ['gltf'],
|
|
1476
1467
|
'model/gltf-binary': ['glb'],
|
|
@@ -1480,6 +1471,7 @@ const types = {
|
|
|
1480
1471
|
'model/mtl': ['mtl'],
|
|
1481
1472
|
'model/obj': ['obj'],
|
|
1482
1473
|
'model/prc': ['prc'],
|
|
1474
|
+
'model/step': ['step', 'stp', 'stpnc', 'p21', '210'],
|
|
1483
1475
|
'model/step+xml': ['stpx'],
|
|
1484
1476
|
'model/step+zip': ['stpz'],
|
|
1485
1477
|
'model/step-xml+zip': ['stpxz'],
|
|
@@ -1586,8 +1578,8 @@ class Mime {
|
|
|
1586
1578
|
getType(path) {
|
|
1587
1579
|
if (typeof path !== 'string')
|
|
1588
1580
|
return null;
|
|
1589
|
-
const last = path.replace(/^.*[/\\]
|
|
1590
|
-
const ext = last.replace(
|
|
1581
|
+
const last = path.replace(/^.*[/\\]/s, '').toLowerCase();
|
|
1582
|
+
const ext = last.replace(/^.*\./s, '').toLowerCase();
|
|
1591
1583
|
const hasPath = last.length < path.length;
|
|
1592
1584
|
const hasDot = ext.length < last.length - 1;
|
|
1593
1585
|
if (!hasDot && hasPath)
|
|
@@ -2453,7 +2445,7 @@ const type = async (context, selector, text, options = {}) => {
|
|
|
2453
2445
|
return { unreleased: Array.from(unreleased) };
|
|
2454
2446
|
};
|
|
2455
2447
|
|
|
2456
|
-
const upload = async (context, selector, files) => {
|
|
2448
|
+
const upload = async (context, selector, files, options) => {
|
|
2457
2449
|
const testPath = context.testPath;
|
|
2458
2450
|
if (!testPath) {
|
|
2459
2451
|
throw new Error(`Cannot upload files outside of a test`);
|
|
@@ -2471,7 +2463,7 @@ const upload = async (context, selector, files) => {
|
|
|
2471
2463
|
buffer: Buffer.from(file.base64, "base64")
|
|
2472
2464
|
};
|
|
2473
2465
|
});
|
|
2474
|
-
await iframe.locator(selector).setInputFiles(playwrightFiles);
|
|
2466
|
+
await iframe.locator(selector).setInputFiles(playwrightFiles, options);
|
|
2475
2467
|
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2476
2468
|
for (const file of files) {
|
|
2477
2469
|
if (typeof file !== "string") {
|
|
@@ -2634,7 +2626,7 @@ class ParentBrowserProject {
|
|
|
2634
2626
|
if (mod) {
|
|
2635
2627
|
return id;
|
|
2636
2628
|
}
|
|
2637
|
-
const resolvedPath = resolve(
|
|
2629
|
+
const resolvedPath = resolve(this.vite.config.root, id.slice(1));
|
|
2638
2630
|
const modUrl = this.vite.moduleGraph.getModuleById(resolvedPath);
|
|
2639
2631
|
if (modUrl) {
|
|
2640
2632
|
return resolvedPath;
|
|
@@ -2718,14 +2710,13 @@ class ParentBrowserProject {
|
|
|
2718
2710
|
if (!provider.getCDPSession) {
|
|
2719
2711
|
throw new Error(`CDP is not supported by the provider "${provider.name}".`);
|
|
2720
2712
|
}
|
|
2721
|
-
const
|
|
2713
|
+
const session = await this.cdpSessionsPromises.get(rpcId) ?? await (async () => {
|
|
2722
2714
|
const promise = provider.getCDPSession(sessionId).finally(() => {
|
|
2723
2715
|
this.cdpSessionsPromises.delete(rpcId);
|
|
2724
2716
|
});
|
|
2725
2717
|
this.cdpSessionsPromises.set(rpcId, promise);
|
|
2726
2718
|
return promise;
|
|
2727
2719
|
})();
|
|
2728
|
-
const session = await promise;
|
|
2729
2720
|
const rpc = browser.state.testers.get(rpcId);
|
|
2730
2721
|
if (!rpc) {
|
|
2731
2722
|
throw new Error(`Tester RPC "${rpcId}" was not established.`);
|
|
@@ -2781,6 +2772,8 @@ class ParentBrowserProject {
|
|
|
2781
2772
|
}
|
|
2782
2773
|
}
|
|
2783
2774
|
|
|
2775
|
+
const TYPE_REQUEST = "q";
|
|
2776
|
+
const TYPE_RESPONSE = "s";
|
|
2784
2777
|
const DEFAULT_TIMEOUT = 6e4;
|
|
2785
2778
|
function defaultSerialize(i) {
|
|
2786
2779
|
return i;
|
|
@@ -2813,7 +2806,7 @@ function createBirpc(functions, options) {
|
|
|
2813
2806
|
if (method === "then" && !eventNames.includes("then") && !("then" in functions))
|
|
2814
2807
|
return void 0;
|
|
2815
2808
|
const sendEvent = (...args) => {
|
|
2816
|
-
post(serialize({ m: method, a: args, t:
|
|
2809
|
+
post(serialize({ m: method, a: args, t: TYPE_REQUEST }));
|
|
2817
2810
|
};
|
|
2818
2811
|
if (eventNames.includes(method)) {
|
|
2819
2812
|
sendEvent.asEvent = sendEvent;
|
|
@@ -2835,8 +2828,9 @@ function createBirpc(functions, options) {
|
|
|
2835
2828
|
if (timeout >= 0) {
|
|
2836
2829
|
timeoutId = setTimeout(() => {
|
|
2837
2830
|
try {
|
|
2838
|
-
options.onTimeoutError?.(method, args);
|
|
2839
|
-
|
|
2831
|
+
const handleResult = options.onTimeoutError?.(method, args);
|
|
2832
|
+
if (handleResult !== true)
|
|
2833
|
+
throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
2840
2834
|
} catch (e) {
|
|
2841
2835
|
reject(e);
|
|
2842
2836
|
}
|
|
@@ -2853,17 +2847,24 @@ function createBirpc(functions, options) {
|
|
|
2853
2847
|
return sendCall;
|
|
2854
2848
|
}
|
|
2855
2849
|
});
|
|
2856
|
-
function close() {
|
|
2850
|
+
function close(error) {
|
|
2857
2851
|
closed = true;
|
|
2858
2852
|
rpcPromiseMap.forEach(({ reject, method }) => {
|
|
2859
|
-
reject(new Error(`[birpc] rpc is closed, cannot call "${method}"`));
|
|
2853
|
+
reject(error || new Error(`[birpc] rpc is closed, cannot call "${method}"`));
|
|
2860
2854
|
});
|
|
2861
2855
|
rpcPromiseMap.clear();
|
|
2862
2856
|
off(onMessage);
|
|
2863
2857
|
}
|
|
2864
2858
|
async function onMessage(data, ...extra) {
|
|
2865
|
-
|
|
2866
|
-
|
|
2859
|
+
let msg;
|
|
2860
|
+
try {
|
|
2861
|
+
msg = deserialize(data);
|
|
2862
|
+
} catch (e) {
|
|
2863
|
+
if (options.onGeneralError?.(e) !== true)
|
|
2864
|
+
throw e;
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
if (msg.t === TYPE_REQUEST) {
|
|
2867
2868
|
const { m: method, a: args } = msg;
|
|
2868
2869
|
let result, error;
|
|
2869
2870
|
const fn = resolver ? resolver(method, functions[method]) : functions[method];
|
|
@@ -2879,7 +2880,26 @@ function createBirpc(functions, options) {
|
|
|
2879
2880
|
if (msg.i) {
|
|
2880
2881
|
if (error && options.onError)
|
|
2881
2882
|
options.onError(error, method, args);
|
|
2882
|
-
|
|
2883
|
+
if (error && options.onFunctionError) {
|
|
2884
|
+
if (options.onFunctionError(error, method, args) === true)
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
if (!error) {
|
|
2888
|
+
try {
|
|
2889
|
+
post(serialize({ t: TYPE_RESPONSE, i: msg.i, r: result }), ...extra);
|
|
2890
|
+
return;
|
|
2891
|
+
} catch (e) {
|
|
2892
|
+
error = e;
|
|
2893
|
+
if (options.onGeneralError?.(e, method, args) !== true)
|
|
2894
|
+
throw e;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
try {
|
|
2898
|
+
post(serialize({ t: TYPE_RESPONSE, i: msg.i, e: error }), ...extra);
|
|
2899
|
+
} catch (e) {
|
|
2900
|
+
if (options.onGeneralError?.(e, method, args) !== true)
|
|
2901
|
+
throw e;
|
|
2902
|
+
}
|
|
2883
2903
|
}
|
|
2884
2904
|
} else {
|
|
2885
2905
|
const { i: ack, r: result, e: error } = msg;
|
|
@@ -2934,9 +2954,9 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
2934
2954
|
if (!sessionId || !rpcId || projectName == null) {
|
|
2935
2955
|
return error(new Error(`[vitest] Invalid URL ${request.url}. "projectName", "sessionId" and "rpcId" queries are required.`));
|
|
2936
2956
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
return error(new Error(`[vitest]
|
|
2957
|
+
if (!vitest._browserSessions.sessionIds.has(sessionId)) {
|
|
2958
|
+
const ids = [...vitest._browserSessions.sessionIds].join(", ");
|
|
2959
|
+
return error(new Error(`[vitest] Unknown session id "${sessionId}". Expected one of ${ids}.`));
|
|
2940
2960
|
}
|
|
2941
2961
|
if (type === "orchestrator") {
|
|
2942
2962
|
const session = vitest._browserSessions.getSession(sessionId);
|
|
@@ -2948,7 +2968,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
2948
2968
|
}
|
|
2949
2969
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
2950
2970
|
wss.emit("connection", ws, request);
|
|
2951
|
-
const rpc = setupClient(project, rpcId, ws
|
|
2971
|
+
const rpc = setupClient(project, rpcId, ws);
|
|
2952
2972
|
const state = project.browser.state;
|
|
2953
2973
|
const clients = type === "tester" ? state.testers : state.orchestrators;
|
|
2954
2974
|
clients.set(rpcId, rpc);
|
|
@@ -2957,6 +2977,10 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
2957
2977
|
debug$1?.("[%s] Browser API disconnected from %s", rpcId, type);
|
|
2958
2978
|
clients.delete(rpcId);
|
|
2959
2979
|
globalServer.removeCDPHandler(rpcId);
|
|
2980
|
+
if (type === "orchestrator") {
|
|
2981
|
+
vitest._browserSessions.destroySession(sessionId);
|
|
2982
|
+
}
|
|
2983
|
+
rpc.$close(new Error(`[vitest] Browser connection was closed while running tests. Was the page closed unexpectedly?`));
|
|
2960
2984
|
});
|
|
2961
2985
|
});
|
|
2962
2986
|
});
|
|
@@ -2969,7 +2993,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
2969
2993
|
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
2970
2994
|
}
|
|
2971
2995
|
}
|
|
2972
|
-
function setupClient(project, rpcId, ws
|
|
2996
|
+
function setupClient(project, rpcId, ws) {
|
|
2973
2997
|
const mockResolver = new ServerMockResolver(globalServer.vite, { moduleDirectories: project.config.server?.deps?.moduleDirectories });
|
|
2974
2998
|
const mocker = project.browser?.provider.mocker;
|
|
2975
2999
|
const rpc = createBirpc({
|
|
@@ -2980,21 +3004,21 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
2980
3004
|
}
|
|
2981
3005
|
vitest.state.catchError(error, type);
|
|
2982
3006
|
},
|
|
2983
|
-
async onQueued(file) {
|
|
3007
|
+
async onQueued(method, file) {
|
|
2984
3008
|
if (method === "collect") {
|
|
2985
3009
|
vitest.state.collectFiles(project, [file]);
|
|
2986
3010
|
} else {
|
|
2987
3011
|
await vitest._testRun.enqueued(project, file);
|
|
2988
3012
|
}
|
|
2989
3013
|
},
|
|
2990
|
-
async onCollected(files) {
|
|
3014
|
+
async onCollected(method, files) {
|
|
2991
3015
|
if (method === "collect") {
|
|
2992
3016
|
vitest.state.collectFiles(project, files);
|
|
2993
3017
|
} else {
|
|
2994
3018
|
await vitest._testRun.collected(project, files);
|
|
2995
3019
|
}
|
|
2996
3020
|
},
|
|
2997
|
-
async onTaskUpdate(packs, events) {
|
|
3021
|
+
async onTaskUpdate(method, packs, events) {
|
|
2998
3022
|
if (method === "collect") {
|
|
2999
3023
|
vitest.state.updateTasks(packs);
|
|
3000
3024
|
} else {
|
|
@@ -3004,7 +3028,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3004
3028
|
onAfterSuiteRun(meta) {
|
|
3005
3029
|
vitest.coverageProvider?.onAfterSuiteRun(meta);
|
|
3006
3030
|
},
|
|
3007
|
-
async sendLog(log) {
|
|
3031
|
+
async sendLog(method, log) {
|
|
3008
3032
|
if (method === "collect") {
|
|
3009
3033
|
vitest.state.updateUserLog(log);
|
|
3010
3034
|
} else {
|
|
@@ -3088,10 +3112,6 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3088
3112
|
}, provider.getCommandsContext(sessionId));
|
|
3089
3113
|
return await commands[command](context, ...payload);
|
|
3090
3114
|
},
|
|
3091
|
-
finishBrowserTests(sessionId) {
|
|
3092
|
-
debug$1?.("[%s] Finishing browser tests for session", sessionId);
|
|
3093
|
-
return vitest._browserSessions.getSession(sessionId)?.resolve();
|
|
3094
|
-
},
|
|
3095
3115
|
resolveMock(rawId, importer, options) {
|
|
3096
3116
|
return mockResolver.resolveMock(rawId, importer, options);
|
|
3097
3117
|
},
|
|
@@ -3160,6 +3180,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3160
3180
|
on: (fn) => ws.on("message", fn),
|
|
3161
3181
|
eventNames: ["onCancel", "cdpEvent"],
|
|
3162
3182
|
serialize: (data) => stringify(data, stringifyReplace),
|
|
3183
|
+
timeout: -1,
|
|
3163
3184
|
deserialize: parse,
|
|
3164
3185
|
onTimeoutError(functionName) {
|
|
3165
3186
|
throw new Error(`[vitest-api]: Timeout calling "${functionName}"`);
|
|
@@ -3194,69 +3215,30 @@ function stringifyReplace(key, value) {
|
|
|
3194
3215
|
}
|
|
3195
3216
|
|
|
3196
3217
|
const debug = createDebugger("vitest:browser:pool");
|
|
3197
|
-
async function waitForTests(method, sessionId, project, files) {
|
|
3198
|
-
const context = project.vitest._browserSessions.createAsyncSession(method, sessionId, files, project);
|
|
3199
|
-
return await context;
|
|
3200
|
-
}
|
|
3201
3218
|
function createBrowserPool(vitest) {
|
|
3202
3219
|
const providers = new Set();
|
|
3203
|
-
const
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3220
|
+
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
3221
|
+
const threadsCount = vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
|
|
3222
|
+
const projectPools = new WeakMap();
|
|
3223
|
+
const ensurePool = (project) => {
|
|
3224
|
+
if (projectPools.has(project)) {
|
|
3225
|
+
return projectPools.get(project);
|
|
3226
|
+
}
|
|
3227
|
+
debug?.("creating pool for project %s", project.name);
|
|
3228
|
+
const resolvedUrls = project.browser.vite.resolvedUrls;
|
|
3210
3229
|
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
3211
3230
|
if (!origin) {
|
|
3212
|
-
throw new Error(`Can't find browser origin URL for project "${project.name}"
|
|
3231
|
+
throw new Error(`Can't find browser origin URL for project "${project.name}"`);
|
|
3213
3232
|
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
}
|
|
3218
|
-
if (!provider.getCDPSession) {
|
|
3219
|
-
throw new Error("Unable to set breakpoint, CDP not supported");
|
|
3220
|
-
}
|
|
3221
|
-
const session = await provider.getCDPSession(sessionId);
|
|
3222
|
-
await session.send("Debugger.enable", {});
|
|
3223
|
-
await session.send("Debugger.setBreakpointByUrl", {
|
|
3224
|
-
lineNumber: 0,
|
|
3225
|
-
urlRegex: escapePathToRegexp(file)
|
|
3226
|
-
});
|
|
3227
|
-
}
|
|
3228
|
-
const filesPerThread = Math.ceil(files.length / threadsCount);
|
|
3229
|
-
const chunks = [];
|
|
3230
|
-
for (let i = 0; i < files.length; i += filesPerThread) {
|
|
3231
|
-
const chunk = files.slice(i, i + filesPerThread);
|
|
3232
|
-
chunks.push(chunk);
|
|
3233
|
-
}
|
|
3234
|
-
debug?.(`[%s] Running %s tests in %s chunks (%s threads)`, project.name || "core", files.length, chunks.length, threadsCount);
|
|
3235
|
-
const orchestrators = [...browser.state.orchestrators.entries()];
|
|
3236
|
-
const promises = [];
|
|
3237
|
-
chunks.forEach((files, index) => {
|
|
3238
|
-
if (orchestrators[index]) {
|
|
3239
|
-
const [sessionId, orchestrator] = orchestrators[index];
|
|
3240
|
-
debug?.("Reusing orchestrator (session %s) for files: %s", sessionId, [...files.map((f) => relative(project.config.root, f))].join(", "));
|
|
3241
|
-
const promise = waitForTests(method, sessionId, project, files);
|
|
3242
|
-
const tester = orchestrator.createTesters(files).catch((error) => {
|
|
3243
|
-
if (error instanceof Error && error.message.startsWith("[birpc] rpc is closed")) {
|
|
3244
|
-
return;
|
|
3245
|
-
}
|
|
3246
|
-
return Promise.reject(error);
|
|
3247
|
-
});
|
|
3248
|
-
promises.push(promise, tester);
|
|
3249
|
-
} else {
|
|
3250
|
-
const sessionId = crypto.randomUUID();
|
|
3251
|
-
const waitPromise = waitForTests(method, sessionId, project, files);
|
|
3252
|
-
debug?.("Opening a new session %s for files: %s", sessionId, [...files.map((f) => relative(project.config.root, f))].join(", "));
|
|
3253
|
-
const url = new URL("/", origin);
|
|
3254
|
-
url.searchParams.set("sessionId", sessionId);
|
|
3255
|
-
const page = provider.openPage(sessionId, url.toString(), () => setBreakpoint(sessionId, files[0]));
|
|
3256
|
-
promises.push(page, waitPromise);
|
|
3257
|
-
}
|
|
3233
|
+
const pool = new BrowserPool(project, {
|
|
3234
|
+
maxWorkers: getThreadsCount(project),
|
|
3235
|
+
origin
|
|
3258
3236
|
});
|
|
3259
|
-
|
|
3237
|
+
projectPools.set(project, pool);
|
|
3238
|
+
vitest.onCancel(() => {
|
|
3239
|
+
pool.cancel();
|
|
3240
|
+
});
|
|
3241
|
+
return pool;
|
|
3260
3242
|
};
|
|
3261
3243
|
const runWorkspaceTests = async (method, specs) => {
|
|
3262
3244
|
const groupedFiles = new Map();
|
|
@@ -3269,41 +3251,43 @@ function createBrowserPool(vitest) {
|
|
|
3269
3251
|
vitest.onCancel(() => {
|
|
3270
3252
|
isCancelled = true;
|
|
3271
3253
|
});
|
|
3272
|
-
|
|
3273
|
-
if (isCancelled) {
|
|
3274
|
-
break;
|
|
3275
|
-
}
|
|
3254
|
+
await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
|
|
3276
3255
|
await project._initBrowserProvider();
|
|
3277
3256
|
if (!project.browser) {
|
|
3278
3257
|
throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
3279
3258
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3259
|
+
if (isCancelled) {
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
debug?.("provider is ready for %s project", project.name);
|
|
3263
|
+
const pool = ensurePool(project);
|
|
3264
|
+
vitest.state.clearFiles(project, files);
|
|
3265
|
+
providers.add(project.browser.provider);
|
|
3266
|
+
await pool.runTests(method, files);
|
|
3267
|
+
}));
|
|
3282
3268
|
};
|
|
3283
|
-
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
3284
3269
|
function getThreadsCount(project) {
|
|
3285
3270
|
const config = project.config.browser;
|
|
3286
|
-
if (!config.headless || !project.browser.provider.supportsParallelism) {
|
|
3287
|
-
return 1;
|
|
3288
|
-
}
|
|
3289
|
-
if (!config.fileParallelism) {
|
|
3271
|
+
if (!config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism) {
|
|
3290
3272
|
return 1;
|
|
3291
3273
|
}
|
|
3292
3274
|
if (project.config.maxWorkers) {
|
|
3293
3275
|
return project.config.maxWorkers;
|
|
3294
3276
|
}
|
|
3295
|
-
return
|
|
3277
|
+
return threadsCount;
|
|
3296
3278
|
}
|
|
3297
3279
|
return {
|
|
3298
3280
|
name: "browser",
|
|
3299
3281
|
async close() {
|
|
3300
3282
|
await Promise.all([...providers].map((provider) => provider.close()));
|
|
3283
|
+
vitest._browserSessions.sessionIds.clear();
|
|
3301
3284
|
providers.clear();
|
|
3302
3285
|
vitest.projects.forEach((project) => {
|
|
3303
3286
|
project.browser?.state.orchestrators.forEach((orchestrator) => {
|
|
3304
3287
|
orchestrator.$close();
|
|
3305
3288
|
});
|
|
3306
3289
|
});
|
|
3290
|
+
debug?.("browser pool closed all providers");
|
|
3307
3291
|
},
|
|
3308
3292
|
runTests: (files) => runWorkspaceTests("run", files),
|
|
3309
3293
|
collectTests: (files) => runWorkspaceTests("collect", files)
|
|
@@ -3312,6 +3296,141 @@ function createBrowserPool(vitest) {
|
|
|
3312
3296
|
function escapePathToRegexp(path) {
|
|
3313
3297
|
return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
|
|
3314
3298
|
}
|
|
3299
|
+
class BrowserPool {
|
|
3300
|
+
_queue = [];
|
|
3301
|
+
_promise;
|
|
3302
|
+
_providedContext;
|
|
3303
|
+
readySessions = new Set();
|
|
3304
|
+
constructor(project, options) {
|
|
3305
|
+
this.project = project;
|
|
3306
|
+
this.options = options;
|
|
3307
|
+
}
|
|
3308
|
+
cancel() {
|
|
3309
|
+
this._queue = [];
|
|
3310
|
+
}
|
|
3311
|
+
reject(error) {
|
|
3312
|
+
this._promise?.reject(error);
|
|
3313
|
+
this._promise = undefined;
|
|
3314
|
+
this.cancel();
|
|
3315
|
+
}
|
|
3316
|
+
get orchestrators() {
|
|
3317
|
+
return this.project.browser.state.orchestrators;
|
|
3318
|
+
}
|
|
3319
|
+
async runTests(method, files) {
|
|
3320
|
+
this._promise ??= createDefer();
|
|
3321
|
+
if (!files.length) {
|
|
3322
|
+
debug?.("no tests found, finishing test run immediately");
|
|
3323
|
+
this._promise.resolve();
|
|
3324
|
+
return this._promise;
|
|
3325
|
+
}
|
|
3326
|
+
this._providedContext = stringify(this.project.getProvidedContext());
|
|
3327
|
+
this._queue.push(...files);
|
|
3328
|
+
this.readySessions.forEach((sessionId) => {
|
|
3329
|
+
if (this._queue.length) {
|
|
3330
|
+
this.readySessions.delete(sessionId);
|
|
3331
|
+
this.runNextTest(method, sessionId);
|
|
3332
|
+
}
|
|
3333
|
+
});
|
|
3334
|
+
if (this.orchestrators.size >= this.options.maxWorkers) {
|
|
3335
|
+
debug?.("all orchestrators are ready, not creating more");
|
|
3336
|
+
return this._promise;
|
|
3337
|
+
}
|
|
3338
|
+
const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length);
|
|
3339
|
+
const promises = [];
|
|
3340
|
+
for (let i = 0; i < workerCount; i++) {
|
|
3341
|
+
const sessionId = crypto.randomUUID();
|
|
3342
|
+
this.project.vitest._browserSessions.sessionIds.add(sessionId);
|
|
3343
|
+
const project = this.project.name;
|
|
3344
|
+
debug?.("[%s] creating session for %s", sessionId, project);
|
|
3345
|
+
const page = this.openPage(sessionId).then(() => {
|
|
3346
|
+
this.runNextTest(method, sessionId);
|
|
3347
|
+
});
|
|
3348
|
+
promises.push(page);
|
|
3349
|
+
}
|
|
3350
|
+
await Promise.all(promises);
|
|
3351
|
+
debug?.("all sessions are created");
|
|
3352
|
+
return this._promise;
|
|
3353
|
+
}
|
|
3354
|
+
async openPage(sessionId) {
|
|
3355
|
+
const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
|
|
3356
|
+
const url = new URL("/", this.options.origin);
|
|
3357
|
+
url.searchParams.set("sessionId", sessionId);
|
|
3358
|
+
const pagePromise = this.project.browser.provider.openPage(sessionId, url.toString());
|
|
3359
|
+
await Promise.all([sessionPromise, pagePromise]);
|
|
3360
|
+
}
|
|
3361
|
+
getOrchestrator(sessionId) {
|
|
3362
|
+
const orchestrator = this.orchestrators.get(sessionId);
|
|
3363
|
+
if (!orchestrator) {
|
|
3364
|
+
throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
3365
|
+
}
|
|
3366
|
+
return orchestrator;
|
|
3367
|
+
}
|
|
3368
|
+
finishSession(sessionId) {
|
|
3369
|
+
this.readySessions.add(sessionId);
|
|
3370
|
+
if (this.readySessions.size === this.orchestrators.size) {
|
|
3371
|
+
this._promise?.resolve();
|
|
3372
|
+
this._promise = undefined;
|
|
3373
|
+
debug?.("[%s] all tests finished running", sessionId);
|
|
3374
|
+
} else {
|
|
3375
|
+
debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
runNextTest(method, sessionId) {
|
|
3379
|
+
const file = this._queue.shift();
|
|
3380
|
+
if (!file) {
|
|
3381
|
+
debug?.("[%s] no more tests to run", sessionId);
|
|
3382
|
+
const isolate = this.project.config.browser.isolate;
|
|
3383
|
+
if (isolate) {
|
|
3384
|
+
this.finishSession(sessionId);
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
const orchestrator = this.getOrchestrator(sessionId);
|
|
3388
|
+
orchestrator.cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
if (!this._promise) {
|
|
3392
|
+
throw new Error(`Unexpected empty queue`);
|
|
3393
|
+
}
|
|
3394
|
+
const orchestrator = this.getOrchestrator(sessionId);
|
|
3395
|
+
debug?.("[%s] run test %s", sessionId, file);
|
|
3396
|
+
this.setBreakpoint(sessionId, file).then(() => {
|
|
3397
|
+
orchestrator.createTesters({
|
|
3398
|
+
method,
|
|
3399
|
+
files: [file],
|
|
3400
|
+
providedContext: this._providedContext || "[{}]"
|
|
3401
|
+
}).then(() => {
|
|
3402
|
+
debug?.("[%s] test %s finished running", sessionId, file);
|
|
3403
|
+
this.runNextTest(method, sessionId);
|
|
3404
|
+
}).catch((error) => {
|
|
3405
|
+
if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
|
|
3406
|
+
this.cancel();
|
|
3407
|
+
this._promise?.resolve();
|
|
3408
|
+
this._promise = undefined;
|
|
3409
|
+
debug?.("[%s] browser connection was closed", sessionId);
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
debug?.("[%s] error during %s test run: %s", sessionId, file, error);
|
|
3413
|
+
this.reject(error);
|
|
3414
|
+
});
|
|
3415
|
+
}).catch((err) => this.reject(err));
|
|
3416
|
+
}
|
|
3417
|
+
async setBreakpoint(sessionId, file) {
|
|
3418
|
+
if (!this.project.config.inspector.waitForDebugger) {
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
const provider = this.project.browser.provider;
|
|
3422
|
+
if (!provider.getCDPSession) {
|
|
3423
|
+
throw new Error("Unable to set breakpoint, CDP not supported");
|
|
3424
|
+
}
|
|
3425
|
+
debug?.("[%s] set breakpoint for %s", sessionId, file);
|
|
3426
|
+
const session = await provider.getCDPSession(sessionId);
|
|
3427
|
+
await session.send("Debugger.enable", {});
|
|
3428
|
+
await session.send("Debugger.setBreakpointByUrl", {
|
|
3429
|
+
lineNumber: 0,
|
|
3430
|
+
urlRegex: escapePathToRegexp(file)
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3315
3434
|
|
|
3316
3435
|
async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
|
|
3317
3436
|
if (project.vitest.version !== version) {
|
|
@@ -3325,6 +3444,7 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
|
|
|
3325
3444
|
const vite = await createViteServer({
|
|
3326
3445
|
...project.options,
|
|
3327
3446
|
base: "/",
|
|
3447
|
+
root: project.config.root,
|
|
3328
3448
|
logLevel,
|
|
3329
3449
|
customLogger: {
|
|
3330
3450
|
...logger,
|