@vitest/browser 3.0.9 → 3.1.0-beta.2

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/index.js CHANGED
@@ -11,12 +11,12 @@ import { fileURLToPath } from 'node:url';
11
11
  import crypto from 'node:crypto';
12
12
  import { mkdir, readFile as readFile$1 } from 'node:fs/promises';
13
13
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
14
- import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-C5-VI7VH.js';
14
+ import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-BP2w_ajA.js';
15
15
  import { resolve as resolve$1, dirname as dirname$1, basename as basename$1, normalize as normalize$1 } from 'node:path';
16
16
  import { WebSocketServer } from 'ws';
17
17
  import * as nodeos from 'node:os';
18
18
 
19
- var version = "3.0.9";
19
+ var version = "3.1.0-beta.2";
20
20
 
21
21
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
22
22
  function normalizeWindowsPath(input = "") {
@@ -212,130 +212,117 @@ const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
212
212
  const distRoot = resolve(pkgRoot, "dist");
213
213
 
214
214
  function replacer(code, values) {
215
- return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
215
+ return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
216
216
  }
217
- const builtinProviders = ["webdriverio", "playwright", "preview"];
217
+ const builtinProviders = [
218
+ "webdriverio",
219
+ "playwright",
220
+ "preview"
221
+ ];
218
222
  async function getBrowserProvider(options, project) {
219
- if (options.provider == null || builtinProviders.includes(options.provider)) {
220
- const providers = await import('./providers.js');
221
- const provider = options.provider || "preview";
222
- return providers[provider];
223
- }
224
- let customProviderModule;
225
- try {
226
- customProviderModule = await project.import(
227
- options.provider
228
- );
229
- } catch (error) {
230
- throw new Error(
231
- `Failed to load custom BrowserProvider from ${options.provider}`,
232
- { cause: error }
233
- );
234
- }
235
- if (customProviderModule.default == null) {
236
- throw new Error(
237
- `Custom BrowserProvider loaded from ${options.provider} was not the default export`
238
- );
239
- }
240
- return customProviderModule.default;
223
+ if (options.provider == null || builtinProviders.includes(options.provider)) {
224
+ const providers = await import('./providers.js');
225
+ const provider = options.provider || "preview";
226
+ return providers[provider];
227
+ }
228
+ let customProviderModule;
229
+ try {
230
+ customProviderModule = await project.import(options.provider);
231
+ } catch (error) {
232
+ throw new Error(`Failed to load custom BrowserProvider from ${options.provider}`, { cause: error });
233
+ }
234
+ if (customProviderModule.default == null) {
235
+ throw new Error(`Custom BrowserProvider loaded from ${options.provider} was not the default export`);
236
+ }
237
+ return customProviderModule.default;
241
238
  }
242
239
  function slash(path) {
243
- return path.replace(/\\/g, "/").replace(/\/+/g, "/");
240
+ return path.replace(/\\/g, "/").replace(/\/+/g, "/");
244
241
  }
245
242
 
246
243
  async function resolveOrchestrator(globalServer, url, res) {
247
- let sessionId = url.searchParams.get("sessionId");
248
- if (!sessionId) {
249
- const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
250
- sessionId = contexts[contexts.length - 1] ?? "none";
251
- }
252
- const session = globalServer.vitest._browserSessions.getSession(sessionId);
253
- const files = session?.files ?? [];
254
- const browserProject = session?.project.browser || [...globalServer.children][0];
255
- if (!browserProject) {
256
- return;
257
- }
258
- const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
259
- const injector = replacer(injectorJs, {
260
- __VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider || "preview"),
261
- __VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
262
- __VITEST_VITE_CONFIG__: JSON.stringify({
263
- root: browserProject.vite.config.root
264
- }),
265
- __VITEST_METHOD__: JSON.stringify(session?.method || "run"),
266
- __VITEST_FILES__: JSON.stringify(files),
267
- __VITEST_TYPE__: '"orchestrator"',
268
- __VITEST_SESSION_ID__: JSON.stringify(sessionId),
269
- __VITEST_TESTER_ID__: '"none"',
270
- __VITEST_PROVIDED_CONTEXT__: "{}",
271
- __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
272
- });
273
- res.removeHeader("Content-Security-Policy");
274
- if (!globalServer.orchestratorScripts) {
275
- globalServer.orchestratorScripts = (await globalServer.formatScripts(
276
- globalServer.config.browser.orchestratorScripts
277
- )).map((script) => {
278
- let html = "<script ";
279
- for (const attr in script.attrs || {}) {
280
- html += `${attr}="${script.attrs[attr]}" `;
281
- }
282
- html += `>${script.children}<\/script>`;
283
- return html;
284
- }).join("\n");
285
- }
286
- let baseHtml = typeof globalServer.orchestratorHtml === "string" ? globalServer.orchestratorHtml : await globalServer.orchestratorHtml;
287
- if (globalServer.config.browser.ui) {
288
- const manifestContent = globalServer.manifest instanceof Promise ? await globalServer.manifest : globalServer.manifest;
289
- const jsEntry = manifestContent["orchestrator.html"].file;
290
- const base = browserProject.parent.vite.config.base || "/";
291
- baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace(
292
- "<!-- !LOAD_METADATA! -->",
293
- [
294
- "{__VITEST_INJECTOR__}",
295
- "{__VITEST_ERROR_CATCHER__}",
296
- "{__VITEST_SCRIPTS__}",
297
- `<script type="module" crossorigin src="${base}${jsEntry}"><\/script>`
298
- ].join("\n")
299
- );
300
- }
301
- return replacer(baseHtml, {
302
- __VITEST_FAVICON__: globalServer.faviconUrl,
303
- __VITEST_TITLE__: "Vitest Browser Runner",
304
- __VITEST_SCRIPTS__: globalServer.orchestratorScripts,
305
- __VITEST_INJECTOR__: `<script type="module">${injector}<\/script>`,
306
- __VITEST_ERROR_CATCHER__: `<script type="module" src="${globalServer.errorCatcherUrl}"><\/script>`,
307
- __VITEST_SESSION_ID__: JSON.stringify(sessionId)
308
- });
244
+ let sessionId = url.searchParams.get("sessionId");
245
+ if (!sessionId) {
246
+ const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
247
+ sessionId = contexts[contexts.length - 1] ?? "none";
248
+ }
249
+ const session = globalServer.vitest._browserSessions.getSession(sessionId);
250
+ const files = session?.files ?? [];
251
+ const browserProject = session?.project.browser || [...globalServer.children][0];
252
+ if (!browserProject) {
253
+ return;
254
+ }
255
+ const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
256
+ const injector = replacer(injectorJs, {
257
+ __VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider || "preview"),
258
+ __VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
259
+ __VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
260
+ __VITEST_METHOD__: JSON.stringify(session?.method || "run"),
261
+ __VITEST_FILES__: JSON.stringify(files),
262
+ __VITEST_TYPE__: "\"orchestrator\"",
263
+ __VITEST_SESSION_ID__: JSON.stringify(sessionId),
264
+ __VITEST_TESTER_ID__: "\"none\"",
265
+ __VITEST_PROVIDED_CONTEXT__: "{}",
266
+ __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
267
+ });
268
+ res.removeHeader("Content-Security-Policy");
269
+ if (!globalServer.orchestratorScripts) {
270
+ globalServer.orchestratorScripts = (await globalServer.formatScripts(globalServer.config.browser.orchestratorScripts)).map((script) => {
271
+ let html = "<script ";
272
+ for (const attr in script.attrs || {}) {
273
+ html += `${attr}="${script.attrs[attr]}" `;
274
+ }
275
+ html += `>${script.children}</script>`;
276
+ return html;
277
+ }).join("\n");
278
+ }
279
+ let baseHtml = typeof globalServer.orchestratorHtml === "string" ? globalServer.orchestratorHtml : await globalServer.orchestratorHtml;
280
+ if (globalServer.config.browser.ui) {
281
+ const manifestContent = globalServer.manifest instanceof Promise ? await globalServer.manifest : globalServer.manifest;
282
+ const jsEntry = manifestContent["orchestrator.html"].file;
283
+ const base = browserProject.parent.vite.config.base || "/";
284
+ baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace("<!-- !LOAD_METADATA! -->", [
285
+ "{__VITEST_INJECTOR__}",
286
+ "{__VITEST_ERROR_CATCHER__}",
287
+ "{__VITEST_SCRIPTS__}",
288
+ `<script type="module" crossorigin src="${base}${jsEntry}"></script>`
289
+ ].join("\n"));
290
+ }
291
+ return replacer(baseHtml, {
292
+ __VITEST_FAVICON__: globalServer.faviconUrl,
293
+ __VITEST_TITLE__: "Vitest Browser Runner",
294
+ __VITEST_SCRIPTS__: globalServer.orchestratorScripts,
295
+ __VITEST_INJECTOR__: `<script type="module">${injector}</script>`,
296
+ __VITEST_ERROR_CATCHER__: `<script type="module" src="${globalServer.errorCatcherUrl}"></script>`,
297
+ __VITEST_SESSION_ID__: JSON.stringify(sessionId)
298
+ });
309
299
  }
310
300
 
311
301
  function disableCache(res) {
312
- res.setHeader(
313
- "Cache-Control",
314
- "no-cache, max-age=0, must-revalidate"
315
- );
316
- res.setHeader("Content-Type", "text/html; charset=utf-8");
302
+ res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate");
303
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
317
304
  }
318
305
  function allowIframes(res) {
319
- res.removeHeader("X-Frame-Options");
306
+ res.removeHeader("X-Frame-Options");
320
307
  }
321
308
 
322
309
  function createOrchestratorMiddleware(parentServer) {
323
- return async function vitestOrchestratorMiddleware(req, res, next) {
324
- if (!req.url) {
325
- return next();
326
- }
327
- const url = new URL(req.url, "http://localhost");
328
- if (url.pathname !== parentServer.base) {
329
- return next();
330
- }
331
- const html = await resolveOrchestrator(parentServer, url, res);
332
- if (html) {
333
- disableCache(res);
334
- allowIframes(res);
335
- res.write(html, "utf-8");
336
- res.end();
337
- }
338
- };
310
+ return async function vitestOrchestratorMiddleware(req, res, next) {
311
+ if (!req.url) {
312
+ return next();
313
+ }
314
+ const url = new URL(req.url, "http://localhost");
315
+ if (url.pathname !== parentServer.base) {
316
+ return next();
317
+ }
318
+ const html = await resolveOrchestrator(parentServer, url, res);
319
+ if (html) {
320
+ disableCache(res);
321
+ allowIframes(res);
322
+ res.write(html, "utf-8");
323
+ res.end();
324
+ }
325
+ };
339
326
  }
340
327
 
341
328
  /// <reference types="../types/index.d.ts" />
@@ -446,119 +433,111 @@ const stringify = (value, replacer, space) => {
446
433
  };
447
434
 
448
435
  async function resolveTester(globalServer, url, res, next) {
449
- const csp = res.getHeader("Content-Security-Policy");
450
- if (typeof csp === "string") {
451
- res.setHeader(
452
- "Content-Security-Policy",
453
- csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *")
454
- );
455
- }
456
- const { sessionId, testFile } = globalServer.resolveTesterUrl(url.pathname);
457
- const session = globalServer.vitest._browserSessions.getSession(sessionId);
458
- if (!session) {
459
- res.statusCode = 400;
460
- res.end("Invalid session ID");
461
- return;
462
- }
463
- const project = globalServer.vitest.getProjectByName(session.project.name || "");
464
- const { testFiles } = await project.globTestFiles();
465
- const tests = testFile === "__vitest_all__" || !testFiles.includes(testFile) ? "__vitest_browser_runner__.files" : JSON.stringify([testFile]);
466
- const iframeId = JSON.stringify(testFile);
467
- const files = session.files ?? [];
468
- const method = session.method ?? "run";
469
- const browserProject = project.browser || [...globalServer.children][0];
470
- if (!browserProject) {
471
- res.statusCode = 400;
472
- res.end("Invalid session ID");
473
- return;
474
- }
475
- const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
476
- const injector = replacer(injectorJs, {
477
- __VITEST_PROVIDER__: JSON.stringify(project.browser.provider.name),
478
- __VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
479
- __VITEST_FILES__: JSON.stringify(files),
480
- __VITEST_VITE_CONFIG__: JSON.stringify({
481
- root: browserProject.vite.config.root
482
- }),
483
- __VITEST_TYPE__: '"tester"',
484
- __VITEST_METHOD__: JSON.stringify(method),
485
- __VITEST_SESSION_ID__: JSON.stringify(sessionId),
486
- __VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()),
487
- __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(project.getProvidedContext())),
488
- __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
489
- });
490
- const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
491
- try {
492
- const url2 = join("/@fs/", browserProject.testerFilepath);
493
- const indexhtml = await browserProject.vite.transformIndexHtml(url2, testerHtml);
494
- const html = replacer(indexhtml, {
495
- __VITEST_FAVICON__: globalServer.faviconUrl,
496
- __VITEST_INJECTOR__: injector,
497
- __VITEST_APPEND__: `
436
+ const csp = res.getHeader("Content-Security-Policy");
437
+ if (typeof csp === "string") {
438
+ res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
439
+ }
440
+ const { sessionId, testFile } = globalServer.resolveTesterUrl(url.pathname);
441
+ const session = globalServer.vitest._browserSessions.getSession(sessionId);
442
+ if (!session) {
443
+ res.statusCode = 400;
444
+ res.end("Invalid session ID");
445
+ return;
446
+ }
447
+ const project = globalServer.vitest.getProjectByName(session.project.name || "");
448
+ const { testFiles } = await project.globTestFiles();
449
+ const tests = testFile === "__vitest_all__" || !testFiles.includes(testFile) ? "__vitest_browser_runner__.files" : JSON.stringify([testFile]);
450
+ const iframeId = JSON.stringify(testFile);
451
+ const files = session.files ?? [];
452
+ const method = session.method ?? "run";
453
+ const browserProject = project.browser || [...globalServer.children][0];
454
+ if (!browserProject) {
455
+ res.statusCode = 400;
456
+ res.end("Invalid session ID");
457
+ return;
458
+ }
459
+ const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
460
+ const injector = replacer(injectorJs, {
461
+ __VITEST_PROVIDER__: JSON.stringify(project.browser.provider.name),
462
+ __VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
463
+ __VITEST_FILES__: JSON.stringify(files),
464
+ __VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
465
+ __VITEST_TYPE__: "\"tester\"",
466
+ __VITEST_METHOD__: JSON.stringify(method),
467
+ __VITEST_SESSION_ID__: JSON.stringify(sessionId),
468
+ __VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()),
469
+ __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(project.getProvidedContext())),
470
+ __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
471
+ });
472
+ const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
473
+ try {
474
+ const url = join("/@fs/", browserProject.testerFilepath);
475
+ const indexhtml = await browserProject.vite.transformIndexHtml(url, testerHtml);
476
+ const html = replacer(indexhtml, {
477
+ __VITEST_FAVICON__: globalServer.faviconUrl,
478
+ __VITEST_INJECTOR__: injector,
479
+ __VITEST_APPEND__: `
498
480
  __vitest_browser_runner__.runningFiles = ${tests}
499
481
  __vitest_browser_runner__.iframeId = ${iframeId}
500
482
  __vitest_browser_runner__.${method === "run" ? "runTests" : "collectTests"}(__vitest_browser_runner__.runningFiles)
501
483
  document.querySelector('script[data-vitest-append]').remove()
502
484
  `
503
- });
504
- return html;
505
- } catch (err) {
506
- session.reject(err);
507
- next(err);
508
- }
485
+ });
486
+ return html;
487
+ } catch (err) {
488
+ session.reject(err);
489
+ next(err);
490
+ }
509
491
  }
510
492
 
511
493
  function createTesterMiddleware(browserServer) {
512
- return async function vitestTesterMiddleware(req, res, next) {
513
- if (!req.url) {
514
- return next();
515
- }
516
- const url = new URL(req.url, "http://localhost");
517
- if (!url.pathname.startsWith(browserServer.prefixTesterUrl)) {
518
- return next();
519
- }
520
- const html = await resolveTester(browserServer, url, res, next);
521
- if (html) {
522
- disableCache(res);
523
- allowIframes(res);
524
- res.write(html, "utf-8");
525
- res.end();
526
- }
527
- };
494
+ return async function vitestTesterMiddleware(req, res, next) {
495
+ if (!req.url) {
496
+ return next();
497
+ }
498
+ const url = new URL(req.url, "http://localhost");
499
+ if (!url.pathname.startsWith(browserServer.prefixTesterUrl)) {
500
+ return next();
501
+ }
502
+ const html = await resolveTester(browserServer, url, res, next);
503
+ if (html) {
504
+ disableCache(res);
505
+ allowIframes(res);
506
+ res.write(html, "utf-8");
507
+ res.end();
508
+ }
509
+ };
528
510
  }
529
511
 
530
512
  const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
531
513
  const ID_CONTEXT = "@vitest/browser/context";
532
514
  const __dirname = dirname(fileURLToPath(import.meta.url));
533
515
  function BrowserContext(globalServer) {
534
- return {
535
- name: "vitest:browser:virtual-module:context",
536
- enforce: "pre",
537
- resolveId(id) {
538
- if (id === ID_CONTEXT) {
539
- return VIRTUAL_ID_CONTEXT;
540
- }
541
- },
542
- load(id) {
543
- if (id === VIRTUAL_ID_CONTEXT) {
544
- return generateContextFile.call(this, globalServer);
545
- }
546
- }
547
- };
516
+ return {
517
+ name: "vitest:browser:virtual-module:context",
518
+ enforce: "pre",
519
+ resolveId(id) {
520
+ if (id === ID_CONTEXT) {
521
+ return VIRTUAL_ID_CONTEXT;
522
+ }
523
+ },
524
+ load(id) {
525
+ if (id === VIRTUAL_ID_CONTEXT) {
526
+ return generateContextFile.call(this, globalServer);
527
+ }
528
+ }
529
+ };
548
530
  }
549
531
  async function generateContextFile(globalServer) {
550
- const commands = Object.keys(globalServer.commands);
551
- const provider = [...globalServer.children][0].provider || { name: "preview" };
552
- const providerName = provider.name;
553
- const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
554
- return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
555
- }).join("\n");
556
- const userEventNonProviderImport = await getUserEventImport(
557
- providerName,
558
- this.resolve.bind(this)
559
- );
560
- const distContextPath = slash$1(`/@fs/${resolve(__dirname, "context.js")}`);
561
- return `
532
+ const commands = Object.keys(globalServer.commands);
533
+ const provider = [...globalServer.children][0].provider || { name: "preview" };
534
+ const providerName = provider.name;
535
+ const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
536
+ return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
537
+ }).join("\n");
538
+ const userEventNonProviderImport = await getUserEventImport(providerName, this.resolve.bind(this));
539
+ const distContextPath = slash$1(`/@fs/${resolve(__dirname, "context.js")}`);
540
+ return `
562
541
  import { page, createUserEvent, cdp } from '${distContextPath}'
563
542
  ${userEventNonProviderImport}
564
543
 
@@ -577,410 +556,361 @@ export const userEvent = createUserEvent(_userEventSetup)
577
556
  export { page, cdp }
578
557
  `;
579
558
  }
580
- async function getUserEventImport(provider, resolve2) {
581
- if (provider !== "preview") {
582
- return "const _userEventSetup = undefined";
583
- }
584
- const resolved = await resolve2("@testing-library/user-event", __dirname);
585
- if (!resolved) {
586
- throw new Error(`Failed to resolve user-event package from ${__dirname}`);
587
- }
588
- return `import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
559
+ async function getUserEventImport(provider, resolve) {
560
+ if (provider !== "preview") {
561
+ return "const _userEventSetup = undefined";
562
+ }
563
+ const resolved = await resolve("@testing-library/user-event", __dirname);
564
+ if (!resolved) {
565
+ throw new Error(`Failed to resolve user-event package from ${__dirname}`);
566
+ }
567
+ return `\
568
+ import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
589
569
  const _userEventSetup = __vitest_user_event__
590
570
  `;
591
571
  }
592
572
 
593
573
  const versionRegexp = /(?:\?|&)v=\w{8}/;
594
574
  var BrowserPlugin = (parentServer, base = "/") => {
595
- function isPackageExists(pkg, root) {
596
- return parentServer.vitest.packageInstaller.isPackageExists?.(pkg, {
597
- paths: [root]
598
- });
599
- }
600
- return [
601
- {
602
- enforce: "pre",
603
- name: "vitest:browser",
604
- async configureServer(server) {
605
- parentServer.setServer(server);
606
- server.middlewares.use(function vitestHeaders(_req, res, next) {
607
- const headers = server.config.server.headers;
608
- if (headers) {
609
- for (const name in headers) {
610
- res.setHeader(name, headers[name]);
611
- }
612
- }
613
- next();
614
- });
615
- server.middlewares.use(createOrchestratorMiddleware(parentServer));
616
- server.middlewares.use(createTesterMiddleware(parentServer));
617
- server.middlewares.use(
618
- `${base}favicon.svg`,
619
- (_, res) => {
620
- const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
621
- res.write(content, "utf-8");
622
- res.end();
623
- }
624
- );
625
- const coverageFolder = resolveCoverageFolder(parentServer.vitest);
626
- const coveragePath = coverageFolder ? coverageFolder[1] : void 0;
627
- if (coveragePath && base === coveragePath) {
628
- throw new Error(
629
- `The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`
630
- );
631
- }
632
- if (coverageFolder) {
633
- server.middlewares.use(
634
- coveragePath,
635
- sirv(coverageFolder[0], {
636
- single: true,
637
- dev: true,
638
- setHeaders: (res) => {
639
- res.setHeader(
640
- "Cache-Control",
641
- "public,max-age=0,must-revalidate"
642
- );
643
- }
644
- })
645
- );
646
- }
647
- const uiEnabled = parentServer.config.browser.ui;
648
- if (uiEnabled) {
649
- server.middlewares.use(`${base}__screenshot-error`, function vitestBrowserScreenshotError(req, res) {
650
- if (!req.url) {
651
- res.statusCode = 404;
652
- res.end();
653
- return;
654
- }
655
- const url = new URL(req.url, "http://localhost");
656
- const id = url.searchParams.get("id");
657
- if (!id) {
658
- res.statusCode = 404;
659
- res.end();
660
- return;
661
- }
662
- const task = parentServer.vitest.state.idMap.get(id);
663
- const file = task?.meta.failScreenshotPath;
664
- if (!file) {
665
- res.statusCode = 404;
666
- res.end();
667
- return;
668
- }
669
- let stat;
670
- try {
671
- stat = lstatSync(file);
672
- } catch {
673
- }
674
- if (!stat?.isFile()) {
675
- res.statusCode = 404;
676
- res.end();
677
- return;
678
- }
679
- const ext = extname(file);
680
- const buffer = readFileSync(file);
681
- res.setHeader(
682
- "Cache-Control",
683
- "public,max-age=0,must-revalidate"
684
- );
685
- res.setHeader("Content-Length", buffer.length);
686
- res.setHeader("Content-Type", ext === "jpeg" || ext === "jpg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png");
687
- res.end(buffer);
688
- });
689
- }
690
- server.middlewares.use((req, res, next) => {
691
- if (req.url && versionRegexp.test(req.url) && !req.url.includes("chunk-")) {
692
- res.setHeader("Cache-Control", "no-cache");
693
- const setHeader = res.setHeader.bind(res);
694
- res.setHeader = function(name, value) {
695
- if (name === "Cache-Control") {
696
- return res;
697
- }
698
- return setHeader(name, value);
699
- };
700
- }
701
- next();
702
- });
703
- }
704
- },
705
- {
706
- name: "vitest:browser:tests",
707
- enforce: "pre",
708
- async config() {
709
- const project = parentServer.project;
710
- const { testFiles: allTestFiles } = await project.globTestFiles();
711
- const browserTestFiles = allTestFiles.filter(
712
- (file) => getFilePoolName(project, file) === "browser"
713
- );
714
- const setupFiles = toArray(project.config.setupFiles);
715
- const define = {};
716
- for (const env in project.config.env || {}) {
717
- const stringValue = JSON.stringify(project.config.env[env]);
718
- define[`import.meta.env.${env}`] = stringValue;
719
- }
720
- const entries = [
721
- ...browserTestFiles,
722
- ...setupFiles,
723
- resolve(distDir, "index.js"),
724
- resolve(distDir, "browser.js"),
725
- resolve(distDir, "runners.js"),
726
- resolve(distDir, "utils.js"),
727
- ...project.config.snapshotSerializers || []
728
- ];
729
- const exclude = [
730
- "vitest",
731
- "vitest/utils",
732
- "vitest/browser",
733
- "vitest/runners",
734
- "@vitest/browser",
735
- "@vitest/browser/client",
736
- "@vitest/utils",
737
- "@vitest/utils/source-map",
738
- "@vitest/runner",
739
- "@vitest/spy",
740
- "@vitest/utils/error",
741
- "@vitest/snapshot",
742
- "@vitest/expect",
743
- "std-env",
744
- "tinybench",
745
- "tinyspy",
746
- "tinyrainbow",
747
- "pathe",
748
- "msw",
749
- "msw/browser"
750
- ];
751
- if (typeof project.config.diff === "string") {
752
- entries.push(project.config.diff);
753
- }
754
- if (parentServer.vitest.coverageProvider) {
755
- const coverage = parentServer.vitest.config.coverage;
756
- const provider = coverage.provider;
757
- if (provider === "v8") {
758
- const path = tryResolve("@vitest/coverage-v8", [parentServer.config.root]);
759
- if (path) {
760
- entries.push(path);
761
- exclude.push("@vitest/coverage-v8/browser");
762
- }
763
- } else if (provider === "istanbul") {
764
- const path = tryResolve("@vitest/coverage-istanbul", [parentServer.config.root]);
765
- if (path) {
766
- entries.push(path);
767
- exclude.push("@vitest/coverage-istanbul");
768
- }
769
- } else if (provider === "custom" && coverage.customProviderModule) {
770
- entries.push(coverage.customProviderModule);
771
- }
772
- }
773
- const include = [
774
- "vitest > expect-type",
775
- "vitest > @vitest/snapshot > magic-string",
776
- "vitest > chai",
777
- "vitest > chai > loupe",
778
- "vitest > @vitest/utils > loupe",
779
- "@vitest/browser > @testing-library/user-event",
780
- "@vitest/browser > @testing-library/dom"
781
- ];
782
- const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
783
- const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
784
- if (svelte) {
785
- exclude.push("vitest-browser-svelte");
786
- }
787
- const vue = isPackageExists("vitest-browser-vue", fileRoot);
788
- if (vue) {
789
- include.push(
790
- "vitest-browser-vue",
791
- "vitest-browser-vue > @vue/test-utils",
792
- "vitest-browser-vue > @vue/test-utils > @vue/compiler-core"
793
- );
794
- }
795
- const vueTestUtils = isPackageExists("@vue/test-utils", fileRoot);
796
- if (vueTestUtils) {
797
- include.push("@vue/test-utils");
798
- }
799
- return {
800
- define,
801
- resolve: {
802
- dedupe: ["vitest"]
803
- },
804
- optimizeDeps: {
805
- entries,
806
- exclude,
807
- include
808
- }
809
- };
810
- },
811
- async resolveId(id) {
812
- if (!/\?browserv=\w+$/.test(id)) {
813
- return;
814
- }
815
- let useId = id.slice(0, id.lastIndexOf("?"));
816
- if (useId.startsWith("/@fs/")) {
817
- useId = useId.slice(5);
818
- }
819
- if (/^\w:/.test(useId)) {
820
- useId = useId.replace(/\\/g, "/");
821
- }
822
- return useId;
823
- }
824
- },
825
- {
826
- name: "vitest:browser:resolve-virtual",
827
- async resolveId(rawId) {
828
- if (rawId === "/mockServiceWorker.js") {
829
- return this.resolve("msw/mockServiceWorker.js", distRoot, {
830
- skipSelf: true
831
- });
832
- }
833
- }
834
- },
835
- {
836
- name: "vitest:browser:assets",
837
- configureServer(server) {
838
- server.middlewares.use(
839
- "/__vitest__",
840
- sirv(resolve(distRoot, "client/__vitest__"))
841
- );
842
- },
843
- resolveId(id) {
844
- if (id.startsWith("/__vitest_browser__/")) {
845
- return resolve(distRoot, "client", id.slice(1));
846
- }
847
- },
848
- transform(code, id) {
849
- if (id.includes(parentServer.vite.config.cacheDir) && id.includes("loupe.js")) {
850
- const utilRequire = "nodeUtil = require_util();";
851
- return code.replace(utilRequire, " ".repeat(utilRequire.length));
852
- }
853
- }
854
- },
855
- BrowserContext(parentServer),
856
- dynamicImportPlugin({
857
- globalThisAccessor: '"__vitest_browser_runner__"',
858
- filter(id) {
859
- if (id.includes(distRoot)) {
860
- return false;
861
- }
862
- return true;
863
- }
864
- }),
865
- {
866
- name: "vitest:browser:config",
867
- enforce: "post",
868
- async config(viteConfig) {
869
- // Enables using ignore hint for coverage providers with @preserve keyword
870
- if (viteConfig.esbuild !== false) {
871
- viteConfig.esbuild ||= {};
872
- viteConfig.esbuild.legalComments = "inline";
873
- }
874
- const defaultPort = parentServer.vitest._browserLastPort++;
875
- const api = resolveApiServerConfig(
876
- viteConfig.test?.browser || {},
877
- defaultPort
878
- );
879
- viteConfig.server = {
880
- ...viteConfig.server,
881
- port: defaultPort,
882
- ...api,
883
- middlewareMode: false,
884
- open: false
885
- };
886
- viteConfig.server.fs ??= {};
887
- viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
888
- viteConfig.server.fs.allow.push(
889
- ...resolveFsAllow(
890
- parentServer.vitest.config.root,
891
- parentServer.vitest.vite.config.configFile
892
- ),
893
- distRoot
894
- );
895
- return {
896
- resolve: {
897
- alias: viteConfig.test?.alias
898
- }
899
- };
900
- }
901
- },
902
- {
903
- name: "vitest:browser:in-source-tests",
904
- transform(code, id) {
905
- const project = parentServer.vitest.getProjectByName(parentServer.config.name);
906
- if (!project._isCachedTestFile(id) || !code.includes("import.meta.vitest")) {
907
- return;
908
- }
909
- const s = new MagicString(code, { filename: cleanUrl(id) });
910
- s.prepend(
911
- `import.meta.vitest = __vitest_index__;
912
- `
913
- );
914
- return {
915
- code: s.toString(),
916
- map: s.generateMap({ hires: true })
917
- };
918
- }
919
- },
920
- {
921
- name: "vitest:browser:worker",
922
- transform(code, id, _options) {
923
- if (/(?:\?|&)worker_file&type=\w+(?:&|$)/.test(id)) {
924
- const s = new MagicString(code);
925
- s.prepend("globalThis.__vitest_browser_runner__ = { wrapDynamicImport: f => f() };\n");
926
- return {
927
- code: s.toString(),
928
- map: s.generateMap({ hires: "boundary" })
929
- };
930
- }
931
- }
932
- },
933
- {
934
- name: "vitest:browser:transform-tester-html",
935
- enforce: "pre",
936
- async transformIndexHtml(html, ctx) {
937
- const projectBrowser = [...parentServer.children].find((server) => {
938
- return ctx.filename === server.testerFilepath;
939
- });
940
- if (!projectBrowser) {
941
- return;
942
- }
943
- if (!parentServer.testerScripts) {
944
- const testerScripts = await parentServer.formatScripts(
945
- parentServer.config.browser.testerScripts
946
- );
947
- parentServer.testerScripts = testerScripts;
948
- }
949
- const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
950
- const testerTags = [];
951
- const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
952
- if (!isDefaultTemplate) {
953
- const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
954
- const testerEntry = manifestContent["tester/tester.html"];
955
- testerTags.push({
956
- tag: "script",
957
- attrs: {
958
- type: "module",
959
- crossorigin: "",
960
- src: `${parentServer.base}${testerEntry.file}`
961
- },
962
- injectTo: "head"
963
- });
964
- for (const importName of testerEntry.imports || []) {
965
- const entryManifest = manifestContent[importName];
966
- if (entryManifest) {
967
- testerTags.push(
968
- {
969
- tag: "link",
970
- attrs: {
971
- href: `${parentServer.base}${entryManifest.file}`,
972
- rel: "modulepreload",
973
- crossorigin: ""
974
- },
975
- injectTo: "head"
976
- }
977
- );
978
- }
979
- }
980
- } else {
981
- testerTags.push({
982
- tag: "style",
983
- children: `
575
+ function isPackageExists(pkg, root) {
576
+ return parentServer.vitest.packageInstaller.isPackageExists?.(pkg, { paths: [root] });
577
+ }
578
+ return [
579
+ {
580
+ enforce: "pre",
581
+ name: "vitest:browser",
582
+ async configureServer(server) {
583
+ parentServer.setServer(server);
584
+ server.middlewares.use(function vitestHeaders(_req, res, next) {
585
+ const headers = server.config.server.headers;
586
+ if (headers) {
587
+ for (const name in headers) {
588
+ res.setHeader(name, headers[name]);
589
+ }
590
+ }
591
+ next();
592
+ });
593
+ server.middlewares.use(createOrchestratorMiddleware(parentServer));
594
+ server.middlewares.use(createTesterMiddleware(parentServer));
595
+ server.middlewares.use(`${base}favicon.svg`, (_, res) => {
596
+ const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
597
+ res.write(content, "utf-8");
598
+ res.end();
599
+ });
600
+ const coverageFolder = resolveCoverageFolder(parentServer.vitest);
601
+ const coveragePath = coverageFolder ? coverageFolder[1] : undefined;
602
+ if (coveragePath && base === coveragePath) {
603
+ throw new Error(`The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`);
604
+ }
605
+ if (coverageFolder) {
606
+ server.middlewares.use(coveragePath, sirv(coverageFolder[0], {
607
+ single: true,
608
+ dev: true,
609
+ setHeaders: (res) => {
610
+ res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
611
+ }
612
+ }));
613
+ }
614
+ const uiEnabled = parentServer.config.browser.ui;
615
+ if (uiEnabled) {
616
+ server.middlewares.use(`${base}__screenshot-error`, function vitestBrowserScreenshotError(req, res) {
617
+ if (!req.url) {
618
+ res.statusCode = 404;
619
+ res.end();
620
+ return;
621
+ }
622
+ const url = new URL(req.url, "http://localhost");
623
+ const id = url.searchParams.get("id");
624
+ if (!id) {
625
+ res.statusCode = 404;
626
+ res.end();
627
+ return;
628
+ }
629
+ const task = parentServer.vitest.state.idMap.get(id);
630
+ const file = task?.meta.failScreenshotPath;
631
+ if (!file) {
632
+ res.statusCode = 404;
633
+ res.end();
634
+ return;
635
+ }
636
+ let stat;
637
+ try {
638
+ stat = lstatSync(file);
639
+ } catch {}
640
+ if (!stat?.isFile()) {
641
+ res.statusCode = 404;
642
+ res.end();
643
+ return;
644
+ }
645
+ const ext = extname(file);
646
+ const buffer = readFileSync(file);
647
+ res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
648
+ res.setHeader("Content-Length", buffer.length);
649
+ res.setHeader("Content-Type", ext === "jpeg" || ext === "jpg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png");
650
+ res.end(buffer);
651
+ });
652
+ }
653
+ server.middlewares.use((req, res, next) => {
654
+ if (req.url && versionRegexp.test(req.url) && !req.url.includes("chunk-")) {
655
+ res.setHeader("Cache-Control", "no-cache");
656
+ const setHeader = res.setHeader.bind(res);
657
+ res.setHeader = function(name, value) {
658
+ if (name === "Cache-Control") {
659
+ return res;
660
+ }
661
+ return setHeader(name, value);
662
+ };
663
+ }
664
+ next();
665
+ });
666
+ }
667
+ },
668
+ {
669
+ name: "vitest:browser:tests",
670
+ enforce: "pre",
671
+ async config() {
672
+ const project = parentServer.project;
673
+ const { testFiles: allTestFiles } = await project.globTestFiles();
674
+ const browserTestFiles = allTestFiles.filter((file) => getFilePoolName(project, file) === "browser");
675
+ const setupFiles = toArray(project.config.setupFiles);
676
+ const define = {};
677
+ for (const env in project.config.env || {}) {
678
+ const stringValue = JSON.stringify(project.config.env[env]);
679
+ define[`import.meta.env.${env}`] = stringValue;
680
+ }
681
+ const entries = [
682
+ ...browserTestFiles,
683
+ ...setupFiles,
684
+ resolve(distDir, "index.js"),
685
+ resolve(distDir, "browser.js"),
686
+ resolve(distDir, "runners.js"),
687
+ resolve(distDir, "utils.js"),
688
+ ...project.config.snapshotSerializers || []
689
+ ];
690
+ const exclude = [
691
+ "vitest",
692
+ "vitest/utils",
693
+ "vitest/browser",
694
+ "vitest/runners",
695
+ "@vitest/browser",
696
+ "@vitest/browser/client",
697
+ "@vitest/utils",
698
+ "@vitest/utils/source-map",
699
+ "@vitest/runner",
700
+ "@vitest/spy",
701
+ "@vitest/utils/error",
702
+ "@vitest/snapshot",
703
+ "@vitest/expect",
704
+ "std-env",
705
+ "tinybench",
706
+ "tinyspy",
707
+ "tinyrainbow",
708
+ "pathe",
709
+ "msw",
710
+ "msw/browser"
711
+ ];
712
+ if (typeof project.config.diff === "string") {
713
+ entries.push(project.config.diff);
714
+ }
715
+ if (parentServer.vitest.coverageProvider) {
716
+ const coverage = parentServer.vitest.config.coverage;
717
+ const provider = coverage.provider;
718
+ if (provider === "v8") {
719
+ const path = tryResolve("@vitest/coverage-v8", [parentServer.config.root]);
720
+ if (path) {
721
+ entries.push(path);
722
+ exclude.push("@vitest/coverage-v8/browser");
723
+ }
724
+ } else if (provider === "istanbul") {
725
+ const path = tryResolve("@vitest/coverage-istanbul", [parentServer.config.root]);
726
+ if (path) {
727
+ entries.push(path);
728
+ exclude.push("@vitest/coverage-istanbul");
729
+ }
730
+ } else if (provider === "custom" && coverage.customProviderModule) {
731
+ entries.push(coverage.customProviderModule);
732
+ }
733
+ }
734
+ const include = [
735
+ "vitest > expect-type",
736
+ "vitest > @vitest/snapshot > magic-string",
737
+ "vitest > chai",
738
+ "vitest > chai > loupe",
739
+ "vitest > @vitest/utils > loupe",
740
+ "@vitest/browser > @testing-library/user-event",
741
+ "@vitest/browser > @testing-library/dom"
742
+ ];
743
+ const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
744
+ const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
745
+ if (svelte) {
746
+ exclude.push("vitest-browser-svelte");
747
+ }
748
+ const vue = isPackageExists("vitest-browser-vue", fileRoot);
749
+ if (vue) {
750
+ include.push("vitest-browser-vue", "vitest-browser-vue > @vue/test-utils", "vitest-browser-vue > @vue/test-utils > @vue/compiler-core");
751
+ }
752
+ const vueTestUtils = isPackageExists("@vue/test-utils", fileRoot);
753
+ if (vueTestUtils) {
754
+ include.push("@vue/test-utils");
755
+ }
756
+ return {
757
+ define,
758
+ resolve: { dedupe: ["vitest"] },
759
+ optimizeDeps: {
760
+ entries,
761
+ exclude,
762
+ include
763
+ }
764
+ };
765
+ },
766
+ async resolveId(id) {
767
+ if (!/\?browserv=\w+$/.test(id)) {
768
+ return;
769
+ }
770
+ let useId = id.slice(0, id.lastIndexOf("?"));
771
+ if (useId.startsWith("/@fs/")) {
772
+ useId = useId.slice(5);
773
+ }
774
+ if (/^\w:/.test(useId)) {
775
+ useId = useId.replace(/\\/g, "/");
776
+ }
777
+ return useId;
778
+ }
779
+ },
780
+ {
781
+ name: "vitest:browser:resolve-virtual",
782
+ async resolveId(rawId) {
783
+ if (rawId === "/mockServiceWorker.js") {
784
+ return this.resolve("msw/mockServiceWorker.js", distRoot, { skipSelf: true });
785
+ }
786
+ }
787
+ },
788
+ {
789
+ name: "vitest:browser:assets",
790
+ configureServer(server) {
791
+ server.middlewares.use("/__vitest__", sirv(resolve(distRoot, "client/__vitest__")));
792
+ },
793
+ resolveId(id) {
794
+ if (id.startsWith("/__vitest_browser__/")) {
795
+ return resolve(distRoot, "client", id.slice(1));
796
+ }
797
+ },
798
+ transform(code, id) {
799
+ if (id.includes(parentServer.vite.config.cacheDir) && id.includes("loupe.js")) {
800
+ const utilRequire = "nodeUtil = require_util();";
801
+ return code.replace(utilRequire, " ".repeat(utilRequire.length));
802
+ }
803
+ }
804
+ },
805
+ BrowserContext(parentServer),
806
+ dynamicImportPlugin({
807
+ globalThisAccessor: "\"__vitest_browser_runner__\"",
808
+ filter(id) {
809
+ if (id.includes(distRoot)) {
810
+ return false;
811
+ }
812
+ return true;
813
+ }
814
+ }),
815
+ {
816
+ name: "vitest:browser:config",
817
+ enforce: "post",
818
+ async config(viteConfig) {
819
+ // Enables using ignore hint for coverage providers with @preserve keyword
820
+ if (viteConfig.esbuild !== false) {
821
+ viteConfig.esbuild ||= {};
822
+ viteConfig.esbuild.legalComments = "inline";
823
+ }
824
+ const defaultPort = parentServer.vitest._browserLastPort++;
825
+ const api = resolveApiServerConfig(viteConfig.test?.browser || {}, defaultPort);
826
+ viteConfig.server = {
827
+ ...viteConfig.server,
828
+ port: defaultPort,
829
+ ...api,
830
+ middlewareMode: false,
831
+ open: false
832
+ };
833
+ viteConfig.server.fs ??= {};
834
+ viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
835
+ viteConfig.server.fs.allow.push(...resolveFsAllow(parentServer.vitest.config.root, parentServer.vitest.vite.config.configFile), distRoot);
836
+ return { resolve: { alias: viteConfig.test?.alias } };
837
+ }
838
+ },
839
+ {
840
+ name: "vitest:browser:in-source-tests",
841
+ transform(code, id) {
842
+ const project = parentServer.vitest.getProjectByName(parentServer.config.name);
843
+ if (!project._isCachedTestFile(id) || !code.includes("import.meta.vitest")) {
844
+ return;
845
+ }
846
+ const s = new MagicString(code, { filename: cleanUrl(id) });
847
+ s.prepend(`import.meta.vitest = __vitest_index__;\n`);
848
+ return {
849
+ code: s.toString(),
850
+ map: s.generateMap({ hires: true })
851
+ };
852
+ }
853
+ },
854
+ {
855
+ name: "vitest:browser:worker",
856
+ transform(code, id, _options) {
857
+ if (/(?:\?|&)worker_file&type=\w+(?:&|$)/.test(id)) {
858
+ const s = new MagicString(code);
859
+ s.prepend("globalThis.__vitest_browser_runner__ = { wrapDynamicImport: f => f() };\n");
860
+ return {
861
+ code: s.toString(),
862
+ map: s.generateMap({ hires: "boundary" })
863
+ };
864
+ }
865
+ }
866
+ },
867
+ {
868
+ name: "vitest:browser:transform-tester-html",
869
+ enforce: "pre",
870
+ async transformIndexHtml(html, ctx) {
871
+ const projectBrowser = [...parentServer.children].find((server) => {
872
+ return ctx.filename === server.testerFilepath;
873
+ });
874
+ if (!projectBrowser) {
875
+ return;
876
+ }
877
+ if (!parentServer.testerScripts) {
878
+ const testerScripts = await parentServer.formatScripts(parentServer.config.browser.testerScripts);
879
+ parentServer.testerScripts = testerScripts;
880
+ }
881
+ const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
882
+ const testerTags = [];
883
+ const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
884
+ if (!isDefaultTemplate) {
885
+ const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
886
+ const testerEntry = manifestContent["tester/tester.html"];
887
+ testerTags.push({
888
+ tag: "script",
889
+ attrs: {
890
+ type: "module",
891
+ crossorigin: "",
892
+ src: `${parentServer.base}${testerEntry.file}`
893
+ },
894
+ injectTo: "head"
895
+ });
896
+ for (const importName of testerEntry.imports || []) {
897
+ const entryManifest = manifestContent[importName];
898
+ if (entryManifest) {
899
+ testerTags.push({
900
+ tag: "link",
901
+ attrs: {
902
+ href: `${parentServer.base}${entryManifest.file}`,
903
+ rel: "modulepreload",
904
+ crossorigin: ""
905
+ },
906
+ injectTo: "head"
907
+ });
908
+ }
909
+ }
910
+ } else {
911
+ testerTags.push({
912
+ tag: "style",
913
+ children: `
984
914
  html {
985
915
  padding: 0;
986
916
  margin: 0;
@@ -990,244 +920,249 @@ body {
990
920
  margin: 0;
991
921
  min-height: 100vh;
992
922
  }`,
993
- injectTo: "head"
994
- });
995
- }
996
- return [
997
- {
998
- tag: "script",
999
- children: "{__VITEST_INJECTOR__}",
1000
- injectTo: "head-prepend"
1001
- },
1002
- {
1003
- tag: "script",
1004
- children: stateJs,
1005
- injectTo: "head-prepend"
1006
- },
1007
- {
1008
- tag: "script",
1009
- attrs: {
1010
- type: "module",
1011
- src: parentServer.errorCatcherUrl
1012
- },
1013
- injectTo: "head"
1014
- },
1015
- parentServer.locatorsUrl ? {
1016
- tag: "script",
1017
- attrs: {
1018
- type: "module",
1019
- src: parentServer.locatorsUrl
1020
- },
1021
- injectTo: "head"
1022
- } : null,
1023
- ...parentServer.testerScripts,
1024
- ...testerTags,
1025
- {
1026
- tag: "script",
1027
- attrs: {
1028
- "type": "module",
1029
- "data-vitest-append": ""
1030
- },
1031
- children: "{__VITEST_APPEND__}",
1032
- injectTo: "body"
1033
- }
1034
- ].filter((s) => s != null);
1035
- }
1036
- },
1037
- {
1038
- name: "vitest:browser:support-testing-library",
1039
- config() {
1040
- return {
1041
- optimizeDeps: {
1042
- esbuildOptions: {
1043
- plugins: [
1044
- {
1045
- name: "test-utils-rewrite",
1046
- setup(build) {
1047
- build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
1048
- const resolved = getRequire().resolve(args.path, {
1049
- paths: [args.importer]
1050
- });
1051
- return { path: resolved };
1052
- });
1053
- }
1054
- }
1055
- ]
1056
- }
1057
- }
1058
- };
1059
- }
1060
- }
1061
- ];
923
+ injectTo: "head"
924
+ });
925
+ }
926
+ return [
927
+ {
928
+ tag: "script",
929
+ children: "{__VITEST_INJECTOR__}",
930
+ injectTo: "head-prepend"
931
+ },
932
+ {
933
+ tag: "script",
934
+ children: stateJs,
935
+ injectTo: "head-prepend"
936
+ },
937
+ {
938
+ tag: "script",
939
+ attrs: {
940
+ type: "module",
941
+ src: parentServer.errorCatcherUrl
942
+ },
943
+ injectTo: "head"
944
+ },
945
+ parentServer.locatorsUrl ? {
946
+ tag: "script",
947
+ attrs: {
948
+ type: "module",
949
+ src: parentServer.locatorsUrl
950
+ },
951
+ injectTo: "head"
952
+ } : null,
953
+ ...parentServer.testerScripts,
954
+ ...testerTags,
955
+ {
956
+ tag: "script",
957
+ attrs: {
958
+ "type": "module",
959
+ "data-vitest-append": ""
960
+ },
961
+ children: "{__VITEST_APPEND__}",
962
+ injectTo: "body"
963
+ }
964
+ ].filter((s) => s != null);
965
+ }
966
+ },
967
+ {
968
+ name: "vitest:browser:support-testing-library",
969
+ config() {
970
+ return { optimizeDeps: { esbuildOptions: { plugins: [{
971
+ name: "test-utils-rewrite",
972
+ setup(build) {
973
+ build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
974
+ const resolved = getRequire().resolve(args.path, { paths: [args.importer] });
975
+ return { path: resolved };
976
+ });
977
+ }
978
+ }] } } };
979
+ }
980
+ }
981
+ ];
1062
982
  };
1063
983
  function tryResolve(path, paths) {
1064
- try {
1065
- const _require2 = getRequire();
1066
- return _require2.resolve(path, { paths });
1067
- } catch {
1068
- return void 0;
1069
- }
984
+ try {
985
+ const _require = getRequire();
986
+ return _require.resolve(path, { paths });
987
+ } catch {
988
+ return undefined;
989
+ }
1070
990
  }
1071
991
  let _require;
1072
992
  function getRequire() {
1073
- if (!_require) {
1074
- _require = createRequire(import.meta.url);
1075
- }
1076
- return _require;
993
+ if (!_require) {
994
+ _require = createRequire(import.meta.url);
995
+ }
996
+ return _require;
1077
997
  }
1078
998
  function resolveCoverageFolder(vitest) {
1079
- const options = vitest.config;
1080
- const htmlReporter = options.coverage?.enabled ? toArray(options.coverage.reporter).find((reporter) => {
1081
- if (typeof reporter === "string") {
1082
- return reporter === "html";
1083
- }
1084
- return reporter[0] === "html";
1085
- }) : void 0;
1086
- if (!htmlReporter) {
1087
- return void 0;
1088
- }
1089
- const root = resolve(
1090
- options.root || process.cwd(),
1091
- options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory
1092
- );
1093
- const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : void 0;
1094
- if (!subdir || typeof subdir !== "string") {
1095
- return [root, `/${basename(root)}/`];
1096
- }
1097
- return [resolve(root, subdir), `/${basename(root)}/${subdir}/`];
999
+ const options = vitest.config;
1000
+ const htmlReporter = options.coverage?.enabled ? toArray(options.coverage.reporter).find((reporter) => {
1001
+ if (typeof reporter === "string") {
1002
+ return reporter === "html";
1003
+ }
1004
+ return reporter[0] === "html";
1005
+ }) : undefined;
1006
+ if (!htmlReporter) {
1007
+ return undefined;
1008
+ }
1009
+ const root = resolve(options.root || process.cwd(), options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory);
1010
+ const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : undefined;
1011
+ if (!subdir || typeof subdir !== "string") {
1012
+ return [root, `/${basename(root)}/`];
1013
+ }
1014
+ return [resolve(root, subdir), `/${basename(root)}/${subdir}/`];
1098
1015
  }
1099
1016
  const postfixRE = /[?#].*$/;
1100
1017
  function cleanUrl(url) {
1101
- return url.replace(postfixRE, "");
1018
+ return url.replace(postfixRE, "");
1102
1019
  }
1103
1020
 
1104
1021
  class BrowserServerCDPHandler {
1105
- constructor(session, tester) {
1106
- this.session = session;
1107
- this.tester = tester;
1108
- }
1109
- listenerIds = {};
1110
- listeners = {};
1111
- send(method, params) {
1112
- return this.session.send(method, params);
1113
- }
1114
- on(event, id, once = false) {
1115
- if (!this.listenerIds[event]) {
1116
- this.listenerIds[event] = [];
1117
- }
1118
- this.listenerIds[event].push(id);
1119
- if (!this.listeners[event]) {
1120
- this.listeners[event] = (payload) => {
1121
- this.tester.cdpEvent(
1122
- event,
1123
- payload
1124
- );
1125
- if (once) {
1126
- this.off(event, id);
1127
- }
1128
- };
1129
- this.session.on(event, this.listeners[event]);
1130
- }
1131
- }
1132
- off(event, id) {
1133
- if (!this.listenerIds[event]) {
1134
- this.listenerIds[event] = [];
1135
- }
1136
- this.listenerIds[event] = this.listenerIds[event].filter((l) => l !== id);
1137
- if (!this.listenerIds[event].length) {
1138
- this.session.off(event, this.listeners[event]);
1139
- delete this.listeners[event];
1140
- }
1141
- }
1142
- once(event, listener) {
1143
- this.on(event, listener, true);
1144
- }
1022
+ listenerIds = {};
1023
+ listeners = {};
1024
+ constructor(session, tester) {
1025
+ this.session = session;
1026
+ this.tester = tester;
1027
+ }
1028
+ send(method, params) {
1029
+ return this.session.send(method, params);
1030
+ }
1031
+ on(event, id, once = false) {
1032
+ if (!this.listenerIds[event]) {
1033
+ this.listenerIds[event] = [];
1034
+ }
1035
+ this.listenerIds[event].push(id);
1036
+ if (!this.listeners[event]) {
1037
+ this.listeners[event] = (payload) => {
1038
+ this.tester.cdpEvent(event, payload);
1039
+ if (once) {
1040
+ this.off(event, id);
1041
+ }
1042
+ };
1043
+ this.session.on(event, this.listeners[event]);
1044
+ }
1045
+ }
1046
+ off(event, id) {
1047
+ if (!this.listenerIds[event]) {
1048
+ this.listenerIds[event] = [];
1049
+ }
1050
+ this.listenerIds[event] = this.listenerIds[event].filter((l) => l !== id);
1051
+ if (!this.listenerIds[event].length) {
1052
+ this.session.off(event, this.listeners[event]);
1053
+ delete this.listeners[event];
1054
+ }
1055
+ }
1056
+ once(event, listener) {
1057
+ this.on(event, listener, true);
1058
+ }
1145
1059
  }
1146
1060
 
1147
1061
  const clear = async (context, selector) => {
1148
- if (context.provider instanceof PlaywrightBrowserProvider) {
1149
- const { iframe } = context;
1150
- const element = iframe.locator(selector);
1151
- await element.clear();
1152
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1153
- const browser = context.browser;
1154
- const element = await browser.$(selector);
1155
- await element.clearValue();
1156
- } else {
1157
- throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1158
- }
1062
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1063
+ const { iframe } = context;
1064
+ const element = iframe.locator(selector);
1065
+ await element.clear();
1066
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1067
+ const browser = context.browser;
1068
+ const element = await browser.$(selector);
1069
+ await element.clearValue();
1070
+ } else {
1071
+ throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1072
+ }
1159
1073
  };
1160
1074
 
1161
1075
  const click = async (context, selector, options = {}) => {
1162
- const provider = context.provider;
1163
- if (provider instanceof PlaywrightBrowserProvider) {
1164
- const tester = context.iframe;
1165
- await tester.locator(selector).click(options);
1166
- } else if (provider instanceof WebdriverBrowserProvider) {
1167
- const browser = context.browser;
1168
- await browser.$(selector).click(options);
1169
- } else {
1170
- throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
1171
- }
1076
+ const provider = context.provider;
1077
+ if (provider instanceof PlaywrightBrowserProvider) {
1078
+ const tester = context.iframe;
1079
+ await tester.locator(selector).click(options);
1080
+ } else if (provider instanceof WebdriverBrowserProvider) {
1081
+ const browser = context.browser;
1082
+ await browser.$(selector).click(options);
1083
+ } else {
1084
+ throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
1085
+ }
1172
1086
  };
1173
1087
  const dblClick = async (context, selector, options = {}) => {
1174
- const provider = context.provider;
1175
- if (provider instanceof PlaywrightBrowserProvider) {
1176
- const tester = context.iframe;
1177
- await tester.locator(selector).dblclick(options);
1178
- } else if (provider instanceof WebdriverBrowserProvider) {
1179
- const browser = context.browser;
1180
- await browser.$(selector).doubleClick();
1181
- } else {
1182
- throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
1183
- }
1088
+ const provider = context.provider;
1089
+ if (provider instanceof PlaywrightBrowserProvider) {
1090
+ const tester = context.iframe;
1091
+ await tester.locator(selector).dblclick(options);
1092
+ } else if (provider instanceof WebdriverBrowserProvider) {
1093
+ const browser = context.browser;
1094
+ await browser.$(selector).doubleClick();
1095
+ } else {
1096
+ throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
1097
+ }
1184
1098
  };
1185
1099
  const tripleClick = async (context, selector, options = {}) => {
1186
- const provider = context.provider;
1187
- if (provider instanceof PlaywrightBrowserProvider) {
1188
- const tester = context.iframe;
1189
- await tester.locator(selector).click({
1190
- ...options,
1191
- clickCount: 3
1192
- });
1193
- } else if (provider instanceof WebdriverBrowserProvider) {
1194
- const browser = context.browser;
1195
- await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
1196
- } else {
1197
- throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
1198
- }
1100
+ const provider = context.provider;
1101
+ if (provider instanceof PlaywrightBrowserProvider) {
1102
+ const tester = context.iframe;
1103
+ await tester.locator(selector).click({
1104
+ ...options,
1105
+ clickCount: 3
1106
+ });
1107
+ } else if (provider instanceof WebdriverBrowserProvider) {
1108
+ const browser = context.browser;
1109
+ await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
1110
+ } else {
1111
+ throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
1112
+ }
1199
1113
  };
1200
1114
 
1201
1115
  const dragAndDrop = async (context, source, target, options_) => {
1202
- if (context.provider instanceof PlaywrightBrowserProvider) {
1203
- const frame = await context.frame();
1204
- await frame.dragAndDrop(
1205
- source,
1206
- target,
1207
- options_
1208
- );
1209
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1210
- const $source = context.browser.$(source);
1211
- const $target = context.browser.$(target);
1212
- const options = options_ || {};
1213
- const duration = options.duration ?? 10;
1214
- await context.browser.action("pointer").move({ duration: 0, origin: $source, x: options.sourceX ?? 0, y: options.sourceY ?? 0 }).down({ button: 0 }).move({ duration: 0, origin: "pointer", x: 0, y: 0 }).pause(duration).move({ duration: 0, origin: $target, x: options.targetX ?? 0, y: options.targetY ?? 0 }).move({ duration: 0, origin: "pointer", x: 1, y: 0 }).move({ duration: 0, origin: "pointer", x: -1, y: 0 }).up({ button: 0 }).perform();
1215
- } else {
1216
- throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
1217
- }
1116
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1117
+ const frame = await context.frame();
1118
+ await frame.dragAndDrop(source, target, options_);
1119
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1120
+ const $source = context.browser.$(source);
1121
+ const $target = context.browser.$(target);
1122
+ const options = options_ || {};
1123
+ const duration = options.duration ?? 10;
1124
+ await context.browser.action("pointer").move({
1125
+ duration: 0,
1126
+ origin: $source,
1127
+ x: options.sourceX ?? 0,
1128
+ y: options.sourceY ?? 0
1129
+ }).down({ button: 0 }).move({
1130
+ duration: 0,
1131
+ origin: "pointer",
1132
+ x: 0,
1133
+ y: 0
1134
+ }).pause(duration).move({
1135
+ duration: 0,
1136
+ origin: $target,
1137
+ x: options.targetX ?? 0,
1138
+ y: options.targetY ?? 0
1139
+ }).move({
1140
+ duration: 0,
1141
+ origin: "pointer",
1142
+ x: 1,
1143
+ y: 0
1144
+ }).move({
1145
+ duration: 0,
1146
+ origin: "pointer",
1147
+ x: -1,
1148
+ y: 0
1149
+ }).up({ button: 0 }).perform();
1150
+ } else {
1151
+ throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
1152
+ }
1218
1153
  };
1219
1154
 
1220
1155
  const fill = async (context, selector, text, options = {}) => {
1221
- if (context.provider instanceof PlaywrightBrowserProvider) {
1222
- const { iframe } = context;
1223
- const element = iframe.locator(selector);
1224
- await element.fill(text, options);
1225
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1226
- const browser = context.browser;
1227
- await browser.$(selector).setValue(text);
1228
- } else {
1229
- throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`);
1230
- }
1156
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1157
+ const { iframe } = context;
1158
+ const element = iframe.locator(selector);
1159
+ await element.fill(text, options);
1160
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1161
+ const browser = context.browser;
1162
+ await browser.$(selector).setValue(text);
1163
+ } else {
1164
+ throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`);
1165
+ }
1231
1166
  };
1232
1167
 
1233
1168
  const types = {
@@ -1671,54 +1606,52 @@ _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _M
1671
1606
  var mime = new Mime(types)._freeze();
1672
1607
 
1673
1608
  function assertFileAccess(path, project) {
1674
- if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.server)) {
1675
- throw new Error(
1676
- `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`
1677
- );
1678
- }
1609
+ if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.server)) {
1610
+ throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
1611
+ }
1679
1612
  }
1680
1613
  const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => {
1681
- const filepath = resolve$1(dirname$1(testPath), path);
1682
- assertFileAccess(filepath, project);
1683
- if (typeof options === "object" && !options.encoding) {
1684
- options.encoding = "utf-8";
1685
- }
1686
- return promises.readFile(filepath, options);
1614
+ const filepath = resolve$1(dirname$1(testPath), path);
1615
+ assertFileAccess(filepath, project);
1616
+ if (typeof options === "object" && !options.encoding) {
1617
+ options.encoding = "utf-8";
1618
+ }
1619
+ return promises.readFile(filepath, options);
1687
1620
  };
1688
1621
  const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => {
1689
- const filepath = resolve$1(dirname$1(testPath), path);
1690
- assertFileAccess(filepath, project);
1691
- const dir = dirname$1(filepath);
1692
- if (!fs.existsSync(dir)) {
1693
- await promises.mkdir(dir, { recursive: true });
1694
- }
1695
- await promises.writeFile(filepath, data, options);
1622
+ const filepath = resolve$1(dirname$1(testPath), path);
1623
+ assertFileAccess(filepath, project);
1624
+ const dir = dirname$1(filepath);
1625
+ if (!fs.existsSync(dir)) {
1626
+ await promises.mkdir(dir, { recursive: true });
1627
+ }
1628
+ await promises.writeFile(filepath, data, options);
1696
1629
  };
1697
1630
  const removeFile = async ({ project, testPath = process.cwd() }, path) => {
1698
- const filepath = resolve$1(dirname$1(testPath), path);
1699
- assertFileAccess(filepath, project);
1700
- await promises.rm(filepath);
1631
+ const filepath = resolve$1(dirname$1(testPath), path);
1632
+ assertFileAccess(filepath, project);
1633
+ await promises.rm(filepath);
1701
1634
  };
1702
1635
  const _fileInfo = async ({ project, testPath = process.cwd() }, path, encoding) => {
1703
- const filepath = resolve$1(dirname$1(testPath), path);
1704
- assertFileAccess(filepath, project);
1705
- const content = await promises.readFile(filepath, encoding || "base64");
1706
- return {
1707
- content,
1708
- basename: basename$1(filepath),
1709
- mime: mime.getType(filepath)
1710
- };
1636
+ const filepath = resolve$1(dirname$1(testPath), path);
1637
+ assertFileAccess(filepath, project);
1638
+ const content = await promises.readFile(filepath, encoding || "base64");
1639
+ return {
1640
+ content,
1641
+ basename: basename$1(filepath),
1642
+ mime: mime.getType(filepath)
1643
+ };
1711
1644
  };
1712
1645
 
1713
1646
  const hover = async (context, selector, options = {}) => {
1714
- if (context.provider instanceof PlaywrightBrowserProvider) {
1715
- await context.iframe.locator(selector).hover(options);
1716
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1717
- const browser = context.browser;
1718
- await browser.$(selector).moveTo(options);
1719
- } else {
1720
- throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
1721
- }
1647
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1648
+ await context.iframe.locator(selector).hover(options);
1649
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1650
+ const browser = context.browser;
1651
+ await browser.$(selector).moveTo(options);
1652
+ } else {
1653
+ throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
1654
+ }
1722
1655
  };
1723
1656
 
1724
1657
  var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
@@ -2031,648 +1964,799 @@ function getErrorMessage(expected, found, text, context) {
2031
1964
  }
2032
1965
 
2033
1966
  const keyboard = async (context, text, state) => {
2034
- if (context.provider instanceof PlaywrightBrowserProvider) {
2035
- const frame = await context.frame();
2036
- await frame.evaluate(focusIframe);
2037
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2038
- await context.browser.execute(focusIframe);
2039
- }
2040
- const pressed = new Set(state.unreleased);
2041
- await keyboardImplementation(
2042
- pressed,
2043
- context.provider,
2044
- context.sessionId,
2045
- text,
2046
- async () => {
2047
- if (context.provider instanceof PlaywrightBrowserProvider) {
2048
- const frame = await context.frame();
2049
- await frame.evaluate(selectAll);
2050
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2051
- await context.browser.execute(selectAll);
2052
- } else {
2053
- throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
2054
- }
2055
- },
2056
- true
2057
- );
2058
- return {
2059
- unreleased: Array.from(pressed)
2060
- };
1967
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1968
+ const frame = await context.frame();
1969
+ await frame.evaluate(focusIframe);
1970
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1971
+ await context.browser.execute(focusIframe);
1972
+ }
1973
+ const pressed = new Set(state.unreleased);
1974
+ await keyboardImplementation(pressed, context.provider, context.sessionId, text, async () => {
1975
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1976
+ const frame = await context.frame();
1977
+ await frame.evaluate(selectAll);
1978
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1979
+ await context.browser.execute(selectAll);
1980
+ } else {
1981
+ throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
1982
+ }
1983
+ }, true);
1984
+ return { unreleased: Array.from(pressed) };
2061
1985
  };
2062
1986
  const keyboardCleanup = async (context, state) => {
2063
- const { provider, sessionId } = context;
2064
- if (!state.unreleased) {
2065
- return;
2066
- }
2067
- if (provider instanceof PlaywrightBrowserProvider) {
2068
- const page = provider.getPage(sessionId);
2069
- for (const key of state.unreleased) {
2070
- await page.keyboard.up(key);
2071
- }
2072
- } else if (provider instanceof WebdriverBrowserProvider) {
2073
- const keyboard2 = provider.browser.action("key");
2074
- for (const key of state.unreleased) {
2075
- keyboard2.up(key);
2076
- }
2077
- await keyboard2.perform();
2078
- } else {
2079
- throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`);
2080
- }
1987
+ const { provider, sessionId } = context;
1988
+ if (!state.unreleased) {
1989
+ return;
1990
+ }
1991
+ if (provider instanceof PlaywrightBrowserProvider) {
1992
+ const page = provider.getPage(sessionId);
1993
+ for (const key of state.unreleased) {
1994
+ await page.keyboard.up(key);
1995
+ }
1996
+ } else if (provider instanceof WebdriverBrowserProvider) {
1997
+ const keyboard = provider.browser.action("key");
1998
+ for (const key of state.unreleased) {
1999
+ keyboard.up(key);
2000
+ }
2001
+ await keyboard.perform();
2002
+ } else {
2003
+ throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`);
2004
+ }
2081
2005
  };
2082
- const VALID_KEYS = /* @__PURE__ */ new Set(["Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Backquote", "`", "~", "Digit1", "1", "!", "Digit2", "2", "@", "Digit3", "3", "#", "Digit4", "4", "$", "Digit5", "5", "%", "Digit6", "6", "^", "Digit7", "7", "&", "Digit8", "8", "*", "Digit9", "9", "(", "Digit0", "0", ")", "Minus", "-", "_", "Equal", "=", "+", "Backslash", "\\", "|", "Backspace", "Tab", "KeyQ", "q", "Q", "KeyW", "w", "W", "KeyE", "e", "E", "KeyR", "r", "R", "KeyT", "t", "T", "KeyY", "y", "Y", "KeyU", "u", "U", "KeyI", "i", "I", "KeyO", "o", "O", "KeyP", "p", "P", "BracketLeft", "[", "{", "BracketRight", "]", "}", "CapsLock", "KeyA", "a", "A", "KeyS", "s", "S", "KeyD", "d", "D", "KeyF", "f", "F", "KeyG", "g", "G", "KeyH", "h", "H", "KeyJ", "j", "J", "KeyK", "k", "K", "KeyL", "l", "L", "Semicolon", ";", ":", "Quote", "'", '"', "Enter", "\n", "\r", "ShiftLeft", "Shift", "KeyZ", "z", "Z", "KeyX", "x", "X", "KeyC", "c", "C", "KeyV", "v", "V", "KeyB", "b", "B", "KeyN", "n", "N", "KeyM", "m", "M", "Comma", ",", "<", "Period", ".", ">", "Slash", "/", "?", "ShiftRight", "ControlLeft", "Control", "MetaLeft", "Meta", "AltLeft", "Alt", "Space", " ", "AltRight", "AltGraph", "MetaRight", "ContextMenu", "ControlRight", "PrintScreen", "ScrollLock", "Pause", "PageUp", "PageDown", "Insert", "Delete", "Home", "End", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", "NumLock", "NumpadDivide", "NumpadMultiply", "NumpadSubtract", "Numpad7", "Numpad8", "Numpad9", "Numpad4", "Numpad5", "Numpad6", "NumpadAdd", "Numpad1", "Numpad2", "Numpad3", "Numpad0", "NumpadDecimal", "NumpadEnter", "ControlOrMeta"]);
2083
- async function keyboardImplementation(pressed, provider, sessionId, text, selectAll2, skipRelease) {
2084
- if (provider instanceof PlaywrightBrowserProvider) {
2085
- const page = provider.getPage(sessionId);
2086
- const actions = parseKeyDef(defaultKeyMap, text);
2087
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2088
- const key = keyDef.key;
2089
- if (pressed.has(key)) {
2090
- if (VALID_KEYS.has(key)) {
2091
- await page.keyboard.up(key);
2092
- }
2093
- pressed.delete(key);
2094
- }
2095
- if (!releasePrevious) {
2096
- if (key === "selectall") {
2097
- await selectAll2();
2098
- continue;
2099
- }
2100
- for (let i = 1; i <= repeat; i++) {
2101
- if (VALID_KEYS.has(key)) {
2102
- await page.keyboard.down(key);
2103
- } else {
2104
- await page.keyboard.insertText(key);
2105
- }
2106
- }
2107
- if (releaseSelf) {
2108
- if (VALID_KEYS.has(key)) {
2109
- await page.keyboard.up(key);
2110
- }
2111
- } else {
2112
- pressed.add(key);
2113
- }
2114
- }
2115
- }
2116
- if (!skipRelease && pressed.size) {
2117
- for (const key of pressed) {
2118
- if (VALID_KEYS.has(key)) {
2119
- await page.keyboard.up(key);
2120
- }
2121
- }
2122
- }
2123
- } else if (provider instanceof WebdriverBrowserProvider) {
2124
- const { Key } = await import('webdriverio');
2125
- const browser = provider.browser;
2126
- const actions = parseKeyDef(defaultKeyMap, text);
2127
- let keyboard2 = browser.action("key");
2128
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2129
- let key = keyDef.key;
2130
- const special = Key[key];
2131
- if (special) {
2132
- key = special;
2133
- }
2134
- if (pressed.has(key)) {
2135
- keyboard2.up(key);
2136
- pressed.delete(key);
2137
- }
2138
- if (!releasePrevious) {
2139
- if (key === "selectall") {
2140
- await keyboard2.perform();
2141
- keyboard2 = browser.action("key");
2142
- await selectAll2();
2143
- continue;
2144
- }
2145
- for (let i = 1; i <= repeat; i++) {
2146
- keyboard2.down(key);
2147
- }
2148
- if (releaseSelf) {
2149
- keyboard2.up(key);
2150
- } else {
2151
- pressed.add(key);
2152
- }
2153
- }
2154
- }
2155
- const allRelease = keyboard2.toJSON().actions.every((action) => action.type === "keyUp");
2156
- await keyboard2.perform(allRelease ? false : skipRelease);
2157
- }
2158
- return {
2159
- pressed
2160
- };
2006
+ const VALID_KEYS = new Set([
2007
+ "Escape",
2008
+ "F1",
2009
+ "F2",
2010
+ "F3",
2011
+ "F4",
2012
+ "F5",
2013
+ "F6",
2014
+ "F7",
2015
+ "F8",
2016
+ "F9",
2017
+ "F10",
2018
+ "F11",
2019
+ "F12",
2020
+ "Backquote",
2021
+ "`",
2022
+ "~",
2023
+ "Digit1",
2024
+ "1",
2025
+ "!",
2026
+ "Digit2",
2027
+ "2",
2028
+ "@",
2029
+ "Digit3",
2030
+ "3",
2031
+ "#",
2032
+ "Digit4",
2033
+ "4",
2034
+ "$",
2035
+ "Digit5",
2036
+ "5",
2037
+ "%",
2038
+ "Digit6",
2039
+ "6",
2040
+ "^",
2041
+ "Digit7",
2042
+ "7",
2043
+ "&",
2044
+ "Digit8",
2045
+ "8",
2046
+ "*",
2047
+ "Digit9",
2048
+ "9",
2049
+ "(",
2050
+ "Digit0",
2051
+ "0",
2052
+ ")",
2053
+ "Minus",
2054
+ "-",
2055
+ "_",
2056
+ "Equal",
2057
+ "=",
2058
+ "+",
2059
+ "Backslash",
2060
+ "\\",
2061
+ "|",
2062
+ "Backspace",
2063
+ "Tab",
2064
+ "KeyQ",
2065
+ "q",
2066
+ "Q",
2067
+ "KeyW",
2068
+ "w",
2069
+ "W",
2070
+ "KeyE",
2071
+ "e",
2072
+ "E",
2073
+ "KeyR",
2074
+ "r",
2075
+ "R",
2076
+ "KeyT",
2077
+ "t",
2078
+ "T",
2079
+ "KeyY",
2080
+ "y",
2081
+ "Y",
2082
+ "KeyU",
2083
+ "u",
2084
+ "U",
2085
+ "KeyI",
2086
+ "i",
2087
+ "I",
2088
+ "KeyO",
2089
+ "o",
2090
+ "O",
2091
+ "KeyP",
2092
+ "p",
2093
+ "P",
2094
+ "BracketLeft",
2095
+ "[",
2096
+ "{",
2097
+ "BracketRight",
2098
+ "]",
2099
+ "}",
2100
+ "CapsLock",
2101
+ "KeyA",
2102
+ "a",
2103
+ "A",
2104
+ "KeyS",
2105
+ "s",
2106
+ "S",
2107
+ "KeyD",
2108
+ "d",
2109
+ "D",
2110
+ "KeyF",
2111
+ "f",
2112
+ "F",
2113
+ "KeyG",
2114
+ "g",
2115
+ "G",
2116
+ "KeyH",
2117
+ "h",
2118
+ "H",
2119
+ "KeyJ",
2120
+ "j",
2121
+ "J",
2122
+ "KeyK",
2123
+ "k",
2124
+ "K",
2125
+ "KeyL",
2126
+ "l",
2127
+ "L",
2128
+ "Semicolon",
2129
+ ";",
2130
+ ":",
2131
+ "Quote",
2132
+ "'",
2133
+ "\"",
2134
+ "Enter",
2135
+ "\n",
2136
+ "\r",
2137
+ "ShiftLeft",
2138
+ "Shift",
2139
+ "KeyZ",
2140
+ "z",
2141
+ "Z",
2142
+ "KeyX",
2143
+ "x",
2144
+ "X",
2145
+ "KeyC",
2146
+ "c",
2147
+ "C",
2148
+ "KeyV",
2149
+ "v",
2150
+ "V",
2151
+ "KeyB",
2152
+ "b",
2153
+ "B",
2154
+ "KeyN",
2155
+ "n",
2156
+ "N",
2157
+ "KeyM",
2158
+ "m",
2159
+ "M",
2160
+ "Comma",
2161
+ ",",
2162
+ "<",
2163
+ "Period",
2164
+ ".",
2165
+ ">",
2166
+ "Slash",
2167
+ "/",
2168
+ "?",
2169
+ "ShiftRight",
2170
+ "ControlLeft",
2171
+ "Control",
2172
+ "MetaLeft",
2173
+ "Meta",
2174
+ "AltLeft",
2175
+ "Alt",
2176
+ "Space",
2177
+ " ",
2178
+ "AltRight",
2179
+ "AltGraph",
2180
+ "MetaRight",
2181
+ "ContextMenu",
2182
+ "ControlRight",
2183
+ "PrintScreen",
2184
+ "ScrollLock",
2185
+ "Pause",
2186
+ "PageUp",
2187
+ "PageDown",
2188
+ "Insert",
2189
+ "Delete",
2190
+ "Home",
2191
+ "End",
2192
+ "ArrowLeft",
2193
+ "ArrowUp",
2194
+ "ArrowRight",
2195
+ "ArrowDown",
2196
+ "NumLock",
2197
+ "NumpadDivide",
2198
+ "NumpadMultiply",
2199
+ "NumpadSubtract",
2200
+ "Numpad7",
2201
+ "Numpad8",
2202
+ "Numpad9",
2203
+ "Numpad4",
2204
+ "Numpad5",
2205
+ "Numpad6",
2206
+ "NumpadAdd",
2207
+ "Numpad1",
2208
+ "Numpad2",
2209
+ "Numpad3",
2210
+ "Numpad0",
2211
+ "NumpadDecimal",
2212
+ "NumpadEnter",
2213
+ "ControlOrMeta"
2214
+ ]);
2215
+ async function keyboardImplementation(pressed, provider, sessionId, text, selectAll, skipRelease) {
2216
+ if (provider instanceof PlaywrightBrowserProvider) {
2217
+ const page = provider.getPage(sessionId);
2218
+ const actions = parseKeyDef(defaultKeyMap, text);
2219
+ for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2220
+ const key = keyDef.key;
2221
+ if (pressed.has(key)) {
2222
+ if (VALID_KEYS.has(key)) {
2223
+ await page.keyboard.up(key);
2224
+ }
2225
+ pressed.delete(key);
2226
+ }
2227
+ if (!releasePrevious) {
2228
+ if (key === "selectall") {
2229
+ await selectAll();
2230
+ continue;
2231
+ }
2232
+ for (let i = 1; i <= repeat; i++) {
2233
+ if (VALID_KEYS.has(key)) {
2234
+ await page.keyboard.down(key);
2235
+ } else {
2236
+ await page.keyboard.insertText(key);
2237
+ }
2238
+ }
2239
+ if (releaseSelf) {
2240
+ if (VALID_KEYS.has(key)) {
2241
+ await page.keyboard.up(key);
2242
+ }
2243
+ } else {
2244
+ pressed.add(key);
2245
+ }
2246
+ }
2247
+ }
2248
+ if (!skipRelease && pressed.size) {
2249
+ for (const key of pressed) {
2250
+ if (VALID_KEYS.has(key)) {
2251
+ await page.keyboard.up(key);
2252
+ }
2253
+ }
2254
+ }
2255
+ } else if (provider instanceof WebdriverBrowserProvider) {
2256
+ const { Key } = await import('webdriverio');
2257
+ const browser = provider.browser;
2258
+ const actions = parseKeyDef(defaultKeyMap, text);
2259
+ let keyboard = browser.action("key");
2260
+ for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2261
+ let key = keyDef.key;
2262
+ const special = Key[key];
2263
+ if (special) {
2264
+ key = special;
2265
+ }
2266
+ if (pressed.has(key)) {
2267
+ keyboard.up(key);
2268
+ pressed.delete(key);
2269
+ }
2270
+ if (!releasePrevious) {
2271
+ if (key === "selectall") {
2272
+ await keyboard.perform();
2273
+ keyboard = browser.action("key");
2274
+ await selectAll();
2275
+ continue;
2276
+ }
2277
+ for (let i = 1; i <= repeat; i++) {
2278
+ keyboard.down(key);
2279
+ }
2280
+ if (releaseSelf) {
2281
+ keyboard.up(key);
2282
+ } else {
2283
+ pressed.add(key);
2284
+ }
2285
+ }
2286
+ }
2287
+ const allRelease = keyboard.toJSON().actions.every((action) => action.type === "keyUp");
2288
+ await keyboard.perform(allRelease ? false : skipRelease);
2289
+ }
2290
+ return { pressed };
2161
2291
  }
2162
2292
  function focusIframe() {
2163
- if (!document.activeElement || document.activeElement.ownerDocument !== document || document.activeElement === document.body) {
2164
- window.focus();
2165
- }
2293
+ if (!document.activeElement || document.activeElement.ownerDocument !== document || document.activeElement === document.body) {
2294
+ window.focus();
2295
+ }
2166
2296
  }
2167
2297
  function selectAll() {
2168
- const element = document.activeElement;
2169
- if (element && typeof element.select === "function") {
2170
- element.select();
2171
- }
2298
+ const element = document.activeElement;
2299
+ if (element && typeof element.select === "function") {
2300
+ element.select();
2301
+ }
2172
2302
  }
2173
2303
 
2174
2304
  const screenshot = async (context, name, options = {}) => {
2175
- if (!context.testPath) {
2176
- throw new Error(`Cannot take a screenshot without a test path`);
2177
- }
2178
- const path = options.path ? resolve(dirname(context.testPath), options.path) : resolveScreenshotPath(
2179
- context.testPath,
2180
- name,
2181
- context.project.config
2182
- );
2183
- const savePath = normalize$1(path);
2184
- await mkdir(dirname(path), { recursive: true });
2185
- if (context.provider instanceof PlaywrightBrowserProvider) {
2186
- if (options.element) {
2187
- const { element: selector, ...config } = options;
2188
- const element = context.iframe.locator(`${selector}`);
2189
- const buffer2 = await element.screenshot({
2190
- ...config,
2191
- path: savePath
2192
- });
2193
- return returnResult(options, path, buffer2);
2194
- }
2195
- const buffer = await context.iframe.locator("body").screenshot({
2196
- ...options,
2197
- path: savePath
2198
- });
2199
- return returnResult(options, path, buffer);
2200
- }
2201
- if (context.provider instanceof WebdriverBrowserProvider) {
2202
- const page = context.provider.browser;
2203
- if (!options.element) {
2204
- const body = await page.$("body");
2205
- const buffer2 = await body.saveScreenshot(savePath);
2206
- return returnResult(options, path, buffer2);
2207
- }
2208
- const element = await page.$(`${options.element}`);
2209
- const buffer = await element.saveScreenshot(savePath);
2210
- return returnResult(options, path, buffer);
2211
- }
2212
- throw new Error(
2213
- `Provider "${context.provider.name}" does not support screenshots`
2214
- );
2305
+ if (!context.testPath) {
2306
+ throw new Error(`Cannot take a screenshot without a test path`);
2307
+ }
2308
+ const path = options.path ? resolve(dirname(context.testPath), options.path) : resolveScreenshotPath(context.testPath, name, context.project.config);
2309
+ const savePath = normalize$1(path);
2310
+ await mkdir(dirname(path), { recursive: true });
2311
+ if (context.provider instanceof PlaywrightBrowserProvider) {
2312
+ if (options.element) {
2313
+ const { element: selector,...config } = options;
2314
+ const element = context.iframe.locator(`${selector}`);
2315
+ const buffer = await element.screenshot({
2316
+ ...config,
2317
+ path: savePath
2318
+ });
2319
+ return returnResult(options, path, buffer);
2320
+ }
2321
+ const buffer = await context.iframe.locator("body").screenshot({
2322
+ ...options,
2323
+ path: savePath
2324
+ });
2325
+ return returnResult(options, path, buffer);
2326
+ }
2327
+ if (context.provider instanceof WebdriverBrowserProvider) {
2328
+ const page = context.provider.browser;
2329
+ if (!options.element) {
2330
+ const body = await page.$("body");
2331
+ const buffer = await body.saveScreenshot(savePath);
2332
+ return returnResult(options, path, buffer);
2333
+ }
2334
+ const element = await page.$(`${options.element}`);
2335
+ const buffer = await element.saveScreenshot(savePath);
2336
+ return returnResult(options, path, buffer);
2337
+ }
2338
+ throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
2215
2339
  };
2216
2340
  function resolveScreenshotPath(testPath, name, config) {
2217
- const dir = dirname(testPath);
2218
- const base = basename(testPath);
2219
- if (config.browser.screenshotDirectory) {
2220
- return resolve(
2221
- config.browser.screenshotDirectory,
2222
- relative(config.root, dir),
2223
- base,
2224
- name
2225
- );
2226
- }
2227
- return resolve(dir, "__screenshots__", base, name);
2341
+ const dir = dirname(testPath);
2342
+ const base = basename(testPath);
2343
+ if (config.browser.screenshotDirectory) {
2344
+ return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
2345
+ }
2346
+ return resolve(dir, "__screenshots__", base, name);
2228
2347
  }
2229
2348
  function returnResult(options, path, buffer) {
2230
- if (options.base64) {
2231
- return { path, base64: buffer.toString("base64") };
2232
- }
2233
- return path;
2349
+ if (options.base64) {
2350
+ return {
2351
+ path,
2352
+ base64: buffer.toString("base64")
2353
+ };
2354
+ }
2355
+ return path;
2234
2356
  }
2235
2357
 
2236
2358
  const selectOptions = async (context, selector, userValues, options = {}) => {
2237
- if (context.provider instanceof PlaywrightBrowserProvider) {
2238
- const value = userValues;
2239
- const { iframe } = context;
2240
- const selectElement = iframe.locator(selector);
2241
- const values = await Promise.all(value.map(async (v) => {
2242
- if (typeof v === "string") {
2243
- return v;
2244
- }
2245
- const elementHandler = await iframe.locator(v.element).elementHandle();
2246
- if (!elementHandler) {
2247
- throw new Error(`Element not found: ${v.element}`);
2248
- }
2249
- return elementHandler;
2250
- }));
2251
- await selectElement.selectOption(values, options);
2252
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2253
- const values = userValues;
2254
- if (!values.length) {
2255
- return;
2256
- }
2257
- const browser = context.browser;
2258
- if (values.length === 1 && "index" in values[0]) {
2259
- const selectElement = browser.$(selector);
2260
- await selectElement.selectByIndex(values[0].index);
2261
- } else {
2262
- throw new Error(`Provider "webdriverio" doesn't support selecting multiple values at once`);
2263
- }
2264
- } else {
2265
- throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
2266
- }
2359
+ if (context.provider instanceof PlaywrightBrowserProvider) {
2360
+ const value = userValues;
2361
+ const { iframe } = context;
2362
+ const selectElement = iframe.locator(selector);
2363
+ const values = await Promise.all(value.map(async (v) => {
2364
+ if (typeof v === "string") {
2365
+ return v;
2366
+ }
2367
+ const elementHandler = await iframe.locator(v.element).elementHandle();
2368
+ if (!elementHandler) {
2369
+ throw new Error(`Element not found: ${v.element}`);
2370
+ }
2371
+ return elementHandler;
2372
+ }));
2373
+ await selectElement.selectOption(values, options);
2374
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
2375
+ const values = userValues;
2376
+ if (!values.length) {
2377
+ return;
2378
+ }
2379
+ const browser = context.browser;
2380
+ if (values.length === 1 && "index" in values[0]) {
2381
+ const selectElement = browser.$(selector);
2382
+ await selectElement.selectByIndex(values[0].index);
2383
+ } else {
2384
+ throw new Error("Provider \"webdriverio\" doesn't support selecting multiple values at once");
2385
+ }
2386
+ } else {
2387
+ throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
2388
+ }
2267
2389
  };
2268
2390
 
2269
2391
  const tab = async (context, options = {}) => {
2270
- const provider = context.provider;
2271
- if (provider instanceof PlaywrightBrowserProvider) {
2272
- const page = context.page;
2273
- await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
2274
- return;
2275
- }
2276
- if (provider instanceof WebdriverBrowserProvider) {
2277
- const { Key } = await import('webdriverio');
2278
- const browser = context.browser;
2279
- await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
2280
- return;
2281
- }
2282
- throw new Error(`Provider "${provider.name}" doesn't support tab command`);
2392
+ const provider = context.provider;
2393
+ if (provider instanceof PlaywrightBrowserProvider) {
2394
+ const page = context.page;
2395
+ await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
2396
+ return;
2397
+ }
2398
+ if (provider instanceof WebdriverBrowserProvider) {
2399
+ const { Key } = await import('webdriverio');
2400
+ const browser = context.browser;
2401
+ await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
2402
+ return;
2403
+ }
2404
+ throw new Error(`Provider "${provider.name}" doesn't support tab command`);
2283
2405
  };
2284
2406
 
2285
2407
  const type = async (context, selector, text, options = {}) => {
2286
- const { skipClick = false, skipAutoClose = false } = options;
2287
- const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
2288
- if (context.provider instanceof PlaywrightBrowserProvider) {
2289
- const { iframe } = context;
2290
- const element = iframe.locator(selector);
2291
- if (!skipClick) {
2292
- await element.focus();
2293
- }
2294
- await keyboardImplementation(
2295
- unreleased,
2296
- context.provider,
2297
- context.sessionId,
2298
- text,
2299
- () => element.selectText(),
2300
- skipAutoClose
2301
- );
2302
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2303
- const browser = context.browser;
2304
- const element = browser.$(selector);
2305
- if (!skipClick && !await element.isFocused()) {
2306
- await element.click();
2307
- }
2308
- await keyboardImplementation(
2309
- unreleased,
2310
- context.provider,
2311
- context.sessionId,
2312
- text,
2313
- () => browser.execute(() => {
2314
- const element2 = document.activeElement;
2315
- if (element2 && typeof element2.select === "function") {
2316
- element2.select();
2317
- }
2318
- }),
2319
- skipAutoClose
2320
- );
2321
- } else {
2322
- throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
2323
- }
2324
- return {
2325
- unreleased: Array.from(unreleased)
2326
- };
2408
+ const { skipClick = false, skipAutoClose = false } = options;
2409
+ const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
2410
+ if (context.provider instanceof PlaywrightBrowserProvider) {
2411
+ const { iframe } = context;
2412
+ const element = iframe.locator(selector);
2413
+ if (!skipClick) {
2414
+ await element.focus();
2415
+ }
2416
+ await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => element.selectText(), skipAutoClose);
2417
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
2418
+ const browser = context.browser;
2419
+ const element = browser.$(selector);
2420
+ if (!skipClick && !await element.isFocused()) {
2421
+ await element.click();
2422
+ }
2423
+ await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => browser.execute(() => {
2424
+ const element = document.activeElement;
2425
+ if (element && typeof element.select === "function") {
2426
+ element.select();
2427
+ }
2428
+ }), skipAutoClose);
2429
+ } else {
2430
+ throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
2431
+ }
2432
+ return { unreleased: Array.from(unreleased) };
2327
2433
  };
2328
2434
 
2329
2435
  const upload = async (context, selector, files) => {
2330
- const testPath = context.testPath;
2331
- if (!testPath) {
2332
- throw new Error(`Cannot upload files outside of a test`);
2333
- }
2334
- const testDir = dirname(testPath);
2335
- if (context.provider instanceof PlaywrightBrowserProvider) {
2336
- const { iframe } = context;
2337
- const playwrightFiles = files.map((file) => {
2338
- if (typeof file === "string") {
2339
- return resolve(testDir, file);
2340
- }
2341
- return {
2342
- name: file.name,
2343
- mimeType: file.mimeType,
2344
- buffer: Buffer.from(file.base64, "base64")
2345
- };
2346
- });
2347
- await iframe.locator(selector).setInputFiles(playwrightFiles);
2348
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2349
- for (const file of files) {
2350
- if (typeof file !== "string") {
2351
- throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`);
2352
- }
2353
- }
2354
- const element = context.browser.$(selector);
2355
- for (const file of files) {
2356
- const filepath = resolve(testDir, file);
2357
- const remoteFilePath = await context.browser.uploadFile(filepath);
2358
- await element.addValue(remoteFilePath);
2359
- }
2360
- } else {
2361
- throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`);
2362
- }
2436
+ const testPath = context.testPath;
2437
+ if (!testPath) {
2438
+ throw new Error(`Cannot upload files outside of a test`);
2439
+ }
2440
+ const testDir = dirname(testPath);
2441
+ if (context.provider instanceof PlaywrightBrowserProvider) {
2442
+ const { iframe } = context;
2443
+ const playwrightFiles = files.map((file) => {
2444
+ if (typeof file === "string") {
2445
+ return resolve(testDir, file);
2446
+ }
2447
+ return {
2448
+ name: file.name,
2449
+ mimeType: file.mimeType,
2450
+ buffer: Buffer.from(file.base64, "base64")
2451
+ };
2452
+ });
2453
+ await iframe.locator(selector).setInputFiles(playwrightFiles);
2454
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
2455
+ for (const file of files) {
2456
+ if (typeof file !== "string") {
2457
+ throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`);
2458
+ }
2459
+ }
2460
+ const element = context.browser.$(selector);
2461
+ for (const file of files) {
2462
+ const filepath = resolve(testDir, file);
2463
+ const remoteFilePath = await context.browser.uploadFile(filepath);
2464
+ await element.addValue(remoteFilePath);
2465
+ }
2466
+ } else {
2467
+ throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`);
2468
+ }
2363
2469
  };
2364
2470
 
2365
2471
  var builtinCommands = {
2366
- readFile,
2367
- removeFile,
2368
- writeFile,
2369
- __vitest_fileInfo: _fileInfo,
2370
- __vitest_upload: upload,
2371
- __vitest_click: click,
2372
- __vitest_dblClick: dblClick,
2373
- __vitest_tripleClick: tripleClick,
2374
- __vitest_screenshot: screenshot,
2375
- __vitest_type: type,
2376
- __vitest_clear: clear,
2377
- __vitest_fill: fill,
2378
- __vitest_tab: tab,
2379
- __vitest_keyboard: keyboard,
2380
- __vitest_selectOptions: selectOptions,
2381
- __vitest_dragAndDrop: dragAndDrop,
2382
- __vitest_hover: hover,
2383
- __vitest_cleanup: keyboardCleanup
2472
+ readFile,
2473
+ removeFile,
2474
+ writeFile,
2475
+ __vitest_fileInfo: _fileInfo,
2476
+ __vitest_upload: upload,
2477
+ __vitest_click: click,
2478
+ __vitest_dblClick: dblClick,
2479
+ __vitest_tripleClick: tripleClick,
2480
+ __vitest_screenshot: screenshot,
2481
+ __vitest_type: type,
2482
+ __vitest_clear: clear,
2483
+ __vitest_fill: fill,
2484
+ __vitest_tab: tab,
2485
+ __vitest_keyboard: keyboard,
2486
+ __vitest_selectOptions: selectOptions,
2487
+ __vitest_dragAndDrop: dragAndDrop,
2488
+ __vitest_hover: hover,
2489
+ __vitest_cleanup: keyboardCleanup
2384
2490
  };
2385
2491
 
2386
2492
  class BrowserServerState {
2387
- orchestrators = /* @__PURE__ */ new Map();
2388
- testers = /* @__PURE__ */ new Map();
2493
+ orchestrators = new Map();
2494
+ testers = new Map();
2389
2495
  }
2390
2496
 
2391
2497
  class ProjectBrowser {
2392
- constructor(project, base) {
2393
- this.project = project;
2394
- this.base = base;
2395
- this.vitest = project.vitest;
2396
- this.config = project.config;
2397
- const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
2398
- const distRoot = resolve(pkgRoot, "dist");
2399
- const testerHtmlPath = project.config.browser.testerHtmlPath ? resolve(project.config.root, project.config.browser.testerHtmlPath) : resolve(distRoot, "client/tester/tester.html");
2400
- if (!existsSync(testerHtmlPath)) {
2401
- throw new Error(`Tester HTML file "${testerHtmlPath}" doesn't exist.`);
2402
- }
2403
- this.testerFilepath = testerHtmlPath;
2404
- this.testerHtml = readFile$1(
2405
- testerHtmlPath,
2406
- "utf8"
2407
- ).then((html) => this.testerHtml = html);
2408
- }
2409
- testerHtml;
2410
- testerFilepath;
2411
- locatorsUrl;
2412
- provider;
2413
- vitest;
2414
- config;
2415
- children = /* @__PURE__ */ new Set();
2416
- parent;
2417
- state = new BrowserServerState();
2418
- get vite() {
2419
- return this.parent.vite;
2420
- }
2421
- wrapSerializedConfig() {
2422
- const config = wrapConfig(this.project.serializedConfig);
2423
- config.env ??= {};
2424
- config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
2425
- return config;
2426
- }
2427
- async initBrowserProvider(project) {
2428
- if (this.provider) {
2429
- return;
2430
- }
2431
- const Provider = await getBrowserProvider(project.config.browser, project);
2432
- this.provider = new Provider();
2433
- const browser = project.config.browser.name;
2434
- const name = project.name ? `[${project.name}] ` : "";
2435
- if (!browser) {
2436
- throw new Error(
2437
- `${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`
2438
- );
2439
- }
2440
- const supportedBrowsers = this.provider.getSupportedBrowsers();
2441
- if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
2442
- throw new Error(
2443
- `${name}Browser "${browser}" is not supported by the browser provider "${this.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`
2444
- );
2445
- }
2446
- const providerOptions = project.config.browser.providerOptions;
2447
- await this.provider.initialize(project, {
2448
- browser,
2449
- options: providerOptions
2450
- });
2451
- }
2452
- parseErrorStacktrace(e, options = {}) {
2453
- return this.parent.parseErrorStacktrace(e, options);
2454
- }
2455
- parseStacktrace(trace, options = {}) {
2456
- return this.parent.parseStacktrace(trace, options);
2457
- }
2458
- async close() {
2459
- await this.parent.vite.close();
2460
- }
2498
+ testerHtml;
2499
+ testerFilepath;
2500
+ locatorsUrl;
2501
+ provider;
2502
+ vitest;
2503
+ config;
2504
+ children = new Set();
2505
+ parent;
2506
+ state = new BrowserServerState();
2507
+ constructor(project, base) {
2508
+ this.project = project;
2509
+ this.base = base;
2510
+ this.vitest = project.vitest;
2511
+ this.config = project.config;
2512
+ const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
2513
+ const distRoot = resolve(pkgRoot, "dist");
2514
+ const testerHtmlPath = project.config.browser.testerHtmlPath ? resolve(project.config.root, project.config.browser.testerHtmlPath) : resolve(distRoot, "client/tester/tester.html");
2515
+ if (!existsSync(testerHtmlPath)) {
2516
+ throw new Error(`Tester HTML file "${testerHtmlPath}" doesn't exist.`);
2517
+ }
2518
+ this.testerFilepath = testerHtmlPath;
2519
+ this.testerHtml = readFile$1(testerHtmlPath, "utf8").then((html) => this.testerHtml = html);
2520
+ }
2521
+ get vite() {
2522
+ return this.parent.vite;
2523
+ }
2524
+ wrapSerializedConfig() {
2525
+ const config = wrapConfig(this.project.serializedConfig);
2526
+ config.env ??= {};
2527
+ config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
2528
+ return config;
2529
+ }
2530
+ async initBrowserProvider(project) {
2531
+ if (this.provider) {
2532
+ return;
2533
+ }
2534
+ const Provider = await getBrowserProvider(project.config.browser, project);
2535
+ this.provider = new Provider();
2536
+ const browser = project.config.browser.name;
2537
+ const name = project.name ? `[${project.name}] ` : "";
2538
+ if (!browser) {
2539
+ throw new Error(`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`);
2540
+ }
2541
+ const supportedBrowsers = this.provider.getSupportedBrowsers();
2542
+ if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
2543
+ throw new Error(`${name}Browser "${browser}" is not supported by the browser provider "${this.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`);
2544
+ }
2545
+ const providerOptions = project.config.browser.providerOptions;
2546
+ await this.provider.initialize(project, {
2547
+ browser,
2548
+ options: providerOptions
2549
+ });
2550
+ }
2551
+ parseErrorStacktrace(e, options = {}) {
2552
+ return this.parent.parseErrorStacktrace(e, options);
2553
+ }
2554
+ parseStacktrace(trace, options = {}) {
2555
+ return this.parent.parseStacktrace(trace, options);
2556
+ }
2557
+ async close() {
2558
+ await this.parent.vite.close();
2559
+ }
2461
2560
  }
2462
2561
  function wrapConfig(config) {
2463
- return {
2464
- ...config,
2465
- // workaround RegExp serialization
2466
- testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : void 0
2467
- };
2562
+ return {
2563
+ ...config,
2564
+ testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : undefined
2565
+ };
2468
2566
  }
2469
2567
 
2470
2568
  class ParentBrowserProject {
2471
- constructor(project, base) {
2472
- this.project = project;
2473
- this.base = base;
2474
- this.vitest = project.vitest;
2475
- this.config = project.config;
2476
- this.stackTraceOptions = {
2477
- frameFilter: project.config.onStackTrace,
2478
- getSourceMap: (id) => {
2479
- if (this.sourceMapCache.has(id)) {
2480
- return this.sourceMapCache.get(id);
2481
- }
2482
- const result = this.vite.moduleGraph.getModuleById(id)?.transformResult;
2483
- if (result && !result.map) {
2484
- const sourceMapUrl = this.retrieveSourceMapURL(result.code);
2485
- if (!sourceMapUrl) {
2486
- return null;
2487
- }
2488
- const filepathDir = dirname(id);
2489
- const sourceMapPath = resolve(filepathDir, sourceMapUrl);
2490
- const map = JSON.parse(readFileSync(sourceMapPath, "utf-8"));
2491
- this.sourceMapCache.set(id, map);
2492
- return map;
2493
- }
2494
- return result?.map;
2495
- },
2496
- getUrlId: (id) => {
2497
- const mod = this.vite.moduleGraph.getModuleById(id);
2498
- if (mod) {
2499
- return id;
2500
- }
2501
- const resolvedPath = resolve(project.config.root, id.slice(1));
2502
- const modUrl = this.vite.moduleGraph.getModuleById(resolvedPath);
2503
- if (modUrl) {
2504
- return resolvedPath;
2505
- }
2506
- const files = this.vite.moduleGraph.getModulesByFile(resolvedPath);
2507
- if (files && files.size) {
2508
- return files.values().next().value.id;
2509
- }
2510
- return id;
2511
- }
2512
- };
2513
- for (const [name, command] of Object.entries(builtinCommands)) {
2514
- this.commands[name] ??= command;
2515
- }
2516
- for (const command in project.config.browser.commands) {
2517
- if (!/^[a-z_$][\w$]*$/i.test(command)) {
2518
- throw new Error(
2519
- `Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`
2520
- );
2521
- }
2522
- this.commands[command] = project.config.browser.commands[command];
2523
- }
2524
- this.prefixTesterUrl = `${base}__vitest_test__/__test__/`;
2525
- this.faviconUrl = `${base}__vitest__/favicon.svg`;
2526
- this.manifest = (async () => {
2527
- return JSON.parse(
2528
- await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8")
2529
- );
2530
- })().then((manifest) => this.manifest = manifest);
2531
- this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
2532
- this.injectorJs = readFile$1(
2533
- resolve(distRoot, "client/esm-client-injector.js"),
2534
- "utf8"
2535
- ).then((js) => this.injectorJs = js);
2536
- this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
2537
- const builtinProviders = ["playwright", "webdriverio", "preview"];
2538
- const providerName = project.config.browser.provider || "preview";
2539
- if (builtinProviders.includes(providerName)) {
2540
- this.locatorsUrl = join("/@fs/", distRoot, "locators", `${providerName}.js`);
2541
- }
2542
- this.stateJs = readFile$1(
2543
- resolve(distRoot, "state.js"),
2544
- "utf-8"
2545
- ).then((js) => this.stateJs = js);
2546
- }
2547
- orchestratorScripts;
2548
- testerScripts;
2549
- faviconUrl;
2550
- prefixTesterUrl;
2551
- manifest;
2552
- vite;
2553
- stackTraceOptions;
2554
- orchestratorHtml;
2555
- injectorJs;
2556
- errorCatcherUrl;
2557
- locatorsUrl;
2558
- stateJs;
2559
- commands = {};
2560
- children = /* @__PURE__ */ new Set();
2561
- vitest;
2562
- config;
2563
- // cache for non-vite source maps
2564
- sourceMapCache = /* @__PURE__ */ new Map();
2565
- setServer(vite) {
2566
- this.vite = vite;
2567
- }
2568
- spawn(project) {
2569
- if (!this.vite) {
2570
- throw new Error(`Cannot spawn child server without a parent dev server.`);
2571
- }
2572
- const clone = new ProjectBrowser(
2573
- project,
2574
- "/"
2575
- );
2576
- clone.parent = this;
2577
- this.children.add(clone);
2578
- return clone;
2579
- }
2580
- parseErrorStacktrace(e, options = {}) {
2581
- return parseErrorStacktrace(e, {
2582
- ...this.stackTraceOptions,
2583
- ...options
2584
- });
2585
- }
2586
- parseStacktrace(trace, options = {}) {
2587
- return parseStacktrace(trace, {
2588
- ...this.stackTraceOptions,
2589
- ...options
2590
- });
2591
- }
2592
- cdps = /* @__PURE__ */ new Map();
2593
- cdpSessionsPromises = /* @__PURE__ */ new Map();
2594
- async ensureCDPHandler(sessionId, rpcId) {
2595
- const cachedHandler = this.cdps.get(rpcId);
2596
- if (cachedHandler) {
2597
- return cachedHandler;
2598
- }
2599
- const browserSession = this.vitest._browserSessions.getSession(sessionId);
2600
- if (!browserSession) {
2601
- throw new Error(`Session "${sessionId}" not found.`);
2602
- }
2603
- const browser = browserSession.project.browser;
2604
- const provider = browser.provider;
2605
- if (!provider) {
2606
- throw new Error(`Browser provider is not defined for the project "${browserSession.project.name}".`);
2607
- }
2608
- if (!provider.getCDPSession) {
2609
- throw new Error(`CDP is not supported by the provider "${provider.name}".`);
2610
- }
2611
- const promise = this.cdpSessionsPromises.get(rpcId) ?? await (async () => {
2612
- const promise2 = provider.getCDPSession(sessionId).finally(() => {
2613
- this.cdpSessionsPromises.delete(rpcId);
2614
- });
2615
- this.cdpSessionsPromises.set(rpcId, promise2);
2616
- return promise2;
2617
- })();
2618
- const session = await promise;
2619
- const rpc = browser.state.testers.get(rpcId);
2620
- if (!rpc) {
2621
- throw new Error(`Tester RPC "${rpcId}" was not established.`);
2622
- }
2623
- const handler = new BrowserServerCDPHandler(session, rpc);
2624
- this.cdps.set(
2625
- rpcId,
2626
- handler
2627
- );
2628
- return handler;
2629
- }
2630
- removeCDPHandler(sessionId) {
2631
- this.cdps.delete(sessionId);
2632
- }
2633
- async formatScripts(scripts) {
2634
- if (!scripts?.length) {
2635
- return [];
2636
- }
2637
- const server = this.vite;
2638
- const promises = scripts.map(
2639
- async ({ content, src, async, id, type = "module" }, index) => {
2640
- const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : void 0) || src;
2641
- const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`);
2642
- await server.moduleGraph.ensureEntryFromUrl(transformId);
2643
- const contentProcessed = content && type === "module" ? (await server.pluginContainer.transform(content, transformId)).code : content;
2644
- return {
2645
- tag: "script",
2646
- attrs: {
2647
- type,
2648
- ...async ? { async: "" } : {},
2649
- ...srcLink ? {
2650
- src: srcLink.startsWith("http") ? srcLink : slash(`/@fs/${srcLink}`)
2651
- } : {}
2652
- },
2653
- injectTo: "head",
2654
- children: contentProcessed || ""
2655
- };
2656
- }
2657
- );
2658
- return await Promise.all(promises);
2659
- }
2660
- resolveTesterUrl(pathname) {
2661
- const [sessionId, testFile] = pathname.slice(this.prefixTesterUrl.length).split("/");
2662
- const decodedTestFile = decodeURIComponent(testFile);
2663
- return { sessionId, testFile: decodedTestFile };
2664
- }
2665
- retrieveSourceMapURL(source) {
2666
- const re = /\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm;
2667
- let lastMatch, match;
2668
- while (match = re.exec(source)) {
2669
- lastMatch = match;
2670
- }
2671
- if (!lastMatch) {
2672
- return null;
2673
- }
2674
- return lastMatch[1];
2675
- }
2569
+ orchestratorScripts;
2570
+ testerScripts;
2571
+ faviconUrl;
2572
+ prefixTesterUrl;
2573
+ manifest;
2574
+ vite;
2575
+ stackTraceOptions;
2576
+ orchestratorHtml;
2577
+ injectorJs;
2578
+ errorCatcherUrl;
2579
+ locatorsUrl;
2580
+ stateJs;
2581
+ commands = {};
2582
+ children = new Set();
2583
+ vitest;
2584
+ config;
2585
+ sourceMapCache = new Map();
2586
+ constructor(project, base) {
2587
+ this.project = project;
2588
+ this.base = base;
2589
+ this.vitest = project.vitest;
2590
+ this.config = project.config;
2591
+ this.stackTraceOptions = {
2592
+ frameFilter: project.config.onStackTrace,
2593
+ getSourceMap: (id) => {
2594
+ if (this.sourceMapCache.has(id)) {
2595
+ return this.sourceMapCache.get(id);
2596
+ }
2597
+ const result = this.vite.moduleGraph.getModuleById(id)?.transformResult;
2598
+ if (result && !result.map) {
2599
+ const sourceMapUrl = this.retrieveSourceMapURL(result.code);
2600
+ if (!sourceMapUrl) {
2601
+ return null;
2602
+ }
2603
+ const filepathDir = dirname(id);
2604
+ const sourceMapPath = resolve(filepathDir, sourceMapUrl);
2605
+ const map = JSON.parse(readFileSync(sourceMapPath, "utf-8"));
2606
+ this.sourceMapCache.set(id, map);
2607
+ return map;
2608
+ }
2609
+ return result?.map;
2610
+ },
2611
+ getUrlId: (id) => {
2612
+ const mod = this.vite.moduleGraph.getModuleById(id);
2613
+ if (mod) {
2614
+ return id;
2615
+ }
2616
+ const resolvedPath = resolve(project.config.root, id.slice(1));
2617
+ const modUrl = this.vite.moduleGraph.getModuleById(resolvedPath);
2618
+ if (modUrl) {
2619
+ return resolvedPath;
2620
+ }
2621
+ const files = this.vite.moduleGraph.getModulesByFile(resolvedPath);
2622
+ if (files && files.size) {
2623
+ return files.values().next().value.id;
2624
+ }
2625
+ return id;
2626
+ }
2627
+ };
2628
+ for (const [name, command] of Object.entries(builtinCommands)) {
2629
+ this.commands[name] ??= command;
2630
+ }
2631
+ for (const command in project.config.browser.commands) {
2632
+ if (!/^[a-z_$][\w$]*$/i.test(command)) {
2633
+ throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
2634
+ }
2635
+ this.commands[command] = project.config.browser.commands[command];
2636
+ }
2637
+ this.prefixTesterUrl = `${base}__vitest_test__/__test__/`;
2638
+ this.faviconUrl = `${base}__vitest__/favicon.svg`;
2639
+ this.manifest = (async () => {
2640
+ return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
2641
+ })().then((manifest) => this.manifest = manifest);
2642
+ this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
2643
+ this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
2644
+ this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
2645
+ const builtinProviders = [
2646
+ "playwright",
2647
+ "webdriverio",
2648
+ "preview"
2649
+ ];
2650
+ const providerName = project.config.browser.provider || "preview";
2651
+ if (builtinProviders.includes(providerName)) {
2652
+ this.locatorsUrl = join("/@fs/", distRoot, "locators", `${providerName}.js`);
2653
+ }
2654
+ this.stateJs = readFile$1(resolve(distRoot, "state.js"), "utf-8").then((js) => this.stateJs = js);
2655
+ }
2656
+ setServer(vite) {
2657
+ this.vite = vite;
2658
+ }
2659
+ spawn(project) {
2660
+ if (!this.vite) {
2661
+ throw new Error(`Cannot spawn child server without a parent dev server.`);
2662
+ }
2663
+ const clone = new ProjectBrowser(project, "/");
2664
+ clone.parent = this;
2665
+ this.children.add(clone);
2666
+ return clone;
2667
+ }
2668
+ parseErrorStacktrace(e, options = {}) {
2669
+ return parseErrorStacktrace(e, {
2670
+ ...this.stackTraceOptions,
2671
+ ...options
2672
+ });
2673
+ }
2674
+ parseStacktrace(trace, options = {}) {
2675
+ return parseStacktrace(trace, {
2676
+ ...this.stackTraceOptions,
2677
+ ...options
2678
+ });
2679
+ }
2680
+ cdps = new Map();
2681
+ cdpSessionsPromises = new Map();
2682
+ async ensureCDPHandler(sessionId, rpcId) {
2683
+ const cachedHandler = this.cdps.get(rpcId);
2684
+ if (cachedHandler) {
2685
+ return cachedHandler;
2686
+ }
2687
+ const browserSession = this.vitest._browserSessions.getSession(sessionId);
2688
+ if (!browserSession) {
2689
+ throw new Error(`Session "${sessionId}" not found.`);
2690
+ }
2691
+ const browser = browserSession.project.browser;
2692
+ const provider = browser.provider;
2693
+ if (!provider) {
2694
+ throw new Error(`Browser provider is not defined for the project "${browserSession.project.name}".`);
2695
+ }
2696
+ if (!provider.getCDPSession) {
2697
+ throw new Error(`CDP is not supported by the provider "${provider.name}".`);
2698
+ }
2699
+ const promise = this.cdpSessionsPromises.get(rpcId) ?? await (async () => {
2700
+ const promise = provider.getCDPSession(sessionId).finally(() => {
2701
+ this.cdpSessionsPromises.delete(rpcId);
2702
+ });
2703
+ this.cdpSessionsPromises.set(rpcId, promise);
2704
+ return promise;
2705
+ })();
2706
+ const session = await promise;
2707
+ const rpc = browser.state.testers.get(rpcId);
2708
+ if (!rpc) {
2709
+ throw new Error(`Tester RPC "${rpcId}" was not established.`);
2710
+ }
2711
+ const handler = new BrowserServerCDPHandler(session, rpc);
2712
+ this.cdps.set(rpcId, handler);
2713
+ return handler;
2714
+ }
2715
+ removeCDPHandler(sessionId) {
2716
+ this.cdps.delete(sessionId);
2717
+ }
2718
+ async formatScripts(scripts) {
2719
+ if (!scripts?.length) {
2720
+ return [];
2721
+ }
2722
+ const server = this.vite;
2723
+ const promises = scripts.map(async ({ content, src, async, id, type = "module" }, index) => {
2724
+ const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : undefined) || src;
2725
+ const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`);
2726
+ await server.moduleGraph.ensureEntryFromUrl(transformId);
2727
+ const contentProcessed = content && type === "module" ? (await server.pluginContainer.transform(content, transformId)).code : content;
2728
+ return {
2729
+ tag: "script",
2730
+ attrs: {
2731
+ type,
2732
+ ...async ? { async: "" } : {},
2733
+ ...srcLink ? { src: srcLink.startsWith("http") ? srcLink : slash(`/@fs/${srcLink}`) } : {}
2734
+ },
2735
+ injectTo: "head",
2736
+ children: contentProcessed || ""
2737
+ };
2738
+ });
2739
+ return await Promise.all(promises);
2740
+ }
2741
+ resolveTesterUrl(pathname) {
2742
+ const [sessionId, testFile] = pathname.slice(this.prefixTesterUrl.length).split("/");
2743
+ const decodedTestFile = decodeURIComponent(testFile);
2744
+ return {
2745
+ sessionId,
2746
+ testFile: decodedTestFile
2747
+ };
2748
+ }
2749
+ retrieveSourceMapURL(source) {
2750
+ const re = /\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm;
2751
+ let lastMatch, match;
2752
+ while (match = re.exec(source)) {
2753
+ lastMatch = match;
2754
+ }
2755
+ if (!lastMatch) {
2756
+ return null;
2757
+ }
2758
+ return lastMatch[1];
2759
+ }
2676
2760
  }
2677
2761
 
2678
2762
  const DEFAULT_TIMEOUT = 6e4;
@@ -2803,452 +2887,397 @@ function nanoid(size = 21) {
2803
2887
  const debug$1 = createDebugger("vitest:browser:api");
2804
2888
  const BROWSER_API_PATH = "/__vitest_browser_api__";
2805
2889
  function setupBrowserRpc(globalServer) {
2806
- const vite = globalServer.vite;
2807
- const vitest = globalServer.vitest;
2808
- const wss = new WebSocketServer({ noServer: true });
2809
- vite.httpServer?.on("upgrade", (request, socket, head) => {
2810
- if (!request.url) {
2811
- return;
2812
- }
2813
- const { pathname, searchParams } = new URL(request.url, "http://localhost");
2814
- if (pathname !== BROWSER_API_PATH) {
2815
- return;
2816
- }
2817
- if (!isValidApiRequest(vitest.config, request)) {
2818
- socket.destroy();
2819
- return;
2820
- }
2821
- const type = searchParams.get("type");
2822
- const rpcId = searchParams.get("rpcId");
2823
- const sessionId = searchParams.get("sessionId");
2824
- const projectName = searchParams.get("projectName");
2825
- if (type !== "tester" && type !== "orchestrator") {
2826
- return error(
2827
- new Error(`[vitest] Type query in ${request.url} is invalid. Type should be either "tester" or "orchestrator".`)
2828
- );
2829
- }
2830
- if (!sessionId || !rpcId || projectName == null) {
2831
- return error(
2832
- new Error(`[vitest] Invalid URL ${request.url}. "projectName", "sessionId" and "rpcId" queries are required.`)
2833
- );
2834
- }
2835
- const method = searchParams.get("method");
2836
- if (method !== "run" && method !== "collect") {
2837
- return error(
2838
- new Error(`[vitest] Method query in ${request.url} is invalid. Method should be either "run" or "collect".`)
2839
- );
2840
- }
2841
- if (type === "orchestrator") {
2842
- const session = vitest._browserSessions.getSession(sessionId);
2843
- session?.connected();
2844
- }
2845
- const project = vitest.getProjectByName(projectName);
2846
- if (!project) {
2847
- return error(
2848
- new Error(`[vitest] Project "${projectName}" not found.`)
2849
- );
2850
- }
2851
- wss.handleUpgrade(request, socket, head, (ws) => {
2852
- wss.emit("connection", ws, request);
2853
- const rpc = setupClient(project, rpcId, ws, method);
2854
- const state = project.browser.state;
2855
- const clients = type === "tester" ? state.testers : state.orchestrators;
2856
- clients.set(rpcId, rpc);
2857
- debug$1?.("[%s] Browser API connected to %s", rpcId, type);
2858
- ws.on("close", () => {
2859
- debug$1?.("[%s] Browser API disconnected from %s", rpcId, type);
2860
- clients.delete(rpcId);
2861
- globalServer.removeCDPHandler(rpcId);
2862
- });
2863
- });
2864
- });
2865
- function error(err) {
2866
- console.error(err);
2867
- vitest.state.catchError(err, "RPC Error");
2868
- }
2869
- function checkFileAccess(path) {
2870
- if (!isFileServingAllowed(path, vite)) {
2871
- throw new Error(
2872
- `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`
2873
- );
2874
- }
2875
- }
2876
- function setupClient(project, rpcId, ws, method) {
2877
- const mockResolver = new ServerMockResolver(globalServer.vite, {
2878
- moduleDirectories: project.config.server?.deps?.moduleDirectories
2879
- });
2880
- const rpc = createBirpc(
2881
- {
2882
- async onUnhandledError(error2, type) {
2883
- if (error2 && typeof error2 === "object") {
2884
- const _error = error2;
2885
- _error.stacks = globalServer.parseErrorStacktrace(_error);
2886
- }
2887
- vitest.state.catchError(error2, type);
2888
- },
2889
- async onQueued(file) {
2890
- if (method === "collect") {
2891
- vitest.state.collectFiles(project, [file]);
2892
- } else {
2893
- await vitest._testRun.enqueued(project, file);
2894
- }
2895
- },
2896
- async onCollected(files) {
2897
- if (method === "collect") {
2898
- vitest.state.collectFiles(project, files);
2899
- } else {
2900
- await vitest._testRun.collected(project, files);
2901
- }
2902
- },
2903
- async onTaskUpdate(packs, events) {
2904
- if (method === "collect") {
2905
- vitest.state.updateTasks(packs);
2906
- } else {
2907
- await vitest._testRun.updated(packs, events);
2908
- }
2909
- },
2910
- onAfterSuiteRun(meta) {
2911
- vitest.coverageProvider?.onAfterSuiteRun(meta);
2912
- },
2913
- async sendLog(log) {
2914
- if (method === "collect") {
2915
- vitest.state.updateUserLog(log);
2916
- } else {
2917
- await vitest._testRun.log(log);
2918
- }
2919
- },
2920
- resolveSnapshotPath(testPath) {
2921
- return vitest.snapshot.resolvePath(testPath, {
2922
- config: project.serializedConfig
2923
- });
2924
- },
2925
- resolveSnapshotRawPath(testPath, rawPath) {
2926
- return vitest.snapshot.resolveRawPath(testPath, rawPath);
2927
- },
2928
- snapshotSaved(snapshot) {
2929
- vitest.snapshot.add(snapshot);
2930
- },
2931
- async readSnapshotFile(snapshotPath) {
2932
- checkFileAccess(snapshotPath);
2933
- if (!existsSync(snapshotPath)) {
2934
- return null;
2935
- }
2936
- return promises.readFile(snapshotPath, "utf-8");
2937
- },
2938
- async saveSnapshotFile(id, content) {
2939
- checkFileAccess(id);
2940
- await promises.mkdir(dirname(id), { recursive: true });
2941
- return promises.writeFile(id, content, "utf-8");
2942
- },
2943
- async removeSnapshotFile(id) {
2944
- checkFileAccess(id);
2945
- if (!existsSync(id)) {
2946
- throw new Error(`Snapshot file "${id}" does not exist.`);
2947
- }
2948
- return promises.unlink(id);
2949
- },
2950
- getBrowserFileSourceMap(id) {
2951
- const mod = globalServer.vite.moduleGraph.getModuleById(id);
2952
- return mod?.transformResult?.map;
2953
- },
2954
- onCancel(reason) {
2955
- vitest.cancelCurrentRun(reason);
2956
- },
2957
- async resolveId(id, importer) {
2958
- return mockResolver.resolveId(id, importer);
2959
- },
2960
- debug(...args) {
2961
- vitest.logger.console.debug(...args);
2962
- },
2963
- getCountOfFailedTests() {
2964
- return vitest.state.getCountOfFailedTests();
2965
- },
2966
- async wdioSwitchContext(direction) {
2967
- const provider = project.browser.provider;
2968
- if (!provider) {
2969
- throw new Error("Commands are only available for browser tests.");
2970
- }
2971
- if (provider.name !== "webdriverio") {
2972
- throw new Error("Switch context is only available for WebDriverIO provider.");
2973
- }
2974
- if (direction === "iframe") {
2975
- await provider.switchToTestFrame();
2976
- } else {
2977
- await provider.switchToMainFrame();
2978
- }
2979
- },
2980
- async triggerCommand(sessionId, command, testPath, payload) {
2981
- debug$1?.('[%s] Triggering command "%s"', sessionId, command);
2982
- const provider = project.browser.provider;
2983
- if (!provider) {
2984
- throw new Error("Commands are only available for browser tests.");
2985
- }
2986
- const commands = globalServer.commands;
2987
- if (!commands || !commands[command]) {
2988
- throw new Error(`Unknown command "${command}".`);
2989
- }
2990
- const context = Object.assign(
2991
- {
2992
- testPath,
2993
- project,
2994
- provider,
2995
- contextId: sessionId,
2996
- sessionId
2997
- },
2998
- provider.getCommandsContext(sessionId)
2999
- );
3000
- return await commands[command](context, ...payload);
3001
- },
3002
- finishBrowserTests(sessionId) {
3003
- debug$1?.("[%s] Finishing browser tests for session", sessionId);
3004
- return vitest._browserSessions.getSession(sessionId)?.resolve();
3005
- },
3006
- resolveMock(rawId, importer, options) {
3007
- return mockResolver.resolveMock(rawId, importer, options);
3008
- },
3009
- invalidate(ids) {
3010
- return mockResolver.invalidate(ids);
3011
- },
3012
- // CDP
3013
- async sendCdpEvent(sessionId, event, payload) {
3014
- const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
3015
- return cdp.send(event, payload);
3016
- },
3017
- async trackCdpEvent(sessionId, type, event, listenerId) {
3018
- const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
3019
- cdp[type](event, listenerId);
3020
- }
3021
- },
3022
- {
3023
- post: (msg) => ws.send(msg),
3024
- on: (fn) => ws.on("message", fn),
3025
- eventNames: ["onCancel", "cdpEvent"],
3026
- serialize: (data) => stringify(data, stringifyReplace),
3027
- deserialize: parse,
3028
- onTimeoutError(functionName) {
3029
- throw new Error(`[vitest-api]: Timeout calling "${functionName}"`);
3030
- }
3031
- }
3032
- );
3033
- vitest.onCancel((reason) => rpc.onCancel(reason));
3034
- return rpc;
3035
- }
2890
+ const vite = globalServer.vite;
2891
+ const vitest = globalServer.vitest;
2892
+ const wss = new WebSocketServer({ noServer: true });
2893
+ vite.httpServer?.on("upgrade", (request, socket, head) => {
2894
+ if (!request.url) {
2895
+ return;
2896
+ }
2897
+ const { pathname, searchParams } = new URL(request.url, "http://localhost");
2898
+ if (pathname !== BROWSER_API_PATH) {
2899
+ return;
2900
+ }
2901
+ if (!isValidApiRequest(vitest.config, request)) {
2902
+ socket.destroy();
2903
+ return;
2904
+ }
2905
+ const type = searchParams.get("type");
2906
+ const rpcId = searchParams.get("rpcId");
2907
+ const sessionId = searchParams.get("sessionId");
2908
+ const projectName = searchParams.get("projectName");
2909
+ if (type !== "tester" && type !== "orchestrator") {
2910
+ return error(new Error(`[vitest] Type query in ${request.url} is invalid. Type should be either "tester" or "orchestrator".`));
2911
+ }
2912
+ if (!sessionId || !rpcId || projectName == null) {
2913
+ return error(new Error(`[vitest] Invalid URL ${request.url}. "projectName", "sessionId" and "rpcId" queries are required.`));
2914
+ }
2915
+ const method = searchParams.get("method");
2916
+ if (method !== "run" && method !== "collect") {
2917
+ return error(new Error(`[vitest] Method query in ${request.url} is invalid. Method should be either "run" or "collect".`));
2918
+ }
2919
+ if (type === "orchestrator") {
2920
+ const session = vitest._browserSessions.getSession(sessionId);
2921
+ session?.connected();
2922
+ }
2923
+ const project = vitest.getProjectByName(projectName);
2924
+ if (!project) {
2925
+ return error(new Error(`[vitest] Project "${projectName}" not found.`));
2926
+ }
2927
+ wss.handleUpgrade(request, socket, head, (ws) => {
2928
+ wss.emit("connection", ws, request);
2929
+ const rpc = setupClient(project, rpcId, ws, method);
2930
+ const state = project.browser.state;
2931
+ const clients = type === "tester" ? state.testers : state.orchestrators;
2932
+ clients.set(rpcId, rpc);
2933
+ debug$1?.("[%s] Browser API connected to %s", rpcId, type);
2934
+ ws.on("close", () => {
2935
+ debug$1?.("[%s] Browser API disconnected from %s", rpcId, type);
2936
+ clients.delete(rpcId);
2937
+ globalServer.removeCDPHandler(rpcId);
2938
+ });
2939
+ });
2940
+ });
2941
+ function error(err) {
2942
+ console.error(err);
2943
+ vitest.state.catchError(err, "RPC Error");
2944
+ }
2945
+ function checkFileAccess(path) {
2946
+ if (!isFileServingAllowed(path, vite)) {
2947
+ throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
2948
+ }
2949
+ }
2950
+ function setupClient(project, rpcId, ws, method) {
2951
+ const mockResolver = new ServerMockResolver(globalServer.vite, { moduleDirectories: project.config.server?.deps?.moduleDirectories });
2952
+ const rpc = createBirpc({
2953
+ async onUnhandledError(error, type) {
2954
+ if (error && typeof error === "object") {
2955
+ const _error = error;
2956
+ _error.stacks = globalServer.parseErrorStacktrace(_error);
2957
+ }
2958
+ vitest.state.catchError(error, type);
2959
+ },
2960
+ async onQueued(file) {
2961
+ if (method === "collect") {
2962
+ vitest.state.collectFiles(project, [file]);
2963
+ } else {
2964
+ await vitest._testRun.enqueued(project, file);
2965
+ }
2966
+ },
2967
+ async onCollected(files) {
2968
+ if (method === "collect") {
2969
+ vitest.state.collectFiles(project, files);
2970
+ } else {
2971
+ await vitest._testRun.collected(project, files);
2972
+ }
2973
+ },
2974
+ async onTaskUpdate(packs, events) {
2975
+ if (method === "collect") {
2976
+ vitest.state.updateTasks(packs);
2977
+ } else {
2978
+ await vitest._testRun.updated(packs, events);
2979
+ }
2980
+ },
2981
+ onAfterSuiteRun(meta) {
2982
+ vitest.coverageProvider?.onAfterSuiteRun(meta);
2983
+ },
2984
+ async sendLog(log) {
2985
+ if (method === "collect") {
2986
+ vitest.state.updateUserLog(log);
2987
+ } else {
2988
+ await vitest._testRun.log(log);
2989
+ }
2990
+ },
2991
+ resolveSnapshotPath(testPath) {
2992
+ return vitest.snapshot.resolvePath(testPath, { config: project.serializedConfig });
2993
+ },
2994
+ resolveSnapshotRawPath(testPath, rawPath) {
2995
+ return vitest.snapshot.resolveRawPath(testPath, rawPath);
2996
+ },
2997
+ snapshotSaved(snapshot) {
2998
+ vitest.snapshot.add(snapshot);
2999
+ },
3000
+ async readSnapshotFile(snapshotPath) {
3001
+ checkFileAccess(snapshotPath);
3002
+ if (!existsSync(snapshotPath)) {
3003
+ return null;
3004
+ }
3005
+ return promises.readFile(snapshotPath, "utf-8");
3006
+ },
3007
+ async saveSnapshotFile(id, content) {
3008
+ checkFileAccess(id);
3009
+ await promises.mkdir(dirname(id), { recursive: true });
3010
+ return promises.writeFile(id, content, "utf-8");
3011
+ },
3012
+ async removeSnapshotFile(id) {
3013
+ checkFileAccess(id);
3014
+ if (!existsSync(id)) {
3015
+ throw new Error(`Snapshot file "${id}" does not exist.`);
3016
+ }
3017
+ return promises.unlink(id);
3018
+ },
3019
+ getBrowserFileSourceMap(id) {
3020
+ const mod = globalServer.vite.moduleGraph.getModuleById(id);
3021
+ return mod?.transformResult?.map;
3022
+ },
3023
+ onCancel(reason) {
3024
+ vitest.cancelCurrentRun(reason);
3025
+ },
3026
+ async resolveId(id, importer) {
3027
+ return mockResolver.resolveId(id, importer);
3028
+ },
3029
+ debug(...args) {
3030
+ vitest.logger.console.debug(...args);
3031
+ },
3032
+ getCountOfFailedTests() {
3033
+ return vitest.state.getCountOfFailedTests();
3034
+ },
3035
+ async wdioSwitchContext(direction) {
3036
+ const provider = project.browser.provider;
3037
+ if (!provider) {
3038
+ throw new Error("Commands are only available for browser tests.");
3039
+ }
3040
+ if (provider.name !== "webdriverio") {
3041
+ throw new Error("Switch context is only available for WebDriverIO provider.");
3042
+ }
3043
+ if (direction === "iframe") {
3044
+ await provider.switchToTestFrame();
3045
+ } else {
3046
+ await provider.switchToMainFrame();
3047
+ }
3048
+ },
3049
+ async triggerCommand(sessionId, command, testPath, payload) {
3050
+ debug$1?.("[%s] Triggering command \"%s\"", sessionId, command);
3051
+ const provider = project.browser.provider;
3052
+ if (!provider) {
3053
+ throw new Error("Commands are only available for browser tests.");
3054
+ }
3055
+ const commands = globalServer.commands;
3056
+ if (!commands || !commands[command]) {
3057
+ throw new Error(`Unknown command "${command}".`);
3058
+ }
3059
+ const context = Object.assign({
3060
+ testPath,
3061
+ project,
3062
+ provider,
3063
+ contextId: sessionId,
3064
+ sessionId
3065
+ }, provider.getCommandsContext(sessionId));
3066
+ return await commands[command](context, ...payload);
3067
+ },
3068
+ finishBrowserTests(sessionId) {
3069
+ debug$1?.("[%s] Finishing browser tests for session", sessionId);
3070
+ return vitest._browserSessions.getSession(sessionId)?.resolve();
3071
+ },
3072
+ resolveMock(rawId, importer, options) {
3073
+ return mockResolver.resolveMock(rawId, importer, options);
3074
+ },
3075
+ invalidate(ids) {
3076
+ return mockResolver.invalidate(ids);
3077
+ },
3078
+ async sendCdpEvent(sessionId, event, payload) {
3079
+ const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
3080
+ return cdp.send(event, payload);
3081
+ },
3082
+ async trackCdpEvent(sessionId, type, event, listenerId) {
3083
+ const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
3084
+ cdp[type](event, listenerId);
3085
+ }
3086
+ }, {
3087
+ post: (msg) => ws.send(msg),
3088
+ on: (fn) => ws.on("message", fn),
3089
+ eventNames: ["onCancel", "cdpEvent"],
3090
+ serialize: (data) => stringify(data, stringifyReplace),
3091
+ deserialize: parse,
3092
+ onTimeoutError(functionName) {
3093
+ throw new Error(`[vitest-api]: Timeout calling "${functionName}"`);
3094
+ }
3095
+ });
3096
+ vitest.onCancel((reason) => rpc.onCancel(reason));
3097
+ return rpc;
3098
+ }
3036
3099
  }
3037
3100
  function cloneByOwnProperties(value) {
3038
- return Object.getOwnPropertyNames(value).reduce(
3039
- (clone, prop) => ({
3040
- ...clone,
3041
- [prop]: value[prop]
3042
- }),
3043
- {}
3044
- );
3101
+ return Object.getOwnPropertyNames(value).reduce((clone, prop) => ({
3102
+ ...clone,
3103
+ [prop]: value[prop]
3104
+ }), {});
3045
3105
  }
3106
+ /**
3107
+ * Replacer function for serialization methods such as JS.stringify() or
3108
+ * flatted.stringify().
3109
+ */
3046
3110
  function stringifyReplace(key, value) {
3047
- if (value instanceof Error) {
3048
- const cloned = cloneByOwnProperties(value);
3049
- return {
3050
- name: value.name,
3051
- message: value.message,
3052
- stack: value.stack,
3053
- ...cloned
3054
- };
3055
- } else {
3056
- return value;
3057
- }
3111
+ if (value instanceof Error) {
3112
+ const cloned = cloneByOwnProperties(value);
3113
+ return {
3114
+ name: value.name,
3115
+ message: value.message,
3116
+ stack: value.stack,
3117
+ ...cloned
3118
+ };
3119
+ } else {
3120
+ return value;
3121
+ }
3058
3122
  }
3059
3123
 
3060
3124
  const debug = createDebugger("vitest:browser:pool");
3061
3125
  async function waitForTests(method, sessionId, project, files) {
3062
- const context = project.vitest._browserSessions.createAsyncSession(method, sessionId, files, project);
3063
- return await context;
3126
+ const context = project.vitest._browserSessions.createAsyncSession(method, sessionId, files, project);
3127
+ return await context;
3064
3128
  }
3065
3129
  function createBrowserPool(vitest) {
3066
- const providers = /* @__PURE__ */ new Set();
3067
- const executeTests = async (method, project, files) => {
3068
- vitest.state.clearFiles(project, files);
3069
- const browser = project.browser;
3070
- const threadsCount = getThreadsCount(project);
3071
- const provider = browser.provider;
3072
- providers.add(provider);
3073
- const resolvedUrls = browser.vite.resolvedUrls;
3074
- const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
3075
- if (!origin) {
3076
- throw new Error(
3077
- `Can't find browser origin URL for project "${project.name}" when running tests for files "${files.join('", "')}"`
3078
- );
3079
- }
3080
- async function setBreakpoint(sessionId, file) {
3081
- if (!project.config.inspector.waitForDebugger) {
3082
- return;
3083
- }
3084
- if (!provider.getCDPSession) {
3085
- throw new Error("Unable to set breakpoint, CDP not supported");
3086
- }
3087
- const session = await provider.getCDPSession(sessionId);
3088
- await session.send("Debugger.enable", {});
3089
- await session.send("Debugger.setBreakpointByUrl", {
3090
- lineNumber: 0,
3091
- urlRegex: escapePathToRegexp(file)
3092
- });
3093
- }
3094
- const filesPerThread = Math.ceil(files.length / threadsCount);
3095
- const chunks = [];
3096
- for (let i = 0; i < files.length; i += filesPerThread) {
3097
- const chunk = files.slice(i, i + filesPerThread);
3098
- chunks.push(chunk);
3099
- }
3100
- debug?.(
3101
- `[%s] Running %s tests in %s chunks (%s threads)`,
3102
- project.name || "core",
3103
- files.length,
3104
- chunks.length,
3105
- threadsCount
3106
- );
3107
- const orchestrators = [...browser.state.orchestrators.entries()];
3108
- const promises = [];
3109
- chunks.forEach((files2, index) => {
3110
- if (orchestrators[index]) {
3111
- const [sessionId, orchestrator] = orchestrators[index];
3112
- debug?.(
3113
- "Reusing orchestrator (session %s) for files: %s",
3114
- sessionId,
3115
- [...files2.map((f) => relative(project.config.root, f))].join(", ")
3116
- );
3117
- const promise = waitForTests(method, sessionId, project, files2);
3118
- const tester = orchestrator.createTesters(files2).catch((error) => {
3119
- if (error instanceof Error && error.message.startsWith("[birpc] rpc is closed")) {
3120
- return;
3121
- }
3122
- return Promise.reject(error);
3123
- });
3124
- promises.push(promise, tester);
3125
- } else {
3126
- const sessionId = crypto.randomUUID();
3127
- const waitPromise = waitForTests(method, sessionId, project, files2);
3128
- debug?.(
3129
- "Opening a new session %s for files: %s",
3130
- sessionId,
3131
- [...files2.map((f) => relative(project.config.root, f))].join(", ")
3132
- );
3133
- const url = new URL("/", origin);
3134
- url.searchParams.set("sessionId", sessionId);
3135
- const page = provider.openPage(sessionId, url.toString(), () => setBreakpoint(sessionId, files2[0]));
3136
- promises.push(page, waitPromise);
3137
- }
3138
- });
3139
- await Promise.all(promises);
3140
- };
3141
- const runWorkspaceTests = async (method, specs) => {
3142
- const groupedFiles = /* @__PURE__ */ new Map();
3143
- for (const { project, moduleId } of specs) {
3144
- const files = groupedFiles.get(project) || [];
3145
- files.push(moduleId);
3146
- groupedFiles.set(project, files);
3147
- }
3148
- let isCancelled = false;
3149
- vitest.onCancel(() => {
3150
- isCancelled = true;
3151
- });
3152
- for (const [project, files] of groupedFiles.entries()) {
3153
- if (isCancelled) {
3154
- break;
3155
- }
3156
- await project._initBrowserProvider();
3157
- if (!project.browser) {
3158
- throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
3159
- }
3160
- await executeTests(method, project, files);
3161
- }
3162
- };
3163
- const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
3164
- function getThreadsCount(project) {
3165
- const config = project.config.browser;
3166
- if (!config.headless || !project.browser.provider.supportsParallelism) {
3167
- return 1;
3168
- }
3169
- if (!config.fileParallelism) {
3170
- return 1;
3171
- }
3172
- if (project.config.maxWorkers) {
3173
- return project.config.maxWorkers;
3174
- }
3175
- return vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
3176
- }
3177
- return {
3178
- name: "browser",
3179
- async close() {
3180
- await Promise.all([...providers].map((provider) => provider.close()));
3181
- providers.clear();
3182
- vitest.resolvedProjects.forEach((project) => {
3183
- project.browser?.state.orchestrators.forEach((orchestrator) => {
3184
- orchestrator.$close();
3185
- });
3186
- });
3187
- },
3188
- runTests: (files) => runWorkspaceTests("run", files),
3189
- collectTests: (files) => runWorkspaceTests("collect", files)
3190
- };
3130
+ const providers = new Set();
3131
+ const executeTests = async (method, project, files) => {
3132
+ vitest.state.clearFiles(project, files);
3133
+ const browser = project.browser;
3134
+ const threadsCount = getThreadsCount(project);
3135
+ const provider = browser.provider;
3136
+ providers.add(provider);
3137
+ const resolvedUrls = browser.vite.resolvedUrls;
3138
+ const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
3139
+ if (!origin) {
3140
+ throw new Error(`Can't find browser origin URL for project "${project.name}" when running tests for files "${files.join("\", \"")}"`);
3141
+ }
3142
+ async function setBreakpoint(sessionId, file) {
3143
+ if (!project.config.inspector.waitForDebugger) {
3144
+ return;
3145
+ }
3146
+ if (!provider.getCDPSession) {
3147
+ throw new Error("Unable to set breakpoint, CDP not supported");
3148
+ }
3149
+ const session = await provider.getCDPSession(sessionId);
3150
+ await session.send("Debugger.enable", {});
3151
+ await session.send("Debugger.setBreakpointByUrl", {
3152
+ lineNumber: 0,
3153
+ urlRegex: escapePathToRegexp(file)
3154
+ });
3155
+ }
3156
+ const filesPerThread = Math.ceil(files.length / threadsCount);
3157
+ const chunks = [];
3158
+ for (let i = 0; i < files.length; i += filesPerThread) {
3159
+ const chunk = files.slice(i, i + filesPerThread);
3160
+ chunks.push(chunk);
3161
+ }
3162
+ debug?.(`[%s] Running %s tests in %s chunks (%s threads)`, project.name || "core", files.length, chunks.length, threadsCount);
3163
+ const orchestrators = [...browser.state.orchestrators.entries()];
3164
+ const promises = [];
3165
+ chunks.forEach((files, index) => {
3166
+ if (orchestrators[index]) {
3167
+ const [sessionId, orchestrator] = orchestrators[index];
3168
+ debug?.("Reusing orchestrator (session %s) for files: %s", sessionId, [...files.map((f) => relative(project.config.root, f))].join(", "));
3169
+ const promise = waitForTests(method, sessionId, project, files);
3170
+ const tester = orchestrator.createTesters(files).catch((error) => {
3171
+ if (error instanceof Error && error.message.startsWith("[birpc] rpc is closed")) {
3172
+ return;
3173
+ }
3174
+ return Promise.reject(error);
3175
+ });
3176
+ promises.push(promise, tester);
3177
+ } else {
3178
+ const sessionId = crypto.randomUUID();
3179
+ const waitPromise = waitForTests(method, sessionId, project, files);
3180
+ debug?.("Opening a new session %s for files: %s", sessionId, [...files.map((f) => relative(project.config.root, f))].join(", "));
3181
+ const url = new URL("/", origin);
3182
+ url.searchParams.set("sessionId", sessionId);
3183
+ const page = provider.openPage(sessionId, url.toString(), () => setBreakpoint(sessionId, files[0]));
3184
+ promises.push(page, waitPromise);
3185
+ }
3186
+ });
3187
+ await Promise.all(promises);
3188
+ };
3189
+ const runWorkspaceTests = async (method, specs) => {
3190
+ const groupedFiles = new Map();
3191
+ for (const { project, moduleId } of specs) {
3192
+ const files = groupedFiles.get(project) || [];
3193
+ files.push(moduleId);
3194
+ groupedFiles.set(project, files);
3195
+ }
3196
+ let isCancelled = false;
3197
+ vitest.onCancel(() => {
3198
+ isCancelled = true;
3199
+ });
3200
+ for (const [project, files] of groupedFiles.entries()) {
3201
+ if (isCancelled) {
3202
+ break;
3203
+ }
3204
+ await project._initBrowserProvider();
3205
+ if (!project.browser) {
3206
+ throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
3207
+ }
3208
+ await executeTests(method, project, files);
3209
+ }
3210
+ };
3211
+ const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
3212
+ function getThreadsCount(project) {
3213
+ const config = project.config.browser;
3214
+ if (!config.headless || !project.browser.provider.supportsParallelism) {
3215
+ return 1;
3216
+ }
3217
+ if (!config.fileParallelism) {
3218
+ return 1;
3219
+ }
3220
+ if (project.config.maxWorkers) {
3221
+ return project.config.maxWorkers;
3222
+ }
3223
+ return vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
3224
+ }
3225
+ return {
3226
+ name: "browser",
3227
+ async close() {
3228
+ await Promise.all([...providers].map((provider) => provider.close()));
3229
+ providers.clear();
3230
+ vitest.projects.forEach((project) => {
3231
+ project.browser?.state.orchestrators.forEach((orchestrator) => {
3232
+ orchestrator.$close();
3233
+ });
3234
+ });
3235
+ },
3236
+ runTests: (files) => runWorkspaceTests("run", files),
3237
+ collectTests: (files) => runWorkspaceTests("collect", files)
3238
+ };
3191
3239
  }
3192
3240
  function escapePathToRegexp(path) {
3193
- return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
3241
+ return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
3194
3242
  }
3195
3243
 
3196
3244
  async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
3197
- if (project.vitest.version !== version) {
3198
- project.vitest.logger.warn(
3199
- c.yellow(
3200
- `Loaded ${c.inverse(c.yellow(` vitest@${project.vitest.version} `))} and ${c.inverse(c.yellow(` @vitest/browser@${version} `))}.
3201
- Running mixed versions is not supported and may lead into bugs
3202
- Update your dependencies and make sure the versions match.`
3203
- )
3204
- );
3205
- }
3206
- const server = new ParentBrowserProject(project, "/");
3207
- const configPath = typeof configFile === "string" ? configFile : false;
3208
- const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
3209
- const logger = createViteLogger(project.vitest.logger, logLevel, {
3210
- allowClearScreen: false
3211
- });
3212
- const vite = await createViteServer({
3213
- ...project.options,
3214
- // spread project config inlined in root workspace config
3215
- base: "/",
3216
- logLevel,
3217
- customLogger: {
3218
- ...logger,
3219
- info(msg, options) {
3220
- logger.info(msg, options);
3221
- if (msg.includes("optimized dependencies changed. reloading")) {
3222
- logger.warn(
3223
- [
3224
- c.yellow(`
3225
- ${c.bold("[vitest]")} Vite unexpectedly reloaded a test. This may cause tests to fail, lead to flaky behaviour or duplicated test runs.
3226
- `),
3227
- c.yellow(`For a stable experience, please add mentioned dependencies to your config's ${c.bold("`optimizeDeps.include`")} field manually.
3228
-
3229
- `)
3230
- ].join("")
3231
- );
3232
- }
3233
- }
3234
- },
3235
- mode: project.config.mode,
3236
- configFile: configPath,
3237
- // watch is handled by Vitest
3238
- server: {
3239
- hmr: false,
3240
- watch: null
3241
- },
3242
- plugins: [
3243
- ...prePlugins,
3244
- ...project.options?.plugins || [],
3245
- BrowserPlugin(server),
3246
- ...postPlugins
3247
- ]
3248
- });
3249
- await vite.listen();
3250
- setupBrowserRpc(server);
3251
- return server;
3245
+ if (project.vitest.version !== version) {
3246
+ project.vitest.logger.warn(c.yellow(`Loaded ${c.inverse(c.yellow(` vitest@${project.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."));
3247
+ }
3248
+ const server = new ParentBrowserProject(project, "/");
3249
+ const configPath = typeof configFile === "string" ? configFile : false;
3250
+ const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
3251
+ const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
3252
+ const vite = await createViteServer({
3253
+ ...project.options,
3254
+ base: "/",
3255
+ logLevel,
3256
+ customLogger: {
3257
+ ...logger,
3258
+ info(msg, options) {
3259
+ logger.info(msg, options);
3260
+ if (msg.includes("optimized dependencies changed. reloading")) {
3261
+ logger.warn([c.yellow(`\n${c.bold("[vitest]")} Vite unexpectedly reloaded a test. This may cause tests to fail, lead to flaky behaviour or duplicated test runs.\n`), c.yellow(`For a stable experience, please add mentioned dependencies to your config\'s ${c.bold("`optimizeDeps.include`")} field manually.\n\n`)].join(""));
3262
+ }
3263
+ }
3264
+ },
3265
+ mode: project.config.mode,
3266
+ configFile: configPath,
3267
+ server: {
3268
+ hmr: false,
3269
+ watch: null
3270
+ },
3271
+ plugins: [
3272
+ ...prePlugins,
3273
+ ...project.options?.plugins || [],
3274
+ BrowserPlugin(server),
3275
+ ...postPlugins
3276
+ ]
3277
+ });
3278
+ await vite.listen();
3279
+ setupBrowserRpc(server);
3280
+ return server;
3252
3281
  }
3253
3282
 
3254
3283
  export { createBrowserPool, createBrowserServer, distRoot };