@vitest/browser 5.0.0-beta.5 → 5.0.0-beta.7
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_browser__/{orchestrator-bDJB3Bid.js → orchestrator-Bdv4lWwV.js} +60 -14
- package/dist/client/__vitest_browser__/rrweb-snapshot-DkFyu2W8.js +4665 -0
- package/dist/client/__vitest_browser__/{tester-C1kIlbPd.js → tester-CREI5Ivt.js} +73 -50
- package/dist/client/orchestrator.html +1 -1
- package/dist/client/tester/tester.html +1 -1
- package/dist/context.js +6 -5
- package/dist/expect-element.js +2 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +543 -550
- package/dist/{locators-CPBpJv8y.js → locators-Cxk-0tRh.js} +1 -1
- package/dist/locators.js +1 -1
- package/dist/state.js +1 -0
- package/package.json +9 -9
- package/dist/client/__vitest_browser__/rrweb-snapshot-iZCFA2to.js +0 -4388
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
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 { isValidApiRequest, isFileServingAllowed, distDir, rolldownVersion, resolveApiServerConfig, resolveFsAllow, isFileLoadingAllowed, 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';
|
|
12
|
+
import MagicString from 'magic-string';
|
|
13
13
|
import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
|
|
14
14
|
import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
15
15
|
import { extractSourcemapFromFile } from '@vitest/utils/source-map/node';
|
|
@@ -19,8 +19,6 @@ import { PNG } from 'pngjs';
|
|
|
19
19
|
import { diff } from '@blazediff/core';
|
|
20
20
|
import { WebSocketServer } from 'ws';
|
|
21
21
|
|
|
22
|
-
var version = "5.0.0-beta.5";
|
|
23
|
-
|
|
24
22
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
25
23
|
function normalizeWindowsPath(input = "") {
|
|
26
24
|
if (!input) {
|
|
@@ -211,6 +209,8 @@ const basename = function(p, extension) {
|
|
|
211
209
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
212
210
|
};
|
|
213
211
|
|
|
212
|
+
var version = "5.0.0-beta.7";
|
|
213
|
+
|
|
214
214
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
215
215
|
const distRoot = resolve(pkgRoot, "dist");
|
|
216
216
|
|
|
@@ -675,6 +675,17 @@ async function getBrowserProvider(options, project) {
|
|
|
675
675
|
function slash(path) {
|
|
676
676
|
return path.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
677
677
|
}
|
|
678
|
+
function assertBrowserFileAccess(project, path) {
|
|
679
|
+
const normalized = slash(path);
|
|
680
|
+
if (!isFileLoadingAllowed(project.vite.config, normalized) && !isFileLoadingAllowed(project.vitest.vite.config, normalized)) {
|
|
681
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function assertBrowserApiWrite(project, path) {
|
|
685
|
+
if (!project.config.api.allowWrite || !project.vitest.config.api.allowWrite) {
|
|
686
|
+
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.`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
678
689
|
|
|
679
690
|
async function resolveOrchestrator(globalServer, url, res) {
|
|
680
691
|
const sessionId = url.searchParams.get("sessionId");
|
|
@@ -802,16 +813,21 @@ async function resolveTester(globalServer, url, res, next) {
|
|
|
802
813
|
__VITEST_PROVIDED_CONTEXT__: "{}",
|
|
803
814
|
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
804
815
|
});
|
|
805
|
-
const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
|
|
806
816
|
try {
|
|
807
|
-
|
|
808
|
-
|
|
817
|
+
browserProject.testerHtmlTransformed ??= (async () => {
|
|
818
|
+
const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
|
|
819
|
+
const url = join("/@fs/", browserProject.testerFilepath);
|
|
820
|
+
return await browserProject.vite.transformIndexHtml(url, testerHtml);
|
|
821
|
+
})();
|
|
822
|
+
const indexhtml = await browserProject.testerHtmlTransformed;
|
|
809
823
|
const html = replacer(indexhtml, {
|
|
810
824
|
__VITEST_FAVICON__: globalServer.faviconUrl,
|
|
811
825
|
__VITEST_INJECTOR__: injector
|
|
812
826
|
});
|
|
813
827
|
return html;
|
|
814
828
|
} catch (err) {
|
|
829
|
+
// don't cache a rejection — the next request should retry the transform
|
|
830
|
+
browserProject.testerHtmlTransformed = undefined;
|
|
815
831
|
session.fail(err);
|
|
816
832
|
next(err);
|
|
817
833
|
}
|
|
@@ -838,49 +854,41 @@ function createTesterMiddleware(browserServer) {
|
|
|
838
854
|
|
|
839
855
|
const VIRTUAL_ID_CONTEXT = "\0vitest/browser";
|
|
840
856
|
const ID_CONTEXT = "vitest/browser";
|
|
841
|
-
// for libraries that use an older import but are not type checked
|
|
842
|
-
const DEPRECATED_ID_CONTEXT = "@vitest/browser/context";
|
|
843
|
-
const DEPRECATED_VIRTUAL_ID_UTILS = "\0@vitest/browser/utils";
|
|
844
|
-
const DEPRECATED_ID_UTILS = "@vitest/browser/utils";
|
|
845
857
|
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
846
|
-
function BrowserContext(
|
|
858
|
+
function BrowserContext(contribution) {
|
|
847
859
|
return {
|
|
848
860
|
name: "vitest:browser:virtual-module:context",
|
|
849
861
|
enforce: "pre",
|
|
850
|
-
resolveId
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
if (importer && !importer.includes("/node_modules/")) {
|
|
856
|
-
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.`);
|
|
862
|
+
resolveId: {
|
|
863
|
+
order: "pre",
|
|
864
|
+
handler(id) {
|
|
865
|
+
if (id === ID_CONTEXT) {
|
|
866
|
+
return VIRTUAL_ID_CONTEXT;
|
|
857
867
|
}
|
|
858
|
-
return VIRTUAL_ID_CONTEXT;
|
|
859
|
-
}
|
|
860
|
-
if (id === DEPRECATED_ID_UTILS) {
|
|
861
|
-
return DEPRECATED_VIRTUAL_ID_UTILS;
|
|
862
868
|
}
|
|
863
869
|
},
|
|
864
870
|
load(id) {
|
|
871
|
+
const globalServer = contribution.parent;
|
|
865
872
|
if (id === VIRTUAL_ID_CONTEXT) {
|
|
866
873
|
return generateContextFile.call(this, globalServer);
|
|
867
874
|
}
|
|
868
|
-
if (id === DEPRECATED_VIRTUAL_ID_UTILS) {
|
|
869
|
-
return `
|
|
870
|
-
import { utils } from 'vitest/browser'
|
|
871
|
-
export const getElementLocatorSelectors = utils.getElementLocatorSelectors
|
|
872
|
-
export const debug = utils.debug
|
|
873
|
-
export const prettyDOM = utils.prettyDOM
|
|
874
|
-
export const getElementError = utils.getElementError
|
|
875
|
-
`;
|
|
876
|
-
}
|
|
877
875
|
}
|
|
878
876
|
};
|
|
879
877
|
}
|
|
880
878
|
async function generateContextFile(globalServer) {
|
|
881
879
|
const commands = Object.keys(globalServer.commands);
|
|
882
|
-
|
|
883
|
-
|
|
880
|
+
// The provider instance is initialized lazily when a page opens, so reading
|
|
881
|
+
// `child.provider` here races and can be `undefined` before the first page is
|
|
882
|
+
// opened. The provider name is uniform across a project's instances and is
|
|
883
|
+
// already resolved on the config, so read it from there for this shared
|
|
884
|
+
// (cached) context module. The instance is still used below for the
|
|
885
|
+
// preview-only user-event import.
|
|
886
|
+
const child = [...globalServer.children][0];
|
|
887
|
+
const provider = child.provider;
|
|
888
|
+
const providerName = child.config.browser.provider?.name;
|
|
889
|
+
if (!providerName) {
|
|
890
|
+
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.`);
|
|
891
|
+
}
|
|
884
892
|
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
885
893
|
return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
|
|
886
894
|
}).join("\n");
|
|
@@ -915,256 +923,41 @@ async function getUserEventImport(provider, resolve) {
|
|
|
915
923
|
throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
|
|
916
924
|
}
|
|
917
925
|
return `\
|
|
918
|
-
import { userEvent as
|
|
919
|
-
const _userEventSetup = __vitest_user_event__
|
|
926
|
+
import { userEvent as _userEventSetup } from '${slash$1(`/@fs/${resolved.id}`)}'
|
|
920
927
|
`;
|
|
921
928
|
}
|
|
922
929
|
|
|
923
|
-
|
|
924
|
-
var BrowserPlugin = (parentServer, base = "/") => {
|
|
925
|
-
function isPackageExists(pkg, root) {
|
|
926
|
-
return parentServer.vitest.packageInstaller.isPackageExists?.(pkg, { paths: [root] });
|
|
927
|
-
}
|
|
930
|
+
var BrowserPlugin = (contribution) => {
|
|
928
931
|
return [
|
|
929
|
-
{
|
|
930
|
-
enforce: "pre",
|
|
931
|
-
name: "vitest:browser",
|
|
932
|
-
async configureServer(server) {
|
|
933
|
-
parentServer.setServer(server);
|
|
934
|
-
// eslint-disable-next-line prefer-arrow-callback
|
|
935
|
-
server.middlewares.use(function vitestHeaders(_req, res, next) {
|
|
936
|
-
const headers = server.config.server.headers;
|
|
937
|
-
if (headers) {
|
|
938
|
-
for (const name in headers) {
|
|
939
|
-
res.setHeader(name, headers[name]);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
next();
|
|
943
|
-
});
|
|
944
|
-
// strip _vitest_original query added by importActual so that
|
|
945
|
-
// the plugin pipeline sees the original import id (e.g. virtual modules's load hook).
|
|
946
|
-
server.middlewares.use((req, _res, next) => {
|
|
947
|
-
if (req.url?.includes("_vitest_original") && parentServer.project.config.browser.provider?.name === "playwright") {
|
|
948
|
-
req.url = req.url.replace(/[?&]_vitest_original(?=[&#]|$)/, "").replace(/[?&]ext\b[^&#]*/, "").replace(/\?$/, "");
|
|
949
|
-
}
|
|
950
|
-
next();
|
|
951
|
-
});
|
|
952
|
-
server.middlewares.use(createOrchestratorMiddleware(parentServer));
|
|
953
|
-
server.middlewares.use(createTesterMiddleware(parentServer));
|
|
954
|
-
server.middlewares.use(`${base}favicon.svg`, (_, res) => {
|
|
955
|
-
const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
|
|
956
|
-
res.write(content, "utf-8");
|
|
957
|
-
res.end();
|
|
958
|
-
});
|
|
959
|
-
// Serve coverage HTML at ./coverage if configured
|
|
960
|
-
const coverageHtmlDir = parentServer.vitest.config.coverage?.htmlDir;
|
|
961
|
-
if (coverageHtmlDir) {
|
|
962
|
-
server.middlewares.use("/__vitest_test__/coverage", sirv(coverageHtmlDir, {
|
|
963
|
-
single: true,
|
|
964
|
-
dev: true,
|
|
965
|
-
setHeaders: (res) => {
|
|
966
|
-
const csp = res.getHeader("Content-Security-Policy");
|
|
967
|
-
if (typeof csp === "string") {
|
|
968
|
-
// add frame-ancestors to allow the iframe to be loaded by Vitest,
|
|
969
|
-
// but keep the rest of the CSP
|
|
970
|
-
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
971
|
-
}
|
|
972
|
-
res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
|
|
973
|
-
}
|
|
974
|
-
}));
|
|
975
|
-
}
|
|
976
|
-
server.middlewares.use((req, res, next) => {
|
|
977
|
-
// 9000 mega head move
|
|
978
|
-
// Vite always caches optimized dependencies, but users might mock
|
|
979
|
-
// them in _some_ tests, while keeping original modules in others
|
|
980
|
-
// there is no way to configure that in Vite, so we patch it here
|
|
981
|
-
// to always ignore the cache-control set by Vite in the next middleware
|
|
982
|
-
if (req.url && versionRegexp.test(req.url) && !req.url.includes("chunk-")) {
|
|
983
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
984
|
-
const setHeader = res.setHeader.bind(res);
|
|
985
|
-
res.setHeader = function(name, value) {
|
|
986
|
-
if (name === "Cache-Control") {
|
|
987
|
-
return res;
|
|
988
|
-
}
|
|
989
|
-
return setHeader(name, value);
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
next();
|
|
993
|
-
});
|
|
994
|
-
// handle attachments the same way as in packages/ui/node/index.ts
|
|
995
|
-
server.middlewares.use((req, res, next) => {
|
|
996
|
-
if (!req.url) {
|
|
997
|
-
return next();
|
|
998
|
-
}
|
|
999
|
-
const url = new URL(req.url, "http://localhost");
|
|
1000
|
-
if (url.pathname !== "/__vitest_attachment__") {
|
|
1001
|
-
return next();
|
|
1002
|
-
}
|
|
1003
|
-
const path = url.searchParams.get("path");
|
|
1004
|
-
const contentType = url.searchParams.get("contentType");
|
|
1005
|
-
if (!isValidApiRequest(parentServer.config, req) || !contentType || !path) {
|
|
1006
|
-
return next();
|
|
1007
|
-
}
|
|
1008
|
-
const fsPath = decodeURIComponent(path);
|
|
1009
|
-
if (!isFileServingAllowed(parentServer.vite.config, fsPath)) {
|
|
1010
|
-
return next();
|
|
1011
|
-
}
|
|
1012
|
-
try {
|
|
1013
|
-
res.setHeader("content-type", contentType);
|
|
1014
|
-
return createReadStream(fsPath).pipe(res).on("close", () => res.end());
|
|
1015
|
-
} catch (err) {
|
|
1016
|
-
return next(err);
|
|
1017
|
-
}
|
|
1018
|
-
});
|
|
1019
|
-
}
|
|
1020
|
-
},
|
|
1021
932
|
{
|
|
1022
933
|
name: "vitest:browser:tests",
|
|
1023
934
|
enforce: "pre",
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
// the workspace — keeps the optimizeDeps entries symmetrical across
|
|
1030
|
-
// the test/bench project clones so the optimizer doesn't re-scan when
|
|
1031
|
-
// the user switches modes
|
|
1032
|
-
const hasBrowserBenchProject = parentServer.vitest.projects.some((p) => p.config.browser.enabled && p.config.benchmark.enabled);
|
|
1033
|
-
const benchInclude = hasBrowserBenchProject ? project.config.benchmark.include : [];
|
|
1034
|
-
const dir = project.config.dir || project.config.root;
|
|
1035
|
-
const [{ testFiles: browserTestFiles }, browserBenchFiles] = await Promise.all([project.globTestFiles(), benchInclude.length > 0 ? project.globFiles(benchInclude, project.config.benchmark.exclude ?? project.config.exclude, dir) : []]);
|
|
1036
|
-
const setupFiles = toArray(project.config.setupFiles);
|
|
1037
|
-
// replace env values - cannot be reassign at runtime
|
|
1038
|
-
const define = {};
|
|
1039
|
-
for (const env in project.config.env || {}) {
|
|
1040
|
-
const stringValue = JSON.stringify(project.config.env[env]);
|
|
1041
|
-
define[`import.meta.env.${env}`] = stringValue;
|
|
1042
|
-
}
|
|
1043
|
-
const entries = [
|
|
1044
|
-
...new Set([...browserTestFiles, ...browserBenchFiles]),
|
|
1045
|
-
...setupFiles,
|
|
1046
|
-
resolve(distDir, "index.js"),
|
|
1047
|
-
resolve(distDir, "browser.js"),
|
|
1048
|
-
resolve(distDir, "runners.js"),
|
|
1049
|
-
resolve(distDir, "utils.js"),
|
|
1050
|
-
...project.config.snapshotSerializers || []
|
|
1051
|
-
];
|
|
1052
|
-
const exclude = [
|
|
1053
|
-
"vitest",
|
|
1054
|
-
"vitest/browser",
|
|
1055
|
-
"vitest/internal/browser",
|
|
1056
|
-
"vite/module-runner",
|
|
1057
|
-
"@vitest/browser/utils",
|
|
1058
|
-
"@vitest/browser/context",
|
|
1059
|
-
"@vitest/browser/client",
|
|
1060
|
-
"@vitest/utils",
|
|
1061
|
-
"@vitest/utils/source-map",
|
|
1062
|
-
"@vitest/spy",
|
|
1063
|
-
"@vitest/utils/error",
|
|
1064
|
-
"std-env",
|
|
1065
|
-
"tinybench",
|
|
1066
|
-
"tinyspy",
|
|
1067
|
-
"tinyrainbow",
|
|
1068
|
-
"pathe",
|
|
1069
|
-
"msw",
|
|
1070
|
-
"msw/browser"
|
|
1071
|
-
];
|
|
1072
|
-
if (typeof project.config.diff === "string") {
|
|
1073
|
-
entries.push(project.config.diff);
|
|
1074
|
-
}
|
|
1075
|
-
if (parentServer.vitest.coverageProvider) {
|
|
1076
|
-
const coverage = parentServer.vitest._coverageOptions;
|
|
1077
|
-
const provider = coverage.provider;
|
|
1078
|
-
if (provider === "v8") {
|
|
1079
|
-
const path = tryResolve("@vitest/coverage-v8", [parentServer.config.root]);
|
|
1080
|
-
if (path) {
|
|
1081
|
-
entries.push(path);
|
|
1082
|
-
exclude.push("@vitest/coverage-v8/browser");
|
|
1083
|
-
}
|
|
1084
|
-
} else if (provider === "istanbul") {
|
|
1085
|
-
const path = tryResolve("@vitest/coverage-istanbul", [parentServer.config.root]);
|
|
1086
|
-
if (path) {
|
|
1087
|
-
entries.push(path);
|
|
1088
|
-
exclude.push("@vitest/coverage-istanbul");
|
|
1089
|
-
}
|
|
1090
|
-
} else if (provider === "custom" && coverage.customProviderModule) {
|
|
1091
|
-
entries.push(coverage.customProviderModule);
|
|
935
|
+
resolveId: {
|
|
936
|
+
order: "pre",
|
|
937
|
+
handler(id) {
|
|
938
|
+
if (!/\?browserv=\w+$/.test(id)) {
|
|
939
|
+
return;
|
|
1092
940
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
"vitest > magic-string",
|
|
1097
|
-
"vitest > chai"
|
|
1098
|
-
];
|
|
1099
|
-
const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
|
|
1100
|
-
if (provider?.name === "preview") {
|
|
1101
|
-
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
1102
|
-
}
|
|
1103
|
-
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
|
|
1104
|
-
const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
|
|
1105
|
-
if (svelte) {
|
|
1106
|
-
exclude.push("vitest-browser-svelte");
|
|
1107
|
-
}
|
|
1108
|
-
// since we override the resolution in the esbuild plugin, Vite can no longer optimizer it
|
|
1109
|
-
const vue = isPackageExists("vitest-browser-vue", fileRoot);
|
|
1110
|
-
if (vue) {
|
|
1111
|
-
// we override them in the esbuild plugin so optimizer can no longer intercept it
|
|
1112
|
-
include.push("vitest-browser-vue", "vitest-browser-vue > @vue/test-utils", "vitest-browser-vue > @vue/test-utils > @vue/compiler-core");
|
|
1113
|
-
}
|
|
1114
|
-
const vueTestUtils = isPackageExists("@vue/test-utils", fileRoot);
|
|
1115
|
-
if (vueTestUtils) {
|
|
1116
|
-
include.push("@vue/test-utils");
|
|
1117
|
-
}
|
|
1118
|
-
const otelConfig = project.config.experimental.openTelemetry;
|
|
1119
|
-
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
|
|
1120
|
-
entries.push(otelConfig.browserSdkPath);
|
|
1121
|
-
include.push("@opentelemetry/api");
|
|
1122
|
-
} else {
|
|
1123
|
-
exclude.push("@opentelemetry/api");
|
|
1124
|
-
}
|
|
1125
|
-
return {
|
|
1126
|
-
define,
|
|
1127
|
-
resolve: { dedupe: ["vitest"] },
|
|
1128
|
-
optimizeDeps: {
|
|
1129
|
-
entries,
|
|
1130
|
-
exclude,
|
|
1131
|
-
include
|
|
941
|
+
let useId = id.slice(0, id.lastIndexOf("?"));
|
|
942
|
+
if (useId.startsWith("/@fs/")) {
|
|
943
|
+
useId = useId.slice(5);
|
|
1132
944
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
let useId = id.slice(0, id.lastIndexOf("?"));
|
|
1140
|
-
if (useId.startsWith("/@fs/")) {
|
|
1141
|
-
useId = useId.slice(5);
|
|
1142
|
-
}
|
|
1143
|
-
if (/^\w:/.test(useId)) {
|
|
1144
|
-
useId = useId.replace(/\\/g, "/");
|
|
1145
|
-
}
|
|
1146
|
-
return useId;
|
|
1147
|
-
}
|
|
1148
|
-
},
|
|
1149
|
-
{
|
|
1150
|
-
name: "vitest:browser:resolve-virtual",
|
|
1151
|
-
async resolveId(rawId) {
|
|
1152
|
-
if (rawId === "/mockServiceWorker.js") {
|
|
1153
|
-
return this.resolve("msw/mockServiceWorker.js", distRoot, { skipSelf: true });
|
|
945
|
+
if (/^\w:/.test(useId)) {
|
|
946
|
+
useId = useId.replace(/\\/g, "/");
|
|
947
|
+
}
|
|
948
|
+
return useId;
|
|
1154
949
|
}
|
|
1155
950
|
}
|
|
1156
951
|
},
|
|
1157
952
|
{
|
|
1158
953
|
name: "vitest:browser:assets",
|
|
1159
|
-
configureServer(server) {
|
|
1160
|
-
server.middlewares.use("/__vitest__", sirv(distClientRoot));
|
|
1161
|
-
},
|
|
1162
954
|
resolveId(id) {
|
|
1163
955
|
if (id.startsWith("/__vitest_browser__/")) {
|
|
1164
956
|
return resolve(distRoot, "client", id.slice(1));
|
|
1165
957
|
}
|
|
1166
958
|
},
|
|
1167
959
|
transform(code, id) {
|
|
960
|
+
const parentServer = contribution.parent;
|
|
1168
961
|
if (id.includes(parentServer.vite.config.cacheDir) && id.includes("loupe.js")) {
|
|
1169
962
|
// loupe bundle has a nasty require('util') call that leaves a warning in the console
|
|
1170
963
|
const utilRequire = "nodeUtil = require_util();";
|
|
@@ -1172,45 +965,16 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
1172
965
|
}
|
|
1173
966
|
}
|
|
1174
967
|
},
|
|
1175
|
-
BrowserContext(
|
|
968
|
+
BrowserContext(contribution),
|
|
1176
969
|
dynamicImportPlugin({
|
|
1177
970
|
globalThisAccessor: "\"__vitest_browser_runner__\"",
|
|
1178
|
-
filter(id
|
|
1179
|
-
if (environment.name !== "client") {
|
|
1180
|
-
return false;
|
|
1181
|
-
}
|
|
971
|
+
filter(id) {
|
|
1182
972
|
if (id.includes(distRoot)) {
|
|
1183
973
|
return false;
|
|
1184
974
|
}
|
|
1185
975
|
return true;
|
|
1186
976
|
}
|
|
1187
977
|
}),
|
|
1188
|
-
{
|
|
1189
|
-
name: "vitest:browser:config",
|
|
1190
|
-
enforce: "post",
|
|
1191
|
-
async config(viteConfig) {
|
|
1192
|
-
// Enables using ignore hint for coverage providers with @preserve keyword
|
|
1193
|
-
// Only set esbuild options when not using rolldown-vite (Vite 8+),
|
|
1194
|
-
// which uses oxc for transformation instead of esbuild
|
|
1195
|
-
if (!rolldownVersion && viteConfig.esbuild !== false) {
|
|
1196
|
-
viteConfig.esbuild ||= {};
|
|
1197
|
-
viteConfig.esbuild.legalComments = "inline";
|
|
1198
|
-
}
|
|
1199
|
-
const defaultPort = parentServer.vitest.state._data.browserLastPort++;
|
|
1200
|
-
const api = resolveApiServerConfig(viteConfig.test?.browser || {}, defaultPort);
|
|
1201
|
-
viteConfig.server = {
|
|
1202
|
-
...viteConfig.server,
|
|
1203
|
-
port: defaultPort,
|
|
1204
|
-
...api,
|
|
1205
|
-
middlewareMode: false,
|
|
1206
|
-
open: false
|
|
1207
|
-
};
|
|
1208
|
-
viteConfig.server.fs ??= {};
|
|
1209
|
-
viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
|
|
1210
|
-
viteConfig.server.fs.allow.push(...resolveFsAllow(parentServer.vitest.config.root, parentServer.vitest.vite.config.configFile), distRoot);
|
|
1211
|
-
return { resolve: { alias: viteConfig.test?.alias } };
|
|
1212
|
-
}
|
|
1213
|
-
},
|
|
1214
978
|
{
|
|
1215
979
|
name: "vitest:browser:in-source-tests",
|
|
1216
980
|
transform: {
|
|
@@ -1243,130 +1007,6 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
1243
1007
|
}
|
|
1244
1008
|
}
|
|
1245
1009
|
},
|
|
1246
|
-
{
|
|
1247
|
-
name: "vitest:browser:transform-tester-html",
|
|
1248
|
-
enforce: "pre",
|
|
1249
|
-
async transformIndexHtml(html, ctx) {
|
|
1250
|
-
const projectBrowser = [...parentServer.children].find((server) => {
|
|
1251
|
-
return ctx.filename === server.testerFilepath;
|
|
1252
|
-
});
|
|
1253
|
-
if (!projectBrowser) {
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
|
|
1257
|
-
const testerTags = [];
|
|
1258
|
-
const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
|
|
1259
|
-
if (!isDefaultTemplate) {
|
|
1260
|
-
const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
|
|
1261
|
-
const testerEntry = manifestContent["tester/tester.html"];
|
|
1262
|
-
testerTags.push({
|
|
1263
|
-
tag: "script",
|
|
1264
|
-
attrs: {
|
|
1265
|
-
type: "module",
|
|
1266
|
-
crossorigin: "",
|
|
1267
|
-
src: `${parentServer.base}${testerEntry.file}`
|
|
1268
|
-
},
|
|
1269
|
-
injectTo: "head"
|
|
1270
|
-
});
|
|
1271
|
-
for (const importName of testerEntry.imports || []) {
|
|
1272
|
-
const entryManifest = manifestContent[importName];
|
|
1273
|
-
if (entryManifest) {
|
|
1274
|
-
testerTags.push({
|
|
1275
|
-
tag: "link",
|
|
1276
|
-
attrs: {
|
|
1277
|
-
href: `${parentServer.base}${entryManifest.file}`,
|
|
1278
|
-
rel: "modulepreload",
|
|
1279
|
-
crossorigin: ""
|
|
1280
|
-
},
|
|
1281
|
-
injectTo: "head"
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
} else {
|
|
1286
|
-
// inject the reset style only in the default template,
|
|
1287
|
-
// allowing users to customize the style in their own template
|
|
1288
|
-
testerTags.push({
|
|
1289
|
-
tag: "style",
|
|
1290
|
-
children: `
|
|
1291
|
-
html {
|
|
1292
|
-
padding: 0;
|
|
1293
|
-
margin: 0;
|
|
1294
|
-
}
|
|
1295
|
-
body {
|
|
1296
|
-
padding: 0;
|
|
1297
|
-
margin: 0;
|
|
1298
|
-
min-height: 100vh;
|
|
1299
|
-
}`,
|
|
1300
|
-
injectTo: "head"
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
return [
|
|
1304
|
-
{
|
|
1305
|
-
tag: "script",
|
|
1306
|
-
children: "{__VITEST_INJECTOR__}",
|
|
1307
|
-
injectTo: "head-prepend"
|
|
1308
|
-
},
|
|
1309
|
-
{
|
|
1310
|
-
tag: "script",
|
|
1311
|
-
children: stateJs,
|
|
1312
|
-
injectTo: "head-prepend"
|
|
1313
|
-
},
|
|
1314
|
-
{
|
|
1315
|
-
tag: "script",
|
|
1316
|
-
attrs: {
|
|
1317
|
-
type: "module",
|
|
1318
|
-
src: parentServer.errorCatcherUrl
|
|
1319
|
-
},
|
|
1320
|
-
injectTo: "head"
|
|
1321
|
-
},
|
|
1322
|
-
{
|
|
1323
|
-
tag: "script",
|
|
1324
|
-
attrs: {
|
|
1325
|
-
type: "module",
|
|
1326
|
-
src: parentServer.matchersUrl
|
|
1327
|
-
},
|
|
1328
|
-
injectTo: "head"
|
|
1329
|
-
},
|
|
1330
|
-
...parentServer.initScripts.map((script) => ({
|
|
1331
|
-
tag: "script",
|
|
1332
|
-
attrs: {
|
|
1333
|
-
type: "module",
|
|
1334
|
-
src: join("/@fs/", script)
|
|
1335
|
-
},
|
|
1336
|
-
injectTo: "head"
|
|
1337
|
-
})),
|
|
1338
|
-
...testerTags
|
|
1339
|
-
].filter((s) => s != null);
|
|
1340
|
-
}
|
|
1341
|
-
},
|
|
1342
|
-
{
|
|
1343
|
-
name: "vitest:browser:support-testing-library",
|
|
1344
|
-
enforce: "pre",
|
|
1345
|
-
config() {
|
|
1346
|
-
const rolldownPlugin = {
|
|
1347
|
-
name: "vue-test-utils-rewrite",
|
|
1348
|
-
resolveId: {
|
|
1349
|
-
filter: { id: /^@vue\/(test-utils|compiler-core)$/ },
|
|
1350
|
-
handler(source, importer) {
|
|
1351
|
-
const resolved = getRequire().resolve(source, { paths: [importer] });
|
|
1352
|
-
return resolved;
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
const esbuildPlugin = {
|
|
1357
|
-
name: "test-utils-rewrite",
|
|
1358
|
-
setup(build) {
|
|
1359
|
-
// test-utils: resolve to CJS instead of the browser because the browser version expects a global Vue object
|
|
1360
|
-
// compiler-core: only CJS version allows slots as strings
|
|
1361
|
-
build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
|
|
1362
|
-
const resolved = getRequire().resolve(args.path, { paths: [args.importer] });
|
|
1363
|
-
return { path: resolved };
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
};
|
|
1367
|
-
return { optimizeDeps: rolldownVersion ? { rolldownOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
|
|
1368
|
-
}
|
|
1369
|
-
},
|
|
1370
1010
|
{
|
|
1371
1011
|
name: "vitest:browser:__vitest_browser_import_meta_env_init__",
|
|
1372
1012
|
transform: { handler(code) {
|
|
@@ -1379,21 +1019,6 @@ body {
|
|
|
1379
1019
|
}
|
|
1380
1020
|
];
|
|
1381
1021
|
};
|
|
1382
|
-
function tryResolve(path, paths) {
|
|
1383
|
-
try {
|
|
1384
|
-
const _require = getRequire();
|
|
1385
|
-
return _require.resolve(path, { paths });
|
|
1386
|
-
} catch {
|
|
1387
|
-
return undefined;
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
let _require;
|
|
1391
|
-
function getRequire() {
|
|
1392
|
-
if (!_require) {
|
|
1393
|
-
_require = createRequire(import.meta.url);
|
|
1394
|
-
}
|
|
1395
|
-
return _require;
|
|
1396
|
-
}
|
|
1397
1022
|
const postfixRE = /[?#].*$/;
|
|
1398
1023
|
function cleanUrl(url) {
|
|
1399
1024
|
return url.replace(postfixRE, "");
|
|
@@ -1439,19 +1064,6 @@ class BrowserServerCDPHandler {
|
|
|
1439
1064
|
}
|
|
1440
1065
|
}
|
|
1441
1066
|
|
|
1442
|
-
const _startV8Coverage = async (context) => {
|
|
1443
|
-
const session = await context.__ensureCDPHandler();
|
|
1444
|
-
await session.send("Profiler.enable");
|
|
1445
|
-
await session.send("Profiler.startPreciseCoverage", {
|
|
1446
|
-
callCount: true,
|
|
1447
|
-
detailed: true
|
|
1448
|
-
});
|
|
1449
|
-
};
|
|
1450
|
-
const _takeV8Coverage = async (context) => {
|
|
1451
|
-
const session = await context.__ensureCDPHandler();
|
|
1452
|
-
return session.send("Profiler.takePreciseCoverage");
|
|
1453
|
-
};
|
|
1454
|
-
|
|
1455
1067
|
const types = {
|
|
1456
1068
|
'application/andrew-inset': ['ez'],
|
|
1457
1069
|
'application/appinstaller': ['appinstaller'],
|
|
@@ -1904,19 +1516,9 @@ _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _M
|
|
|
1904
1516
|
|
|
1905
1517
|
var mime = new Mime(types)._freeze();
|
|
1906
1518
|
|
|
1907
|
-
function assertFileAccess(path, project) {
|
|
1908
|
-
if (!isFileLoadingAllowed(project.vite.config, path) && !isFileLoadingAllowed(project.vitest.vite.config, path)) {
|
|
1909
|
-
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
function assertWrite(path, project) {
|
|
1913
|
-
if (!project.config.browser.api.allowWrite || !project.vitest.config.api.allowWrite) {
|
|
1914
|
-
throw new Error(`Cannot modify file "${path}". File writing is disabled because server is exposed to the internet, see https://vitest.dev/config/browser/api.`);
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
1519
|
const readFile = async ({ project }, path, options = {}) => {
|
|
1918
1520
|
const filepath = resolve$1(project.config.root, path);
|
|
1919
|
-
|
|
1521
|
+
assertBrowserFileAccess(project, filepath);
|
|
1920
1522
|
// never return a Buffer
|
|
1921
1523
|
if (typeof options === "object" && !options.encoding) {
|
|
1922
1524
|
options.encoding = "utf-8";
|
|
@@ -1924,9 +1526,9 @@ const readFile = async ({ project }, path, options = {}) => {
|
|
|
1924
1526
|
return promises.readFile(filepath, options);
|
|
1925
1527
|
};
|
|
1926
1528
|
const writeFile = async ({ project }, path, data, options) => {
|
|
1927
|
-
|
|
1529
|
+
assertBrowserApiWrite(project, path);
|
|
1928
1530
|
const filepath = resolve$1(project.config.root, path);
|
|
1929
|
-
|
|
1531
|
+
assertBrowserFileAccess(project, filepath);
|
|
1930
1532
|
const dir = dirname$1(filepath);
|
|
1931
1533
|
if (!fs.existsSync(dir)) {
|
|
1932
1534
|
await promises.mkdir(dir, { recursive: true });
|
|
@@ -1934,14 +1536,14 @@ const writeFile = async ({ project }, path, data, options) => {
|
|
|
1934
1536
|
await promises.writeFile(filepath, data, options);
|
|
1935
1537
|
};
|
|
1936
1538
|
const removeFile = async ({ project }, path) => {
|
|
1937
|
-
|
|
1539
|
+
assertBrowserApiWrite(project, path);
|
|
1938
1540
|
const filepath = resolve$1(project.config.root, path);
|
|
1939
|
-
|
|
1541
|
+
assertBrowserFileAccess(project, filepath);
|
|
1940
1542
|
await promises.rm(filepath);
|
|
1941
1543
|
};
|
|
1942
1544
|
const _fileInfo = async ({ project }, path, encoding) => {
|
|
1943
1545
|
const filepath = resolve$1(project.config.root, path);
|
|
1944
|
-
|
|
1546
|
+
assertBrowserFileAccess(project, filepath);
|
|
1945
1547
|
const content = await promises.readFile(filepath, encoding || "base64");
|
|
1946
1548
|
return {
|
|
1947
1549
|
content,
|
|
@@ -2106,7 +1708,7 @@ function resolveOptions({ context, name, options, testName }) {
|
|
|
2106
1708
|
ext: `.${extension}`,
|
|
2107
1709
|
platform: platform(),
|
|
2108
1710
|
root,
|
|
2109
|
-
screenshotDirectory:
|
|
1711
|
+
screenshotDirectory: context.project.config.browser.expect?.toMatchScreenshot?.screenshotDirectory ?? "__screenshots__",
|
|
2110
1712
|
attachmentsDir: relative(root, context.project.config.attachmentsDir),
|
|
2111
1713
|
testFileDirectory: relative(root, dirname(context.testPath)),
|
|
2112
1714
|
testFileName: basename(context.testPath),
|
|
@@ -2213,7 +1815,7 @@ function asyncTimeout(timeout) {
|
|
|
2213
1815
|
if (timeout === 0) {
|
|
2214
1816
|
resolve(null);
|
|
2215
1817
|
} else {
|
|
2216
|
-
setTimeout(
|
|
1818
|
+
setTimeout(resolve, timeout, null);
|
|
2217
1819
|
}
|
|
2218
1820
|
});
|
|
2219
1821
|
}
|
|
@@ -2287,7 +1889,7 @@ const screenshotMatcher = async (context, name, testName, options) => {
|
|
|
2287
1889
|
comparator,
|
|
2288
1890
|
comparatorOptions
|
|
2289
1891
|
});
|
|
2290
|
-
await performSideEffects(outcome, codec);
|
|
1892
|
+
await performSideEffects(outcome, codec, context.project);
|
|
2291
1893
|
return buildOutput(outcome, timeout);
|
|
2292
1894
|
};
|
|
2293
1895
|
/**
|
|
@@ -2377,17 +1979,17 @@ async function determineOutcome({ comparator, comparatorOptions, paths, referenc
|
|
|
2377
1979
|
*
|
|
2378
1980
|
* Only `missing-reference`, `update-reference`, and `mismatch` write files. Successful matches produce no side effects.
|
|
2379
1981
|
*/
|
|
2380
|
-
async function performSideEffects(outcome, codec) {
|
|
1982
|
+
async function performSideEffects(outcome, codec, project) {
|
|
2381
1983
|
switch (outcome.type) {
|
|
2382
1984
|
case "missing-reference":
|
|
2383
1985
|
case "update-reference": {
|
|
2384
|
-
await writeScreenshot(outcome.reference.path, await encodeScreenshot(outcome.reference, codec));
|
|
1986
|
+
await writeScreenshot(outcome.reference.path, await encodeScreenshot(outcome.reference, codec), project);
|
|
2385
1987
|
break;
|
|
2386
1988
|
}
|
|
2387
1989
|
case "mismatch": {
|
|
2388
|
-
await writeScreenshot(outcome.actual.path, await encodeScreenshot(outcome.actual, codec));
|
|
1990
|
+
await writeScreenshot(outcome.actual.path, await encodeScreenshot(outcome.actual, codec), project);
|
|
2389
1991
|
if (outcome.diff) {
|
|
2390
|
-
await writeScreenshot(outcome.diff.path, await codec.encode(outcome.diff.image, {}));
|
|
1992
|
+
await writeScreenshot(outcome.diff.path, await codec.encode(outcome.diff.image, {}), project);
|
|
2391
1993
|
}
|
|
2392
1994
|
break;
|
|
2393
1995
|
}
|
|
@@ -2549,8 +2151,10 @@ async function takeScreenshotData({ buffer, codec, context, element, name, scree
|
|
|
2549
2151
|
};
|
|
2550
2152
|
}
|
|
2551
2153
|
/** Writes encoded images to disk, creating parent directories as needed. */
|
|
2552
|
-
async function writeScreenshot(path, image) {
|
|
2154
|
+
async function writeScreenshot(path, image, project) {
|
|
2553
2155
|
try {
|
|
2156
|
+
assertBrowserApiWrite(project, path);
|
|
2157
|
+
assertBrowserFileAccess(project, path);
|
|
2554
2158
|
await mkdir(dirname(path), { recursive: true });
|
|
2555
2159
|
await writeFile$1(path, image);
|
|
2556
2160
|
} catch (cause) {
|
|
@@ -2598,8 +2202,6 @@ var builtinCommands = {
|
|
|
2598
2202
|
readFile,
|
|
2599
2203
|
removeFile,
|
|
2600
2204
|
writeFile,
|
|
2601
|
-
__vitest_startV8Coverage: _startV8Coverage,
|
|
2602
|
-
__vitest_takeV8Coverage: _takeV8Coverage,
|
|
2603
2205
|
__vitest_markTrace: _markTrace,
|
|
2604
2206
|
__vitest_groupTraceStart: _groupTraceStart,
|
|
2605
2207
|
__vitest_groupTraceEnd: _groupTraceEnd,
|
|
@@ -2617,6 +2219,10 @@ class BrowserServerState {
|
|
|
2617
2219
|
class ProjectBrowser {
|
|
2618
2220
|
testerHtml;
|
|
2619
2221
|
testerFilepath;
|
|
2222
|
+
// the tester template is read once per server, so the transformed HTML
|
|
2223
|
+
// is stable too — computing it for every iframe request pays the whole
|
|
2224
|
+
// transformIndexHtml plugin pipeline each time
|
|
2225
|
+
testerHtmlTransformed;
|
|
2620
2226
|
provider;
|
|
2621
2227
|
vitest;
|
|
2622
2228
|
vite;
|
|
@@ -2718,21 +2324,27 @@ class ParentBrowserProject {
|
|
|
2718
2324
|
config;
|
|
2719
2325
|
// cache for non-vite source maps
|
|
2720
2326
|
sourceMapCache = new Map();
|
|
2721
|
-
constructor(
|
|
2722
|
-
this.project = project;
|
|
2327
|
+
constructor(ctx, base) {
|
|
2723
2328
|
this.base = base;
|
|
2724
|
-
this.vitest =
|
|
2725
|
-
this.config =
|
|
2329
|
+
this.vitest = ctx.vitest;
|
|
2330
|
+
this.config = ctx.config;
|
|
2726
2331
|
this.stackTraceOptions = {
|
|
2727
|
-
frameFilter:
|
|
2332
|
+
frameFilter: this.config.onStackTrace,
|
|
2728
2333
|
getSourceMap: (id) => {
|
|
2729
2334
|
if (this.sourceMapCache.has(id)) {
|
|
2730
2335
|
return this.sourceMapCache.get(id);
|
|
2731
2336
|
}
|
|
2732
2337
|
const result = this.vite.moduleGraph.getModuleById(id)?.transformResult;
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2338
|
+
const filePath = id.split("?")[0];
|
|
2339
|
+
// prefer the map stored on disk when the transform pipeline can't
|
|
2340
|
+
// provide a usable one:
|
|
2341
|
+
// - pre-bundled deps: the pipeline map resolves back into the
|
|
2342
|
+
// optimizer cache, while the map esbuild wrote next to the file
|
|
2343
|
+
// points at the real package sources
|
|
2344
|
+
// - an empty `mappings` means the map was intentionally not served
|
|
2345
|
+
// to the browser (`vitest:browser:framework-sourcemaps`), but disk
|
|
2346
|
+
// is still the source of truth for error stack traces
|
|
2347
|
+
if (result && (!result.map || result.map.mappings === "" || filePath.startsWith(this.vite.config.cacheDir))) {
|
|
2736
2348
|
const extracted = extractSourcemapFromFile(result.code, filePath);
|
|
2737
2349
|
this.sourceMapCache.set(id, extracted?.map);
|
|
2738
2350
|
return extracted?.map;
|
|
@@ -2763,11 +2375,11 @@ class ParentBrowserProject {
|
|
|
2763
2375
|
this.commands[name] ??= command;
|
|
2764
2376
|
}
|
|
2765
2377
|
// validate names because they can't be used as identifiers
|
|
2766
|
-
for (const command in
|
|
2378
|
+
for (const command in this.config.browser.commands) {
|
|
2767
2379
|
if (!/^[a-z_$][\w$]*$/i.test(command)) {
|
|
2768
2380
|
throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
2769
2381
|
}
|
|
2770
|
-
this.commands[command] =
|
|
2382
|
+
this.commands[command] = this.config.browser.commands[command];
|
|
2771
2383
|
}
|
|
2772
2384
|
this.prefixTesterUrl = `${base || "/"}`;
|
|
2773
2385
|
this.prefixOrchestratorUrl = `${base}__vitest_test__/`;
|
|
@@ -2775,7 +2387,7 @@ class ParentBrowserProject {
|
|
|
2775
2387
|
this.manifest = (async () => {
|
|
2776
2388
|
return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
|
|
2777
2389
|
})().then((manifest) => this.manifest = manifest);
|
|
2778
|
-
this.orchestratorHtml = (
|
|
2390
|
+
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);
|
|
2779
2391
|
this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
|
|
2780
2392
|
this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
|
|
2781
2393
|
this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
|
|
@@ -3153,14 +2765,14 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3153
2765
|
}
|
|
3154
2766
|
}
|
|
3155
2767
|
function canWrite(project) {
|
|
3156
|
-
return project.config.
|
|
2768
|
+
return project.config.api.allowWrite && project.vitest.config.api.allowWrite;
|
|
3157
2769
|
}
|
|
3158
2770
|
function isCdpAllowed(project) {
|
|
3159
|
-
return project.config.api.allowExec && project.
|
|
2771
|
+
return project.config.api.allowExec && project.vitest.config.api.allowExec && project.config.api.allowWrite && project.vitest.config.api.allowWrite;
|
|
3160
2772
|
}
|
|
3161
2773
|
function assertCdpAllowed(project) {
|
|
3162
2774
|
if (!isCdpAllowed(project)) {
|
|
3163
|
-
throw new Error(`Cannot use CDP because browser API write or exec operations are disabled. See https://vitest.dev/config/
|
|
2775
|
+
throw new Error(`Cannot use CDP because browser API write or exec operations are disabled. See https://vitest.dev/config/api.`);
|
|
3164
2776
|
}
|
|
3165
2777
|
}
|
|
3166
2778
|
function setupClient(project, rpcId, ws, options) {
|
|
@@ -3196,13 +2808,23 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3196
2808
|
if (!canWrite(project)) {
|
|
3197
2809
|
if (artifact.type === "internal:annotation" && artifact.annotation.attachment) {
|
|
3198
2810
|
artifact.annotation.attachment = undefined;
|
|
3199
|
-
vitest.logger.error(`[vitest] Cannot record annotation attachment because file writing is disabled. See https://vitest.dev/config/
|
|
2811
|
+
vitest.logger.error(`[vitest] Cannot record annotation attachment because file writing is disabled. See https://vitest.dev/config/api.`);
|
|
3200
2812
|
}
|
|
3201
2813
|
// remove attachments if cannot write
|
|
3202
2814
|
if (artifact.attachments?.length) {
|
|
3203
2815
|
const attachments = artifact.attachments.map((n) => n.path).filter((r) => !!r).join("\", \"");
|
|
3204
2816
|
artifact.attachments = [];
|
|
3205
|
-
vitest.logger.error(`[vitest] Cannot record attachments ("${attachments}") because file writing is disabled, removing attachments from artifact "${artifact.type}". See https://vitest.dev/config/
|
|
2817
|
+
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.`);
|
|
2818
|
+
}
|
|
2819
|
+
} else {
|
|
2820
|
+
// attachment files are copied into `attachmentsDir`, so confine
|
|
2821
|
+
// client-supplied paths to Vite's `server.fs` boundary
|
|
2822
|
+
const attachments = artifact.type === "internal:annotation" ? artifact.annotation.attachment ? [artifact.annotation.attachment] : [] : artifact.attachments ?? [];
|
|
2823
|
+
for (const attachment of attachments) {
|
|
2824
|
+
const path = attachment.path;
|
|
2825
|
+
if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
|
|
2826
|
+
checkFileAccess(resolve(project.config.root, path));
|
|
2827
|
+
}
|
|
3206
2828
|
}
|
|
3207
2829
|
}
|
|
3208
2830
|
return vitest._testRun.recordArtifact(id, artifact);
|
|
@@ -3216,7 +2838,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3216
2838
|
},
|
|
3217
2839
|
async writeBenchmarkResult(relativePath, data) {
|
|
3218
2840
|
if (!canWrite(project)) {
|
|
3219
|
-
vitest.logger.error(`[vitest] Cannot write benchmark artifact "${relativePath}" because file writing is disabled. See https://vitest.dev/config/
|
|
2841
|
+
vitest.logger.error(`[vitest] Cannot write benchmark artifact "${relativePath}" because file writing is disabled. See https://vitest.dev/config/api.`);
|
|
3220
2842
|
return;
|
|
3221
2843
|
}
|
|
3222
2844
|
checkFileAccess(project.benchmark.resolve(relativePath));
|
|
@@ -3258,7 +2880,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3258
2880
|
async saveSnapshotFile(id, content) {
|
|
3259
2881
|
checkFileAccess(id);
|
|
3260
2882
|
if (!canWrite(project)) {
|
|
3261
|
-
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/
|
|
2883
|
+
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.`);
|
|
3262
2884
|
return;
|
|
3263
2885
|
}
|
|
3264
2886
|
await promises.mkdir(dirname(id), { recursive: true });
|
|
@@ -3267,7 +2889,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3267
2889
|
async removeSnapshotFile(id) {
|
|
3268
2890
|
checkFileAccess(id);
|
|
3269
2891
|
if (!canWrite(project)) {
|
|
3270
|
-
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/
|
|
2892
|
+
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.`);
|
|
3271
2893
|
return;
|
|
3272
2894
|
}
|
|
3273
2895
|
if (!existsSync(id)) {
|
|
@@ -3448,66 +3070,437 @@ function stringifyReplace(key, value) {
|
|
|
3448
3070
|
function defineBrowserCommand(fn) {
|
|
3449
3071
|
return fn;
|
|
3450
3072
|
}
|
|
3451
|
-
const
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
|
|
3460
|
-
const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
|
|
3073
|
+
const versionRegexp = /(?:\?|&)v=\w{8}/;
|
|
3074
|
+
/**
|
|
3075
|
+
* The browser provider's `serverFactory`. Returns a `BrowserServerContribution`
|
|
3076
|
+
* that Vitest core uses to build the SINGLE Vite server shared by `project.vite`
|
|
3077
|
+
* and `project.browser.vite`. This factory does NOT create a server (core does);
|
|
3078
|
+
* it only contributes config, plugins, the parent factory, and the RPC setup.
|
|
3079
|
+
*/
|
|
3080
|
+
const createBrowserServer = async () => {
|
|
3461
3081
|
const mockerRegistry = new MockerRegistry();
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3082
|
+
const contribution = {
|
|
3083
|
+
async transformIndexHtml(ctx) {
|
|
3084
|
+
const parentServer = contribution.parent;
|
|
3085
|
+
const projectBrowser = [...parentServer.children].find((server) => {
|
|
3086
|
+
return ctx.filename === server.testerFilepath;
|
|
3087
|
+
});
|
|
3088
|
+
if (!projectBrowser) {
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
|
|
3092
|
+
const testerTags = [];
|
|
3093
|
+
const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
|
|
3094
|
+
if (!isDefaultTemplate) {
|
|
3095
|
+
const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
|
|
3096
|
+
const testerEntry = manifestContent["tester/tester.html"];
|
|
3097
|
+
testerTags.push({
|
|
3098
|
+
tag: "script",
|
|
3099
|
+
attrs: {
|
|
3100
|
+
type: "module",
|
|
3101
|
+
crossorigin: "",
|
|
3102
|
+
src: `${parentServer.base}${testerEntry.file}`
|
|
3103
|
+
},
|
|
3104
|
+
injectTo: "head"
|
|
3105
|
+
});
|
|
3106
|
+
for (const importName of testerEntry.imports || []) {
|
|
3107
|
+
const entryManifest = manifestContent[importName];
|
|
3108
|
+
if (entryManifest) {
|
|
3109
|
+
testerTags.push({
|
|
3110
|
+
tag: "link",
|
|
3111
|
+
attrs: {
|
|
3112
|
+
href: `${parentServer.base}${entryManifest.file}`,
|
|
3113
|
+
rel: "modulepreload",
|
|
3114
|
+
crossorigin: ""
|
|
3115
|
+
},
|
|
3116
|
+
injectTo: "head"
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
} else {
|
|
3121
|
+
// inject the reset style only in the default template,
|
|
3122
|
+
// allowing users to customize the style in their own template
|
|
3123
|
+
testerTags.push({
|
|
3124
|
+
tag: "style",
|
|
3125
|
+
children: `
|
|
3126
|
+
html {
|
|
3127
|
+
padding: 0;
|
|
3128
|
+
margin: 0;
|
|
3129
|
+
}
|
|
3130
|
+
body {
|
|
3131
|
+
padding: 0;
|
|
3132
|
+
margin: 0;
|
|
3133
|
+
min-height: 100vh;
|
|
3134
|
+
}`,
|
|
3135
|
+
injectTo: "head"
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
return [
|
|
3139
|
+
{
|
|
3140
|
+
tag: "script",
|
|
3141
|
+
children: "{__VITEST_INJECTOR__}",
|
|
3142
|
+
injectTo: "head-prepend"
|
|
3143
|
+
},
|
|
3144
|
+
{
|
|
3145
|
+
tag: "script",
|
|
3146
|
+
children: stateJs,
|
|
3147
|
+
injectTo: "head-prepend"
|
|
3148
|
+
},
|
|
3149
|
+
{
|
|
3150
|
+
tag: "script",
|
|
3151
|
+
attrs: {
|
|
3152
|
+
type: "module",
|
|
3153
|
+
src: parentServer.errorCatcherUrl
|
|
3154
|
+
},
|
|
3155
|
+
injectTo: "head"
|
|
3156
|
+
},
|
|
3157
|
+
{
|
|
3158
|
+
tag: "script",
|
|
3159
|
+
attrs: {
|
|
3160
|
+
type: "module",
|
|
3161
|
+
src: parentServer.matchersUrl
|
|
3162
|
+
},
|
|
3163
|
+
injectTo: "head"
|
|
3164
|
+
},
|
|
3165
|
+
...parentServer.initScripts.map((script) => ({
|
|
3166
|
+
tag: "script",
|
|
3167
|
+
attrs: {
|
|
3168
|
+
type: "module",
|
|
3169
|
+
src: join("/@fs/", script)
|
|
3170
|
+
},
|
|
3171
|
+
injectTo: "head"
|
|
3172
|
+
})),
|
|
3173
|
+
...testerTags
|
|
3174
|
+
].filter((s) => s != null);
|
|
3175
|
+
},
|
|
3176
|
+
configureServer(server) {
|
|
3177
|
+
const parentServer = contribution.parent;
|
|
3178
|
+
parentServer.setServer(server);
|
|
3179
|
+
// eslint-disable-next-line prefer-arrow-callback
|
|
3180
|
+
server.middlewares.use(function vitestHeaders(_req, res, next) {
|
|
3181
|
+
const headers = server.config.server.headers;
|
|
3182
|
+
if (headers) {
|
|
3183
|
+
for (const name in headers) {
|
|
3184
|
+
res.setHeader(name, headers[name]);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
next();
|
|
3188
|
+
});
|
|
3189
|
+
// strip _vitest_original query added by importActual so that
|
|
3190
|
+
// the plugin pipeline sees the original import id (e.g. virtual modules's load hook).
|
|
3191
|
+
server.middlewares.use((req, _res, next) => {
|
|
3192
|
+
if (req.url?.includes("_vitest_original") && parentServer.config.browser.provider?.name === "playwright") {
|
|
3193
|
+
req.url = req.url.replace(/[?&]_vitest_original(?=[&#]|$)/, "").replace(/[?&]ext\b[^&#]*/, "").replace(/\?$/, "");
|
|
3194
|
+
}
|
|
3195
|
+
next();
|
|
3196
|
+
});
|
|
3197
|
+
server.middlewares.use(createOrchestratorMiddleware(parentServer));
|
|
3198
|
+
server.middlewares.use(createTesterMiddleware(parentServer));
|
|
3199
|
+
server.middlewares.use(`/favicon.svg`, (_, res) => {
|
|
3200
|
+
const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
|
|
3201
|
+
res.write(content, "utf-8");
|
|
3202
|
+
res.end();
|
|
3203
|
+
});
|
|
3204
|
+
// Serve coverage HTML at ./coverage if configured
|
|
3205
|
+
const coverageHtmlDir = parentServer.vitest.config.coverage?.htmlDir;
|
|
3206
|
+
if (coverageHtmlDir) {
|
|
3207
|
+
server.middlewares.use("/__vitest_test__/coverage", sirv(coverageHtmlDir, {
|
|
3208
|
+
single: true,
|
|
3209
|
+
dev: true,
|
|
3210
|
+
setHeaders: (res) => {
|
|
3211
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
3212
|
+
if (typeof csp === "string") {
|
|
3213
|
+
// add frame-ancestors to allow the iframe to be loaded by Vitest,
|
|
3214
|
+
// but keep the rest of the CSP
|
|
3215
|
+
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
3216
|
+
}
|
|
3217
|
+
res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
|
|
3218
|
+
}
|
|
3219
|
+
}));
|
|
3220
|
+
}
|
|
3221
|
+
server.middlewares.use((req, res, next) => {
|
|
3222
|
+
// 9000 mega head move
|
|
3223
|
+
// Vite always caches optimized dependencies, but users might mock
|
|
3224
|
+
// them in _some_ tests, while keeping original modules in others
|
|
3225
|
+
// there is no way to configure that in Vite, so we patch it here
|
|
3226
|
+
// to always ignore the cache-control set by Vite in the next middleware
|
|
3227
|
+
if (req.url && versionRegexp.test(req.url) && !req.url.includes("chunk-")) {
|
|
3228
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3229
|
+
const setHeader = res.setHeader.bind(res);
|
|
3230
|
+
res.setHeader = function(name, value) {
|
|
3231
|
+
if (name === "Cache-Control") {
|
|
3232
|
+
return res;
|
|
3233
|
+
}
|
|
3234
|
+
return setHeader(name, value);
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
next();
|
|
3238
|
+
});
|
|
3239
|
+
// handle attachments the same way as in packages/ui/node/index.ts
|
|
3240
|
+
server.middlewares.use((req, res, next) => {
|
|
3241
|
+
if (!req.url) {
|
|
3242
|
+
return next();
|
|
3243
|
+
}
|
|
3244
|
+
const url = new URL(req.url, "http://localhost");
|
|
3245
|
+
if (url.pathname !== "/__vitest_attachment__") {
|
|
3246
|
+
return next();
|
|
3247
|
+
}
|
|
3248
|
+
const path = url.searchParams.get("path");
|
|
3249
|
+
const contentType = url.searchParams.get("contentType");
|
|
3250
|
+
if (!isValidApiRequest(parentServer.config, req) || !contentType || !path) {
|
|
3251
|
+
return next();
|
|
3252
|
+
}
|
|
3253
|
+
const fsPath = decodeURIComponent(path);
|
|
3254
|
+
if (!isFileServingAllowed(parentServer.vite.config, fsPath)) {
|
|
3255
|
+
return next();
|
|
3256
|
+
}
|
|
3257
|
+
try {
|
|
3258
|
+
res.setHeader("content-type", contentType);
|
|
3259
|
+
return createReadStream(fsPath).pipe(res).on("close", () => res.end());
|
|
3260
|
+
} catch (err) {
|
|
3261
|
+
return next(err);
|
|
3262
|
+
}
|
|
3263
|
+
});
|
|
3264
|
+
// When the Vitest UI (`test.ui`) is enabled, the `vitest:ui` plugin owns
|
|
3265
|
+
// `/__vitest__` (including the token-injected index.html and its assets),
|
|
3266
|
+
// so registering sirv here would shadow it and serve the page without a token.
|
|
3267
|
+
if (!parentServer.vitest.config.ui) {
|
|
3268
|
+
server.middlewares.use("/__vitest__", sirv(distClientRoot));
|
|
3269
|
+
}
|
|
3270
|
+
},
|
|
3271
|
+
async config(viteConfig) {
|
|
3272
|
+
const testConfig = viteConfig.test || {};
|
|
3273
|
+
const config = {
|
|
3274
|
+
resolve: {
|
|
3275
|
+
alias: testConfig.alias,
|
|
3276
|
+
dedupe: ["vitest"]
|
|
3277
|
+
},
|
|
3278
|
+
server: {
|
|
3279
|
+
fs: { allow: [distRoot] },
|
|
3280
|
+
middlewareMode: false,
|
|
3281
|
+
watch: null,
|
|
3282
|
+
forwardConsole: false
|
|
3475
3283
|
}
|
|
3284
|
+
};
|
|
3285
|
+
// Enables using the @preserve ignore hint for coverage providers. Only for
|
|
3286
|
+
// esbuild (rolldown-vite uses oxc instead).
|
|
3287
|
+
if (!rolldownVersion && viteConfig.esbuild !== false) {
|
|
3288
|
+
config.esbuild = { legalComments: "inline" };
|
|
3289
|
+
}
|
|
3290
|
+
const define = {};
|
|
3291
|
+
const envVars = testConfig.env || {};
|
|
3292
|
+
for (const env in envVars) {
|
|
3293
|
+
define[`import.meta.env.${env}`] = JSON.stringify(envVars[env]);
|
|
3476
3294
|
}
|
|
3295
|
+
config.define = define;
|
|
3296
|
+
return config;
|
|
3477
3297
|
},
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3298
|
+
resolveOptimizeDeps(projectConfigs, testFiles, harness) {
|
|
3299
|
+
return resolveBrowserOptimizeDeps(projectConfigs, testFiles, harness);
|
|
3300
|
+
},
|
|
3301
|
+
plugins: [],
|
|
3302
|
+
createParent({ config, vitest }) {
|
|
3303
|
+
if (vitest.version !== version) {
|
|
3304
|
+
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."));
|
|
3305
|
+
}
|
|
3306
|
+
return new ParentBrowserProject({
|
|
3307
|
+
config,
|
|
3308
|
+
vitest
|
|
3309
|
+
}, "/");
|
|
3485
3310
|
},
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3311
|
+
setupRpc(parent) {
|
|
3312
|
+
setupBrowserRpc(parent, mockerRegistry);
|
|
3313
|
+
}
|
|
3314
|
+
};
|
|
3315
|
+
contribution.plugins = [
|
|
3316
|
+
...BrowserPlugin(contribution),
|
|
3317
|
+
interceptorPlugin({ registry: mockerRegistry }),
|
|
3318
|
+
{
|
|
3319
|
+
name: "vitest:browser:framework-sourcemaps",
|
|
3320
|
+
enforce: "post",
|
|
3321
|
+
transform(code, id) {
|
|
3322
|
+
const parentServer = contribution.parent;
|
|
3323
|
+
// In a headless run nothing can open devtools, so sourcemaps of
|
|
3324
|
+
// Vitest's own pre-built modules are never consumed: their stack
|
|
3325
|
+
// frames are filtered by stackIgnorePatterns. Generating and
|
|
3326
|
+
// inlining these maps costs server CPU and multiplies the bytes
|
|
3327
|
+
// the browser downloads by ~5 for every fresh browser context.
|
|
3328
|
+
// Sourcemaps of user files and (by default) their dependencies are
|
|
3329
|
+
// kept — they point error stacks and devtools at original sources.
|
|
3330
|
+
if (!parentServer || !isHeadlessServer(parentServer) || parentServer.vitest.config.inspector.enabled) {
|
|
3331
|
+
return null;
|
|
3492
3332
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
if (id.includes(distRoot) || id.includes(cacheDir)) {
|
|
3496
|
-
return false;
|
|
3333
|
+
if (isCSSRequest(id)) {
|
|
3334
|
+
return null;
|
|
3497
3335
|
}
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3336
|
+
const path = cleanUrl$1(id);
|
|
3337
|
+
if (path.startsWith(distRoot) || path.startsWith(distDir)) {
|
|
3338
|
+
return {
|
|
3339
|
+
code,
|
|
3340
|
+
map: { mappings: "" }
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
// users that never debug into node_modules can drop dependency
|
|
3344
|
+
// sourcemaps entirely; `server.sourcemapIgnoreList` (default:
|
|
3345
|
+
// node_modules) can opt paths back in even then, e.g. with
|
|
3346
|
+
// `preserveSymlinks` where workspace code keeps its node_modules
|
|
3347
|
+
// path and would be wrongly treated as a dependency
|
|
3348
|
+
if (parentServer.config.browser.dependencySourcemaps === false && path.includes("/node_modules/") && (parentServer.vite.config.server.sourcemapIgnoreList(path, path) ?? true)) {
|
|
3349
|
+
return {
|
|
3350
|
+
code,
|
|
3351
|
+
map: { mappings: "" }
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
return null;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
];
|
|
3358
|
+
return contribution;
|
|
3510
3359
|
};
|
|
3360
|
+
function isHeadlessServer(parentServer) {
|
|
3361
|
+
if (!parentServer.config.browser.headless) {
|
|
3362
|
+
return false;
|
|
3363
|
+
}
|
|
3364
|
+
// sibling instances share this server (and its module graph cache, so a
|
|
3365
|
+
// late-spawned instance would receive already-cached transforms) and can
|
|
3366
|
+
// override `headless` — check the static instance options instead of the
|
|
3367
|
+
// lazily populated `children` to stay deterministic across runs
|
|
3368
|
+
const instances = parentServer.config.browser.instances ?? [];
|
|
3369
|
+
return instances.every((instance) => instance.headless !== false);
|
|
3370
|
+
}
|
|
3371
|
+
function resolveBrowserOptimizeDeps(projectConfigs, testFiles, harness) {
|
|
3372
|
+
// `testFiles` are globbed by the core package and aggregated across every
|
|
3373
|
+
// project that shares this browser Vite server (instance and benchmark
|
|
3374
|
+
// variants). The remaining options are shared by those projects, so the first
|
|
3375
|
+
// config is representative.
|
|
3376
|
+
const testConfig = projectConfigs[0];
|
|
3377
|
+
const root = testConfig.root || process.cwd();
|
|
3378
|
+
const setupFiles = new Set(projectConfigs.flatMap((config) => toArray(config.setupFiles || [])));
|
|
3379
|
+
const entries = [
|
|
3380
|
+
...testFiles,
|
|
3381
|
+
...setupFiles,
|
|
3382
|
+
resolve(distDir, "index.js"),
|
|
3383
|
+
resolve(distDir, "browser.js"),
|
|
3384
|
+
resolve(distDir, "runners.js"),
|
|
3385
|
+
resolve(distDir, "utils.js"),
|
|
3386
|
+
...testConfig.snapshotSerializers || []
|
|
3387
|
+
];
|
|
3388
|
+
// Keep these external (never pre-bundle by optimizer):
|
|
3389
|
+
// - vitest/browser, @vitest/browser/context, @vitest/browser/utils are
|
|
3390
|
+
// VIRTUAL modules generated per-server (see pluginContext.ts) — optimizer
|
|
3391
|
+
// cannot resolve/run their `load`, it would freeze stale/empty content.
|
|
3392
|
+
// - vite/module-runner is small enough to not need pre-bundling.
|
|
3393
|
+
// - msw is a large, side-effectful service-worker library.
|
|
3394
|
+
const exclude = [
|
|
3395
|
+
"vitest/browser",
|
|
3396
|
+
"vite/module-runner",
|
|
3397
|
+
"@vitest/browser/utils",
|
|
3398
|
+
"@vitest/browser/context",
|
|
3399
|
+
"msw",
|
|
3400
|
+
"msw/browser"
|
|
3401
|
+
];
|
|
3402
|
+
if (typeof testConfig.diff === "string") {
|
|
3403
|
+
entries.push(testConfig.diff);
|
|
3404
|
+
}
|
|
3405
|
+
if (testConfig.coverage?.enabled) {
|
|
3406
|
+
const provider = testConfig.coverage.provider ?? "v8";
|
|
3407
|
+
if (provider === "v8") {
|
|
3408
|
+
const path = tryResolve("@vitest/coverage-v8", [root]);
|
|
3409
|
+
if (path) {
|
|
3410
|
+
entries.push(path);
|
|
3411
|
+
exclude.push("@vitest/coverage-v8/browser");
|
|
3412
|
+
}
|
|
3413
|
+
} else if (provider === "istanbul") {
|
|
3414
|
+
const path = tryResolve("@vitest/coverage-istanbul", [root]);
|
|
3415
|
+
if (path) {
|
|
3416
|
+
entries.push(path);
|
|
3417
|
+
exclude.push("@vitest/coverage-istanbul");
|
|
3418
|
+
}
|
|
3419
|
+
} else if (provider === "custom" && testConfig.coverage.customProviderModule) {
|
|
3420
|
+
entries.push(testConfig.coverage.customProviderModule);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
// Pre-bundle the vitest runtime so the browser fetches a few optimized
|
|
3424
|
+
// chunks instead of ~20 separately-served dist chunks (faster startup).
|
|
3425
|
+
// `vitest`, `vitest/internal/browser` and `@vitest/browser/client` are
|
|
3426
|
+
// optimized together in a single pass, so esbuild dedupes their shared
|
|
3427
|
+
// stateful chunks (the test collector, the runner, the RPC client) to a
|
|
3428
|
+
// single instance — preserving module identity between the test files'
|
|
3429
|
+
// `import 'vitest'` and the tester. Their transitive deps (@vitest/utils,
|
|
3430
|
+
// @vitest/spy, pathe, tinyrainbow, …) are inlined into these bundles.
|
|
3431
|
+
const include = [
|
|
3432
|
+
"vitest > expect-type",
|
|
3433
|
+
"vitest > magic-string",
|
|
3434
|
+
"vitest > chai",
|
|
3435
|
+
"vitest",
|
|
3436
|
+
"vitest/internal/browser",
|
|
3437
|
+
"@vitest/browser/client"
|
|
3438
|
+
];
|
|
3439
|
+
const provider = testConfig.browser?.provider;
|
|
3440
|
+
if (provider?.name === "preview") {
|
|
3441
|
+
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
3442
|
+
}
|
|
3443
|
+
const isPackageExists = (pkg) => {
|
|
3444
|
+
return harness.packageInstaller.isPackageExists(pkg, { paths: [root] });
|
|
3445
|
+
};
|
|
3446
|
+
if (isPackageExists("vitest-browser-svelte")) {
|
|
3447
|
+
exclude.push("vitest-browser-svelte");
|
|
3448
|
+
}
|
|
3449
|
+
// since we override the resolution in the esbuild plugin, Vite can no longer optimize it
|
|
3450
|
+
if (isPackageExists("vitest-browser-vue")) {
|
|
3451
|
+
include.push("vitest-browser-vue", "vitest-browser-vue > @vue/test-utils", "vitest-browser-vue > @vue/test-utils > @vue/compiler-core");
|
|
3452
|
+
}
|
|
3453
|
+
if (isPackageExists("@vue/test-utils")) {
|
|
3454
|
+
include.push("@vue/test-utils");
|
|
3455
|
+
}
|
|
3456
|
+
const otelConfig = testConfig.experimental?.openTelemetry;
|
|
3457
|
+
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
|
|
3458
|
+
entries.push(otelConfig.browserSdkPath);
|
|
3459
|
+
include.push("@opentelemetry/api");
|
|
3460
|
+
} else {
|
|
3461
|
+
exclude.push("@opentelemetry/api");
|
|
3462
|
+
}
|
|
3463
|
+
// resolve @vue/(test-utils|compiler-core) to CJS instead of the browser build:
|
|
3464
|
+
// test-utils' browser build expects a global Vue; compiler-core's CJS build is
|
|
3465
|
+
// the only one that allows slots as strings.
|
|
3466
|
+
const rolldownPlugin = {
|
|
3467
|
+
name: "vue-test-utils-rewrite",
|
|
3468
|
+
resolveId: {
|
|
3469
|
+
filter: { id: /^@vue\/(test-utils|compiler-core)$/ },
|
|
3470
|
+
handler(source, importer) {
|
|
3471
|
+
return getRequire().resolve(source, { paths: [importer] });
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
const esbuildPlugin = {
|
|
3476
|
+
name: "test-utils-rewrite",
|
|
3477
|
+
setup(build) {
|
|
3478
|
+
build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
|
|
3479
|
+
return { path: getRequire().resolve(args.path, { paths: [args.importer] }) };
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
};
|
|
3483
|
+
return {
|
|
3484
|
+
entries,
|
|
3485
|
+
exclude,
|
|
3486
|
+
include,
|
|
3487
|
+
...rolldownVersion ? { rolldownOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } }
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
function tryResolve(path, paths) {
|
|
3491
|
+
try {
|
|
3492
|
+
return getRequire().resolve(path, { paths });
|
|
3493
|
+
} catch {
|
|
3494
|
+
return undefined;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
let _require;
|
|
3498
|
+
function getRequire() {
|
|
3499
|
+
if (!_require) {
|
|
3500
|
+
_require = createRequire(import.meta.url);
|
|
3501
|
+
}
|
|
3502
|
+
return _require;
|
|
3503
|
+
}
|
|
3511
3504
|
function defineBrowserProvider(options) {
|
|
3512
3505
|
return {
|
|
3513
3506
|
...options,
|
|
@@ -3516,4 +3509,4 @@ function defineBrowserProvider(options) {
|
|
|
3516
3509
|
};
|
|
3517
3510
|
}
|
|
3518
3511
|
|
|
3519
|
-
export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
|
|
3512
|
+
export { assertBrowserApiWrite, assertBrowserFileAccess, createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
|