@vitest/browser 1.6.0 → 2.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context.d.ts +127 -0
- package/dist/client/.vite/manifest.json +24 -0
- package/dist/client/__vitest__/assets/index-BMAciMM5.js +51 -0
- package/dist/client/__vitest__/assets/index-BcFb8Rbc.css +1 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/orchestrator-BCpOi5ot.js +301 -0
- package/dist/client/__vitest_browser__/{rpc-slP7oy1q.js → rpc-CImfI7bO.js} +288 -392
- package/dist/client/__vitest_browser__/{tester-RmfypyZ0.js → tester-e70VCOgC.js} +351 -44
- package/dist/client/esm-client-injector.js +14 -28
- package/dist/client/{index.html → orchestrator.html} +8 -7
- package/dist/client/tester.html +4 -3
- package/dist/context.js +87 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +406 -268
- package/dist/providers.js +12 -112
- package/dist/webdriver-CXn0ag9T.js +170 -0
- package/package.json +23 -13
- package/providers/playwright.d.ts +14 -2
- package/providers/webdriverio.d.ts +4 -0
- package/providers.d.ts +5 -5
- package/dist/client/__vitest__/assets/index-TpLatVz2.js +0 -35
- package/dist/client/__vitest__/assets/index-fUmMsp0O.css +0 -1
- package/dist/client/__vitest_browser__/main-v2hiOVax.js +0 -102
package/dist/index.js
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile as readFile$1 } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import sirv from 'sirv';
|
|
5
|
+
import { isFileServingAllowed, getFilePoolName, distDir } from 'vitest/node';
|
|
4
6
|
import { coverageConfigDefaults } from 'vitest/config';
|
|
5
|
-
import { slash } from '@vitest/utils';
|
|
7
|
+
import { slash, toArray } from '@vitest/utils';
|
|
8
|
+
import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-CXn0ag9T.js';
|
|
9
|
+
import fs, { promises } from 'node:fs';
|
|
10
|
+
import { resolve as resolve$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
|
|
6
11
|
import MagicString from 'magic-string';
|
|
7
12
|
import { esmWalker } from '@vitest/utils/ast';
|
|
8
13
|
|
|
14
|
+
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
9
15
|
function normalizeWindowsPath(input = "") {
|
|
10
|
-
if (!input
|
|
16
|
+
if (!input) {
|
|
11
17
|
return input;
|
|
12
18
|
}
|
|
13
|
-
return input.replace(/\\/g, "/");
|
|
19
|
+
return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
const _UNC_REGEX = /^[/\\]{2}/;
|
|
17
23
|
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
|
|
18
24
|
const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
|
|
25
|
+
const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
|
|
19
26
|
const normalize = function(path) {
|
|
20
27
|
if (path.length === 0) {
|
|
21
28
|
return ".";
|
|
@@ -65,7 +72,7 @@ const join = function(...arguments_) {
|
|
|
65
72
|
return normalize(joined.replace(/\/\/+/g, "/"));
|
|
66
73
|
};
|
|
67
74
|
function cwd() {
|
|
68
|
-
if (typeof process !== "undefined") {
|
|
75
|
+
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
69
76
|
return process.cwd().replace(/\\/g, "/");
|
|
70
77
|
}
|
|
71
78
|
return "/";
|
|
@@ -150,78 +157,261 @@ function normalizeString(path, allowAboveRoot) {
|
|
|
150
157
|
const isAbsolute = function(p) {
|
|
151
158
|
return _IS_ABSOLUTE_RE.test(p);
|
|
152
159
|
};
|
|
160
|
+
const relative = function(from, to) {
|
|
161
|
+
const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
162
|
+
const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
163
|
+
if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
|
|
164
|
+
return _to.join("/");
|
|
165
|
+
}
|
|
166
|
+
const _fromCopy = [..._from];
|
|
167
|
+
for (const segment of _fromCopy) {
|
|
168
|
+
if (_to[0] !== segment) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
_from.shift();
|
|
172
|
+
_to.shift();
|
|
173
|
+
}
|
|
174
|
+
return [..._from.map(() => ".."), ..._to].join("/");
|
|
175
|
+
};
|
|
176
|
+
const dirname = function(p) {
|
|
177
|
+
const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
|
|
178
|
+
if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
|
|
179
|
+
segments[0] += "/";
|
|
180
|
+
}
|
|
181
|
+
return segments.join("/") || (isAbsolute(p) ? "/" : ".");
|
|
182
|
+
};
|
|
153
183
|
const basename = function(p, extension) {
|
|
154
184
|
const lastSegment = normalizeWindowsPath(p).split("/").pop();
|
|
155
185
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
156
186
|
};
|
|
157
187
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
nodes.push(param);
|
|
175
|
-
break;
|
|
176
|
-
|
|
177
|
-
case 'MemberExpression':
|
|
178
|
-
let object = param;
|
|
179
|
-
while (object.type === 'MemberExpression') {
|
|
180
|
-
object = /** @type {any} */ (object.object);
|
|
181
|
-
}
|
|
182
|
-
nodes.push(/** @type {any} */ (object));
|
|
183
|
-
break;
|
|
184
|
-
|
|
185
|
-
case 'ObjectPattern':
|
|
186
|
-
for (const prop of param.properties) {
|
|
187
|
-
if (prop.type === 'RestElement') {
|
|
188
|
-
extract_identifiers(prop.argument, nodes);
|
|
189
|
-
} else {
|
|
190
|
-
extract_identifiers(prop.value, nodes);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
188
|
+
const click = async (context, xpath, options = {}) => {
|
|
189
|
+
const provider = context.provider;
|
|
190
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
191
|
+
const tester = context.tester;
|
|
192
|
+
await tester.locator(`xpath=${xpath}`).click(options);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (provider instanceof WebdriverBrowserProvider) {
|
|
196
|
+
const page = provider.browser;
|
|
197
|
+
const markedXpath = `//${xpath}`;
|
|
198
|
+
const element = await page.$(markedXpath);
|
|
199
|
+
await element.click(options);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`Provider "${provider.name}" doesn't support click command`);
|
|
203
|
+
};
|
|
193
204
|
|
|
194
|
-
|
|
205
|
+
function assertFileAccess(path, project) {
|
|
206
|
+
if (!isFileServingAllowed(path, project.server) && !isFileServingAllowed(path, project.ctx.server))
|
|
207
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
208
|
+
}
|
|
209
|
+
const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => {
|
|
210
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
211
|
+
assertFileAccess(filepath, project);
|
|
212
|
+
if (typeof options === "object" && !options.encoding)
|
|
213
|
+
options.encoding = "utf-8";
|
|
214
|
+
return promises.readFile(filepath, options);
|
|
215
|
+
};
|
|
216
|
+
const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => {
|
|
217
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
218
|
+
assertFileAccess(filepath, project);
|
|
219
|
+
const dir = dirname$1(filepath);
|
|
220
|
+
if (!fs.existsSync(dir))
|
|
221
|
+
await promises.mkdir(dir, { recursive: true });
|
|
222
|
+
await promises.writeFile(filepath, data, options);
|
|
223
|
+
};
|
|
224
|
+
const removeFile = async ({ project, testPath = process.cwd() }, path) => {
|
|
225
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
226
|
+
assertFileAccess(filepath, project);
|
|
227
|
+
await promises.rm(filepath);
|
|
228
|
+
};
|
|
195
229
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
230
|
+
function isObject(payload) {
|
|
231
|
+
return payload != null && typeof payload === "object";
|
|
232
|
+
}
|
|
233
|
+
function isSendKeysPayload(payload) {
|
|
234
|
+
const validOptions = ["type", "press", "down", "up"];
|
|
235
|
+
if (!isObject(payload))
|
|
236
|
+
throw new Error("You must provide a `SendKeysPayload` object");
|
|
237
|
+
const numberOfValidOptions = Object.keys(payload).filter(
|
|
238
|
+
(key) => validOptions.includes(key)
|
|
239
|
+
).length;
|
|
240
|
+
const unknownOptions = Object.keys(payload).filter((key) => !validOptions.includes(key));
|
|
241
|
+
if (numberOfValidOptions > 1) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`You must provide ONLY one of the following properties to pass to the browser runner: ${validOptions.join(
|
|
244
|
+
", "
|
|
245
|
+
)}.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (numberOfValidOptions === 0) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`You must provide one of the following properties to pass to the browser runner: ${validOptions.join(
|
|
251
|
+
", "
|
|
252
|
+
)}.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (unknownOptions.length > 0)
|
|
256
|
+
throw new Error(`Unknown options \`${unknownOptions.join(", ")}\` present.`);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
function isTypePayload(payload) {
|
|
260
|
+
return "type" in payload;
|
|
261
|
+
}
|
|
262
|
+
function isPressPayload(payload) {
|
|
263
|
+
return "press" in payload;
|
|
264
|
+
}
|
|
265
|
+
function isDownPayload(payload) {
|
|
266
|
+
return "down" in payload;
|
|
267
|
+
}
|
|
268
|
+
function isUpPayload(payload) {
|
|
269
|
+
return "up" in payload;
|
|
270
|
+
}
|
|
271
|
+
const sendKeys = async ({ provider, contextId }, payload) => {
|
|
272
|
+
if (!isSendKeysPayload(payload) || !payload)
|
|
273
|
+
throw new Error("You must provide a `SendKeysPayload` object");
|
|
274
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
275
|
+
const page = provider.getPage(contextId);
|
|
276
|
+
if (isTypePayload(payload))
|
|
277
|
+
await page.keyboard.type(payload.type);
|
|
278
|
+
else if (isPressPayload(payload))
|
|
279
|
+
await page.keyboard.press(payload.press);
|
|
280
|
+
else if (isDownPayload(payload))
|
|
281
|
+
await page.keyboard.down(payload.down);
|
|
282
|
+
else if (isUpPayload(payload))
|
|
283
|
+
await page.keyboard.up(payload.up);
|
|
284
|
+
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
285
|
+
const browser = provider.browser;
|
|
286
|
+
if (isTypePayload(payload))
|
|
287
|
+
await browser.keys(payload.type.split(""));
|
|
288
|
+
else if (isPressPayload(payload))
|
|
289
|
+
await browser.keys([payload.press]);
|
|
290
|
+
else
|
|
291
|
+
throw new Error('Only "press" and "type" are supported by webdriverio.');
|
|
292
|
+
} else {
|
|
293
|
+
throw new TypeError(`"sendKeys" is not supported for ${provider.name} browser provider.`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
200
296
|
|
|
201
|
-
|
|
297
|
+
const screenshot = async (context, name, options = {}) => {
|
|
298
|
+
if (!context.testPath)
|
|
299
|
+
throw new Error(`Cannot take a screenshot without a test path`);
|
|
300
|
+
const path = resolveScreenshotPath(context.testPath, name, context.project.config);
|
|
301
|
+
const savePath = normalize$1(path);
|
|
302
|
+
await mkdir(dirname(path), { recursive: true });
|
|
303
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
304
|
+
if (options.element) {
|
|
305
|
+
const { element: elementXpath, ...config } = options;
|
|
306
|
+
const iframe = context.tester;
|
|
307
|
+
const element = iframe.locator(`xpath=${elementXpath}`);
|
|
308
|
+
await element.screenshot({ ...config, path: savePath });
|
|
309
|
+
} else {
|
|
310
|
+
await context.body.screenshot({ ...options, path: savePath });
|
|
311
|
+
}
|
|
312
|
+
return path;
|
|
313
|
+
}
|
|
314
|
+
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
315
|
+
const page = context.provider.browser;
|
|
316
|
+
if (!options.element) {
|
|
317
|
+
const body = await page.$("body");
|
|
318
|
+
await body.saveScreenshot(savePath);
|
|
319
|
+
return path;
|
|
320
|
+
}
|
|
321
|
+
const xpath = `//${options.element}`;
|
|
322
|
+
const element = await page.$(xpath);
|
|
323
|
+
await element.saveScreenshot(savePath);
|
|
324
|
+
return path;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
|
|
327
|
+
};
|
|
328
|
+
function resolveScreenshotPath(testPath, name, config) {
|
|
329
|
+
const dir = dirname(testPath);
|
|
330
|
+
const base = basename(testPath);
|
|
331
|
+
if (config.browser.screenshotDirectory) {
|
|
332
|
+
return resolve(
|
|
333
|
+
config.browser.screenshotDirectory,
|
|
334
|
+
relative(config.root, dir),
|
|
335
|
+
base,
|
|
336
|
+
name
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return resolve(dir, "__screenshots__", base, name);
|
|
340
|
+
}
|
|
202
341
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
342
|
+
var builtinCommands = {
|
|
343
|
+
readFile,
|
|
344
|
+
removeFile,
|
|
345
|
+
writeFile,
|
|
346
|
+
sendKeys,
|
|
347
|
+
__vitest_click: click,
|
|
348
|
+
__vitest_screenshot: screenshot
|
|
349
|
+
};
|
|
206
350
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
351
|
+
const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
|
|
352
|
+
const ID_CONTEXT = "@vitest/browser/context";
|
|
353
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
354
|
+
function BrowserContext(project) {
|
|
355
|
+
project.config.browser.commands ??= {};
|
|
356
|
+
for (const [name, command] of Object.entries(builtinCommands))
|
|
357
|
+
project.config.browser.commands[name] ??= command;
|
|
358
|
+
for (const command in project.config.browser.commands) {
|
|
359
|
+
if (!/^[a-z_$][\w$]*$/i.test(command))
|
|
360
|
+
throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
name: "vitest:browser:virtual-module:context",
|
|
364
|
+
enforce: "pre",
|
|
365
|
+
resolveId(id) {
|
|
366
|
+
if (id === ID_CONTEXT)
|
|
367
|
+
return VIRTUAL_ID_CONTEXT;
|
|
368
|
+
},
|
|
369
|
+
load(id) {
|
|
370
|
+
if (id === VIRTUAL_ID_CONTEXT)
|
|
371
|
+
return generateContextFile.call(this, project);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function generateContextFile(project) {
|
|
376
|
+
const commands = Object.keys(project.config.browser.commands ?? {});
|
|
377
|
+
const filepathCode = "__vitest_worker__.filepath || __vitest_worker__.current?.file?.filepath || undefined";
|
|
378
|
+
const provider = project.browserProvider;
|
|
379
|
+
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
380
|
+
return ` ["${command}"]: (...args) => rpc().triggerCommand(contextId, "${command}", filepath(), args),`;
|
|
381
|
+
}).join("\n");
|
|
382
|
+
const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
|
|
383
|
+
const distContextPath = slash(`/@fs/${resolve(__dirname, "context.js")}`);
|
|
384
|
+
return `
|
|
385
|
+
import { page, userEvent as __userEvent_CDP__ } from '${distContextPath}'
|
|
386
|
+
${userEventNonProviderImport}
|
|
387
|
+
const filepath = () => ${filepathCode}
|
|
388
|
+
const rpc = () => __vitest_worker__.rpc
|
|
389
|
+
const contextId = __vitest_browser_runner__.contextId
|
|
211
390
|
|
|
212
|
-
|
|
391
|
+
export const server = {
|
|
392
|
+
platform: ${JSON.stringify(process.platform)},
|
|
393
|
+
version: ${JSON.stringify(process.version)},
|
|
394
|
+
provider: ${JSON.stringify(provider.name)},
|
|
395
|
+
browser: ${JSON.stringify(project.config.browser.name)},
|
|
396
|
+
commands: {
|
|
397
|
+
${commandsCode}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
export const commands = server.commands
|
|
401
|
+
export const userEvent = ${provider.name === "preview" ? "__vitest_user_event__" : "__userEvent_CDP__"}
|
|
402
|
+
export { page }
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
async function getUserEventImport(provider, resolve2) {
|
|
406
|
+
if (provider.name !== "preview")
|
|
407
|
+
return "";
|
|
408
|
+
const resolved = await resolve2("@testing-library/user-event", __dirname);
|
|
409
|
+
if (!resolved)
|
|
410
|
+
throw new Error(`Failed to resolve user-event package from ${__dirname}`);
|
|
411
|
+
return `import { userEvent as __vitest_user_event__ } from '${slash(`/@fs/${resolved.id}`)}'`;
|
|
213
412
|
}
|
|
214
413
|
|
|
215
|
-
|
|
216
|
-
const viExportAllHelper = "__vitest_browser_runner__.exportAll";
|
|
217
|
-
const skipHijack = [
|
|
218
|
-
"/@vite/client",
|
|
219
|
-
"/@vite/env",
|
|
220
|
-
/vite\/dist\/client/
|
|
221
|
-
];
|
|
222
|
-
function injectVitestModule(code, id, parse) {
|
|
223
|
-
if (skipHijack.some((skip) => id.match(skip)))
|
|
224
|
-
return;
|
|
414
|
+
function injectDynamicImport(code, id, parse) {
|
|
225
415
|
const s = new MagicString(code);
|
|
226
416
|
let ast;
|
|
227
417
|
try {
|
|
@@ -231,165 +421,16 @@ function injectVitestModule(code, id, parse) {
|
|
|
231
421
|
${err.message}`);
|
|
232
422
|
return;
|
|
233
423
|
}
|
|
234
|
-
let uid = 0;
|
|
235
|
-
const idToImportMap = /* @__PURE__ */ new Map();
|
|
236
|
-
const declaredConst = /* @__PURE__ */ new Set();
|
|
237
|
-
const hoistIndex = 0;
|
|
238
|
-
const transformImportDeclaration = (node) => {
|
|
239
|
-
const source = node.source.value;
|
|
240
|
-
if (skipHijack.some((skip) => source.match(skip)))
|
|
241
|
-
return null;
|
|
242
|
-
const importId = `__vi_esm_${uid++}__`;
|
|
243
|
-
const hasSpecifiers = node.specifiers.length > 0;
|
|
244
|
-
const code2 = hasSpecifiers ? `import { ${viInjectedKey} as ${importId} } from '${source}'
|
|
245
|
-
` : `import '${source}'
|
|
246
|
-
`;
|
|
247
|
-
return {
|
|
248
|
-
code: code2,
|
|
249
|
-
id: importId
|
|
250
|
-
};
|
|
251
|
-
};
|
|
252
|
-
function defineImport(node) {
|
|
253
|
-
const declaration = transformImportDeclaration(node);
|
|
254
|
-
if (!declaration)
|
|
255
|
-
return null;
|
|
256
|
-
s.appendLeft(hoistIndex, declaration.code);
|
|
257
|
-
return declaration.id;
|
|
258
|
-
}
|
|
259
|
-
function defineImportAll(source) {
|
|
260
|
-
const importId = `__vi_esm_${uid++}__`;
|
|
261
|
-
s.appendLeft(hoistIndex, `const { ${viInjectedKey}: ${importId} } = await import(${JSON.stringify(source)});
|
|
262
|
-
`);
|
|
263
|
-
return importId;
|
|
264
|
-
}
|
|
265
|
-
function defineExport(position, name, local = name) {
|
|
266
|
-
s.appendLeft(
|
|
267
|
-
position,
|
|
268
|
-
`
|
|
269
|
-
Object.defineProperty(${viInjectedKey}, "${name}", { enumerable: true, configurable: true, get(){ return ${local} }});`
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
for (const node of ast.body) {
|
|
273
|
-
if (node.type === "ImportDeclaration") {
|
|
274
|
-
const importId = defineImport(node);
|
|
275
|
-
if (!importId)
|
|
276
|
-
continue;
|
|
277
|
-
s.remove(node.start, node.end);
|
|
278
|
-
for (const spec of node.specifiers) {
|
|
279
|
-
if (spec.type === "ImportSpecifier") {
|
|
280
|
-
idToImportMap.set(
|
|
281
|
-
spec.local.name,
|
|
282
|
-
`${importId}.${spec.imported.name}`
|
|
283
|
-
);
|
|
284
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
285
|
-
idToImportMap.set(spec.local.name, `${importId}.default`);
|
|
286
|
-
} else {
|
|
287
|
-
idToImportMap.set(spec.local.name, importId);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
for (const node of ast.body) {
|
|
293
|
-
if (node.type === "ExportNamedDeclaration") {
|
|
294
|
-
if (node.declaration) {
|
|
295
|
-
if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
|
|
296
|
-
defineExport(node.end, node.declaration.id.name);
|
|
297
|
-
} else {
|
|
298
|
-
for (const declaration of node.declaration.declarations) {
|
|
299
|
-
const names = extract_names(declaration.id);
|
|
300
|
-
for (const name of names)
|
|
301
|
-
defineExport(node.end, name);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
s.remove(node.start, node.declaration.start);
|
|
305
|
-
} else {
|
|
306
|
-
s.remove(node.start, node.end);
|
|
307
|
-
if (node.source) {
|
|
308
|
-
const importId = defineImportAll(node.source.value);
|
|
309
|
-
for (const spec of node.specifiers) {
|
|
310
|
-
defineExport(
|
|
311
|
-
hoistIndex,
|
|
312
|
-
spec.exported.name,
|
|
313
|
-
`${importId}.${spec.local.name}`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
} else {
|
|
317
|
-
for (const spec of node.specifiers) {
|
|
318
|
-
const local = spec.local.name;
|
|
319
|
-
const binding = idToImportMap.get(local);
|
|
320
|
-
defineExport(node.end, spec.exported.name, binding || local);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if (node.type === "ExportDefaultDeclaration") {
|
|
326
|
-
const expressionTypes = ["FunctionExpression", "ClassExpression"];
|
|
327
|
-
if ("id" in node.declaration && node.declaration.id && !expressionTypes.includes(node.declaration.type)) {
|
|
328
|
-
const { name } = node.declaration.id;
|
|
329
|
-
s.remove(
|
|
330
|
-
node.start,
|
|
331
|
-
node.start + 15
|
|
332
|
-
/* 'export default '.length */
|
|
333
|
-
);
|
|
334
|
-
s.append(
|
|
335
|
-
`
|
|
336
|
-
Object.defineProperty(${viInjectedKey}, "default", { enumerable: true, configurable: true, value: ${name} });`
|
|
337
|
-
);
|
|
338
|
-
} else {
|
|
339
|
-
s.update(
|
|
340
|
-
node.start,
|
|
341
|
-
node.start + 14,
|
|
342
|
-
`${viInjectedKey}.default =`
|
|
343
|
-
);
|
|
344
|
-
s.append(`
|
|
345
|
-
export default { ${viInjectedKey}: ${viInjectedKey}.default };
|
|
346
|
-
`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (node.type === "ExportAllDeclaration") {
|
|
350
|
-
s.remove(node.start, node.end);
|
|
351
|
-
const importId = defineImportAll(node.source.value);
|
|
352
|
-
if (node.exported)
|
|
353
|
-
defineExport(hoistIndex, node.exported.name, `${importId}`);
|
|
354
|
-
else
|
|
355
|
-
s.appendLeft(hoistIndex, `${viExportAllHelper}(${viInjectedKey}, ${importId});
|
|
356
|
-
`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
424
|
esmWalker(ast, {
|
|
360
|
-
onIdentifier(id2, info, parentStack) {
|
|
361
|
-
const binding = idToImportMap.get(id2.name);
|
|
362
|
-
if (!binding)
|
|
363
|
-
return;
|
|
364
|
-
if (info.hasBindingShortcut) {
|
|
365
|
-
s.appendLeft(id2.end, `: ${binding}`);
|
|
366
|
-
} else if (info.classDeclaration) {
|
|
367
|
-
if (!declaredConst.has(id2.name)) {
|
|
368
|
-
declaredConst.add(id2.name);
|
|
369
|
-
const topNode = parentStack[parentStack.length - 2];
|
|
370
|
-
s.prependRight(topNode.start, `const ${id2.name} = ${binding};
|
|
371
|
-
`);
|
|
372
|
-
}
|
|
373
|
-
} else if (
|
|
374
|
-
// don't transform class name identifier
|
|
375
|
-
!info.classExpression
|
|
376
|
-
) {
|
|
377
|
-
s.update(id2.start, id2.end, binding);
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
425
|
// TODO: make env updatable
|
|
381
426
|
onImportMeta() {
|
|
382
427
|
},
|
|
383
428
|
onDynamicImport(node) {
|
|
384
|
-
const replace = "__vitest_browser_runner__.wrapModule(import(";
|
|
429
|
+
const replace = "__vitest_browser_runner__.wrapModule(() => import(";
|
|
385
430
|
s.overwrite(node.start, node.source.start, replace);
|
|
386
431
|
s.overwrite(node.end - 1, node.end, "))");
|
|
387
432
|
}
|
|
388
433
|
});
|
|
389
|
-
s.prepend(`const ${viInjectedKey} = { [Symbol.toStringTag]: "Module" };
|
|
390
|
-
`);
|
|
391
|
-
s.append(`
|
|
392
|
-
export { ${viInjectedKey} }`);
|
|
393
434
|
return {
|
|
394
435
|
ast,
|
|
395
436
|
code: s.toString(),
|
|
@@ -397,6 +438,23 @@ export { ${viInjectedKey} }`);
|
|
|
397
438
|
};
|
|
398
439
|
}
|
|
399
440
|
|
|
441
|
+
const regexDynamicImport = /import\s*\(/;
|
|
442
|
+
var DynamicImport = () => {
|
|
443
|
+
return {
|
|
444
|
+
name: "vitest:browser:esm-injector",
|
|
445
|
+
enforce: "post",
|
|
446
|
+
transform(source, id) {
|
|
447
|
+
if (!regexDynamicImport.test(source))
|
|
448
|
+
return;
|
|
449
|
+
return injectDynamicImport(source, id, this.parse);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
function defineBrowserCommand(fn) {
|
|
455
|
+
return fn;
|
|
456
|
+
}
|
|
457
|
+
|
|
400
458
|
var index = (project, base = "/") => {
|
|
401
459
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
402
460
|
const distRoot = resolve(pkgRoot, "dist");
|
|
@@ -412,9 +470,12 @@ var index = (project, base = "/") => {
|
|
|
412
470
|
}
|
|
413
471
|
},
|
|
414
472
|
async configureServer(server) {
|
|
415
|
-
const testerHtml = readFile(resolve(distRoot, "client/tester.html"), "utf8");
|
|
416
|
-
const
|
|
417
|
-
const injectorJs = readFile(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
|
|
473
|
+
const testerHtml = readFile$1(resolve(distRoot, "client/tester.html"), "utf8");
|
|
474
|
+
const orchestratorHtml = project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8");
|
|
475
|
+
const injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
|
|
476
|
+
const manifest = (async () => {
|
|
477
|
+
return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
|
|
478
|
+
})();
|
|
418
479
|
const favicon = `${base}favicon.svg`;
|
|
419
480
|
const testerPrefix = `${base}__vitest_test__/__test__/`;
|
|
420
481
|
server.middlewares.use((_req, res, next) => {
|
|
@@ -425,7 +486,7 @@ var index = (project, base = "/") => {
|
|
|
425
486
|
}
|
|
426
487
|
next();
|
|
427
488
|
});
|
|
428
|
-
let
|
|
489
|
+
let orchestratorScripts;
|
|
429
490
|
let testerScripts;
|
|
430
491
|
server.middlewares.use(async (req, res, next) => {
|
|
431
492
|
if (!req.url)
|
|
@@ -435,29 +496,70 @@ var index = (project, base = "/") => {
|
|
|
435
496
|
return next();
|
|
436
497
|
res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate");
|
|
437
498
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
438
|
-
const files = project.browserState?.files ?? [];
|
|
439
499
|
const config = wrapConfig(project.getSerializableConfig());
|
|
440
500
|
config.env ??= {};
|
|
441
501
|
config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
|
|
442
|
-
|
|
443
|
-
__VITEST_CONFIG__: JSON.stringify(config),
|
|
444
|
-
__VITEST_FILES__: JSON.stringify(files)
|
|
445
|
-
});
|
|
502
|
+
res.removeHeader("X-Frame-Options");
|
|
446
503
|
if (url.pathname === base) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
504
|
+
let contextId2 = url.searchParams.get("contextId");
|
|
505
|
+
if (!contextId2)
|
|
506
|
+
contextId2 = project.browserState.keys().next().value ?? "none";
|
|
507
|
+
const files2 = project.browserState.get(contextId2)?.files ?? [];
|
|
508
|
+
const injector2 = replacer(await injectorJs, {
|
|
509
|
+
__VITEST_CONFIG__: JSON.stringify(config),
|
|
510
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({
|
|
511
|
+
root: project.browser.config.root
|
|
512
|
+
}),
|
|
513
|
+
__VITEST_FILES__: JSON.stringify(files2),
|
|
514
|
+
__VITEST_TYPE__: url.pathname === base ? '"orchestrator"' : '"tester"',
|
|
515
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId2)
|
|
516
|
+
});
|
|
517
|
+
res.removeHeader("Content-Security-Policy");
|
|
518
|
+
if (!orchestratorScripts)
|
|
519
|
+
orchestratorScripts = await formatScripts(project.config.browser.orchestratorScripts, server);
|
|
520
|
+
let baseHtml = await orchestratorHtml;
|
|
521
|
+
if (project.config.browser.ui) {
|
|
522
|
+
const manifestContent = await manifest;
|
|
523
|
+
const jsEntry = manifestContent["orchestrator.html"].file;
|
|
524
|
+
baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace(
|
|
525
|
+
"<!-- !LOAD_METADATA! -->",
|
|
526
|
+
[
|
|
527
|
+
"<script>{__VITEST_INJECTOR__}<\/script>",
|
|
528
|
+
"{__VITEST_SCRIPTS__}",
|
|
529
|
+
`<script type="module" crossorigin src="${jsEntry}"><\/script>`
|
|
530
|
+
].join("\n")
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
const html2 = replacer(baseHtml, {
|
|
450
534
|
__VITEST_FAVICON__: favicon,
|
|
451
535
|
__VITEST_TITLE__: "Vitest Browser Runner",
|
|
452
|
-
__VITEST_SCRIPTS__:
|
|
453
|
-
__VITEST_INJECTOR__:
|
|
536
|
+
__VITEST_SCRIPTS__: orchestratorScripts,
|
|
537
|
+
__VITEST_INJECTOR__: injector2,
|
|
538
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId2)
|
|
454
539
|
});
|
|
455
540
|
res.write(html2, "utf-8");
|
|
456
541
|
res.end();
|
|
457
542
|
return;
|
|
458
543
|
}
|
|
459
|
-
const
|
|
460
|
-
|
|
544
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
545
|
+
if (typeof csp === "string") {
|
|
546
|
+
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
547
|
+
}
|
|
548
|
+
const [contextId, testFile] = url.pathname.slice(testerPrefix.length).split("/");
|
|
549
|
+
const decodedTestFile = decodeURIComponent(testFile);
|
|
550
|
+
const testFiles = await project.globTestFiles();
|
|
551
|
+
const tests = decodedTestFile === "__vitest_all__" || !testFiles.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
|
|
552
|
+
const iframeId = JSON.stringify(decodedTestFile);
|
|
553
|
+
const files = project.browserState.get(contextId)?.files ?? [];
|
|
554
|
+
const injector = replacer(await injectorJs, {
|
|
555
|
+
__VITEST_CONFIG__: JSON.stringify(config),
|
|
556
|
+
__VITEST_FILES__: JSON.stringify(files),
|
|
557
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({
|
|
558
|
+
root: project.browser.config.root
|
|
559
|
+
}),
|
|
560
|
+
__VITEST_TYPE__: url.pathname === base ? '"orchestrator"' : '"tester"',
|
|
561
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId)
|
|
562
|
+
});
|
|
461
563
|
if (!testerScripts)
|
|
462
564
|
testerScripts = await formatScripts(project.config.browser.testerScripts, server);
|
|
463
565
|
const html = replacer(await testerHtml, {
|
|
@@ -469,6 +571,7 @@ var index = (project, base = "/") => {
|
|
|
469
571
|
// TODO: have only a single global variable to not pollute the global scope
|
|
470
572
|
`<script type="module">
|
|
471
573
|
__vitest_browser_runner__.runningFiles = ${tests}
|
|
574
|
+
__vitest_browser_runner__.iframeId = ${iframeId}
|
|
472
575
|
__vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
|
|
473
576
|
<\/script>`
|
|
474
577
|
)
|
|
@@ -500,24 +603,21 @@ var index = (project, base = "/") => {
|
|
|
500
603
|
name: "vitest:browser:tests",
|
|
501
604
|
enforce: "pre",
|
|
502
605
|
async config() {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
606
|
+
const allTestFiles = await project.globTestFiles();
|
|
607
|
+
const browserTestFiles = allTestFiles.filter((file) => getFilePoolName(project, file) === "browser");
|
|
608
|
+
const setupFiles = toArray(project.config.setupFiles);
|
|
609
|
+
const vitestPaths = [
|
|
610
|
+
resolve(distDir, "index.js"),
|
|
611
|
+
resolve(distDir, "browser.js"),
|
|
612
|
+
resolve(distDir, "runners.js"),
|
|
613
|
+
resolve(distDir, "utils.js")
|
|
614
|
+
];
|
|
512
615
|
return {
|
|
513
616
|
optimizeDeps: {
|
|
514
617
|
entries: [
|
|
515
|
-
...
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
"vitest/browser",
|
|
519
|
-
"vitest/runners",
|
|
520
|
-
"@vitest/utils"
|
|
618
|
+
...browserTestFiles,
|
|
619
|
+
...setupFiles,
|
|
620
|
+
...vitestPaths
|
|
521
621
|
],
|
|
522
622
|
exclude: [
|
|
523
623
|
"vitest",
|
|
@@ -525,31 +625,35 @@ var index = (project, base = "/") => {
|
|
|
525
625
|
"vitest/browser",
|
|
526
626
|
"vitest/runners",
|
|
527
627
|
"@vitest/utils",
|
|
528
|
-
|
|
529
|
-
"
|
|
628
|
+
"@vitest/runner",
|
|
629
|
+
"@vitest/spy",
|
|
630
|
+
"@vitest/utils/error",
|
|
631
|
+
"@vitest/snapshot",
|
|
632
|
+
"@vitest/expect",
|
|
633
|
+
"std-env",
|
|
634
|
+
"tinybench",
|
|
635
|
+
"tinyspy",
|
|
636
|
+
"pathe",
|
|
637
|
+
"msw",
|
|
638
|
+
"msw/browser"
|
|
530
639
|
],
|
|
531
640
|
include: [
|
|
532
641
|
"vitest > @vitest/utils > pretty-format",
|
|
533
642
|
"vitest > @vitest/snapshot > pretty-format",
|
|
534
643
|
"vitest > @vitest/snapshot > magic-string",
|
|
535
|
-
"vitest > diff-sequences",
|
|
536
644
|
"vitest > pretty-format",
|
|
537
645
|
"vitest > pretty-format > ansi-styles",
|
|
538
646
|
"vitest > pretty-format > ansi-regex",
|
|
539
|
-
"vitest > chai"
|
|
647
|
+
"vitest > chai",
|
|
648
|
+
"vitest > chai > loupe",
|
|
649
|
+
"vitest > @vitest/runner > p-limit",
|
|
650
|
+
"vitest > @vitest/utils > diff-sequences",
|
|
651
|
+
"@vitest/browser > @testing-library/user-event",
|
|
652
|
+
"@vitest/browser > @testing-library/dom"
|
|
540
653
|
]
|
|
541
654
|
}
|
|
542
655
|
};
|
|
543
656
|
},
|
|
544
|
-
transform(code, id) {
|
|
545
|
-
if (id.includes("loupe/loupe.js")) {
|
|
546
|
-
const exportsList = ["custom", "inspect", "registerConstructor", "registerStringTag"];
|
|
547
|
-
const codeAppend = exportsList.map((i) => `export const ${i} = globalThis.loupe.${i}`).join("\n");
|
|
548
|
-
return `${code}
|
|
549
|
-
${codeAppend}
|
|
550
|
-
export default globalThis.loupe`;
|
|
551
|
-
}
|
|
552
|
-
},
|
|
553
657
|
async resolveId(id) {
|
|
554
658
|
if (!/\?browserv=\w+$/.test(id))
|
|
555
659
|
return;
|
|
@@ -562,13 +666,47 @@ export default globalThis.loupe`;
|
|
|
562
666
|
}
|
|
563
667
|
},
|
|
564
668
|
{
|
|
565
|
-
name: "vitest:browser:
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
669
|
+
name: "vitest:browser:resolve-virtual",
|
|
670
|
+
async resolveId(rawId) {
|
|
671
|
+
if (rawId.startsWith("/__virtual_vitest__:")) {
|
|
672
|
+
let id = rawId.slice("/__virtual_vitest__:".length);
|
|
673
|
+
if (id === "mocker-worker.js")
|
|
674
|
+
id = "msw/mockServiceWorker.js";
|
|
675
|
+
const resolved = await this.resolve(
|
|
676
|
+
id,
|
|
677
|
+
distRoot,
|
|
678
|
+
{
|
|
679
|
+
skipSelf: true
|
|
680
|
+
}
|
|
681
|
+
);
|
|
682
|
+
return resolved;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
BrowserContext(project),
|
|
687
|
+
DynamicImport(),
|
|
688
|
+
// TODO: remove this when @testing-library/vue supports ESM
|
|
689
|
+
{
|
|
690
|
+
name: "vitest:browser:support-vue-testing-library",
|
|
691
|
+
config() {
|
|
692
|
+
return {
|
|
693
|
+
optimizeDeps: {
|
|
694
|
+
esbuildOptions: {
|
|
695
|
+
plugins: [
|
|
696
|
+
{
|
|
697
|
+
name: "test-utils-rewrite",
|
|
698
|
+
setup(build) {
|
|
699
|
+
const _require = createRequire(import.meta.url);
|
|
700
|
+
build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
|
|
701
|
+
const resolved = _require.resolve(args.path, { paths: [args.importer] });
|
|
702
|
+
return { path: resolved };
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
]
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
572
710
|
}
|
|
573
711
|
}
|
|
574
712
|
];
|
|
@@ -583,7 +721,7 @@ function resolveCoverageFolder(project) {
|
|
|
583
721
|
if (!htmlReporter)
|
|
584
722
|
return void 0;
|
|
585
723
|
const root = resolve(
|
|
586
|
-
options.root ||
|
|
724
|
+
options.root || process.cwd(),
|
|
587
725
|
options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory
|
|
588
726
|
);
|
|
589
727
|
const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : void 0;
|
|
@@ -599,7 +737,7 @@ function wrapConfig(config) {
|
|
|
599
737
|
};
|
|
600
738
|
}
|
|
601
739
|
function replacer(code, values) {
|
|
602
|
-
return code.replace(
|
|
740
|
+
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? "");
|
|
603
741
|
}
|
|
604
742
|
async function formatScripts(scripts, server) {
|
|
605
743
|
if (!scripts?.length)
|
|
@@ -614,4 +752,4 @@ async function formatScripts(scripts, server) {
|
|
|
614
752
|
return (await Promise.all(promises)).join("\n");
|
|
615
753
|
}
|
|
616
754
|
|
|
617
|
-
export { index as default };
|
|
755
|
+
export { index as default, defineBrowserCommand };
|