@vitest/browser 4.0.0-beta.9 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +6 -14
  2. package/context.d.ts +86 -29
  3. package/context.js +3 -2
  4. package/dist/client/.vite/manifest.json +6 -6
  5. package/dist/client/__vitest__/assets/index-COTh6lXR.css +1 -0
  6. package/dist/client/__vitest__/assets/index-DOkKC3NI.js +53 -0
  7. package/dist/client/__vitest__/index.html +2 -2
  8. package/dist/client/__vitest_browser__/orchestrator-CFVVvVT1.js +313 -0
  9. package/dist/client/__vitest_browser__/tester-BNxij3za.js +2133 -0
  10. package/dist/client/__vitest_browser__/{utils-FY_Qin7d.js → utils-uxqdqUz8.js} +48 -24
  11. package/dist/client/orchestrator.html +2 -2
  12. package/dist/client/tester/tester.html +2 -2
  13. package/dist/client.js +1 -1
  14. package/dist/context.js +80 -19
  15. package/dist/expect-element.js +14 -14
  16. package/dist/index-BnLTaCRv.js +6 -0
  17. package/dist/index.d.ts +64 -165
  18. package/dist/index.js +572 -1431
  19. package/dist/{locators/index.d.ts → locators.d.ts} +27 -3
  20. package/dist/locators.js +1 -0
  21. package/dist/state.js +0 -1
  22. package/dist/types.d.ts +0 -1
  23. package/jest-dom.d.ts +5 -5
  24. package/matchers.d.ts +2 -2
  25. package/package.json +18 -54
  26. package/utils.d.ts +5 -5
  27. package/dist/client/__vitest__/assets/index-CBcuRGkf.js +0 -57
  28. package/dist/client/__vitest__/assets/index-KbpJLW--.css +0 -1
  29. package/dist/client/__vitest_browser__/orchestrator-C2rrmv36.js +0 -3198
  30. package/dist/client/__vitest_browser__/tester-mSVktQ7a.js +0 -3282
  31. package/dist/index-B7Hfmz-h.js +0 -1
  32. package/dist/locators/index.js +0 -1
  33. package/dist/locators/playwright.js +0 -1
  34. package/dist/locators/preview.js +0 -1
  35. package/dist/locators/webdriverio.js +0 -1
  36. package/dist/providers.js +0 -47
  37. package/dist/public-utils-Kx5DUGWa.js +0 -6
  38. package/dist/utils.js +0 -1
  39. package/dist/webdriver-AHRa6U3j.js +0 -517
  40. package/providers/playwright.d.ts +0 -97
  41. package/providers/webdriverio.d.ts +0 -35
  42. package/providers.d.ts +0 -7
package/dist/index.js CHANGED
@@ -1,28 +1,24 @@
1
1
  import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule, MockerRegistry } from '@vitest/mocker';
2
2
  import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '@vitest/mocker/node';
3
3
  import c from 'tinyrainbow';
4
- import { isValidApiRequest, isFileServingAllowed, distDir, resolveApiServerConfig, resolveFsAllow, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
4
+ import { isValidApiRequest, isFileServingAllowed, distDir, resolveApiServerConfig, resolveFsAllow, rolldownVersion, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import fs, { readFileSync, lstatSync, createReadStream, promises, existsSync } from 'node:fs';
6
7
  import { createRequire } from 'node:module';
7
- import { slash as slash$1, toArray, deepMerge, createDefer } from '@vitest/utils';
8
+ import { slash as slash$1, toArray, deepMerge } from '@vitest/utils/helpers';
8
9
  import MagicString from 'magic-string';
9
10
  import sirv from 'sirv';
10
- import * as vite from 'vite';
11
11
  import { coverageConfigDefaults } from 'vitest/config';
12
- import { fileURLToPath } from 'node:url';
13
12
  import crypto from 'node:crypto';
14
- import { mkdir, rm, readFile as readFile$1, writeFile as writeFile$1 } from 'node:fs/promises';
13
+ import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
15
14
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
16
- import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-AHRa6U3j.js';
17
- import { resolve as resolve$1, basename as basename$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
18
- import * as nodeos from 'node:os';
15
+ import { resolve as resolve$1, basename as basename$1, dirname as dirname$1 } from 'node:path';
19
16
  import { platform } from 'node:os';
20
17
  import { PNG } from 'pngjs';
21
18
  import pm from 'pixelmatch';
22
19
  import { WebSocketServer } from 'ws';
23
- import { performance } from 'node:perf_hooks';
24
20
 
25
- var version = "4.0.0-beta.9";
21
+ var version = "4.0.1";
26
22
 
27
23
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
28
24
  function normalizeWindowsPath(input = "") {
@@ -324,64 +320,383 @@ const stringify = (value, replacer, space) => {
324
320
  }
325
321
  };
326
322
 
327
- function replacer(code, values) {
328
- return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
329
- }
330
- const builtinProviders = [
331
- "webdriverio",
332
- "playwright",
333
- "preview"
323
+ var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
324
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
325
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
326
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
327
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
328
+ return DOM_KEY_LOCATION;
329
+ }({});
330
+
331
+ /**
332
+ * Mapping for a default US-104-QWERTY keyboard
333
+ */ const defaultKeyMap = [
334
+ // alphanumeric block - writing system
335
+ ...'0123456789'.split('').map((c)=>({
336
+ code: `Digit${c}`,
337
+ key: c
338
+ })),
339
+ ...')!@#$%^&*('.split('').map((c, i)=>({
340
+ code: `Digit${i}`,
341
+ key: c,
342
+ shiftKey: true
343
+ })),
344
+ ...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
345
+ code: `Key${c.toUpperCase()}`,
346
+ key: c
347
+ })),
348
+ ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
349
+ code: `Key${c}`,
350
+ key: c,
351
+ shiftKey: true
352
+ })),
353
+ {
354
+ code: 'BracketLeft',
355
+ key: '['
356
+ },
357
+ {
358
+ code: 'BracketLeft',
359
+ key: '{',
360
+ shiftKey: true
361
+ },
362
+ {
363
+ code: 'BracketRight',
364
+ key: ']'
365
+ },
366
+ {
367
+ code: 'BracketRight',
368
+ key: '}',
369
+ shiftKey: true
370
+ },
371
+ // alphanumeric block - functional
372
+ {
373
+ code: 'Space',
374
+ key: ' '
375
+ },
376
+ {
377
+ code: 'AltLeft',
378
+ key: 'Alt',
379
+ location: DOM_KEY_LOCATION.LEFT
380
+ },
381
+ {
382
+ code: 'AltRight',
383
+ key: 'Alt',
384
+ location: DOM_KEY_LOCATION.RIGHT
385
+ },
386
+ {
387
+ code: 'ShiftLeft',
388
+ key: 'Shift',
389
+ location: DOM_KEY_LOCATION.LEFT
390
+ },
391
+ {
392
+ code: 'ShiftRight',
393
+ key: 'Shift',
394
+ location: DOM_KEY_LOCATION.RIGHT
395
+ },
396
+ {
397
+ code: 'ControlLeft',
398
+ key: 'Control',
399
+ location: DOM_KEY_LOCATION.LEFT
400
+ },
401
+ {
402
+ code: 'ControlRight',
403
+ key: 'Control',
404
+ location: DOM_KEY_LOCATION.RIGHT
405
+ },
406
+ {
407
+ code: 'MetaLeft',
408
+ key: 'Meta',
409
+ location: DOM_KEY_LOCATION.LEFT
410
+ },
411
+ {
412
+ code: 'MetaRight',
413
+ key: 'Meta',
414
+ location: DOM_KEY_LOCATION.RIGHT
415
+ },
416
+ {
417
+ code: 'OSLeft',
418
+ key: 'OS',
419
+ location: DOM_KEY_LOCATION.LEFT
420
+ },
421
+ {
422
+ code: 'OSRight',
423
+ key: 'OS',
424
+ location: DOM_KEY_LOCATION.RIGHT
425
+ },
426
+ {
427
+ code: 'ContextMenu',
428
+ key: 'ContextMenu'
429
+ },
430
+ {
431
+ code: 'Tab',
432
+ key: 'Tab'
433
+ },
434
+ {
435
+ code: 'CapsLock',
436
+ key: 'CapsLock'
437
+ },
438
+ {
439
+ code: 'Backspace',
440
+ key: 'Backspace'
441
+ },
442
+ {
443
+ code: 'Enter',
444
+ key: 'Enter'
445
+ },
446
+ // function
447
+ {
448
+ code: 'Escape',
449
+ key: 'Escape'
450
+ },
451
+ // arrows
452
+ {
453
+ code: 'ArrowUp',
454
+ key: 'ArrowUp'
455
+ },
456
+ {
457
+ code: 'ArrowDown',
458
+ key: 'ArrowDown'
459
+ },
460
+ {
461
+ code: 'ArrowLeft',
462
+ key: 'ArrowLeft'
463
+ },
464
+ {
465
+ code: 'ArrowRight',
466
+ key: 'ArrowRight'
467
+ },
468
+ // control pad
469
+ {
470
+ code: 'Home',
471
+ key: 'Home'
472
+ },
473
+ {
474
+ code: 'End',
475
+ key: 'End'
476
+ },
477
+ {
478
+ code: 'Delete',
479
+ key: 'Delete'
480
+ },
481
+ {
482
+ code: 'PageUp',
483
+ key: 'PageUp'
484
+ },
485
+ {
486
+ code: 'PageDown',
487
+ key: 'PageDown'
488
+ },
489
+ // Special keys that are not part of a default US-layout but included for specific behavior
490
+ {
491
+ code: 'Fn',
492
+ key: 'Fn'
493
+ },
494
+ {
495
+ code: 'Symbol',
496
+ key: 'Symbol'
497
+ },
498
+ {
499
+ code: 'AltRight',
500
+ key: 'AltGraph'
501
+ }
334
502
  ];
335
- async function getBrowserProvider(options, project) {
336
- if (options.provider == null || builtinProviders.includes(options.provider)) {
337
- const providers = await import('./providers.js');
338
- const provider = options.provider || "preview";
339
- return providers[provider];
340
- }
341
- let customProviderModule;
342
- try {
343
- customProviderModule = await project.import(options.provider);
344
- } catch (error) {
345
- throw new Error(`Failed to load custom BrowserProvider from ${options.provider}`, { cause: error });
346
- }
347
- if (customProviderModule.default == null) {
348
- throw new Error(`Custom BrowserProvider loaded from ${options.provider} was not the default export`);
349
- }
350
- return customProviderModule.default;
503
+
504
+ var bracketDict = /*#__PURE__*/ function(bracketDict) {
505
+ bracketDict["{"] = "}";
506
+ bracketDict["["] = "]";
507
+ return bracketDict;
508
+ }(bracketDict || {});
509
+ /**
510
+ * Read the next key definition from user input
511
+ *
512
+ * Describe key per `{descriptor}` or `[descriptor]`.
513
+ * Everything else will be interpreted as a single character as descriptor - e.g. `a`.
514
+ * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
515
+ * A previously pressed key can be released per `{/descriptor}`.
516
+ * Keeping the key pressed can be written as `{descriptor>}`.
517
+ * When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
518
+ * You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
519
+ */ function readNextDescriptor(text, context) {
520
+ let pos = 0;
521
+ const startBracket = text[pos] in bracketDict ? text[pos] : '';
522
+ pos += startBracket.length;
523
+ const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
524
+ const type = isEscapedChar ? '' : startBracket;
525
+ return {
526
+ type,
527
+ ...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
528
+ };
351
529
  }
352
- function slash(path) {
353
- return path.replace(/\\/g, "/").replace(/\/+/g, "/");
530
+ function readPrintableChar(text, pos, context) {
531
+ const descriptor = text[pos];
532
+ assertDescriptor(descriptor, text, pos);
533
+ pos += descriptor.length;
534
+ return {
535
+ consumedLength: pos,
536
+ descriptor,
537
+ releasePrevious: false,
538
+ releaseSelf: true,
539
+ repeat: 1
540
+ };
354
541
  }
355
-
356
- async function resolveOrchestrator(globalServer, url, res) {
357
- let sessionId = url.searchParams.get("sessionId");
358
- // it's possible to open the page without a context
359
- if (!sessionId) {
360
- const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
361
- sessionId = contexts[contexts.length - 1] ?? "none";
362
- }
363
- // it's ok to not have a session here, especially in the preview provider
364
- // because the user could refresh the page which would remove the session id from the url
365
- const session = globalServer.vitest._browserSessions.getSession(sessionId);
366
- const browserProject = session?.project.browser || [...globalServer.children][0];
367
- if (!browserProject) {
368
- return;
369
- }
370
- // ignore unknown pages
371
- if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
372
- return;
373
- }
374
- const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
375
- const injector = replacer(injectorJs, {
376
- __VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider || "preview"),
377
- __VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
378
- __VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
379
- __VITEST_METHOD__: JSON.stringify("orchestrate"),
380
- __VITEST_TYPE__: "\"orchestrator\"",
381
- __VITEST_SESSION_ID__: JSON.stringify(sessionId),
382
- __VITEST_TESTER_ID__: "\"none\"",
383
- __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
384
- __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
542
+ function readTag(text, pos, startBracket, context) {
543
+ var _text_slice_match, _text_slice_match1;
544
+ const releasePreviousModifier = text[pos] === '/' ? '/' : '';
545
+ pos += releasePreviousModifier.length;
546
+ const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
547
+ pos += Number(escapedDescriptor);
548
+ const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === undefined ? undefined : _text_slice_match[0];
549
+ assertDescriptor(descriptor, text, pos);
550
+ pos += descriptor.length;
551
+ var _text_slice_match_;
552
+ const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === undefined ? undefined : _text_slice_match1[0]) !== null && _text_slice_match_ !== undefined ? _text_slice_match_ : '';
553
+ pos += repeatModifier.length;
554
+ const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
555
+ pos += releaseSelfModifier.length;
556
+ const expectedEndBracket = bracketDict[startBracket];
557
+ const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
558
+ if (!endBracket) {
559
+ throw new Error(getErrorMessage([
560
+ !repeatModifier && 'repeat modifier',
561
+ !releaseSelfModifier && 'release modifier',
562
+ `"${expectedEndBracket}"`
563
+ ].filter(Boolean).join(' or '), text[pos], text));
564
+ }
565
+ pos += endBracket.length;
566
+ return {
567
+ consumedLength: pos,
568
+ descriptor,
569
+ releasePrevious: !!releasePreviousModifier,
570
+ repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
571
+ releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
572
+ };
573
+ }
574
+ function assertDescriptor(descriptor, text, pos, context) {
575
+ if (!descriptor) {
576
+ throw new Error(getErrorMessage('key descriptor', text[pos], text));
577
+ }
578
+ }
579
+ function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
580
+ if (releaseSelfModifier) {
581
+ return releaseSelfModifier === '/';
582
+ }
583
+ if (repeatModifier) {
584
+ return false;
585
+ }
586
+ }
587
+ function getErrorMessage(expected, found, text, context) {
588
+ return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
589
+ See ${`https://testing-library.com/docs/user-event/keyboard`}
590
+ for more information about how userEvent parses your input.`;
591
+ }
592
+
593
+ /**
594
+ * Parse key definitions per `keyboardMap`
595
+ *
596
+ * Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
597
+ * Everything else will be interpreted as a typed character - e.g. `a`.
598
+ * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
599
+ * Keeping the key pressed can be written as `{key>}`.
600
+ * When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
601
+ * You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
602
+ */ function parseKeyDef$1(keyboardMap, text) {
603
+ const defs = [];
604
+ do {
605
+ const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
606
+ var _keyboardMap_find;
607
+ const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
608
+ if (type === '[') {
609
+ var _def_code;
610
+ return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
611
+ } else if (type === '{') {
612
+ var _def_key;
613
+ return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
614
+ }
615
+ return def.key === descriptor;
616
+ })) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
617
+ key: 'Unknown',
618
+ code: 'Unknown',
619
+ [type === '[' ? 'code' : 'key']: descriptor
620
+ };
621
+ defs.push({
622
+ keyDef,
623
+ releasePrevious,
624
+ releaseSelf,
625
+ repeat
626
+ });
627
+ text = text.slice(consumedLength);
628
+ }while (text)
629
+ return defs;
630
+ }
631
+
632
+ function parseKeyDef(text) {
633
+ return parseKeyDef$1(defaultKeyMap, text);
634
+ }
635
+ function replacer(code, values) {
636
+ return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
637
+ }
638
+ function resolveScreenshotPath(testPath, name, config, customPath) {
639
+ if (customPath) {
640
+ return resolve(dirname(testPath), customPath);
641
+ }
642
+ const dir = dirname(testPath);
643
+ const base = basename(testPath);
644
+ if (config.browser.screenshotDirectory) {
645
+ return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
646
+ }
647
+ return resolve(dir, "__screenshots__", base, name);
648
+ }
649
+ async function getBrowserProvider(options, project) {
650
+ const browser = project.config.browser.name;
651
+ const name = project.name ? `[${project.name}] ` : "";
652
+ if (!browser) {
653
+ throw new Error(`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`);
654
+ }
655
+ if (options.provider == null) {
656
+ throw new Error(`Browser Mode requires the "provider" to always be specified.`);
657
+ }
658
+ const supportedBrowsers = options.provider.supportedBrowser || [];
659
+ if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
660
+ throw new Error(`${name}Browser "${browser}" is not supported by the browser provider "${options.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`);
661
+ }
662
+ if (typeof options.provider.providerFactory !== "function") {
663
+ throw new TypeError(`The "${name}" browser provider does not provide a "providerFactory" function. Received ${typeof options.provider.providerFactory}.`);
664
+ }
665
+ return options.provider.providerFactory(project);
666
+ }
667
+ function slash(path) {
668
+ return path.replace(/\\/g, "/").replace(/\/+/g, "/");
669
+ }
670
+
671
+ async function resolveOrchestrator(globalServer, url, res) {
672
+ let sessionId = url.searchParams.get("sessionId");
673
+ // it's possible to open the page without a context
674
+ if (!sessionId) {
675
+ const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
676
+ sessionId = contexts.at(-1) ?? "none";
677
+ }
678
+ // it's ok to not have a session here, especially in the preview provider
679
+ // because the user could refresh the page which would remove the session id from the url
680
+ const session = globalServer.vitest._browserSessions.getSession(sessionId);
681
+ const browserProject = session?.project.browser || [...globalServer.children][0];
682
+ if (!browserProject) {
683
+ return;
684
+ }
685
+ // ignore unknown pages
686
+ if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
687
+ return;
688
+ }
689
+ const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
690
+ const injector = replacer(injectorJs, {
691
+ __VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider?.name || "preview"),
692
+ __VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
693
+ __VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
694
+ __VITEST_METHOD__: JSON.stringify("orchestrate"),
695
+ __VITEST_TYPE__: "\"orchestrator\"",
696
+ __VITEST_SESSION_ID__: JSON.stringify(sessionId),
697
+ __VITEST_TESTER_ID__: "\"none\"",
698
+ __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
699
+ __VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
385
700
  });
386
701
  // disable CSP for the orchestrator as we are the ones controlling it
387
702
  res.removeHeader("Content-Security-Policy");
@@ -513,36 +828,58 @@ function createTesterMiddleware(browserServer) {
513
828
  };
514
829
  }
515
830
 
516
- const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
517
- const ID_CONTEXT = "@vitest/browser/context";
831
+ const VIRTUAL_ID_CONTEXT = "\0vitest/browser";
832
+ const ID_CONTEXT = "vitest/browser";
833
+ // for libraries that use an older import but are not type checked
834
+ const DEPRECATED_ID_CONTEXT = "@vitest/browser/context";
835
+ const DEPRECATED_VIRTUAL_ID_UTILS = "\0@vitest/browser/utils";
836
+ const DEPRECATED_ID_UTILS = "@vitest/browser/utils";
518
837
  const __dirname = dirname(fileURLToPath(import.meta.url));
519
838
  function BrowserContext(globalServer) {
520
839
  return {
521
840
  name: "vitest:browser:virtual-module:context",
522
841
  enforce: "pre",
523
- resolveId(id) {
842
+ resolveId(id, importer) {
524
843
  if (id === ID_CONTEXT) {
525
844
  return VIRTUAL_ID_CONTEXT;
526
845
  }
846
+ if (id === DEPRECATED_ID_CONTEXT) {
847
+ if (importer) {
848
+ globalServer.vitest.logger.deprecate(`${importer} tries to load a deprecated "${id}" module. ` + `This import will stop working in the next major version. ` + `Please, use "vitest/browser" instead.`);
849
+ }
850
+ return VIRTUAL_ID_CONTEXT;
851
+ }
852
+ if (id === DEPRECATED_ID_UTILS) {
853
+ return DEPRECATED_VIRTUAL_ID_UTILS;
854
+ }
527
855
  },
528
856
  load(id) {
529
857
  if (id === VIRTUAL_ID_CONTEXT) {
530
858
  return generateContextFile.call(this, globalServer);
531
859
  }
860
+ if (id === DEPRECATED_VIRTUAL_ID_UTILS) {
861
+ return `
862
+ import { utils } from 'vitest/browser'
863
+ export const getElementLocatorSelectors = utils.getElementLocatorSelectors
864
+ export const debug = utils.debug
865
+ export const prettyDOM = utils.prettyDOM
866
+ export const getElementError = utils.getElementError
867
+ `;
868
+ }
532
869
  }
533
870
  };
534
871
  }
535
872
  async function generateContextFile(globalServer) {
536
873
  const commands = Object.keys(globalServer.commands);
537
- const provider = [...globalServer.children][0].provider || { name: "preview" };
538
- const providerName = provider.name;
874
+ const provider = [...globalServer.children][0].provider;
875
+ const providerName = provider?.name || "preview";
539
876
  const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
540
877
  return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
541
878
  }).join("\n");
542
- const userEventNonProviderImport = await getUserEventImport(providerName, this.resolve.bind(this));
879
+ const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
543
880
  const distContextPath = slash$1(`/@fs/${resolve(__dirname, "context.js")}`);
544
881
  return `
545
- import { page, createUserEvent, cdp, locators } from '${distContextPath}'
882
+ import { page, createUserEvent, cdp, locators, utils } from '${distContextPath}'
546
883
  ${userEventNonProviderImport}
547
884
 
548
885
  export const server = {
@@ -557,16 +894,17 @@ export const server = {
557
894
  }
558
895
  export const commands = server.commands
559
896
  export const userEvent = createUserEvent(_userEventSetup)
560
- export { page, cdp, locators }
897
+ export { page, cdp, locators, utils }
561
898
  `;
562
899
  }
563
900
  async function getUserEventImport(provider, resolve) {
564
- if (provider !== "preview") {
901
+ if (!provider || provider.name !== "preview") {
565
902
  return "const _userEventSetup = undefined";
566
903
  }
567
- const resolved = await resolve("@testing-library/user-event", __dirname);
904
+ const previewDistRoot = provider.distRoot;
905
+ const resolved = await resolve("@testing-library/user-event", previewDistRoot);
568
906
  if (!resolved) {
569
- throw new Error(`Failed to resolve user-event package from ${__dirname}`);
907
+ throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
570
908
  }
571
909
  return `\
572
910
  import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
@@ -727,9 +1065,12 @@ var BrowserPlugin = (parentServer, base = "/") => {
727
1065
  ];
728
1066
  const exclude = [
729
1067
  "vitest",
1068
+ "vitest/browser",
730
1069
  "vitest/internal/browser",
731
1070
  "vitest/runners",
732
- "@vitest/browser",
1071
+ "vite/module-runner",
1072
+ "@vitest/browser/utils",
1073
+ "@vitest/browser/context",
733
1074
  "@vitest/browser/client",
734
1075
  "@vitest/utils",
735
1076
  "@vitest/utils/source-map",
@@ -771,13 +1112,12 @@ var BrowserPlugin = (parentServer, base = "/") => {
771
1112
  const include = [
772
1113
  "vitest > expect-type",
773
1114
  "vitest > @vitest/snapshot > magic-string",
774
- "vitest > @vitest/runner > strip-literal",
775
- "vitest > @vitest/expect > chai",
776
- "vitest > @vitest/expect > chai > loupe",
777
- "vitest > @vitest/utils > loupe",
778
- "@vitest/browser > @testing-library/user-event",
779
- "@vitest/browser > @testing-library/dom"
1115
+ "vitest > @vitest/expect > chai"
780
1116
  ];
1117
+ const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
1118
+ if (provider?.name === "preview") {
1119
+ include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
1120
+ }
781
1121
  const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
782
1122
  const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
783
1123
  if (svelte) {
@@ -991,20 +1331,21 @@ body {
991
1331
  },
992
1332
  injectTo: "head"
993
1333
  },
994
- parentServer.locatorsUrl ? {
1334
+ ...parentServer.initScripts.map((script) => ({
995
1335
  tag: "script",
996
1336
  attrs: {
997
1337
  type: "module",
998
- src: parentServer.locatorsUrl
1338
+ src: join("/@fs/", script)
999
1339
  },
1000
1340
  injectTo: "head"
1001
- } : null,
1341
+ })),
1002
1342
  ...testerTags
1003
1343
  ].filter((s) => s != null);
1004
1344
  }
1005
1345
  },
1006
1346
  {
1007
1347
  name: "vitest:browser:support-testing-library",
1348
+ enforce: "pre",
1008
1349
  config() {
1009
1350
  const rolldownPlugin = {
1010
1351
  name: "vue-test-utils-rewrite",
@@ -1027,7 +1368,7 @@ body {
1027
1368
  });
1028
1369
  }
1029
1370
  };
1030
- return { optimizeDeps: "rolldownVersion" in vite ? { rollupOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
1371
+ return { optimizeDeps: rolldownVersion ? { rollupOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
1031
1372
  }
1032
1373
  }
1033
1374
  ];
@@ -1112,114 +1453,6 @@ class BrowserServerCDPHandler {
1112
1453
  }
1113
1454
  }
1114
1455
 
1115
- const clear = async (context, selector) => {
1116
- if (context.provider instanceof PlaywrightBrowserProvider) {
1117
- const { iframe } = context;
1118
- const element = iframe.locator(selector);
1119
- await element.clear();
1120
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1121
- const browser = context.browser;
1122
- const element = await browser.$(selector);
1123
- await element.clearValue();
1124
- } else {
1125
- throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1126
- }
1127
- };
1128
-
1129
- const click = async (context, selector, options = {}) => {
1130
- const provider = context.provider;
1131
- if (provider instanceof PlaywrightBrowserProvider) {
1132
- const tester = context.iframe;
1133
- await tester.locator(selector).click(options);
1134
- } else if (provider instanceof WebdriverBrowserProvider) {
1135
- const browser = context.browser;
1136
- await browser.$(selector).click(options);
1137
- } else {
1138
- throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
1139
- }
1140
- };
1141
- const dblClick = async (context, selector, options = {}) => {
1142
- const provider = context.provider;
1143
- if (provider instanceof PlaywrightBrowserProvider) {
1144
- const tester = context.iframe;
1145
- await tester.locator(selector).dblclick(options);
1146
- } else if (provider instanceof WebdriverBrowserProvider) {
1147
- const browser = context.browser;
1148
- await browser.$(selector).doubleClick();
1149
- } else {
1150
- throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
1151
- }
1152
- };
1153
- const tripleClick = async (context, selector, options = {}) => {
1154
- const provider = context.provider;
1155
- if (provider instanceof PlaywrightBrowserProvider) {
1156
- const tester = context.iframe;
1157
- await tester.locator(selector).click({
1158
- ...options,
1159
- clickCount: 3
1160
- });
1161
- } else if (provider instanceof WebdriverBrowserProvider) {
1162
- const browser = context.browser;
1163
- await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
1164
- } else {
1165
- throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
1166
- }
1167
- };
1168
-
1169
- const dragAndDrop = async (context, source, target, options_) => {
1170
- if (context.provider instanceof PlaywrightBrowserProvider) {
1171
- const frame = await context.frame();
1172
- await frame.dragAndDrop(source, target, options_);
1173
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1174
- const $source = context.browser.$(source);
1175
- const $target = context.browser.$(target);
1176
- const options = options_ || {};
1177
- const duration = options.duration ?? 10;
1178
- // https://github.com/webdriverio/webdriverio/issues/8022#issuecomment-1700919670
1179
- await context.browser.action("pointer").move({
1180
- duration: 0,
1181
- origin: $source,
1182
- x: options.sourceX ?? 0,
1183
- y: options.sourceY ?? 0
1184
- }).down({ button: 0 }).move({
1185
- duration: 0,
1186
- origin: "pointer",
1187
- x: 0,
1188
- y: 0
1189
- }).pause(duration).move({
1190
- duration: 0,
1191
- origin: $target,
1192
- x: options.targetX ?? 0,
1193
- y: options.targetY ?? 0
1194
- }).move({
1195
- duration: 0,
1196
- origin: "pointer",
1197
- x: 1,
1198
- y: 0
1199
- }).move({
1200
- duration: 0,
1201
- origin: "pointer",
1202
- x: -1,
1203
- y: 0
1204
- }).up({ button: 0 }).perform();
1205
- } else {
1206
- throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
1207
- }
1208
- };
1209
-
1210
- const fill = async (context, selector, text, options = {}) => {
1211
- if (context.provider instanceof PlaywrightBrowserProvider) {
1212
- const { iframe } = context;
1213
- const element = iframe.locator(selector);
1214
- await element.fill(text, options);
1215
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1216
- const browser = context.browser;
1217
- await browser.$(selector).setValue(text);
1218
- } else {
1219
- throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`);
1220
- }
1221
- };
1222
-
1223
1456
  const types = {
1224
1457
  'application/andrew-inset': ['ez'],
1225
1458
  'application/appinstaller': ['appinstaller'],
@@ -1638,818 +1871,87 @@ class Mime {
1638
1871
  const hasDot = ext.length < last.length - 1;
1639
1872
  if (!hasDot && hasPath)
1640
1873
  return null;
1641
- return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
1642
- }
1643
- getExtension(type) {
1644
- if (typeof type !== 'string')
1645
- return null;
1646
- type = type?.split?.(';')[0];
1647
- return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, "f").get(type.trim().toLowerCase())) ?? null);
1648
- }
1649
- getAllExtensions(type) {
1650
- if (typeof type !== 'string')
1651
- return null;
1652
- return __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type.toLowerCase()) ?? null;
1653
- }
1654
- _freeze() {
1655
- this.define = () => {
1656
- throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');
1657
- };
1658
- Object.freeze(this);
1659
- for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").values()) {
1660
- Object.freeze(extensions);
1661
- }
1662
- return this;
1663
- }
1664
- _getTestState() {
1665
- return {
1666
- types: __classPrivateFieldGet(this, _Mime_extensionToType, "f"),
1667
- extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, "f"),
1668
- };
1669
- }
1670
- }
1671
- _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
1672
-
1673
- var mime = new Mime(types)._freeze();
1674
-
1675
- function assertFileAccess(path, project) {
1676
- if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
1677
- throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
1678
- }
1679
- }
1680
- const readFile = async ({ project }, path, options = {}) => {
1681
- const filepath = resolve$1(project.config.root, path);
1682
- assertFileAccess(filepath, project);
1683
- // never return a Buffer
1684
- if (typeof options === "object" && !options.encoding) {
1685
- options.encoding = "utf-8";
1686
- }
1687
- return promises.readFile(filepath, options);
1688
- };
1689
- const writeFile = async ({ project }, path, data, options) => {
1690
- const filepath = resolve$1(project.config.root, path);
1691
- assertFileAccess(filepath, project);
1692
- const dir = dirname$1(filepath);
1693
- if (!fs.existsSync(dir)) {
1694
- await promises.mkdir(dir, { recursive: true });
1695
- }
1696
- await promises.writeFile(filepath, data, options);
1697
- };
1698
- const removeFile = async ({ project }, path) => {
1699
- const filepath = resolve$1(project.config.root, path);
1700
- assertFileAccess(filepath, project);
1701
- await promises.rm(filepath);
1702
- };
1703
- const _fileInfo = async ({ project }, path, encoding) => {
1704
- const filepath = resolve$1(project.config.root, path);
1705
- assertFileAccess(filepath, project);
1706
- const content = await promises.readFile(filepath, encoding || "base64");
1707
- return {
1708
- content,
1709
- basename: basename$1(filepath),
1710
- mime: mime.getType(filepath)
1711
- };
1712
- };
1713
-
1714
- const hover = async (context, selector, options = {}) => {
1715
- if (context.provider instanceof PlaywrightBrowserProvider) {
1716
- await context.iframe.locator(selector).hover(options);
1717
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1718
- const browser = context.browser;
1719
- await browser.$(selector).moveTo(options);
1720
- } else {
1721
- throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
1722
- }
1723
- };
1724
-
1725
- var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
1726
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
1727
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
1728
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
1729
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
1730
- return DOM_KEY_LOCATION;
1731
- }({});
1732
-
1733
- /**
1734
- * Mapping for a default US-104-QWERTY keyboard
1735
- */ const defaultKeyMap = [
1736
- // alphanumeric block - writing system
1737
- ...'0123456789'.split('').map((c)=>({
1738
- code: `Digit${c}`,
1739
- key: c
1740
- })),
1741
- ...')!@#$%^&*('.split('').map((c, i)=>({
1742
- code: `Digit${i}`,
1743
- key: c,
1744
- shiftKey: true
1745
- })),
1746
- ...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
1747
- code: `Key${c.toUpperCase()}`,
1748
- key: c
1749
- })),
1750
- ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
1751
- code: `Key${c}`,
1752
- key: c,
1753
- shiftKey: true
1754
- })),
1755
- {
1756
- code: 'BracketLeft',
1757
- key: '['
1758
- },
1759
- {
1760
- code: 'BracketLeft',
1761
- key: '{',
1762
- shiftKey: true
1763
- },
1764
- {
1765
- code: 'BracketRight',
1766
- key: ']'
1767
- },
1768
- {
1769
- code: 'BracketRight',
1770
- key: '}',
1771
- shiftKey: true
1772
- },
1773
- // alphanumeric block - functional
1774
- {
1775
- code: 'Space',
1776
- key: ' '
1777
- },
1778
- {
1779
- code: 'AltLeft',
1780
- key: 'Alt',
1781
- location: DOM_KEY_LOCATION.LEFT
1782
- },
1783
- {
1784
- code: 'AltRight',
1785
- key: 'Alt',
1786
- location: DOM_KEY_LOCATION.RIGHT
1787
- },
1788
- {
1789
- code: 'ShiftLeft',
1790
- key: 'Shift',
1791
- location: DOM_KEY_LOCATION.LEFT
1792
- },
1793
- {
1794
- code: 'ShiftRight',
1795
- key: 'Shift',
1796
- location: DOM_KEY_LOCATION.RIGHT
1797
- },
1798
- {
1799
- code: 'ControlLeft',
1800
- key: 'Control',
1801
- location: DOM_KEY_LOCATION.LEFT
1802
- },
1803
- {
1804
- code: 'ControlRight',
1805
- key: 'Control',
1806
- location: DOM_KEY_LOCATION.RIGHT
1807
- },
1808
- {
1809
- code: 'MetaLeft',
1810
- key: 'Meta',
1811
- location: DOM_KEY_LOCATION.LEFT
1812
- },
1813
- {
1814
- code: 'MetaRight',
1815
- key: 'Meta',
1816
- location: DOM_KEY_LOCATION.RIGHT
1817
- },
1818
- {
1819
- code: 'OSLeft',
1820
- key: 'OS',
1821
- location: DOM_KEY_LOCATION.LEFT
1822
- },
1823
- {
1824
- code: 'OSRight',
1825
- key: 'OS',
1826
- location: DOM_KEY_LOCATION.RIGHT
1827
- },
1828
- {
1829
- code: 'ContextMenu',
1830
- key: 'ContextMenu'
1831
- },
1832
- {
1833
- code: 'Tab',
1834
- key: 'Tab'
1835
- },
1836
- {
1837
- code: 'CapsLock',
1838
- key: 'CapsLock'
1839
- },
1840
- {
1841
- code: 'Backspace',
1842
- key: 'Backspace'
1843
- },
1844
- {
1845
- code: 'Enter',
1846
- key: 'Enter'
1847
- },
1848
- // function
1849
- {
1850
- code: 'Escape',
1851
- key: 'Escape'
1852
- },
1853
- // arrows
1854
- {
1855
- code: 'ArrowUp',
1856
- key: 'ArrowUp'
1857
- },
1858
- {
1859
- code: 'ArrowDown',
1860
- key: 'ArrowDown'
1861
- },
1862
- {
1863
- code: 'ArrowLeft',
1864
- key: 'ArrowLeft'
1865
- },
1866
- {
1867
- code: 'ArrowRight',
1868
- key: 'ArrowRight'
1869
- },
1870
- // control pad
1871
- {
1872
- code: 'Home',
1873
- key: 'Home'
1874
- },
1875
- {
1876
- code: 'End',
1877
- key: 'End'
1878
- },
1879
- {
1880
- code: 'Delete',
1881
- key: 'Delete'
1882
- },
1883
- {
1884
- code: 'PageUp',
1885
- key: 'PageUp'
1886
- },
1887
- {
1888
- code: 'PageDown',
1889
- key: 'PageDown'
1890
- },
1891
- // Special keys that are not part of a default US-layout but included for specific behavior
1892
- {
1893
- code: 'Fn',
1894
- key: 'Fn'
1895
- },
1896
- {
1897
- code: 'Symbol',
1898
- key: 'Symbol'
1899
- },
1900
- {
1901
- code: 'AltRight',
1902
- key: 'AltGraph'
1903
- }
1904
- ];
1905
-
1906
- var bracketDict = /*#__PURE__*/ function(bracketDict) {
1907
- bracketDict["{"] = "}";
1908
- bracketDict["["] = "]";
1909
- return bracketDict;
1910
- }(bracketDict || {});
1911
- /**
1912
- * Read the next key definition from user input
1913
- *
1914
- * Describe key per `{descriptor}` or `[descriptor]`.
1915
- * Everything else will be interpreted as a single character as descriptor - e.g. `a`.
1916
- * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
1917
- * A previously pressed key can be released per `{/descriptor}`.
1918
- * Keeping the key pressed can be written as `{descriptor>}`.
1919
- * When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
1920
- * You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
1921
- */ function readNextDescriptor(text, context) {
1922
- let pos = 0;
1923
- const startBracket = text[pos] in bracketDict ? text[pos] : '';
1924
- pos += startBracket.length;
1925
- const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
1926
- const type = isEscapedChar ? '' : startBracket;
1927
- return {
1928
- type,
1929
- ...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
1930
- };
1931
- }
1932
- function readPrintableChar(text, pos, context) {
1933
- const descriptor = text[pos];
1934
- assertDescriptor(descriptor, text, pos);
1935
- pos += descriptor.length;
1936
- return {
1937
- consumedLength: pos,
1938
- descriptor,
1939
- releasePrevious: false,
1940
- releaseSelf: true,
1941
- repeat: 1
1942
- };
1943
- }
1944
- function readTag(text, pos, startBracket, context) {
1945
- var _text_slice_match, _text_slice_match1;
1946
- const releasePreviousModifier = text[pos] === '/' ? '/' : '';
1947
- pos += releasePreviousModifier.length;
1948
- const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
1949
- pos += Number(escapedDescriptor);
1950
- const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === undefined ? undefined : _text_slice_match[0];
1951
- assertDescriptor(descriptor, text, pos);
1952
- pos += descriptor.length;
1953
- var _text_slice_match_;
1954
- const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === undefined ? undefined : _text_slice_match1[0]) !== null && _text_slice_match_ !== undefined ? _text_slice_match_ : '';
1955
- pos += repeatModifier.length;
1956
- const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
1957
- pos += releaseSelfModifier.length;
1958
- const expectedEndBracket = bracketDict[startBracket];
1959
- const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
1960
- if (!endBracket) {
1961
- throw new Error(getErrorMessage([
1962
- !repeatModifier && 'repeat modifier',
1963
- !releaseSelfModifier && 'release modifier',
1964
- `"${expectedEndBracket}"`
1965
- ].filter(Boolean).join(' or '), text[pos], text));
1966
- }
1967
- pos += endBracket.length;
1968
- return {
1969
- consumedLength: pos,
1970
- descriptor,
1971
- releasePrevious: !!releasePreviousModifier,
1972
- repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
1973
- releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
1974
- };
1975
- }
1976
- function assertDescriptor(descriptor, text, pos, context) {
1977
- if (!descriptor) {
1978
- throw new Error(getErrorMessage('key descriptor', text[pos], text));
1979
- }
1980
- }
1981
- function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
1982
- if (releaseSelfModifier) {
1983
- return releaseSelfModifier === '/';
1984
- }
1985
- if (repeatModifier) {
1986
- return false;
1987
- }
1988
- }
1989
- function getErrorMessage(expected, found, text, context) {
1990
- return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
1991
- See ${`https://testing-library.com/docs/user-event/keyboard`}
1992
- for more information about how userEvent parses your input.`;
1993
- }
1994
-
1995
- /**
1996
- * Parse key definitions per `keyboardMap`
1997
- *
1998
- * Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
1999
- * Everything else will be interpreted as a typed character - e.g. `a`.
2000
- * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
2001
- * Keeping the key pressed can be written as `{key>}`.
2002
- * When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
2003
- * You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
2004
- */ function parseKeyDef(keyboardMap, text) {
2005
- const defs = [];
2006
- do {
2007
- const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
2008
- var _keyboardMap_find;
2009
- const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
2010
- if (type === '[') {
2011
- var _def_code;
2012
- return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
2013
- } else if (type === '{') {
2014
- var _def_key;
2015
- return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
2016
- }
2017
- return def.key === descriptor;
2018
- })) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
2019
- key: 'Unknown',
2020
- code: 'Unknown',
2021
- [type === '[' ? 'code' : 'key']: descriptor
1874
+ return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
1875
+ }
1876
+ getExtension(type) {
1877
+ if (typeof type !== 'string')
1878
+ return null;
1879
+ type = type?.split?.(';')[0];
1880
+ return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, "f").get(type.trim().toLowerCase())) ?? null);
1881
+ }
1882
+ getAllExtensions(type) {
1883
+ if (typeof type !== 'string')
1884
+ return null;
1885
+ return __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type.toLowerCase()) ?? null;
1886
+ }
1887
+ _freeze() {
1888
+ this.define = () => {
1889
+ throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');
2022
1890
  };
2023
- defs.push({
2024
- keyDef,
2025
- releasePrevious,
2026
- releaseSelf,
2027
- repeat
2028
- });
2029
- text = text.slice(consumedLength);
2030
- }while (text)
2031
- return defs;
1891
+ Object.freeze(this);
1892
+ for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").values()) {
1893
+ Object.freeze(extensions);
1894
+ }
1895
+ return this;
1896
+ }
1897
+ _getTestState() {
1898
+ return {
1899
+ types: __classPrivateFieldGet(this, _Mime_extensionToType, "f"),
1900
+ extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, "f"),
1901
+ };
1902
+ }
2032
1903
  }
1904
+ _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
2033
1905
 
2034
- const keyboard = async (context, text, state) => {
2035
- if (context.provider instanceof PlaywrightBrowserProvider) {
2036
- const frame = await context.frame();
2037
- await frame.evaluate(focusIframe);
2038
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2039
- await context.browser.execute(focusIframe);
2040
- }
2041
- const pressed = new Set(state.unreleased);
2042
- await keyboardImplementation(pressed, context.provider, context.sessionId, text, async () => {
2043
- if (context.provider instanceof PlaywrightBrowserProvider) {
2044
- const frame = await context.frame();
2045
- await frame.evaluate(selectAll);
2046
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2047
- await context.browser.execute(selectAll);
2048
- } else {
2049
- throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
2050
- }
2051
- }, true);
2052
- return { unreleased: Array.from(pressed) };
2053
- };
2054
- const keyboardCleanup = async (context, state) => {
2055
- const { provider, sessionId } = context;
2056
- if (!state.unreleased) {
2057
- return;
2058
- }
2059
- if (provider instanceof PlaywrightBrowserProvider) {
2060
- const page = provider.getPage(sessionId);
2061
- for (const key of state.unreleased) {
2062
- await page.keyboard.up(key);
2063
- }
2064
- } else if (provider instanceof WebdriverBrowserProvider) {
2065
- const keyboard = provider.browser.action("key");
2066
- for (const key of state.unreleased) {
2067
- keyboard.up(key);
2068
- }
2069
- await keyboard.perform();
2070
- } else {
2071
- throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`);
2072
- }
2073
- };
2074
- // fallback to insertText for non US key
2075
- // https://github.com/microsoft/playwright/blob/50775698ae13642742f2a1e8983d1d686d7f192d/packages/playwright-core/src/server/input.ts#L95
2076
- const VALID_KEYS = new Set([
2077
- "Escape",
2078
- "F1",
2079
- "F2",
2080
- "F3",
2081
- "F4",
2082
- "F5",
2083
- "F6",
2084
- "F7",
2085
- "F8",
2086
- "F9",
2087
- "F10",
2088
- "F11",
2089
- "F12",
2090
- "Backquote",
2091
- "`",
2092
- "~",
2093
- "Digit1",
2094
- "1",
2095
- "!",
2096
- "Digit2",
2097
- "2",
2098
- "@",
2099
- "Digit3",
2100
- "3",
2101
- "#",
2102
- "Digit4",
2103
- "4",
2104
- "$",
2105
- "Digit5",
2106
- "5",
2107
- "%",
2108
- "Digit6",
2109
- "6",
2110
- "^",
2111
- "Digit7",
2112
- "7",
2113
- "&",
2114
- "Digit8",
2115
- "8",
2116
- "*",
2117
- "Digit9",
2118
- "9",
2119
- "(",
2120
- "Digit0",
2121
- "0",
2122
- ")",
2123
- "Minus",
2124
- "-",
2125
- "_",
2126
- "Equal",
2127
- "=",
2128
- "+",
2129
- "Backslash",
2130
- "\\",
2131
- "|",
2132
- "Backspace",
2133
- "Tab",
2134
- "KeyQ",
2135
- "q",
2136
- "Q",
2137
- "KeyW",
2138
- "w",
2139
- "W",
2140
- "KeyE",
2141
- "e",
2142
- "E",
2143
- "KeyR",
2144
- "r",
2145
- "R",
2146
- "KeyT",
2147
- "t",
2148
- "T",
2149
- "KeyY",
2150
- "y",
2151
- "Y",
2152
- "KeyU",
2153
- "u",
2154
- "U",
2155
- "KeyI",
2156
- "i",
2157
- "I",
2158
- "KeyO",
2159
- "o",
2160
- "O",
2161
- "KeyP",
2162
- "p",
2163
- "P",
2164
- "BracketLeft",
2165
- "[",
2166
- "{",
2167
- "BracketRight",
2168
- "]",
2169
- "}",
2170
- "CapsLock",
2171
- "KeyA",
2172
- "a",
2173
- "A",
2174
- "KeyS",
2175
- "s",
2176
- "S",
2177
- "KeyD",
2178
- "d",
2179
- "D",
2180
- "KeyF",
2181
- "f",
2182
- "F",
2183
- "KeyG",
2184
- "g",
2185
- "G",
2186
- "KeyH",
2187
- "h",
2188
- "H",
2189
- "KeyJ",
2190
- "j",
2191
- "J",
2192
- "KeyK",
2193
- "k",
2194
- "K",
2195
- "KeyL",
2196
- "l",
2197
- "L",
2198
- "Semicolon",
2199
- ";",
2200
- ":",
2201
- "Quote",
2202
- "'",
2203
- "\"",
2204
- "Enter",
2205
- "\n",
2206
- "\r",
2207
- "ShiftLeft",
2208
- "Shift",
2209
- "KeyZ",
2210
- "z",
2211
- "Z",
2212
- "KeyX",
2213
- "x",
2214
- "X",
2215
- "KeyC",
2216
- "c",
2217
- "C",
2218
- "KeyV",
2219
- "v",
2220
- "V",
2221
- "KeyB",
2222
- "b",
2223
- "B",
2224
- "KeyN",
2225
- "n",
2226
- "N",
2227
- "KeyM",
2228
- "m",
2229
- "M",
2230
- "Comma",
2231
- ",",
2232
- "<",
2233
- "Period",
2234
- ".",
2235
- ">",
2236
- "Slash",
2237
- "/",
2238
- "?",
2239
- "ShiftRight",
2240
- "ControlLeft",
2241
- "Control",
2242
- "MetaLeft",
2243
- "Meta",
2244
- "AltLeft",
2245
- "Alt",
2246
- "Space",
2247
- " ",
2248
- "AltRight",
2249
- "AltGraph",
2250
- "MetaRight",
2251
- "ContextMenu",
2252
- "ControlRight",
2253
- "PrintScreen",
2254
- "ScrollLock",
2255
- "Pause",
2256
- "PageUp",
2257
- "PageDown",
2258
- "Insert",
2259
- "Delete",
2260
- "Home",
2261
- "End",
2262
- "ArrowLeft",
2263
- "ArrowUp",
2264
- "ArrowRight",
2265
- "ArrowDown",
2266
- "NumLock",
2267
- "NumpadDivide",
2268
- "NumpadMultiply",
2269
- "NumpadSubtract",
2270
- "Numpad7",
2271
- "Numpad8",
2272
- "Numpad9",
2273
- "Numpad4",
2274
- "Numpad5",
2275
- "Numpad6",
2276
- "NumpadAdd",
2277
- "Numpad1",
2278
- "Numpad2",
2279
- "Numpad3",
2280
- "Numpad0",
2281
- "NumpadDecimal",
2282
- "NumpadEnter",
2283
- "ControlOrMeta"
2284
- ]);
2285
- async function keyboardImplementation(pressed, provider, sessionId, text, selectAll, skipRelease) {
2286
- if (provider instanceof PlaywrightBrowserProvider) {
2287
- const page = provider.getPage(sessionId);
2288
- const actions = parseKeyDef(defaultKeyMap, text);
2289
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2290
- const key = keyDef.key;
2291
- // TODO: instead of calling down/up for each key, join non special
2292
- // together, and call `type` once for all non special keys,
2293
- // and then `press` for special keys
2294
- if (pressed.has(key)) {
2295
- if (VALID_KEYS.has(key)) {
2296
- await page.keyboard.up(key);
2297
- }
2298
- pressed.delete(key);
2299
- }
2300
- if (!releasePrevious) {
2301
- if (key === "selectall") {
2302
- await selectAll();
2303
- continue;
2304
- }
2305
- for (let i = 1; i <= repeat; i++) {
2306
- if (VALID_KEYS.has(key)) {
2307
- await page.keyboard.down(key);
2308
- } else {
2309
- await page.keyboard.insertText(key);
2310
- }
2311
- }
2312
- if (releaseSelf) {
2313
- if (VALID_KEYS.has(key)) {
2314
- await page.keyboard.up(key);
2315
- }
2316
- } else {
2317
- pressed.add(key);
2318
- }
2319
- }
2320
- }
2321
- if (!skipRelease && pressed.size) {
2322
- for (const key of pressed) {
2323
- if (VALID_KEYS.has(key)) {
2324
- await page.keyboard.up(key);
2325
- }
2326
- }
2327
- }
2328
- } else if (provider instanceof WebdriverBrowserProvider) {
2329
- const { Key } = await import('webdriverio');
2330
- const browser = provider.browser;
2331
- const actions = parseKeyDef(defaultKeyMap, text);
2332
- let keyboard = browser.action("key");
2333
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2334
- let key = keyDef.key;
2335
- const special = Key[key];
2336
- if (special) {
2337
- key = special;
2338
- }
2339
- if (pressed.has(key)) {
2340
- keyboard.up(key);
2341
- pressed.delete(key);
2342
- }
2343
- if (!releasePrevious) {
2344
- if (key === "selectall") {
2345
- await keyboard.perform();
2346
- keyboard = browser.action("key");
2347
- await selectAll();
2348
- continue;
2349
- }
2350
- for (let i = 1; i <= repeat; i++) {
2351
- keyboard.down(key);
2352
- }
2353
- if (releaseSelf) {
2354
- keyboard.up(key);
2355
- } else {
2356
- pressed.add(key);
2357
- }
2358
- }
2359
- }
2360
- // seems like webdriverio doesn't release keys automatically if skipRelease is true and all events are keyUp
2361
- const allRelease = keyboard.toJSON().actions.every((action) => action.type === "keyUp");
2362
- await keyboard.perform(allRelease ? false : skipRelease);
1906
+ var mime = new Mime(types)._freeze();
1907
+
1908
+ function assertFileAccess(path, project) {
1909
+ if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
1910
+ throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
2363
1911
  }
2364
- return { pressed };
2365
1912
  }
2366
- function focusIframe() {
2367
- if (!document.activeElement || document.activeElement.ownerDocument !== document || document.activeElement === document.body) {
2368
- window.focus();
1913
+ const readFile = async ({ project }, path, options = {}) => {
1914
+ const filepath = resolve$1(project.config.root, path);
1915
+ assertFileAccess(filepath, project);
1916
+ // never return a Buffer
1917
+ if (typeof options === "object" && !options.encoding) {
1918
+ options.encoding = "utf-8";
2369
1919
  }
2370
- }
2371
- function selectAll() {
2372
- const element = document.activeElement;
2373
- if (element && typeof element.select === "function") {
2374
- element.select();
1920
+ return promises.readFile(filepath, options);
1921
+ };
1922
+ const writeFile = async ({ project }, path, data, options) => {
1923
+ const filepath = resolve$1(project.config.root, path);
1924
+ assertFileAccess(filepath, project);
1925
+ const dir = dirname$1(filepath);
1926
+ if (!fs.existsSync(dir)) {
1927
+ await promises.mkdir(dir, { recursive: true });
2375
1928
  }
2376
- }
1929
+ await promises.writeFile(filepath, data, options);
1930
+ };
1931
+ const removeFile = async ({ project }, path) => {
1932
+ const filepath = resolve$1(project.config.root, path);
1933
+ assertFileAccess(filepath, project);
1934
+ await promises.rm(filepath);
1935
+ };
1936
+ const _fileInfo = async ({ project }, path, encoding) => {
1937
+ const filepath = resolve$1(project.config.root, path);
1938
+ assertFileAccess(filepath, project);
1939
+ const content = await promises.readFile(filepath, encoding || "base64");
1940
+ return {
1941
+ content,
1942
+ basename: basename$1(filepath),
1943
+ mime: mime.getType(filepath)
1944
+ };
1945
+ };
2377
1946
 
2378
1947
  const screenshot = async (context, name, options = {}) => {
2379
1948
  options.save ??= true;
2380
1949
  if (!options.save) {
2381
1950
  options.base64 = true;
2382
1951
  }
2383
- const { buffer, path } = await takeScreenshot(context, name, options);
1952
+ const { buffer, path } = await context.triggerCommand("__vitest_takeScreenshot", name, options);
2384
1953
  return returnResult(options, path, buffer);
2385
1954
  };
2386
- /**
2387
- * Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
2388
- *
2389
- * **Note**: the returned `path` indicates where the screenshot *might* be found.
2390
- * It is not guaranteed to exist, especially if `options.save` is `false`.
2391
- *
2392
- * @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
2393
- */
2394
- async function takeScreenshot(context, name, options) {
2395
- if (!context.testPath) {
2396
- throw new Error(`Cannot take a screenshot without a test path`);
2397
- }
2398
- const path = resolveScreenshotPath(context.testPath, name, context.project.config, options.path);
2399
- const savePath = normalize$1(path);
2400
- await mkdir(dirname(path), { recursive: true });
2401
- if (context.provider instanceof PlaywrightBrowserProvider) {
2402
- const mask = options.mask?.map((selector) => context.iframe.locator(selector));
2403
- if (options.element) {
2404
- const { element: selector,...config } = options;
2405
- const element = context.iframe.locator(selector);
2406
- const buffer = await element.screenshot({
2407
- ...config,
2408
- mask,
2409
- path: options.save ? savePath : undefined
2410
- });
2411
- return {
2412
- buffer,
2413
- path
2414
- };
2415
- }
2416
- const buffer = await context.iframe.locator("body").screenshot({
2417
- ...options,
2418
- mask,
2419
- path: options.save ? savePath : undefined
2420
- });
2421
- return {
2422
- buffer,
2423
- path
2424
- };
2425
- }
2426
- if (context.provider instanceof WebdriverBrowserProvider) {
2427
- const page = context.provider.browser;
2428
- const element = !options.element ? await page.$("body") : await page.$(`${options.element}`);
2429
- // webdriverio expects the path to contain the extension and only works with PNG files
2430
- const savePathWithExtension = savePath.endsWith(".png") ? savePath : `${savePath}.png`;
2431
- const buffer = await element.saveScreenshot(savePathWithExtension);
2432
- if (!options.save) {
2433
- await rm(savePathWithExtension, { force: true });
2434
- }
2435
- return {
2436
- buffer,
2437
- path
2438
- };
2439
- }
2440
- throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
2441
- }
2442
- function resolveScreenshotPath(testPath, name, config, customPath) {
2443
- if (customPath) {
2444
- return resolve(dirname(testPath), customPath);
2445
- }
2446
- const dir = dirname(testPath);
2447
- const base = basename(testPath);
2448
- if (config.browser.screenshotDirectory) {
2449
- return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
2450
- }
2451
- return resolve(dir, "__screenshots__", base, name);
2452
- }
2453
1955
  function returnResult(options, path, buffer) {
2454
1956
  if (!options.save) {
2455
1957
  return buffer.toString("base64");
@@ -2544,10 +2046,14 @@ const pixelmatch = (reference, actual, { createDiff,...options }) => {
2544
2046
  };
2545
2047
  };
2546
2048
 
2547
- const comparators = new Map(Object.entries({ pixelmatch }));
2548
- function getComparator(comparator) {
2549
- if (comparators.has(comparator)) {
2550
- return comparators.get(comparator);
2049
+ const comparators = { pixelmatch };
2050
+ function getComparator(comparator, context) {
2051
+ if (comparator in comparators) {
2052
+ return comparators[comparator];
2053
+ }
2054
+ const customComparators = context.project.config.browser.expect?.toMatchScreenshot?.comparators;
2055
+ if (customComparators && comparator in customComparators) {
2056
+ return customComparators[comparator];
2551
2057
  }
2552
2058
  throw new Error(`Unrecognized comparator ${comparator}`);
2553
2059
  }
@@ -2602,7 +2108,7 @@ function resolveOptions({ context, name, options, testName }) {
2602
2108
  };
2603
2109
  return {
2604
2110
  codec: getCodec(extension),
2605
- comparator: getComparator(resolvedOptions.comparatorName),
2111
+ comparator: getComparator(resolvedOptions.comparatorName, context),
2606
2112
  resolvedOptions,
2607
2113
  paths: {
2608
2114
  reference: resolvedOptions.resolveScreenshotPath(resolvePathData),
@@ -2677,7 +2183,7 @@ function sanitizeArg(input) {
2677
2183
  * @returns `Promise` resolving to the decoded screenshot data
2678
2184
  */
2679
2185
  function takeDecodedScreenshot({ codec, context, element, name, screenshotOptions }) {
2680
- return takeScreenshot(context, name, {
2186
+ return context.triggerCommand("__vitest_takeScreenshot", name, {
2681
2187
  ...screenshotOptions,
2682
2188
  save: false,
2683
2189
  element
@@ -2858,147 +2364,12 @@ async function getStableScreenshots({ codec, context, comparator, comparatorOpti
2858
2364
  };
2859
2365
  }
2860
2366
 
2861
- const selectOptions = async (context, selector, userValues, options = {}) => {
2862
- if (context.provider instanceof PlaywrightBrowserProvider) {
2863
- const value = userValues;
2864
- const { iframe } = context;
2865
- const selectElement = iframe.locator(selector);
2866
- const values = await Promise.all(value.map(async (v) => {
2867
- if (typeof v === "string") {
2868
- return v;
2869
- }
2870
- const elementHandler = await iframe.locator(v.element).elementHandle();
2871
- if (!elementHandler) {
2872
- throw new Error(`Element not found: ${v.element}`);
2873
- }
2874
- return elementHandler;
2875
- }));
2876
- await selectElement.selectOption(values, options);
2877
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2878
- const values = userValues;
2879
- if (!values.length) {
2880
- return;
2881
- }
2882
- const browser = context.browser;
2883
- if (values.length === 1 && "index" in values[0]) {
2884
- const selectElement = browser.$(selector);
2885
- await selectElement.selectByIndex(values[0].index);
2886
- } else {
2887
- throw new Error("Provider \"webdriverio\" doesn't support selecting multiple values at once");
2888
- }
2889
- } else {
2890
- throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
2891
- }
2892
- };
2893
-
2894
- const tab = async (context, options = {}) => {
2895
- const provider = context.provider;
2896
- if (provider instanceof PlaywrightBrowserProvider) {
2897
- const page = context.page;
2898
- await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
2899
- return;
2900
- }
2901
- if (provider instanceof WebdriverBrowserProvider) {
2902
- const { Key } = await import('webdriverio');
2903
- const browser = context.browser;
2904
- await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
2905
- return;
2906
- }
2907
- throw new Error(`Provider "${provider.name}" doesn't support tab command`);
2908
- };
2909
-
2910
- const type = async (context, selector, text, options = {}) => {
2911
- const { skipClick = false, skipAutoClose = false } = options;
2912
- const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
2913
- if (context.provider instanceof PlaywrightBrowserProvider) {
2914
- const { iframe } = context;
2915
- const element = iframe.locator(selector);
2916
- if (!skipClick) {
2917
- await element.focus();
2918
- }
2919
- await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => element.selectText(), skipAutoClose);
2920
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2921
- const browser = context.browser;
2922
- const element = browser.$(selector);
2923
- if (!skipClick && !await element.isFocused()) {
2924
- await element.click();
2925
- }
2926
- await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => browser.execute(() => {
2927
- const element = document.activeElement;
2928
- if (element && typeof element.select === "function") {
2929
- element.select();
2930
- }
2931
- }), skipAutoClose);
2932
- } else {
2933
- throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
2934
- }
2935
- return { unreleased: Array.from(unreleased) };
2936
- };
2937
-
2938
- const upload = async (context, selector, files, options) => {
2939
- const testPath = context.testPath;
2940
- if (!testPath) {
2941
- throw new Error(`Cannot upload files outside of a test`);
2942
- }
2943
- const root = context.project.config.root;
2944
- if (context.provider instanceof PlaywrightBrowserProvider) {
2945
- const { iframe } = context;
2946
- const playwrightFiles = files.map((file) => {
2947
- if (typeof file === "string") {
2948
- return resolve(root, file);
2949
- }
2950
- return {
2951
- name: file.name,
2952
- mimeType: file.mimeType,
2953
- buffer: Buffer.from(file.base64, "base64")
2954
- };
2955
- });
2956
- await iframe.locator(selector).setInputFiles(playwrightFiles, options);
2957
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2958
- for (const file of files) {
2959
- if (typeof file !== "string") {
2960
- throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`);
2961
- }
2962
- }
2963
- const element = context.browser.$(selector);
2964
- for (const file of files) {
2965
- const filepath = resolve(root, file);
2966
- const remoteFilePath = await context.browser.uploadFile(filepath);
2967
- await element.addValue(remoteFilePath);
2968
- }
2969
- } else {
2970
- throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`);
2971
- }
2972
- };
2973
-
2974
- const viewport = async (context, options) => {
2975
- if (context.provider instanceof WebdriverBrowserProvider) {
2976
- await context.provider.setViewport(options);
2977
- } else {
2978
- throw new TypeError(`Provider ${context.provider.name} doesn't support "viewport" command`);
2979
- }
2980
- };
2981
-
2982
2367
  var builtinCommands = {
2983
2368
  readFile,
2984
2369
  removeFile,
2985
2370
  writeFile,
2986
2371
  __vitest_fileInfo: _fileInfo,
2987
- __vitest_upload: upload,
2988
- __vitest_click: click,
2989
- __vitest_dblClick: dblClick,
2990
- __vitest_tripleClick: tripleClick,
2991
2372
  __vitest_screenshot: screenshot,
2992
- __vitest_type: type,
2993
- __vitest_clear: clear,
2994
- __vitest_fill: fill,
2995
- __vitest_tab: tab,
2996
- __vitest_keyboard: keyboard,
2997
- __vitest_selectOptions: selectOptions,
2998
- __vitest_dragAndDrop: dragAndDrop,
2999
- __vitest_hover: hover,
3000
- __vitest_cleanup: keyboardCleanup,
3001
- __vitest_viewport: viewport,
3002
2373
  __vitest_screenshotMatcher: screenshotMatcher
3003
2374
  };
3004
2375
 
@@ -3033,6 +2404,22 @@ class ProjectBrowser {
3033
2404
  get vite() {
3034
2405
  return this.parent.vite;
3035
2406
  }
2407
+ commands = {};
2408
+ registerCommand(name, cb) {
2409
+ if (!/^[a-z_$][\w$]*$/i.test(name)) {
2410
+ throw new Error(`Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`);
2411
+ }
2412
+ this.commands[name] = cb;
2413
+ }
2414
+ triggerCommand = ((name, context, ...args) => {
2415
+ if (name in this.commands) {
2416
+ return this.commands[name](context, ...args);
2417
+ }
2418
+ if (name in this.parent.commands) {
2419
+ return this.parent.commands[name](context, ...args);
2420
+ }
2421
+ throw new Error(`Provider ${this.provider.name} does not support command "${name}".`);
2422
+ });
3036
2423
  wrapSerializedConfig() {
3037
2424
  const config = wrapConfig(this.project.serializedConfig);
3038
2425
  config.env ??= {};
@@ -3043,22 +2430,17 @@ class ProjectBrowser {
3043
2430
  if (this.provider) {
3044
2431
  return;
3045
2432
  }
3046
- const Provider = await getBrowserProvider(project.config.browser, project);
3047
- this.provider = new Provider();
3048
- const browser = project.config.browser.name;
3049
- const name = project.name ? `[${project.name}] ` : "";
3050
- if (!browser) {
3051
- throw new Error(`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`);
3052
- }
3053
- const supportedBrowsers = this.provider.getSupportedBrowsers();
3054
- if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
3055
- throw new Error(`${name}Browser "${browser}" is not supported by the browser provider "${this.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`);
2433
+ this.provider = await getBrowserProvider(project.config.browser, project);
2434
+ if (this.provider.initScripts) {
2435
+ this.parent.initScripts = this.provider.initScripts;
2436
+ // make sure the script can be imported
2437
+ this.provider.initScripts.forEach((script) => {
2438
+ const allow = this.parent.vite.config.server.fs.allow;
2439
+ if (!allow.includes(script)) {
2440
+ allow.push(script);
2441
+ }
2442
+ });
3056
2443
  }
3057
- const providerOptions = project.config.browser.providerOptions;
3058
- await this.provider.initialize(project, {
3059
- browser,
3060
- options: providerOptions
3061
- });
3062
2444
  }
3063
2445
  parseErrorStacktrace(e, options = {}) {
3064
2446
  return this.parent.parseErrorStacktrace(e, options);
@@ -3091,6 +2473,7 @@ class ParentBrowserProject {
3091
2473
  locatorsUrl;
3092
2474
  matchersUrl;
3093
2475
  stateJs;
2476
+ initScripts = [];
3094
2477
  commands = {};
3095
2478
  children = new Set();
3096
2479
  vitest;
@@ -3124,18 +2507,19 @@ class ParentBrowserProject {
3124
2507
  return result?.map;
3125
2508
  },
3126
2509
  getUrlId: (id) => {
3127
- const mod = this.vite.moduleGraph.getModuleById(id);
2510
+ const moduleGraph = this.vite.environments.client.moduleGraph;
2511
+ const mod = moduleGraph.getModuleById(id);
3128
2512
  if (mod) {
3129
2513
  return id;
3130
2514
  }
3131
2515
  const resolvedPath = resolve(this.vite.config.root, id.slice(1));
3132
- const modUrl = this.vite.moduleGraph.getModuleById(resolvedPath);
2516
+ const modUrl = moduleGraph.getModuleById(resolvedPath);
3133
2517
  if (modUrl) {
3134
2518
  return resolvedPath;
3135
2519
  }
3136
2520
  // some browsers (looking at you, safari) don't report queries in stack traces
3137
2521
  // the next best thing is to try the first id that this file resolves to
3138
- const files = this.vite.moduleGraph.getModulesByFile(resolvedPath);
2522
+ const files = moduleGraph.getModulesByFile(resolvedPath);
3139
2523
  if (files && files.size) {
3140
2524
  return files.values().next().value.id;
3141
2525
  }
@@ -3161,15 +2545,6 @@ class ParentBrowserProject {
3161
2545
  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);
3162
2546
  this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
3163
2547
  this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
3164
- const builtinProviders = [
3165
- "playwright",
3166
- "webdriverio",
3167
- "preview"
3168
- ];
3169
- const providerName = project.config.browser.provider || "preview";
3170
- if (builtinProviders.includes(providerName)) {
3171
- this.locatorsUrl = join("/@fs/", distRoot, "locators", `${providerName}.js`);
3172
- }
3173
2548
  this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
3174
2549
  this.stateJs = readFile$1(resolve(distRoot, "state.js"), "utf-8").then((js) => this.stateJs = js);
3175
2550
  }
@@ -3456,7 +2831,7 @@ function nanoid(size = 21) {
3456
2831
  return id;
3457
2832
  }
3458
2833
 
3459
- const debug$1 = createDebugger("vitest:browser:api");
2834
+ const debug = createDebugger("vitest:browser:api");
3460
2835
  const BROWSER_API_PATH = "/__vitest_browser_api__";
3461
2836
  function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3462
2837
  const vite = globalServer.vite;
@@ -3504,9 +2879,9 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3504
2879
  const state = project.browser.state;
3505
2880
  const clients = type === "tester" ? state.testers : state.orchestrators;
3506
2881
  clients.set(rpcId, rpc);
3507
- debug$1?.("[%s] Browser API connected to %s", rpcId, type);
2882
+ debug?.("[%s] Browser API connected to %s", rpcId, type);
3508
2883
  ws.on("close", () => {
3509
- debug$1?.("[%s] Browser API disconnected from %s", rpcId, type);
2884
+ debug?.("[%s] Browser API disconnected from %s", rpcId, type);
3510
2885
  clients.delete(rpcId);
3511
2886
  globalServer.removeCDPHandler(rpcId);
3512
2887
  if (type === "orchestrator") {
@@ -3631,23 +3006,22 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3631
3006
  }
3632
3007
  },
3633
3008
  async triggerCommand(sessionId, command, testPath, payload) {
3634
- debug$1?.("[%s] Triggering command \"%s\"", sessionId, command);
3009
+ debug?.("[%s] Triggering command \"%s\"", sessionId, command);
3635
3010
  const provider = project.browser.provider;
3636
3011
  if (!provider) {
3637
3012
  throw new Error("Commands are only available for browser tests.");
3638
3013
  }
3639
- const commands = globalServer.commands;
3640
- if (!commands || !commands[command]) {
3641
- throw new Error(`Unknown command "${command}".`);
3642
- }
3643
3014
  const context = Object.assign({
3644
3015
  testPath,
3645
3016
  project,
3646
3017
  provider,
3647
3018
  contextId: sessionId,
3648
- sessionId
3019
+ sessionId,
3020
+ triggerCommand: (name, ...args) => {
3021
+ return project.browser.triggerCommand(name, context, ...args);
3022
+ }
3649
3023
  }, provider.getCommandsContext(sessionId));
3650
- return await commands[command](context, ...payload);
3024
+ return await project.browser.triggerCommand(command, context, ...payload);
3651
3025
  },
3652
3026
  resolveMock(rawId, importer, options) {
3653
3027
  return mockResolver.resolveMock(rawId, importer, options);
@@ -3753,265 +3127,12 @@ function stringifyReplace(key, value) {
3753
3127
  }
3754
3128
  }
3755
3129
 
3756
- const debug = createDebugger("vitest:browser:pool");
3757
- function createBrowserPool(vitest) {
3758
- const providers = new Set();
3759
- const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
3760
- const threadsCount = vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
3761
- const projectPools = new WeakMap();
3762
- const ensurePool = (project) => {
3763
- if (projectPools.has(project)) {
3764
- return projectPools.get(project);
3765
- }
3766
- debug?.("creating pool for project %s", project.name);
3767
- const resolvedUrls = project.browser.vite.resolvedUrls;
3768
- const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
3769
- if (!origin) {
3770
- throw new Error(`Can't find browser origin URL for project "${project.name}"`);
3771
- }
3772
- const pool = new BrowserPool(project, {
3773
- maxWorkers: getThreadsCount(project),
3774
- origin
3775
- });
3776
- projectPools.set(project, pool);
3777
- vitest.onCancel(() => {
3778
- pool.cancel();
3779
- });
3780
- return pool;
3781
- };
3782
- const runWorkspaceTests = async (method, specs) => {
3783
- const groupedFiles = new Map();
3784
- for (const { project, moduleId } of specs) {
3785
- const files = groupedFiles.get(project) || [];
3786
- files.push(moduleId);
3787
- groupedFiles.set(project, files);
3788
- }
3789
- let isCancelled = false;
3790
- vitest.onCancel(() => {
3791
- isCancelled = true;
3792
- });
3793
- const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
3794
- await project._initBrowserProvider();
3795
- if (!project.browser) {
3796
- 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.`);
3797
- }
3798
- if (isCancelled) {
3799
- return;
3800
- }
3801
- debug?.("provider is ready for %s project", project.name);
3802
- const pool = ensurePool(project);
3803
- vitest.state.clearFiles(project, files);
3804
- providers.add(project.browser.provider);
3805
- return {
3806
- pool,
3807
- provider: project.browser.provider,
3808
- runTests: () => pool.runTests(method, files)
3809
- };
3810
- }));
3811
- if (isCancelled) {
3812
- return;
3813
- }
3814
- const parallelPools = [];
3815
- const nonParallelPools = [];
3816
- for (const pool of initialisedPools) {
3817
- if (!pool) {
3818
- // this means it was cancelled
3819
- return;
3820
- }
3821
- if (pool.provider.mocker && pool.provider.supportsParallelism) {
3822
- parallelPools.push(pool.runTests);
3823
- } else {
3824
- nonParallelPools.push(pool.runTests);
3825
- }
3826
- }
3827
- await Promise.all(parallelPools.map((runTests) => runTests()));
3828
- for (const runTests of nonParallelPools) {
3829
- if (isCancelled) {
3830
- return;
3831
- }
3832
- await runTests();
3833
- }
3834
- };
3835
- function getThreadsCount(project) {
3836
- const config = project.config.browser;
3837
- if (!config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism) {
3838
- return 1;
3839
- }
3840
- if (project.config.maxWorkers) {
3841
- return project.config.maxWorkers;
3842
- }
3843
- return threadsCount;
3844
- }
3845
- return {
3846
- name: "browser",
3847
- async close() {
3848
- await Promise.all([...providers].map((provider) => provider.close()));
3849
- vitest._browserSessions.sessionIds.clear();
3850
- providers.clear();
3851
- vitest.projects.forEach((project) => {
3852
- project.browser?.state.orchestrators.forEach((orchestrator) => {
3853
- orchestrator.$close();
3854
- });
3855
- });
3856
- debug?.("browser pool closed all providers");
3857
- },
3858
- runTests: (files) => runWorkspaceTests("run", files),
3859
- collectTests: (files) => runWorkspaceTests("collect", files)
3860
- };
3861
- }
3862
- function escapePathToRegexp(path) {
3863
- return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
3864
- }
3865
- class BrowserPool {
3866
- _queue = [];
3867
- _promise;
3868
- _providedContext;
3869
- readySessions = new Set();
3870
- constructor(project, options) {
3871
- this.project = project;
3872
- this.options = options;
3873
- }
3874
- cancel() {
3875
- this._queue = [];
3876
- }
3877
- reject(error) {
3878
- this._promise?.reject(error);
3879
- this._promise = undefined;
3880
- this.cancel();
3881
- }
3882
- get orchestrators() {
3883
- return this.project.browser.state.orchestrators;
3884
- }
3885
- async runTests(method, files) {
3886
- this._promise ??= createDefer();
3887
- if (!files.length) {
3888
- debug?.("no tests found, finishing test run immediately");
3889
- this._promise.resolve();
3890
- return this._promise;
3891
- }
3892
- this._providedContext = stringify(this.project.getProvidedContext());
3893
- this._queue.push(...files);
3894
- this.readySessions.forEach((sessionId) => {
3895
- if (this._queue.length) {
3896
- this.readySessions.delete(sessionId);
3897
- this.runNextTest(method, sessionId);
3898
- }
3899
- });
3900
- if (this.orchestrators.size >= this.options.maxWorkers) {
3901
- debug?.("all orchestrators are ready, not creating more");
3902
- return this._promise;
3903
- }
3904
- // open the minimum amount of tabs
3905
- // if there is only 1 file running, we don't need 8 tabs running
3906
- const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length);
3907
- const promises = [];
3908
- for (let i = 0; i < workerCount; i++) {
3909
- const sessionId = crypto.randomUUID();
3910
- this.project.vitest._browserSessions.sessionIds.add(sessionId);
3911
- const project = this.project.name;
3912
- debug?.("[%s] creating session for %s", sessionId, project);
3913
- const page = this.openPage(sessionId).then(() => {
3914
- // start running tests on the page when it's ready
3915
- this.runNextTest(method, sessionId);
3916
- });
3917
- promises.push(page);
3918
- }
3919
- await Promise.all(promises);
3920
- debug?.("all sessions are created");
3921
- return this._promise;
3922
- }
3923
- async openPage(sessionId) {
3924
- const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
3925
- const browser = this.project.browser;
3926
- const url = new URL("/__vitest_test__/", this.options.origin);
3927
- url.searchParams.set("sessionId", sessionId);
3928
- const pagePromise = browser.provider.openPage(sessionId, url.toString());
3929
- await Promise.all([sessionPromise, pagePromise]);
3930
- }
3931
- getOrchestrator(sessionId) {
3932
- const orchestrator = this.orchestrators.get(sessionId);
3933
- if (!orchestrator) {
3934
- throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
3935
- }
3936
- return orchestrator;
3937
- }
3938
- finishSession(sessionId) {
3939
- this.readySessions.add(sessionId);
3940
- // the last worker finished running tests
3941
- if (this.readySessions.size === this.orchestrators.size) {
3942
- this._promise?.resolve();
3943
- this._promise = undefined;
3944
- debug?.("[%s] all tests finished running", sessionId);
3945
- } else {
3946
- debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
3947
- }
3948
- }
3949
- runNextTest(method, sessionId) {
3950
- const file = this._queue.shift();
3951
- if (!file) {
3952
- debug?.("[%s] no more tests to run", sessionId);
3953
- const isolate = this.project.config.browser.isolate;
3954
- // we don't need to cleanup testers if isolation is enabled,
3955
- // because cleanup is done at the end of every test
3956
- if (isolate) {
3957
- this.finishSession(sessionId);
3958
- return;
3959
- }
3960
- // we need to cleanup testers first because there is only
3961
- // one iframe and it does the cleanup only after everything is completed
3962
- const orchestrator = this.getOrchestrator(sessionId);
3963
- orchestrator.cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
3964
- return;
3965
- }
3966
- if (!this._promise) {
3967
- throw new Error(`Unexpected empty queue`);
3968
- }
3969
- const startTime = performance.now();
3970
- const orchestrator = this.getOrchestrator(sessionId);
3971
- debug?.("[%s] run test %s", sessionId, file);
3972
- this.setBreakpoint(sessionId, file).then(() => {
3973
- // this starts running tests inside the orchestrator
3974
- orchestrator.createTesters({
3975
- method,
3976
- files: [file],
3977
- providedContext: this._providedContext || "[{}]",
3978
- startTime
3979
- }).then(() => {
3980
- debug?.("[%s] test %s finished running", sessionId, file);
3981
- this.runNextTest(method, sessionId);
3982
- }).catch((error) => {
3983
- // if user cancels the test run manually, ignore the error and exit gracefully
3984
- if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
3985
- this.cancel();
3986
- this._promise?.resolve();
3987
- this._promise = undefined;
3988
- debug?.("[%s] browser connection was closed", sessionId);
3989
- return;
3990
- }
3991
- debug?.("[%s] error during %s test run: %s", sessionId, file, error);
3992
- this.reject(error);
3993
- });
3994
- }).catch((err) => this.reject(err));
3995
- }
3996
- async setBreakpoint(sessionId, file) {
3997
- if (!this.project.config.inspector.waitForDebugger) {
3998
- return;
3999
- }
4000
- const provider = this.project.browser.provider;
4001
- if (!provider.getCDPSession) {
4002
- throw new Error("Unable to set breakpoint, CDP not supported");
4003
- }
4004
- debug?.("[%s] set breakpoint for %s", sessionId, file);
4005
- const session = await provider.getCDPSession(sessionId);
4006
- await session.send("Debugger.enable", {});
4007
- await session.send("Debugger.setBreakpointByUrl", {
4008
- lineNumber: 0,
4009
- urlRegex: escapePathToRegexp(file)
4010
- });
4011
- }
3130
+ function defineBrowserCommand(fn) {
3131
+ return fn;
4012
3132
  }
4013
-
4014
- async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
3133
+ const createBrowserServer = async (options) => {
3134
+ const project = options.project;
3135
+ const configFile = project.vite.config.configFile;
4015
3136
  if (project.vitest.version !== version) {
4016
3137
  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."));
4017
3138
  }
@@ -4020,6 +3141,7 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
4020
3141
  const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
4021
3142
  const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
4022
3143
  const mockerRegistry = new MockerRegistry();
3144
+ let cacheDir;
4023
3145
  const vite = await createViteServer({
4024
3146
  ...project.options,
4025
3147
  base: "/",
@@ -4043,16 +3165,35 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
4043
3165
  },
4044
3166
  cacheDir: project.vite.config.cacheDir,
4045
3167
  plugins: [
4046
- ...prePlugins,
3168
+ {
3169
+ name: "vitest-internal:browser-cacheDir",
3170
+ configResolved(config) {
3171
+ cacheDir = config.cacheDir;
3172
+ }
3173
+ },
3174
+ ...options.mocksPlugins({ filter(id) {
3175
+ if (id.includes(distRoot) || id.includes(cacheDir)) {
3176
+ return false;
3177
+ }
3178
+ return true;
3179
+ } }),
3180
+ options.metaEnvReplacer(),
4047
3181
  ...project.options?.plugins || [],
4048
3182
  BrowserPlugin(server),
4049
3183
  interceptorPlugin({ registry: mockerRegistry }),
4050
- ...postPlugins
3184
+ options.coveragePlugin()
4051
3185
  ]
4052
3186
  });
4053
3187
  await vite.listen();
4054
3188
  setupBrowserRpc(server, mockerRegistry);
4055
3189
  return server;
3190
+ };
3191
+ function defineBrowserProvider(options) {
3192
+ return {
3193
+ ...options,
3194
+ options: options.options || {},
3195
+ serverFactory: createBrowserServer
3196
+ };
4056
3197
  }
4057
3198
 
4058
- export { createBrowserPool, createBrowserServer, distRoot };
3199
+ export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };