@vitest/browser 5.0.0-beta.6 → 5.0.0-rc.1
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 +10 -10
- package/dist/client/__vitest_browser__/{defineProperty-WHpdAQXR.js → defineProperty-BOtRrjRA.js} +13 -1
- package/dist/client/__vitest_browser__/{orchestrator-pKob2Z-i.js → orchestrator-DJgIeTMM.js} +10 -3
- package/dist/client/__vitest_browser__/{rrweb-snapshot-DkFyu2W8.js → rrweb-snapshot-g5vjbXoV.js} +6 -4
- package/dist/client/__vitest_browser__/{tester-DdZeFrjo.js → tester-Ck66x6M6.js} +58 -27
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/tester.html +2 -2
- package/dist/client/tester/trace.d.ts +3 -3
- package/dist/client.js +14 -14
- package/dist/context.js +6 -2
- package/dist/expect-element.js +30 -30
- package/dist/index.d.ts +7 -1
- package/dist/index.js +558 -557
- package/dist/locators-BTqFw5Up.js +5 -0
- package/dist/locators.js +1 -1
- package/dist/state.js +4 -1
- package/dist/types.d.ts +1 -10
- package/jest-dom.d.ts +5 -5
- package/matchers.d.ts +5 -13
- package/package.json +16 -16
- package/dist/locators-CVQtUwrj.js +0 -5
package/dist/index.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
|
+
import fs, { promises, existsSync, readFileSync, createReadStream } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
1
3
|
import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule, MockerRegistry } from '@vitest/mocker';
|
|
2
4
|
import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '@vitest/mocker/node';
|
|
3
|
-
import c from 'tinyrainbow';
|
|
4
|
-
import { isFileLoadingAllowed, isValidApiRequest, isFileServingAllowed, distDir, rolldownVersion, resolveApiServerConfig, resolveFsAllow, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import fs, { readFileSync, createReadStream, promises, existsSync } from 'node:fs';
|
|
7
|
-
import { createRequire } from 'node:module';
|
|
8
5
|
import { distClientRoot } from '@vitest/ui';
|
|
9
|
-
import { slash as slash$1,
|
|
10
|
-
import MagicString from 'magic-string';
|
|
6
|
+
import { slash as slash$1, deepMerge, cleanUrl as cleanUrl$1, toArray } from '@vitest/utils/helpers';
|
|
11
7
|
import sirv from 'sirv';
|
|
8
|
+
import c from 'tinyrainbow';
|
|
9
|
+
import { isFileLoadingAllowed, createDebugger, isValidApiRequest, isCSSRequest, distDir, rolldownVersion, isFileServingAllowed } from 'vitest/node';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
12
11
|
import crypto from 'node:crypto';
|
|
13
|
-
import
|
|
14
|
-
import 'vite';
|
|
12
|
+
import MagicString from 'magic-string';
|
|
15
13
|
import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
|
|
16
14
|
import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
17
15
|
import { extractSourcemapFromFile } from '@vitest/utils/source-map/node';
|
|
18
16
|
import { resolve as resolve$1, basename as basename$1, dirname as dirname$1 } from 'node:path';
|
|
17
|
+
import { platform } from 'node:os';
|
|
19
18
|
import { PNG } from 'pngjs';
|
|
20
19
|
import { diff } from '@blazediff/core';
|
|
21
20
|
import { WebSocketServer } from 'ws';
|
|
22
21
|
|
|
23
|
-
var version = "5.0.0-beta.6";
|
|
24
|
-
|
|
25
22
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
26
23
|
function normalizeWindowsPath(input = "") {
|
|
27
24
|
if (!input) {
|
|
@@ -212,11 +209,11 @@ const basename = function(p, extension) {
|
|
|
212
209
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
213
210
|
};
|
|
214
211
|
|
|
212
|
+
var version = "5.0.0-rc.1";
|
|
213
|
+
|
|
215
214
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
216
215
|
const distRoot = resolve(pkgRoot, "dist");
|
|
217
216
|
|
|
218
|
-
const API_TOKEN_FILE = ".vitest-secret-token";
|
|
219
|
-
|
|
220
217
|
/// <reference types="../types/index.d.ts" />
|
|
221
218
|
|
|
222
219
|
// (c) 2020-present Andrea Giammarchi
|
|
@@ -512,11 +509,10 @@ var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
|
|
|
512
509
|
}
|
|
513
510
|
];
|
|
514
511
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}(bracketDict || {});
|
|
512
|
+
const bracketDict = {
|
|
513
|
+
'{': '}',
|
|
514
|
+
'[': ']'
|
|
515
|
+
};
|
|
520
516
|
/**
|
|
521
517
|
* Read the next key definition from user input
|
|
522
518
|
*
|
|
@@ -551,16 +547,16 @@ function readPrintableChar(text, pos, context) {
|
|
|
551
547
|
};
|
|
552
548
|
}
|
|
553
549
|
function readTag(text, pos, startBracket, context) {
|
|
550
|
+
var _ref;
|
|
554
551
|
var _text_slice_match, _text_slice_match1;
|
|
555
552
|
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
|
|
556
553
|
pos += releasePreviousModifier.length;
|
|
557
554
|
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
|
|
558
555
|
pos += Number(escapedDescriptor);
|
|
559
|
-
const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match ===
|
|
556
|
+
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];
|
|
560
557
|
assertDescriptor(descriptor, text, pos);
|
|
561
558
|
pos += descriptor.length;
|
|
562
|
-
|
|
563
|
-
const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === undefined ? undefined : _text_slice_match1[0]) !== null && _text_slice_match_ !== undefined ? _text_slice_match_ : '';
|
|
559
|
+
const repeatModifier = (_ref = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === void 0 ? void 0 : _text_slice_match1[0]) !== null && _ref !== void 0 ? _ref : '';
|
|
564
560
|
pos += repeatModifier.length;
|
|
565
561
|
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
|
|
566
562
|
pos += releaseSelfModifier.length;
|
|
@@ -596,7 +592,7 @@ function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
|
|
|
596
592
|
}
|
|
597
593
|
}
|
|
598
594
|
function getErrorMessage(expected, found, text, context) {
|
|
599
|
-
return `Expected ${expected} but found "${found !== null && found !==
|
|
595
|
+
return `Expected ${expected} but found "${found !== null && found !== void 0 ? found : ''}" in "${text}"
|
|
600
596
|
See ${`https://testing-library.com/docs/user-event/keyboard`}
|
|
601
597
|
for more information about how userEvent parses your input.`;
|
|
602
598
|
}
|
|
@@ -613,18 +609,18 @@ function getErrorMessage(expected, found, text, context) {
|
|
|
613
609
|
*/ function parseKeyDef$1(keyboardMap, text) {
|
|
614
610
|
const defs = [];
|
|
615
611
|
do {
|
|
616
|
-
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
617
612
|
var _keyboardMap_find;
|
|
613
|
+
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
618
614
|
const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
|
|
619
615
|
if (type === '[') {
|
|
620
616
|
var _def_code;
|
|
621
|
-
return ((_def_code = def.code) === null || _def_code ===
|
|
617
|
+
return ((_def_code = def.code) === null || _def_code === void 0 ? void 0 : _def_code.toLowerCase()) === descriptor.toLowerCase();
|
|
622
618
|
} else if (type === '{') {
|
|
623
619
|
var _def_key;
|
|
624
|
-
return ((_def_key = def.key) === null || _def_key ===
|
|
620
|
+
return ((_def_key = def.key) === null || _def_key === void 0 ? void 0 : _def_key.toLowerCase()) === descriptor.toLowerCase();
|
|
625
621
|
}
|
|
626
622
|
return def.key === descriptor;
|
|
627
|
-
})) !== null && _keyboardMap_find !==
|
|
623
|
+
})) !== null && _keyboardMap_find !== void 0 ? _keyboardMap_find : {
|
|
628
624
|
key: 'Unknown',
|
|
629
625
|
code: 'Unknown',
|
|
630
626
|
[type === '[' ? 'code' : 'key']: descriptor
|
|
@@ -685,7 +681,7 @@ function assertBrowserFileAccess(project, path) {
|
|
|
685
681
|
}
|
|
686
682
|
}
|
|
687
683
|
function assertBrowserApiWrite(project, path) {
|
|
688
|
-
if (!project.config.
|
|
684
|
+
if (!project.config.api.allowWrite || !project.vitest.config.api.allowWrite) {
|
|
689
685
|
throw new Error(`Cannot modify file "${path}". File writing is disabled because the server is exposed to the internet, see https://vitest.dev/config/browser/api.`);
|
|
690
686
|
}
|
|
691
687
|
}
|
|
@@ -816,16 +812,21 @@ async function resolveTester(globalServer, url, res, next) {
|
|
|
816
812
|
__VITEST_PROVIDED_CONTEXT__: "{}",
|
|
817
813
|
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
818
814
|
});
|
|
819
|
-
const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
|
|
820
815
|
try {
|
|
821
|
-
|
|
822
|
-
|
|
816
|
+
browserProject.testerHtmlTransformed ??= (async () => {
|
|
817
|
+
const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
|
|
818
|
+
const url = join("/@fs/", browserProject.testerFilepath);
|
|
819
|
+
return await browserProject.vite.transformIndexHtml(url, testerHtml);
|
|
820
|
+
})();
|
|
821
|
+
const indexhtml = await browserProject.testerHtmlTransformed;
|
|
823
822
|
const html = replacer(indexhtml, {
|
|
824
823
|
__VITEST_FAVICON__: globalServer.faviconUrl,
|
|
825
824
|
__VITEST_INJECTOR__: injector
|
|
826
825
|
});
|
|
827
826
|
return html;
|
|
828
827
|
} catch (err) {
|
|
828
|
+
// don't cache a rejection — the next request should retry the transform
|
|
829
|
+
browserProject.testerHtmlTransformed = undefined;
|
|
829
830
|
session.fail(err);
|
|
830
831
|
next(err);
|
|
831
832
|
}
|
|
@@ -852,49 +853,41 @@ function createTesterMiddleware(browserServer) {
|
|
|
852
853
|
|
|
853
854
|
const VIRTUAL_ID_CONTEXT = "\0vitest/browser";
|
|
854
855
|
const ID_CONTEXT = "vitest/browser";
|
|
855
|
-
// for libraries that use an older import but are not type checked
|
|
856
|
-
const DEPRECATED_ID_CONTEXT = "@vitest/browser/context";
|
|
857
|
-
const DEPRECATED_VIRTUAL_ID_UTILS = "\0@vitest/browser/utils";
|
|
858
|
-
const DEPRECATED_ID_UTILS = "@vitest/browser/utils";
|
|
859
856
|
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
860
|
-
function BrowserContext(
|
|
857
|
+
function BrowserContext(contribution) {
|
|
861
858
|
return {
|
|
862
859
|
name: "vitest:browser:virtual-module:context",
|
|
863
860
|
enforce: "pre",
|
|
864
|
-
resolveId
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (importer && !importer.includes("/node_modules/")) {
|
|
870
|
-
globalServer.vitest.logger.deprecate(`${importer} tries to load a deprecated "${id}" module. ` + `This import will stop working in the next major version. ` + `Please, use "vitest/browser" instead.`);
|
|
861
|
+
resolveId: {
|
|
862
|
+
order: "pre",
|
|
863
|
+
handler(id) {
|
|
864
|
+
if (id === ID_CONTEXT) {
|
|
865
|
+
return VIRTUAL_ID_CONTEXT;
|
|
871
866
|
}
|
|
872
|
-
return VIRTUAL_ID_CONTEXT;
|
|
873
|
-
}
|
|
874
|
-
if (id === DEPRECATED_ID_UTILS) {
|
|
875
|
-
return DEPRECATED_VIRTUAL_ID_UTILS;
|
|
876
867
|
}
|
|
877
868
|
},
|
|
878
869
|
load(id) {
|
|
870
|
+
const globalServer = contribution.parent;
|
|
879
871
|
if (id === VIRTUAL_ID_CONTEXT) {
|
|
880
872
|
return generateContextFile.call(this, globalServer);
|
|
881
873
|
}
|
|
882
|
-
if (id === DEPRECATED_VIRTUAL_ID_UTILS) {
|
|
883
|
-
return `
|
|
884
|
-
import { utils } from 'vitest/browser'
|
|
885
|
-
export const getElementLocatorSelectors = utils.getElementLocatorSelectors
|
|
886
|
-
export const debug = utils.debug
|
|
887
|
-
export const prettyDOM = utils.prettyDOM
|
|
888
|
-
export const getElementError = utils.getElementError
|
|
889
|
-
`;
|
|
890
|
-
}
|
|
891
874
|
}
|
|
892
875
|
};
|
|
893
876
|
}
|
|
894
877
|
async function generateContextFile(globalServer) {
|
|
895
878
|
const commands = Object.keys(globalServer.commands);
|
|
896
|
-
|
|
897
|
-
|
|
879
|
+
// The provider instance is initialized lazily when a page opens, so reading
|
|
880
|
+
// `child.provider` here races and can be `undefined` before the first page is
|
|
881
|
+
// opened. The provider name is uniform across a project's instances and is
|
|
882
|
+
// already resolved on the config, so read it from there for this shared
|
|
883
|
+
// (cached) context module. The instance is still used below for the
|
|
884
|
+
// preview-only user-event import.
|
|
885
|
+
const child = [...globalServer.children][0];
|
|
886
|
+
const provider = child.provider;
|
|
887
|
+
const providerName = child.config.browser.provider?.name;
|
|
888
|
+
if (!providerName) {
|
|
889
|
+
throw new Error(`Browser provider is not defined for the project "${child.project.name}". This is a bug in Vitest. Please, open a new issue with a reproduction.`);
|
|
890
|
+
}
|
|
898
891
|
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
899
892
|
return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
|
|
900
893
|
}).join("\n");
|
|
@@ -929,256 +922,41 @@ async function getUserEventImport(provider, resolve) {
|
|
|
929
922
|
throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
|
|
930
923
|
}
|
|
931
924
|
return `\
|
|
932
|
-
import { userEvent as
|
|
933
|
-
const _userEventSetup = __vitest_user_event__
|
|
925
|
+
import { userEvent as _userEventSetup } from '${slash$1(`/@fs/${resolved.id}`)}'
|
|
934
926
|
`;
|
|
935
927
|
}
|
|
936
928
|
|
|
937
|
-
|
|
938
|
-
var BrowserPlugin = (parentServer, base = "/") => {
|
|
939
|
-
function isPackageExists(pkg, root) {
|
|
940
|
-
return parentServer.vitest.packageInstaller.isPackageExists?.(pkg, { paths: [root] });
|
|
941
|
-
}
|
|
929
|
+
var BrowserPlugin = (contribution) => {
|
|
942
930
|
return [
|
|
943
|
-
{
|
|
944
|
-
enforce: "pre",
|
|
945
|
-
name: "vitest:browser",
|
|
946
|
-
async configureServer(server) {
|
|
947
|
-
parentServer.setServer(server);
|
|
948
|
-
// eslint-disable-next-line prefer-arrow-callback
|
|
949
|
-
server.middlewares.use(function vitestHeaders(_req, res, next) {
|
|
950
|
-
const headers = server.config.server.headers;
|
|
951
|
-
if (headers) {
|
|
952
|
-
for (const name in headers) {
|
|
953
|
-
res.setHeader(name, headers[name]);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
next();
|
|
957
|
-
});
|
|
958
|
-
// strip _vitest_original query added by importActual so that
|
|
959
|
-
// the plugin pipeline sees the original import id (e.g. virtual modules's load hook).
|
|
960
|
-
server.middlewares.use((req, _res, next) => {
|
|
961
|
-
if (req.url?.includes("_vitest_original") && parentServer.project.config.browser.provider?.name === "playwright") {
|
|
962
|
-
req.url = req.url.replace(/[?&]_vitest_original(?=[&#]|$)/, "").replace(/[?&]ext\b[^&#]*/, "").replace(/\?$/, "");
|
|
963
|
-
}
|
|
964
|
-
next();
|
|
965
|
-
});
|
|
966
|
-
server.middlewares.use(createOrchestratorMiddleware(parentServer));
|
|
967
|
-
server.middlewares.use(createTesterMiddleware(parentServer));
|
|
968
|
-
server.middlewares.use(`${base}favicon.svg`, (_, res) => {
|
|
969
|
-
const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
|
|
970
|
-
res.write(content, "utf-8");
|
|
971
|
-
res.end();
|
|
972
|
-
});
|
|
973
|
-
// Serve coverage HTML at ./coverage if configured
|
|
974
|
-
const coverageHtmlDir = parentServer.vitest.config.coverage?.htmlDir;
|
|
975
|
-
if (coverageHtmlDir) {
|
|
976
|
-
server.middlewares.use("/__vitest_test__/coverage", sirv(coverageHtmlDir, {
|
|
977
|
-
single: true,
|
|
978
|
-
dev: true,
|
|
979
|
-
setHeaders: (res) => {
|
|
980
|
-
const csp = res.getHeader("Content-Security-Policy");
|
|
981
|
-
if (typeof csp === "string") {
|
|
982
|
-
// add frame-ancestors to allow the iframe to be loaded by Vitest,
|
|
983
|
-
// but keep the rest of the CSP
|
|
984
|
-
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
985
|
-
}
|
|
986
|
-
res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
|
|
987
|
-
}
|
|
988
|
-
}));
|
|
989
|
-
}
|
|
990
|
-
server.middlewares.use((req, res, next) => {
|
|
991
|
-
// 9000 mega head move
|
|
992
|
-
// Vite always caches optimized dependencies, but users might mock
|
|
993
|
-
// them in _some_ tests, while keeping original modules in others
|
|
994
|
-
// there is no way to configure that in Vite, so we patch it here
|
|
995
|
-
// to always ignore the cache-control set by Vite in the next middleware
|
|
996
|
-
if (req.url && versionRegexp.test(req.url) && !req.url.includes("chunk-")) {
|
|
997
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
998
|
-
const setHeader = res.setHeader.bind(res);
|
|
999
|
-
res.setHeader = function(name, value) {
|
|
1000
|
-
if (name === "Cache-Control") {
|
|
1001
|
-
return res;
|
|
1002
|
-
}
|
|
1003
|
-
return setHeader(name, value);
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
next();
|
|
1007
|
-
});
|
|
1008
|
-
// handle attachments the same way as in packages/ui/node/index.ts
|
|
1009
|
-
server.middlewares.use((req, res, next) => {
|
|
1010
|
-
if (!req.url) {
|
|
1011
|
-
return next();
|
|
1012
|
-
}
|
|
1013
|
-
const url = new URL(req.url, "http://localhost");
|
|
1014
|
-
if (url.pathname !== "/__vitest_attachment__") {
|
|
1015
|
-
return next();
|
|
1016
|
-
}
|
|
1017
|
-
const path = url.searchParams.get("path");
|
|
1018
|
-
const contentType = url.searchParams.get("contentType");
|
|
1019
|
-
if (!isValidApiRequest(parentServer.config, req) || !contentType || !path) {
|
|
1020
|
-
return next();
|
|
1021
|
-
}
|
|
1022
|
-
const fsPath = decodeURIComponent(path);
|
|
1023
|
-
if (!isFileServingAllowed(parentServer.vite.config, fsPath)) {
|
|
1024
|
-
return next();
|
|
1025
|
-
}
|
|
1026
|
-
try {
|
|
1027
|
-
res.setHeader("content-type", contentType);
|
|
1028
|
-
return createReadStream(fsPath).pipe(res).on("close", () => res.end());
|
|
1029
|
-
} catch (err) {
|
|
1030
|
-
return next(err);
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
},
|
|
1035
931
|
{
|
|
1036
932
|
name: "vitest:browser:tests",
|
|
1037
933
|
enforce: "pre",
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
// the workspace — keeps the optimizeDeps entries symmetrical across
|
|
1044
|
-
// the test/bench project clones so the optimizer doesn't re-scan when
|
|
1045
|
-
// the user switches modes
|
|
1046
|
-
const hasBrowserBenchProject = parentServer.vitest.projects.some((p) => p.config.browser.enabled && p.config.benchmark.enabled);
|
|
1047
|
-
const benchInclude = hasBrowserBenchProject ? project.config.benchmark.include : [];
|
|
1048
|
-
const dir = project.config.dir || project.config.root;
|
|
1049
|
-
const [{ testFiles: browserTestFiles }, browserBenchFiles] = await Promise.all([project.globTestFiles(), benchInclude.length > 0 ? project.globFiles(benchInclude, project.config.benchmark.exclude ?? project.config.exclude, dir) : []]);
|
|
1050
|
-
const setupFiles = toArray(project.config.setupFiles);
|
|
1051
|
-
// replace env values - cannot be reassign at runtime
|
|
1052
|
-
const define = {};
|
|
1053
|
-
for (const env in project.config.env || {}) {
|
|
1054
|
-
const stringValue = JSON.stringify(project.config.env[env]);
|
|
1055
|
-
define[`import.meta.env.${env}`] = stringValue;
|
|
1056
|
-
}
|
|
1057
|
-
const entries = [
|
|
1058
|
-
...new Set([...browserTestFiles, ...browserBenchFiles]),
|
|
1059
|
-
...setupFiles,
|
|
1060
|
-
resolve(distDir, "index.js"),
|
|
1061
|
-
resolve(distDir, "browser.js"),
|
|
1062
|
-
resolve(distDir, "runners.js"),
|
|
1063
|
-
resolve(distDir, "utils.js"),
|
|
1064
|
-
...project.config.snapshotSerializers || []
|
|
1065
|
-
];
|
|
1066
|
-
const exclude = [
|
|
1067
|
-
"vitest",
|
|
1068
|
-
"vitest/browser",
|
|
1069
|
-
"vitest/internal/browser",
|
|
1070
|
-
"vite/module-runner",
|
|
1071
|
-
"@vitest/browser/utils",
|
|
1072
|
-
"@vitest/browser/context",
|
|
1073
|
-
"@vitest/browser/client",
|
|
1074
|
-
"@vitest/utils",
|
|
1075
|
-
"@vitest/utils/source-map",
|
|
1076
|
-
"@vitest/spy",
|
|
1077
|
-
"@vitest/utils/error",
|
|
1078
|
-
"std-env",
|
|
1079
|
-
"tinybench",
|
|
1080
|
-
"tinyspy",
|
|
1081
|
-
"tinyrainbow",
|
|
1082
|
-
"pathe",
|
|
1083
|
-
"msw",
|
|
1084
|
-
"msw/browser"
|
|
1085
|
-
];
|
|
1086
|
-
if (typeof project.config.diff === "string") {
|
|
1087
|
-
entries.push(project.config.diff);
|
|
1088
|
-
}
|
|
1089
|
-
if (parentServer.vitest.coverageProvider) {
|
|
1090
|
-
const coverage = parentServer.vitest._coverageOptions;
|
|
1091
|
-
const provider = coverage.provider;
|
|
1092
|
-
if (provider === "v8") {
|
|
1093
|
-
const path = tryResolve("@vitest/coverage-v8", [parentServer.config.root]);
|
|
1094
|
-
if (path) {
|
|
1095
|
-
entries.push(path);
|
|
1096
|
-
exclude.push("@vitest/coverage-v8/browser");
|
|
1097
|
-
}
|
|
1098
|
-
} else if (provider === "istanbul") {
|
|
1099
|
-
const path = tryResolve("@vitest/coverage-istanbul", [parentServer.config.root]);
|
|
1100
|
-
if (path) {
|
|
1101
|
-
entries.push(path);
|
|
1102
|
-
exclude.push("@vitest/coverage-istanbul");
|
|
1103
|
-
}
|
|
1104
|
-
} else if (provider === "custom" && coverage.customProviderModule) {
|
|
1105
|
-
entries.push(coverage.customProviderModule);
|
|
934
|
+
resolveId: {
|
|
935
|
+
order: "pre",
|
|
936
|
+
handler(id) {
|
|
937
|
+
if (!/\?browserv=\w+$/.test(id)) {
|
|
938
|
+
return;
|
|
1106
939
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
"vitest > magic-string",
|
|
1111
|
-
"vitest > chai"
|
|
1112
|
-
];
|
|
1113
|
-
const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
|
|
1114
|
-
if (provider?.name === "preview") {
|
|
1115
|
-
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
1116
|
-
}
|
|
1117
|
-
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
|
|
1118
|
-
const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
|
|
1119
|
-
if (svelte) {
|
|
1120
|
-
exclude.push("vitest-browser-svelte");
|
|
1121
|
-
}
|
|
1122
|
-
// since we override the resolution in the esbuild plugin, Vite can no longer optimizer it
|
|
1123
|
-
const vue = isPackageExists("vitest-browser-vue", fileRoot);
|
|
1124
|
-
if (vue) {
|
|
1125
|
-
// we override them in the esbuild plugin so optimizer can no longer intercept it
|
|
1126
|
-
include.push("vitest-browser-vue", "vitest-browser-vue > @vue/test-utils", "vitest-browser-vue > @vue/test-utils > @vue/compiler-core");
|
|
1127
|
-
}
|
|
1128
|
-
const vueTestUtils = isPackageExists("@vue/test-utils", fileRoot);
|
|
1129
|
-
if (vueTestUtils) {
|
|
1130
|
-
include.push("@vue/test-utils");
|
|
1131
|
-
}
|
|
1132
|
-
const otelConfig = project.config.experimental.openTelemetry;
|
|
1133
|
-
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
|
|
1134
|
-
entries.push(otelConfig.browserSdkPath);
|
|
1135
|
-
include.push("@opentelemetry/api");
|
|
1136
|
-
} else {
|
|
1137
|
-
exclude.push("@opentelemetry/api");
|
|
1138
|
-
}
|
|
1139
|
-
return {
|
|
1140
|
-
define,
|
|
1141
|
-
resolve: { dedupe: ["vitest"] },
|
|
1142
|
-
optimizeDeps: {
|
|
1143
|
-
entries,
|
|
1144
|
-
exclude,
|
|
1145
|
-
include
|
|
940
|
+
let useId = id.slice(0, id.lastIndexOf("?"));
|
|
941
|
+
if (useId.startsWith("/@fs/")) {
|
|
942
|
+
useId = useId.slice(5);
|
|
1146
943
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
let useId = id.slice(0, id.lastIndexOf("?"));
|
|
1154
|
-
if (useId.startsWith("/@fs/")) {
|
|
1155
|
-
useId = useId.slice(5);
|
|
1156
|
-
}
|
|
1157
|
-
if (/^\w:/.test(useId)) {
|
|
1158
|
-
useId = useId.replace(/\\/g, "/");
|
|
1159
|
-
}
|
|
1160
|
-
return useId;
|
|
1161
|
-
}
|
|
1162
|
-
},
|
|
1163
|
-
{
|
|
1164
|
-
name: "vitest:browser:resolve-virtual",
|
|
1165
|
-
async resolveId(rawId) {
|
|
1166
|
-
if (rawId === "/mockServiceWorker.js") {
|
|
1167
|
-
return this.resolve("msw/mockServiceWorker.js", distRoot, { skipSelf: true });
|
|
944
|
+
if (/^\w:/.test(useId)) {
|
|
945
|
+
useId = useId.replace(/\\/g, "/");
|
|
946
|
+
}
|
|
947
|
+
return useId;
|
|
1168
948
|
}
|
|
1169
949
|
}
|
|
1170
950
|
},
|
|
1171
951
|
{
|
|
1172
952
|
name: "vitest:browser:assets",
|
|
1173
|
-
configureServer(server) {
|
|
1174
|
-
server.middlewares.use("/__vitest__", sirv(distClientRoot));
|
|
1175
|
-
},
|
|
1176
953
|
resolveId(id) {
|
|
1177
954
|
if (id.startsWith("/__vitest_browser__/")) {
|
|
1178
955
|
return resolve(distRoot, "client", id.slice(1));
|
|
1179
956
|
}
|
|
1180
957
|
},
|
|
1181
958
|
transform(code, id) {
|
|
959
|
+
const parentServer = contribution.parent;
|
|
1182
960
|
if (id.includes(parentServer.vite.config.cacheDir) && id.includes("loupe.js")) {
|
|
1183
961
|
// loupe bundle has a nasty require('util') call that leaves a warning in the console
|
|
1184
962
|
const utilRequire = "nodeUtil = require_util();";
|
|
@@ -1186,47 +964,17 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
1186
964
|
}
|
|
1187
965
|
}
|
|
1188
966
|
},
|
|
1189
|
-
BrowserContext(
|
|
967
|
+
BrowserContext(contribution),
|
|
1190
968
|
dynamicImportPlugin({
|
|
1191
969
|
globalThisAccessor: "\"__vitest_browser_runner__\"",
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
return false;
|
|
1195
|
-
}
|
|
970
|
+
// injected only into the `client` environment via the browser contribution
|
|
971
|
+
filter(id) {
|
|
1196
972
|
if (id.includes(distRoot)) {
|
|
1197
973
|
return false;
|
|
1198
974
|
}
|
|
1199
975
|
return true;
|
|
1200
976
|
}
|
|
1201
977
|
}),
|
|
1202
|
-
{
|
|
1203
|
-
name: "vitest:browser:config",
|
|
1204
|
-
enforce: "post",
|
|
1205
|
-
async config(viteConfig) {
|
|
1206
|
-
// Enables using ignore hint for coverage providers with @preserve keyword
|
|
1207
|
-
// Only set esbuild options when not using rolldown-vite (Vite 8+),
|
|
1208
|
-
// which uses oxc for transformation instead of esbuild
|
|
1209
|
-
if (!rolldownVersion && viteConfig.esbuild !== false) {
|
|
1210
|
-
viteConfig.esbuild ||= {};
|
|
1211
|
-
viteConfig.esbuild.legalComments = "inline";
|
|
1212
|
-
}
|
|
1213
|
-
const defaultPort = parentServer.vitest.state._data.browserLastPort++;
|
|
1214
|
-
const api = resolveApiServerConfig(viteConfig.test?.browser || {}, defaultPort);
|
|
1215
|
-
viteConfig.server = {
|
|
1216
|
-
...viteConfig.server,
|
|
1217
|
-
port: defaultPort,
|
|
1218
|
-
...api,
|
|
1219
|
-
middlewareMode: false,
|
|
1220
|
-
open: false
|
|
1221
|
-
};
|
|
1222
|
-
viteConfig.server.fs ??= {};
|
|
1223
|
-
viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
|
|
1224
|
-
viteConfig.server.fs.deny ??= [];
|
|
1225
|
-
viteConfig.server.fs.deny.push(API_TOKEN_FILE);
|
|
1226
|
-
viteConfig.server.fs.allow.push(...resolveFsAllow(parentServer.vitest.config.root, parentServer.vitest.vite.config.configFile), distRoot);
|
|
1227
|
-
return { resolve: { alias: viteConfig.test?.alias } };
|
|
1228
|
-
}
|
|
1229
|
-
},
|
|
1230
978
|
{
|
|
1231
979
|
name: "vitest:browser:in-source-tests",
|
|
1232
980
|
transform: {
|
|
@@ -1259,130 +1007,6 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
1259
1007
|
}
|
|
1260
1008
|
}
|
|
1261
1009
|
},
|
|
1262
|
-
{
|
|
1263
|
-
name: "vitest:browser:transform-tester-html",
|
|
1264
|
-
enforce: "pre",
|
|
1265
|
-
async transformIndexHtml(html, ctx) {
|
|
1266
|
-
const projectBrowser = [...parentServer.children].find((server) => {
|
|
1267
|
-
return ctx.filename === server.testerFilepath;
|
|
1268
|
-
});
|
|
1269
|
-
if (!projectBrowser) {
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
|
|
1273
|
-
const testerTags = [];
|
|
1274
|
-
const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
|
|
1275
|
-
if (!isDefaultTemplate) {
|
|
1276
|
-
const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
|
|
1277
|
-
const testerEntry = manifestContent["tester/tester.html"];
|
|
1278
|
-
testerTags.push({
|
|
1279
|
-
tag: "script",
|
|
1280
|
-
attrs: {
|
|
1281
|
-
type: "module",
|
|
1282
|
-
crossorigin: "",
|
|
1283
|
-
src: `${parentServer.base}${testerEntry.file}`
|
|
1284
|
-
},
|
|
1285
|
-
injectTo: "head"
|
|
1286
|
-
});
|
|
1287
|
-
for (const importName of testerEntry.imports || []) {
|
|
1288
|
-
const entryManifest = manifestContent[importName];
|
|
1289
|
-
if (entryManifest) {
|
|
1290
|
-
testerTags.push({
|
|
1291
|
-
tag: "link",
|
|
1292
|
-
attrs: {
|
|
1293
|
-
href: `${parentServer.base}${entryManifest.file}`,
|
|
1294
|
-
rel: "modulepreload",
|
|
1295
|
-
crossorigin: ""
|
|
1296
|
-
},
|
|
1297
|
-
injectTo: "head"
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
} else {
|
|
1302
|
-
// inject the reset style only in the default template,
|
|
1303
|
-
// allowing users to customize the style in their own template
|
|
1304
|
-
testerTags.push({
|
|
1305
|
-
tag: "style",
|
|
1306
|
-
children: `
|
|
1307
|
-
html {
|
|
1308
|
-
padding: 0;
|
|
1309
|
-
margin: 0;
|
|
1310
|
-
}
|
|
1311
|
-
body {
|
|
1312
|
-
padding: 0;
|
|
1313
|
-
margin: 0;
|
|
1314
|
-
min-height: 100vh;
|
|
1315
|
-
}`,
|
|
1316
|
-
injectTo: "head"
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
return [
|
|
1320
|
-
{
|
|
1321
|
-
tag: "script",
|
|
1322
|
-
children: "{__VITEST_INJECTOR__}",
|
|
1323
|
-
injectTo: "head-prepend"
|
|
1324
|
-
},
|
|
1325
|
-
{
|
|
1326
|
-
tag: "script",
|
|
1327
|
-
children: stateJs,
|
|
1328
|
-
injectTo: "head-prepend"
|
|
1329
|
-
},
|
|
1330
|
-
{
|
|
1331
|
-
tag: "script",
|
|
1332
|
-
attrs: {
|
|
1333
|
-
type: "module",
|
|
1334
|
-
src: parentServer.errorCatcherUrl
|
|
1335
|
-
},
|
|
1336
|
-
injectTo: "head"
|
|
1337
|
-
},
|
|
1338
|
-
{
|
|
1339
|
-
tag: "script",
|
|
1340
|
-
attrs: {
|
|
1341
|
-
type: "module",
|
|
1342
|
-
src: parentServer.matchersUrl
|
|
1343
|
-
},
|
|
1344
|
-
injectTo: "head"
|
|
1345
|
-
},
|
|
1346
|
-
...parentServer.initScripts.map((script) => ({
|
|
1347
|
-
tag: "script",
|
|
1348
|
-
attrs: {
|
|
1349
|
-
type: "module",
|
|
1350
|
-
src: join("/@fs/", script)
|
|
1351
|
-
},
|
|
1352
|
-
injectTo: "head"
|
|
1353
|
-
})),
|
|
1354
|
-
...testerTags
|
|
1355
|
-
].filter((s) => s != null);
|
|
1356
|
-
}
|
|
1357
|
-
},
|
|
1358
|
-
{
|
|
1359
|
-
name: "vitest:browser:support-testing-library",
|
|
1360
|
-
enforce: "pre",
|
|
1361
|
-
config() {
|
|
1362
|
-
const rolldownPlugin = {
|
|
1363
|
-
name: "vue-test-utils-rewrite",
|
|
1364
|
-
resolveId: {
|
|
1365
|
-
filter: { id: /^@vue\/(test-utils|compiler-core)$/ },
|
|
1366
|
-
handler(source, importer) {
|
|
1367
|
-
const resolved = getRequire().resolve(source, { paths: [importer] });
|
|
1368
|
-
return resolved;
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
};
|
|
1372
|
-
const esbuildPlugin = {
|
|
1373
|
-
name: "test-utils-rewrite",
|
|
1374
|
-
setup(build) {
|
|
1375
|
-
// test-utils: resolve to CJS instead of the browser because the browser version expects a global Vue object
|
|
1376
|
-
// compiler-core: only CJS version allows slots as strings
|
|
1377
|
-
build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
|
|
1378
|
-
const resolved = getRequire().resolve(args.path, { paths: [args.importer] });
|
|
1379
|
-
return { path: resolved };
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
};
|
|
1383
|
-
return { optimizeDeps: rolldownVersion ? { rolldownOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
|
|
1384
|
-
}
|
|
1385
|
-
},
|
|
1386
1010
|
{
|
|
1387
1011
|
name: "vitest:browser:__vitest_browser_import_meta_env_init__",
|
|
1388
1012
|
transform: { handler(code) {
|
|
@@ -1395,27 +1019,14 @@ body {
|
|
|
1395
1019
|
}
|
|
1396
1020
|
];
|
|
1397
1021
|
};
|
|
1398
|
-
function tryResolve(path, paths) {
|
|
1399
|
-
try {
|
|
1400
|
-
const _require = getRequire();
|
|
1401
|
-
return _require.resolve(path, { paths });
|
|
1402
|
-
} catch {
|
|
1403
|
-
return undefined;
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
let _require;
|
|
1407
|
-
function getRequire() {
|
|
1408
|
-
if (!_require) {
|
|
1409
|
-
_require = createRequire(import.meta.url);
|
|
1410
|
-
}
|
|
1411
|
-
return _require;
|
|
1412
|
-
}
|
|
1413
1022
|
const postfixRE = /[?#].*$/;
|
|
1414
1023
|
function cleanUrl(url) {
|
|
1415
1024
|
return url.replace(postfixRE, "");
|
|
1416
1025
|
}
|
|
1417
1026
|
|
|
1418
1027
|
class BrowserServerCDPHandler {
|
|
1028
|
+
session;
|
|
1029
|
+
tester;
|
|
1419
1030
|
listenerIds = {};
|
|
1420
1031
|
listeners = {};
|
|
1421
1032
|
constructor(session, tester) {
|
|
@@ -1455,19 +1066,6 @@ class BrowserServerCDPHandler {
|
|
|
1455
1066
|
}
|
|
1456
1067
|
}
|
|
1457
1068
|
|
|
1458
|
-
const _startV8Coverage = async (context) => {
|
|
1459
|
-
const session = await context.__ensureCDPHandler();
|
|
1460
|
-
await session.send("Profiler.enable");
|
|
1461
|
-
await session.send("Profiler.startPreciseCoverage", {
|
|
1462
|
-
callCount: true,
|
|
1463
|
-
detailed: true
|
|
1464
|
-
});
|
|
1465
|
-
};
|
|
1466
|
-
const _takeV8Coverage = async (context) => {
|
|
1467
|
-
const session = await context.__ensureCDPHandler();
|
|
1468
|
-
return session.send("Profiler.takePreciseCoverage");
|
|
1469
|
-
};
|
|
1470
|
-
|
|
1471
1069
|
const types = {
|
|
1472
1070
|
'application/andrew-inset': ['ez'],
|
|
1473
1071
|
'application/appinstaller': ['appinstaller'],
|
|
@@ -2072,6 +1670,7 @@ function getComparator(comparator, context) {
|
|
|
2072
1670
|
|
|
2073
1671
|
const defaultOptions = {
|
|
2074
1672
|
comparatorName: "pixelmatch",
|
|
1673
|
+
// these are handled by each comparator on its own
|
|
2075
1674
|
comparatorOptions: {},
|
|
2076
1675
|
screenshotOptions: {
|
|
2077
1676
|
animations: "disabled",
|
|
@@ -2126,6 +1725,7 @@ function resolveOptions({ context, name, options, testName }) {
|
|
|
2126
1725
|
resolvedOptions,
|
|
2127
1726
|
paths: {
|
|
2128
1727
|
reference: resolvedOptions.resolveScreenshotPath(resolvePathData),
|
|
1728
|
+
// lazily initialize this, as it might not be needed at all
|
|
2129
1729
|
get diffs() {
|
|
2130
1730
|
const diffs = {
|
|
2131
1731
|
reference: resolvedOptions.resolveDiffPath({
|
|
@@ -2371,6 +1971,7 @@ async function determineOutcome({ comparator, comparatorOptions, paths, referenc
|
|
|
2371
1971
|
diff: comparisonResult.diff && {
|
|
2372
1972
|
image: {
|
|
2373
1973
|
data: comparisonResult.diff,
|
|
1974
|
+
// `comparator` only returns pixel data; diff dimensions always match reference
|
|
2374
1975
|
metadata: reference.metadata
|
|
2375
1976
|
},
|
|
2376
1977
|
path: paths.diffs.diff
|
|
@@ -2606,8 +2207,7 @@ var builtinCommands = {
|
|
|
2606
2207
|
readFile,
|
|
2607
2208
|
removeFile,
|
|
2608
2209
|
writeFile,
|
|
2609
|
-
|
|
2610
|
-
__vitest_takeV8Coverage: _takeV8Coverage,
|
|
2210
|
+
// private commands
|
|
2611
2211
|
__vitest_markTrace: _markTrace,
|
|
2612
2212
|
__vitest_groupTraceStart: _groupTraceStart,
|
|
2613
2213
|
__vitest_groupTraceEnd: _groupTraceEnd,
|
|
@@ -2623,8 +2223,15 @@ class BrowserServerState {
|
|
|
2623
2223
|
}
|
|
2624
2224
|
|
|
2625
2225
|
class ProjectBrowser {
|
|
2226
|
+
parent;
|
|
2227
|
+
project;
|
|
2228
|
+
base;
|
|
2626
2229
|
testerHtml;
|
|
2627
2230
|
testerFilepath;
|
|
2231
|
+
// the tester template is read once per server, so the transformed HTML
|
|
2232
|
+
// is stable too — computing it for every iframe request pays the whole
|
|
2233
|
+
// transformIndexHtml plugin pipeline each time
|
|
2234
|
+
testerHtmlTransformed;
|
|
2628
2235
|
provider;
|
|
2629
2236
|
vitest;
|
|
2630
2237
|
vite;
|
|
@@ -2706,6 +2313,7 @@ function wrapConfig(config) {
|
|
|
2706
2313
|
}
|
|
2707
2314
|
|
|
2708
2315
|
class ParentBrowserProject {
|
|
2316
|
+
base;
|
|
2709
2317
|
orchestratorScripts;
|
|
2710
2318
|
faviconUrl;
|
|
2711
2319
|
prefixOrchestratorUrl;
|
|
@@ -2726,21 +2334,27 @@ class ParentBrowserProject {
|
|
|
2726
2334
|
config;
|
|
2727
2335
|
// cache for non-vite source maps
|
|
2728
2336
|
sourceMapCache = new Map();
|
|
2729
|
-
constructor(
|
|
2730
|
-
this.project = project;
|
|
2337
|
+
constructor(ctx, base) {
|
|
2731
2338
|
this.base = base;
|
|
2732
|
-
this.vitest =
|
|
2733
|
-
this.config =
|
|
2339
|
+
this.vitest = ctx.vitest;
|
|
2340
|
+
this.config = ctx.config;
|
|
2734
2341
|
this.stackTraceOptions = {
|
|
2735
|
-
frameFilter:
|
|
2342
|
+
frameFilter: this.config.onStackTrace,
|
|
2736
2343
|
getSourceMap: (id) => {
|
|
2737
2344
|
if (this.sourceMapCache.has(id)) {
|
|
2738
2345
|
return this.sourceMapCache.get(id);
|
|
2739
2346
|
}
|
|
2740
2347
|
const result = this.vite.moduleGraph.getModuleById(id)?.transformResult;
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2348
|
+
const filePath = id.split("?")[0];
|
|
2349
|
+
// prefer the map stored on disk when the transform pipeline can't
|
|
2350
|
+
// provide a usable one:
|
|
2351
|
+
// - pre-bundled deps: the pipeline map resolves back into the
|
|
2352
|
+
// optimizer cache, while the map esbuild wrote next to the file
|
|
2353
|
+
// points at the real package sources
|
|
2354
|
+
// - an empty `mappings` means the map was intentionally not served
|
|
2355
|
+
// to the browser (`vitest:browser:framework-sourcemaps`), but disk
|
|
2356
|
+
// is still the source of truth for error stack traces
|
|
2357
|
+
if (result && (!result.map || result.map.mappings === "" || filePath.startsWith(this.vite.config.cacheDir))) {
|
|
2744
2358
|
const extracted = extractSourcemapFromFile(result.code, filePath);
|
|
2745
2359
|
this.sourceMapCache.set(id, extracted?.map);
|
|
2746
2360
|
return extracted?.map;
|
|
@@ -2771,11 +2385,11 @@ class ParentBrowserProject {
|
|
|
2771
2385
|
this.commands[name] ??= command;
|
|
2772
2386
|
}
|
|
2773
2387
|
// validate names because they can't be used as identifiers
|
|
2774
|
-
for (const command in
|
|
2388
|
+
for (const command in this.config.browser.commands) {
|
|
2775
2389
|
if (!/^[a-z_$][\w$]*$/i.test(command)) {
|
|
2776
2390
|
throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
2777
2391
|
}
|
|
2778
|
-
this.commands[command] =
|
|
2392
|
+
this.commands[command] = this.config.browser.commands[command];
|
|
2779
2393
|
}
|
|
2780
2394
|
this.prefixTesterUrl = `${base || "/"}`;
|
|
2781
2395
|
this.prefixOrchestratorUrl = `${base}__vitest_test__/`;
|
|
@@ -2783,7 +2397,7 @@ class ParentBrowserProject {
|
|
|
2783
2397
|
this.manifest = (async () => {
|
|
2784
2398
|
return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
|
|
2785
2399
|
})().then((manifest) => this.manifest = manifest);
|
|
2786
|
-
this.orchestratorHtml = (
|
|
2400
|
+
this.orchestratorHtml = (this.config.browser.ui ? readFile$1(resolve(distClientRoot, "index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
|
|
2787
2401
|
this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
|
|
2788
2402
|
this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
|
|
2789
2403
|
this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
|
|
@@ -2882,11 +2496,6 @@ class ParentBrowserProject {
|
|
|
2882
2496
|
}
|
|
2883
2497
|
}
|
|
2884
2498
|
|
|
2885
|
-
//#region src/messages.ts
|
|
2886
|
-
const TYPE_REQUEST = "q";
|
|
2887
|
-
const TYPE_RESPONSE = "s";
|
|
2888
|
-
|
|
2889
|
-
//#endregion
|
|
2890
2499
|
//#region src/utils.ts
|
|
2891
2500
|
function createPromiseWithResolvers() {
|
|
2892
2501
|
let resolve;
|
|
@@ -2909,7 +2518,6 @@ function nanoid(size = 21) {
|
|
|
2909
2518
|
return id;
|
|
2910
2519
|
}
|
|
2911
2520
|
|
|
2912
|
-
//#endregion
|
|
2913
2521
|
//#region src/main.ts
|
|
2914
2522
|
const DEFAULT_TIMEOUT = 6e4;
|
|
2915
2523
|
const defaultSerialize = (i) => i;
|
|
@@ -2926,7 +2534,7 @@ function createBirpc($functions, options) {
|
|
|
2926
2534
|
const req = {
|
|
2927
2535
|
m: method,
|
|
2928
2536
|
a: args,
|
|
2929
|
-
t:
|
|
2537
|
+
t: "q"
|
|
2930
2538
|
};
|
|
2931
2539
|
if (optional) req.o = true;
|
|
2932
2540
|
const send = async (_req) => post(serialize(_req));
|
|
@@ -2980,7 +2588,7 @@ function createBirpc($functions, options) {
|
|
|
2980
2588
|
$call: (method, ...args) => _call(method, args, false),
|
|
2981
2589
|
$callOptional: (method, ...args) => _call(method, args, false, true),
|
|
2982
2590
|
$callEvent: (method, ...args) => _call(method, args, true),
|
|
2983
|
-
$callRaw: (options
|
|
2591
|
+
$callRaw: (options) => _call(options.method, options.args, options.event, options.optional),
|
|
2984
2592
|
$rejectPendingCalls,
|
|
2985
2593
|
get $closed() {
|
|
2986
2594
|
return $closed;
|
|
@@ -2992,7 +2600,7 @@ function createBirpc($functions, options) {
|
|
|
2992
2600
|
$functions
|
|
2993
2601
|
};
|
|
2994
2602
|
if (proxify) rpc = new Proxy({}, { get(_, method) {
|
|
2995
|
-
if (Object.
|
|
2603
|
+
if (Object.hasOwn(builtinMethods, method)) return builtinMethods[method];
|
|
2996
2604
|
if (method === "then" && !eventNames.includes("then") && !("then" in $functions)) return void 0;
|
|
2997
2605
|
const sendEvent = (...args) => _call(method, args, true);
|
|
2998
2606
|
if (eventNames.includes(method)) {
|
|
@@ -3036,7 +2644,7 @@ function createBirpc($functions, options) {
|
|
|
3036
2644
|
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
|
|
3037
2645
|
return;
|
|
3038
2646
|
}
|
|
3039
|
-
if (msg.t ===
|
|
2647
|
+
if (msg.t === "q") {
|
|
3040
2648
|
const { m: method, a: args, o: optional } = msg;
|
|
3041
2649
|
let result, error;
|
|
3042
2650
|
let fn = await (resolver ? resolver.call(rpc, method, $functions[method]) : $functions[method]);
|
|
@@ -3053,7 +2661,7 @@ function createBirpc($functions, options) {
|
|
|
3053
2661
|
}
|
|
3054
2662
|
if (!error) try {
|
|
3055
2663
|
await post(serialize({
|
|
3056
|
-
t:
|
|
2664
|
+
t: "s",
|
|
3057
2665
|
i: msg.i,
|
|
3058
2666
|
r: result
|
|
3059
2667
|
}), ...extra);
|
|
@@ -3064,7 +2672,7 @@ function createBirpc($functions, options) {
|
|
|
3064
2672
|
}
|
|
3065
2673
|
try {
|
|
3066
2674
|
await post(serialize({
|
|
3067
|
-
t:
|
|
2675
|
+
t: "s",
|
|
3068
2676
|
i: msg.i,
|
|
3069
2677
|
e: error
|
|
3070
2678
|
}), ...extra);
|
|
@@ -3161,14 +2769,14 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3161
2769
|
}
|
|
3162
2770
|
}
|
|
3163
2771
|
function canWrite(project) {
|
|
3164
|
-
return project.config.
|
|
2772
|
+
return project.config.api.allowWrite && project.vitest.config.api.allowWrite;
|
|
3165
2773
|
}
|
|
3166
2774
|
function isCdpAllowed(project) {
|
|
3167
|
-
return project.config.api.allowExec && project.
|
|
2775
|
+
return project.config.api.allowExec && project.vitest.config.api.allowExec && project.config.api.allowWrite && project.vitest.config.api.allowWrite;
|
|
3168
2776
|
}
|
|
3169
2777
|
function assertCdpAllowed(project) {
|
|
3170
2778
|
if (!isCdpAllowed(project)) {
|
|
3171
|
-
throw new Error(`Cannot use CDP because browser API write or exec operations are disabled. See https://vitest.dev/config/
|
|
2779
|
+
throw new Error(`Cannot use CDP because browser API write or exec operations are disabled. See https://vitest.dev/config/api.`);
|
|
3172
2780
|
}
|
|
3173
2781
|
}
|
|
3174
2782
|
function setupClient(project, rpcId, ws, options) {
|
|
@@ -3204,13 +2812,13 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3204
2812
|
if (!canWrite(project)) {
|
|
3205
2813
|
if (artifact.type === "internal:annotation" && artifact.annotation.attachment) {
|
|
3206
2814
|
artifact.annotation.attachment = undefined;
|
|
3207
|
-
vitest.logger.error(`[vitest] Cannot record annotation attachment because file writing is disabled. See https://vitest.dev/config/
|
|
2815
|
+
vitest.logger.error(`[vitest] Cannot record annotation attachment because file writing is disabled. See https://vitest.dev/config/api.`);
|
|
3208
2816
|
}
|
|
3209
2817
|
// remove attachments if cannot write
|
|
3210
2818
|
if (artifact.attachments?.length) {
|
|
3211
2819
|
const attachments = artifact.attachments.map((n) => n.path).filter((r) => !!r).join("\", \"");
|
|
3212
2820
|
artifact.attachments = [];
|
|
3213
|
-
vitest.logger.error(`[vitest] Cannot record attachments ("${attachments}") because file writing is disabled, removing attachments from artifact "${artifact.type}". See https://vitest.dev/config/
|
|
2821
|
+
vitest.logger.error(`[vitest] Cannot record attachments ("${attachments}") because file writing is disabled, removing attachments from artifact "${artifact.type}". See https://vitest.dev/config/api.`);
|
|
3214
2822
|
}
|
|
3215
2823
|
} else {
|
|
3216
2824
|
// attachment files are copied into `attachmentsDir`, so confine
|
|
@@ -3234,7 +2842,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3234
2842
|
},
|
|
3235
2843
|
async writeBenchmarkResult(relativePath, data) {
|
|
3236
2844
|
if (!canWrite(project)) {
|
|
3237
|
-
vitest.logger.error(`[vitest] Cannot write benchmark artifact "${relativePath}" because file writing is disabled. See https://vitest.dev/config/
|
|
2845
|
+
vitest.logger.error(`[vitest] Cannot write benchmark artifact "${relativePath}" because file writing is disabled. See https://vitest.dev/config/api.`);
|
|
3238
2846
|
return;
|
|
3239
2847
|
}
|
|
3240
2848
|
checkFileAccess(project.benchmark.resolve(relativePath));
|
|
@@ -3276,7 +2884,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3276
2884
|
async saveSnapshotFile(id, content) {
|
|
3277
2885
|
checkFileAccess(id);
|
|
3278
2886
|
if (!canWrite(project)) {
|
|
3279
|
-
vitest.logger.error(`[vitest] Cannot save snapshot file "${id}". File writing is disabled because server is exposed to the internet, see https://vitest.dev/config/
|
|
2887
|
+
vitest.logger.error(`[vitest] Cannot save snapshot file "${id}". File writing is disabled because server is exposed to the internet, see https://vitest.dev/config/api.`);
|
|
3280
2888
|
return;
|
|
3281
2889
|
}
|
|
3282
2890
|
await promises.mkdir(dirname(id), { recursive: true });
|
|
@@ -3285,7 +2893,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3285
2893
|
async removeSnapshotFile(id) {
|
|
3286
2894
|
checkFileAccess(id);
|
|
3287
2895
|
if (!canWrite(project)) {
|
|
3288
|
-
vitest.logger.error(`[vitest] Cannot remove snapshot file "${id}". File writing is disabled because server is exposed to the internet, see https://vitest.dev/config/
|
|
2896
|
+
vitest.logger.error(`[vitest] Cannot remove snapshot file "${id}". File writing is disabled because server is exposed to the internet, see https://vitest.dev/config/api.`);
|
|
3289
2897
|
return;
|
|
3290
2898
|
}
|
|
3291
2899
|
if (!existsSync(id)) {
|
|
@@ -3410,6 +3018,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3410
3018
|
}
|
|
3411
3019
|
return mocker.delete(sessionId, id);
|
|
3412
3020
|
},
|
|
3021
|
+
// CDP
|
|
3413
3022
|
async sendCdpEvent(sessionId, event, payload) {
|
|
3414
3023
|
assertCdpAllowed(project);
|
|
3415
3024
|
const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
|
|
@@ -3466,66 +3075,458 @@ function stringifyReplace(key, value) {
|
|
|
3466
3075
|
function defineBrowserCommand(fn) {
|
|
3467
3076
|
return fn;
|
|
3468
3077
|
}
|
|
3469
|
-
const
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3078
|
+
const versionRegexp = /(?:\?|&)v=\w{8}/;
|
|
3079
|
+
// pin a Cache-Control value and stop Vite's later middleware from overriding it
|
|
3080
|
+
function pinCacheControl(res, value) {
|
|
3081
|
+
res.setHeader("Cache-Control", value);
|
|
3082
|
+
const setHeader = res.setHeader.bind(res);
|
|
3083
|
+
res.setHeader = function(name, ...args) {
|
|
3084
|
+
if (name === "Cache-Control") {
|
|
3085
|
+
return res;
|
|
3086
|
+
}
|
|
3087
|
+
return setHeader(name, ...args);
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* The browser provider's `serverFactory`. Returns a `BrowserServerContribution`
|
|
3092
|
+
* that Vitest core uses to build the SINGLE Vite server shared by `project.vite`
|
|
3093
|
+
* and `project.browser.vite`. This factory does NOT create a server (core does);
|
|
3094
|
+
* it only contributes config, plugins, the parent factory, and the RPC setup.
|
|
3095
|
+
*/
|
|
3096
|
+
const createBrowserServer = async () => {
|
|
3479
3097
|
const mockerRegistry = new MockerRegistry();
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3098
|
+
const contribution = {
|
|
3099
|
+
async transformIndexHtml(ctx) {
|
|
3100
|
+
const parentServer = contribution.parent;
|
|
3101
|
+
const projectBrowser = [...parentServer.children].find((server) => {
|
|
3102
|
+
return ctx.filename === server.testerFilepath;
|
|
3103
|
+
});
|
|
3104
|
+
if (!projectBrowser) {
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3107
|
+
const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
|
|
3108
|
+
const testerTags = [];
|
|
3109
|
+
const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
|
|
3110
|
+
if (!isDefaultTemplate) {
|
|
3111
|
+
const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
|
|
3112
|
+
const testerEntry = manifestContent["tester/tester.html"];
|
|
3113
|
+
testerTags.push({
|
|
3114
|
+
tag: "script",
|
|
3115
|
+
attrs: {
|
|
3116
|
+
type: "module",
|
|
3117
|
+
crossorigin: "",
|
|
3118
|
+
src: `${parentServer.base}${testerEntry.file}`
|
|
3119
|
+
},
|
|
3120
|
+
injectTo: "head"
|
|
3121
|
+
});
|
|
3122
|
+
for (const importName of testerEntry.imports || []) {
|
|
3123
|
+
const entryManifest = manifestContent[importName];
|
|
3124
|
+
if (entryManifest) {
|
|
3125
|
+
testerTags.push({
|
|
3126
|
+
tag: "link",
|
|
3127
|
+
attrs: {
|
|
3128
|
+
href: `${parentServer.base}${entryManifest.file}`,
|
|
3129
|
+
rel: "modulepreload",
|
|
3130
|
+
crossorigin: ""
|
|
3131
|
+
},
|
|
3132
|
+
injectTo: "head"
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
} else {
|
|
3137
|
+
// inject the reset style only in the default template,
|
|
3138
|
+
// allowing users to customize the style in their own template
|
|
3139
|
+
testerTags.push({
|
|
3140
|
+
tag: "style",
|
|
3141
|
+
children: `
|
|
3142
|
+
html {
|
|
3143
|
+
padding: 0;
|
|
3144
|
+
margin: 0;
|
|
3145
|
+
}
|
|
3146
|
+
body {
|
|
3147
|
+
padding: 0;
|
|
3148
|
+
margin: 0;
|
|
3149
|
+
min-height: 100vh;
|
|
3150
|
+
}`,
|
|
3151
|
+
injectTo: "head"
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
return [
|
|
3155
|
+
{
|
|
3156
|
+
tag: "script",
|
|
3157
|
+
children: "{__VITEST_INJECTOR__}",
|
|
3158
|
+
injectTo: "head-prepend"
|
|
3159
|
+
},
|
|
3160
|
+
{
|
|
3161
|
+
tag: "script",
|
|
3162
|
+
children: stateJs,
|
|
3163
|
+
injectTo: "head-prepend"
|
|
3164
|
+
},
|
|
3165
|
+
{
|
|
3166
|
+
tag: "script",
|
|
3167
|
+
attrs: {
|
|
3168
|
+
type: "module",
|
|
3169
|
+
src: parentServer.errorCatcherUrl
|
|
3170
|
+
},
|
|
3171
|
+
injectTo: "head"
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
tag: "script",
|
|
3175
|
+
attrs: {
|
|
3176
|
+
type: "module",
|
|
3177
|
+
src: parentServer.matchersUrl
|
|
3178
|
+
},
|
|
3179
|
+
injectTo: "head"
|
|
3180
|
+
},
|
|
3181
|
+
...parentServer.initScripts.map((script) => ({
|
|
3182
|
+
tag: "script",
|
|
3183
|
+
attrs: {
|
|
3184
|
+
type: "module",
|
|
3185
|
+
src: join("/@fs/", script)
|
|
3186
|
+
},
|
|
3187
|
+
injectTo: "head"
|
|
3188
|
+
})),
|
|
3189
|
+
...testerTags
|
|
3190
|
+
].filter((s) => s != null);
|
|
3191
|
+
},
|
|
3192
|
+
configureServer(server) {
|
|
3193
|
+
const parentServer = contribution.parent;
|
|
3194
|
+
parentServer.setServer(server);
|
|
3195
|
+
// eslint-disable-next-line prefer-arrow-callback
|
|
3196
|
+
server.middlewares.use(function vitestHeaders(_req, res, next) {
|
|
3197
|
+
const headers = server.config.server.headers;
|
|
3198
|
+
if (headers) {
|
|
3199
|
+
for (const name in headers) {
|
|
3200
|
+
res.setHeader(name, headers[name]);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
next();
|
|
3204
|
+
});
|
|
3205
|
+
// strip _vitest_original query added by importActual so that
|
|
3206
|
+
// the plugin pipeline sees the original import id (e.g. virtual modules's load hook).
|
|
3207
|
+
server.middlewares.use((req, _res, next) => {
|
|
3208
|
+
if (req.url?.includes("_vitest_original") && parentServer.config.browser.provider?.name === "playwright") {
|
|
3209
|
+
req.url = req.url.replace(/[?&]_vitest_original(?=[&#]|$)/, "").replace(/[?&]ext\b[^&#]*/, "").replace(/\?$/, "");
|
|
3210
|
+
}
|
|
3211
|
+
next();
|
|
3212
|
+
});
|
|
3213
|
+
server.middlewares.use(createOrchestratorMiddleware(parentServer));
|
|
3214
|
+
server.middlewares.use(createTesterMiddleware(parentServer));
|
|
3215
|
+
server.middlewares.use(`/favicon.svg`, (_, res) => {
|
|
3216
|
+
const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
|
|
3217
|
+
res.write(content, "utf-8");
|
|
3218
|
+
res.end();
|
|
3219
|
+
});
|
|
3220
|
+
// Serve coverage HTML at ./coverage if configured
|
|
3221
|
+
const coverageHtmlDir = parentServer.vitest.config.coverage?.htmlDir;
|
|
3222
|
+
if (coverageHtmlDir) {
|
|
3223
|
+
server.middlewares.use("/__vitest_test__/coverage", sirv(coverageHtmlDir, {
|
|
3224
|
+
single: true,
|
|
3225
|
+
dev: true,
|
|
3226
|
+
setHeaders: (res) => {
|
|
3227
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
3228
|
+
if (typeof csp === "string") {
|
|
3229
|
+
// add frame-ancestors to allow the iframe to be loaded by Vitest,
|
|
3230
|
+
// but keep the rest of the CSP
|
|
3231
|
+
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
3232
|
+
}
|
|
3233
|
+
res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
|
|
3234
|
+
}
|
|
3235
|
+
}));
|
|
3236
|
+
}
|
|
3237
|
+
// vitest's own dist files only change with the vitest version and
|
|
3238
|
+
// browser contexts don't outlive the process, so there is no reason
|
|
3239
|
+
// to revalidate them in every tester iframe. Skipped for persistent
|
|
3240
|
+
// contexts and preview providers — their disk cache would survive a vitest upgrade
|
|
3241
|
+
const persistentContext = (parentServer.config.browser.provider?.options)?.persistentContext;
|
|
3242
|
+
const immutablePrefixes = persistentContext || parentServer.config.browser.provider?.name === "preview" ? [] : [
|
|
3243
|
+
"/__vitest_browser__/",
|
|
3244
|
+
`/@fs${distDir}`,
|
|
3245
|
+
`/@fs${distRoot}`
|
|
3246
|
+
];
|
|
3247
|
+
server.middlewares.use((req, res, next) => {
|
|
3248
|
+
const url = req.url;
|
|
3249
|
+
if (url) {
|
|
3250
|
+
if (immutablePrefixes.some((prefix) => url.startsWith(prefix))) {
|
|
3251
|
+
pinCacheControl(res, "public,max-age=31536000,immutable");
|
|
3252
|
+
} else if (versionRegexp.test(url) && !url.includes("chunk-")) {
|
|
3253
|
+
pinCacheControl(res, "no-cache");
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
next();
|
|
3257
|
+
});
|
|
3258
|
+
// handle attachments the same way as in packages/ui/node/index.ts
|
|
3259
|
+
server.middlewares.use((req, res, next) => {
|
|
3260
|
+
if (!req.url) {
|
|
3261
|
+
return next();
|
|
3262
|
+
}
|
|
3263
|
+
const url = new URL(req.url, "http://localhost");
|
|
3264
|
+
if (url.pathname !== "/__vitest_attachment__") {
|
|
3265
|
+
return next();
|
|
3266
|
+
}
|
|
3267
|
+
const path = url.searchParams.get("path");
|
|
3268
|
+
const contentType = url.searchParams.get("contentType");
|
|
3269
|
+
if (!isValidApiRequest(parentServer.config, req) || !contentType || !path) {
|
|
3270
|
+
return next();
|
|
3271
|
+
}
|
|
3272
|
+
const fsPath = decodeURIComponent(path);
|
|
3273
|
+
if (!isFileServingAllowed(parentServer.vite.config, fsPath)) {
|
|
3274
|
+
return next();
|
|
3275
|
+
}
|
|
3276
|
+
try {
|
|
3277
|
+
res.setHeader("content-type", contentType);
|
|
3278
|
+
return createReadStream(fsPath).pipe(res).on("close", () => res.end());
|
|
3279
|
+
} catch (err) {
|
|
3280
|
+
return next(err);
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
// When the Vitest UI (`test.ui`) is enabled, the `vitest:ui` plugin owns
|
|
3284
|
+
// `/__vitest__` (including the token-injected index.html and its assets),
|
|
3285
|
+
// so registering sirv here would shadow it and serve the page without a token.
|
|
3286
|
+
if (!parentServer.vitest.config.ui) {
|
|
3287
|
+
server.middlewares.use("/__vitest__", sirv(distClientRoot));
|
|
3288
|
+
}
|
|
3289
|
+
},
|
|
3290
|
+
// Resolution-time config: only what is derivable from the (partial) user
|
|
3291
|
+
// config. The mocks / coverage / meta-env plugins come from the shared
|
|
3292
|
+
// workspace plugin set, not from here.
|
|
3293
|
+
async config(viteConfig) {
|
|
3294
|
+
const testConfig = viteConfig.test || {};
|
|
3295
|
+
const config = {
|
|
3296
|
+
resolve: {
|
|
3297
|
+
alias: testConfig.alias,
|
|
3298
|
+
dedupe: ["vitest"]
|
|
3299
|
+
},
|
|
3300
|
+
server: {
|
|
3301
|
+
fs: { allow: [distRoot] },
|
|
3302
|
+
middlewareMode: false,
|
|
3303
|
+
watch: null,
|
|
3304
|
+
// Vitest forwards browser console logs and unhandled errors through its
|
|
3305
|
+
// own RPC, so Vite's client forwarding (defaults on under agents) would
|
|
3306
|
+
// double-report them and leak into stderr.
|
|
3307
|
+
forwardConsole: false
|
|
3493
3308
|
}
|
|
3309
|
+
};
|
|
3310
|
+
// Enables using the @preserve ignore hint for coverage providers. Only for
|
|
3311
|
+
// esbuild (rolldown-vite uses oxc instead).
|
|
3312
|
+
if (!rolldownVersion && viteConfig.esbuild !== false) {
|
|
3313
|
+
config.esbuild = { legalComments: "inline" };
|
|
3314
|
+
}
|
|
3315
|
+
const define = {};
|
|
3316
|
+
const envVars = testConfig.env || {};
|
|
3317
|
+
for (const env in envVars) {
|
|
3318
|
+
define[`import.meta.env.${env}`] = JSON.stringify(envVars[env]);
|
|
3494
3319
|
}
|
|
3320
|
+
config.define = define;
|
|
3321
|
+
return config;
|
|
3495
3322
|
},
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3323
|
+
resolveOptimizeDeps(projectConfigs, testFiles, harness) {
|
|
3324
|
+
return resolveBrowserOptimizeDeps(projectConfigs, testFiles, harness);
|
|
3325
|
+
},
|
|
3326
|
+
plugins: [],
|
|
3327
|
+
createParent({ config, vitest }) {
|
|
3328
|
+
if (vitest.version !== version) {
|
|
3329
|
+
vitest.logger.warn(c.yellow(`Loaded ${c.inverse(c.yellow(` vitest@${vitest.version} `))} and ${c.inverse(c.yellow(` @vitest/browser@${version} `))}.` + "\nRunning mixed versions is not supported and may lead into bugs" + "\nUpdate your dependencies and make sure the versions match."));
|
|
3330
|
+
}
|
|
3331
|
+
return new ParentBrowserProject({
|
|
3332
|
+
config,
|
|
3333
|
+
vitest
|
|
3334
|
+
}, "/");
|
|
3503
3335
|
},
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3336
|
+
setupRpc(parent) {
|
|
3337
|
+
setupBrowserRpc(parent, mockerRegistry);
|
|
3338
|
+
}
|
|
3339
|
+
};
|
|
3340
|
+
contribution.plugins = [
|
|
3341
|
+
...BrowserPlugin(contribution),
|
|
3342
|
+
interceptorPlugin({ registry: mockerRegistry }),
|
|
3343
|
+
{
|
|
3344
|
+
name: "vitest:browser:framework-sourcemaps",
|
|
3345
|
+
enforce: "post",
|
|
3346
|
+
transform(code, id) {
|
|
3347
|
+
const parentServer = contribution.parent;
|
|
3348
|
+
// In a headless run nothing can open devtools, so sourcemaps of
|
|
3349
|
+
// Vitest's own pre-built modules are never consumed: their stack
|
|
3350
|
+
// frames are filtered by stackIgnorePatterns. Generating and
|
|
3351
|
+
// inlining these maps costs server CPU and multiplies the bytes
|
|
3352
|
+
// the browser downloads by ~5 for every fresh browser context.
|
|
3353
|
+
// Sourcemaps of user files and (by default) their dependencies are
|
|
3354
|
+
// kept — they point error stacks and devtools at original sources.
|
|
3355
|
+
if (!parentServer || !isHeadlessServer(parentServer) || parentServer.vitest.config.inspector.enabled) {
|
|
3356
|
+
return null;
|
|
3510
3357
|
}
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
if (id.includes(distRoot) || id.includes(cacheDir)) {
|
|
3514
|
-
return false;
|
|
3358
|
+
if (isCSSRequest(id)) {
|
|
3359
|
+
return null;
|
|
3515
3360
|
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3361
|
+
const path = cleanUrl$1(id);
|
|
3362
|
+
if (path.startsWith(distRoot) || path.startsWith(distDir)) {
|
|
3363
|
+
return {
|
|
3364
|
+
code,
|
|
3365
|
+
map: { mappings: "" }
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
// users that never debug into node_modules can drop dependency
|
|
3369
|
+
// sourcemaps entirely; `server.sourcemapIgnoreList` (default:
|
|
3370
|
+
// node_modules) can opt paths back in even then, e.g. with
|
|
3371
|
+
// `preserveSymlinks` where workspace code keeps its node_modules
|
|
3372
|
+
// path and would be wrongly treated as a dependency
|
|
3373
|
+
if (parentServer.config.browser.dependencySourcemaps === false && path.includes("/node_modules/") && (parentServer.vite.config.server.sourcemapIgnoreList(path, path) ?? true)) {
|
|
3374
|
+
return {
|
|
3375
|
+
code,
|
|
3376
|
+
map: { mappings: "" }
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
return null;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
];
|
|
3383
|
+
return contribution;
|
|
3528
3384
|
};
|
|
3385
|
+
function isHeadlessServer(parentServer) {
|
|
3386
|
+
if (!parentServer.config.browser.headless) {
|
|
3387
|
+
return false;
|
|
3388
|
+
}
|
|
3389
|
+
// sibling instances share this server (and its module graph cache, so a
|
|
3390
|
+
// late-spawned instance would receive already-cached transforms) and can
|
|
3391
|
+
// override `headless` — check the static instance options instead of the
|
|
3392
|
+
// lazily populated `children` to stay deterministic across runs
|
|
3393
|
+
const instances = parentServer.config.browser.instances ?? [];
|
|
3394
|
+
return instances.every((instance) => instance.headless !== false);
|
|
3395
|
+
}
|
|
3396
|
+
function resolveBrowserOptimizeDeps(projectConfigs, testFiles, harness) {
|
|
3397
|
+
// `testFiles` are globbed by the core package and aggregated across every
|
|
3398
|
+
// project that shares this browser Vite server (instance and benchmark
|
|
3399
|
+
// variants). The remaining options are shared by those projects, so the first
|
|
3400
|
+
// config is representative.
|
|
3401
|
+
const testConfig = projectConfigs[0];
|
|
3402
|
+
const root = testConfig.root || process.cwd();
|
|
3403
|
+
const setupFiles = new Set(projectConfigs.flatMap((config) => toArray(config.setupFiles || [])));
|
|
3404
|
+
const entries = [
|
|
3405
|
+
...testFiles,
|
|
3406
|
+
...setupFiles,
|
|
3407
|
+
resolve(distDir, "index.js"),
|
|
3408
|
+
resolve(distDir, "browser.js"),
|
|
3409
|
+
resolve(distDir, "runners.js"),
|
|
3410
|
+
resolve(distDir, "utils.js"),
|
|
3411
|
+
...testConfig.snapshotSerializers || []
|
|
3412
|
+
];
|
|
3413
|
+
// Keep these external (never pre-bundle by optimizer):
|
|
3414
|
+
// - vitest/browser, @vitest/browser/context, @vitest/browser/utils are
|
|
3415
|
+
// VIRTUAL modules generated per-server (see pluginContext.ts) — optimizer
|
|
3416
|
+
// cannot resolve/run their `load`, it would freeze stale/empty content.
|
|
3417
|
+
// - msw is a large, side-effectful service-worker library.
|
|
3418
|
+
const exclude = [
|
|
3419
|
+
"vitest/browser",
|
|
3420
|
+
"@vitest/browser/utils",
|
|
3421
|
+
"@vitest/browser/context",
|
|
3422
|
+
"@vitest/browser/locators",
|
|
3423
|
+
"msw",
|
|
3424
|
+
"msw/browser"
|
|
3425
|
+
];
|
|
3426
|
+
if (typeof testConfig.diff === "string") {
|
|
3427
|
+
entries.push(testConfig.diff);
|
|
3428
|
+
}
|
|
3429
|
+
if (testConfig.coverage?.enabled) {
|
|
3430
|
+
const provider = testConfig.coverage.provider ?? "v8";
|
|
3431
|
+
if (provider === "v8") {
|
|
3432
|
+
const path = tryResolve("@vitest/coverage-v8", [root]);
|
|
3433
|
+
if (path) {
|
|
3434
|
+
entries.push(path);
|
|
3435
|
+
exclude.push("@vitest/coverage-v8/browser");
|
|
3436
|
+
}
|
|
3437
|
+
} else if (provider === "istanbul") {
|
|
3438
|
+
const path = tryResolve("@vitest/coverage-istanbul", [root]);
|
|
3439
|
+
if (path) {
|
|
3440
|
+
entries.push(path);
|
|
3441
|
+
exclude.push("@vitest/coverage-istanbul");
|
|
3442
|
+
}
|
|
3443
|
+
} else if (provider === "custom" && testConfig.coverage.customProviderModule) {
|
|
3444
|
+
entries.push(testConfig.coverage.customProviderModule);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
// Pre-bundle the vitest runtime so the browser fetches a few optimized
|
|
3448
|
+
// chunks instead of ~20 separately-served dist chunks (faster startup).
|
|
3449
|
+
// `vitest`, `vitest/internal/browser` and `@vitest/browser/client` are
|
|
3450
|
+
// optimized together in a single pass, so esbuild dedupes their shared
|
|
3451
|
+
// stateful chunks (the test collector, the runner, the RPC client) to a
|
|
3452
|
+
// single instance — preserving module identity between the test files'
|
|
3453
|
+
// `import 'vitest'` and the tester. Their transitive deps (@vitest/utils,
|
|
3454
|
+
// @vitest/spy, pathe, tinyrainbow, …) are inlined into these bundles.
|
|
3455
|
+
const include = [
|
|
3456
|
+
"vitest > expect-type",
|
|
3457
|
+
"vitest > magic-string",
|
|
3458
|
+
"vitest > chai",
|
|
3459
|
+
"vitest > vite/module-runner",
|
|
3460
|
+
"vitest",
|
|
3461
|
+
"vitest/internal/browser",
|
|
3462
|
+
"vitest/internal/traces",
|
|
3463
|
+
"@vitest/browser/client"
|
|
3464
|
+
];
|
|
3465
|
+
const provider = testConfig.browser?.provider;
|
|
3466
|
+
if (provider?.name === "preview") {
|
|
3467
|
+
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
3468
|
+
}
|
|
3469
|
+
const isPackageExists = (pkg) => {
|
|
3470
|
+
return harness.packageInstaller.isPackageExists(pkg, { paths: [root] });
|
|
3471
|
+
};
|
|
3472
|
+
if (isPackageExists("vitest-browser-svelte")) {
|
|
3473
|
+
exclude.push("vitest-browser-svelte");
|
|
3474
|
+
}
|
|
3475
|
+
// since we override the resolution in the esbuild plugin, Vite can no longer optimize it
|
|
3476
|
+
if (isPackageExists("vitest-browser-vue")) {
|
|
3477
|
+
include.push("vitest-browser-vue", "vitest-browser-vue > @vue/test-utils", "vitest-browser-vue > @vue/test-utils > @vue/compiler-core");
|
|
3478
|
+
}
|
|
3479
|
+
if (isPackageExists("@vue/test-utils")) {
|
|
3480
|
+
include.push("@vue/test-utils");
|
|
3481
|
+
}
|
|
3482
|
+
const otelConfig = testConfig.experimental?.openTelemetry;
|
|
3483
|
+
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
|
|
3484
|
+
entries.push(otelConfig.browserSdkPath);
|
|
3485
|
+
include.push("@opentelemetry/api");
|
|
3486
|
+
} else {
|
|
3487
|
+
exclude.push("@opentelemetry/api");
|
|
3488
|
+
}
|
|
3489
|
+
// resolve @vue/(test-utils|compiler-core) to CJS instead of the browser build:
|
|
3490
|
+
// test-utils' browser build expects a global Vue; compiler-core's CJS build is
|
|
3491
|
+
// the only one that allows slots as strings.
|
|
3492
|
+
const rolldownPlugin = {
|
|
3493
|
+
name: "vue-test-utils-rewrite",
|
|
3494
|
+
resolveId: {
|
|
3495
|
+
filter: { id: /^@vue\/(test-utils|compiler-core)$/ },
|
|
3496
|
+
handler(source, importer) {
|
|
3497
|
+
return getRequire().resolve(source, { paths: [importer] });
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
};
|
|
3501
|
+
const esbuildPlugin = {
|
|
3502
|
+
name: "test-utils-rewrite",
|
|
3503
|
+
setup(build) {
|
|
3504
|
+
build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
|
|
3505
|
+
return { path: getRequire().resolve(args.path, { paths: [args.importer] }) };
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
return {
|
|
3510
|
+
entries,
|
|
3511
|
+
exclude,
|
|
3512
|
+
include,
|
|
3513
|
+
...rolldownVersion ? { rolldownOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } }
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
function tryResolve(path, paths) {
|
|
3517
|
+
try {
|
|
3518
|
+
return getRequire().resolve(path, { paths });
|
|
3519
|
+
} catch {
|
|
3520
|
+
return undefined;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
let _require;
|
|
3524
|
+
function getRequire() {
|
|
3525
|
+
if (!_require) {
|
|
3526
|
+
_require = createRequire(import.meta.url);
|
|
3527
|
+
}
|
|
3528
|
+
return _require;
|
|
3529
|
+
}
|
|
3529
3530
|
function defineBrowserProvider(options) {
|
|
3530
3531
|
return {
|
|
3531
3532
|
...options,
|