@vitest/browser 4.0.0-beta.13 → 4.0.0-beta.15

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 (35) hide show
  1. package/context.d.ts +50 -9
  2. package/context.js +3 -2
  3. package/dist/client/.vite/manifest.json +2 -2
  4. package/dist/client/__vitest__/assets/{index-C15NF4dG.js → index-CKAjAT2u.js} +1 -1
  5. package/dist/client/__vitest__/index.html +1 -1
  6. package/dist/client/__vitest_browser__/{orchestrator-Pdu7HCGX.js → orchestrator-Ce7D5fGP.js} +8 -6
  7. package/dist/client/__vitest_browser__/{tester-DYNLfPFH.js → tester-Vm4ppAv-.js} +4 -1
  8. package/dist/client/orchestrator.html +1 -1
  9. package/dist/client/tester/tester.html +1 -1
  10. package/dist/client.js +1 -1
  11. package/dist/context.js +70 -12
  12. package/dist/expect-element.js +1 -1
  13. package/dist/{public-utils-B6exS8fl.js → index-BnLTaCRv.js} +3 -3
  14. package/dist/index.d.ts +36 -172
  15. package/dist/index.js +515 -1487
  16. package/dist/{locators/index.d.ts → locators.d.ts} +25 -2
  17. package/dist/locators.js +1 -0
  18. package/jest-dom.d.ts +5 -5
  19. package/matchers.d.ts +1 -1
  20. package/package.json +11 -33
  21. package/utils.d.ts +5 -5
  22. package/dist/index-BPDFwkoW.js +0 -1
  23. package/dist/index-CwoiDq7G.js +0 -6
  24. package/dist/locators/index.js +0 -1
  25. package/dist/locators/playwright.js +0 -1
  26. package/dist/locators/preview.js +0 -1
  27. package/dist/locators/webdriverio.js +0 -1
  28. package/dist/providers/playwright.d.ts +0 -105
  29. package/dist/providers/playwright.js +0 -385
  30. package/dist/providers/preview.d.ts +0 -16
  31. package/dist/providers/preview.js +0 -44
  32. package/dist/providers/webdriverio.d.ts +0 -51
  33. package/dist/providers/webdriverio.js +0 -206
  34. package/dist/utils.js +0 -1
  35. package/providers.d.ts +0 -7
package/dist/index.js CHANGED
@@ -2,27 +2,23 @@ import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule
2
2
  import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '@vitest/mocker/node';
3
3
  import c from 'tinyrainbow';
4
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, nanoid as nanoid$1, deepMerge, createDefer } from '@vitest/utils/helpers';
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
11
  import { coverageConfigDefaults } from 'vitest/config';
11
- import { fileURLToPath } from 'node:url';
12
12
  import crypto from 'node:crypto';
13
- import { mkdir, rm, readFile as readFile$1, writeFile as writeFile$1, unlink } from 'node:fs/promises';
13
+ import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
14
14
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
15
- import { PlaywrightBrowserProvider } from './providers/playwright.js';
16
- import { WebdriverBrowserProvider } from './providers/webdriverio.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.13";
21
+ var version = "4.0.0-beta.15";
26
22
 
27
23
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
28
24
  function normalizeWindowsPath(input = "") {
@@ -324,54 +320,372 @@ 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
- async function getBrowserProvider(options, project) {
331
- const browser = project.config.browser.name;
332
- const name = project.name ? `[${project.name}] ` : "";
333
- if (!browser) {
334
- throw new Error(`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`);
335
- }
336
- if (options.provider == null || "_cli" in options.provider && typeof options.provider.factory !== "function") {
337
- const providers = await import('./index-CwoiDq7G.js');
338
- const name = options.provider?.name || "preview";
339
- if (!(name in providers)) {
340
- throw new Error(`Unknown browser provider "${name}". Available providers: ${Object.keys(providers).join(", ")}.`);
341
- }
342
- return providers[name](options.provider?.options).factory(project);
343
- }
344
- const supportedBrowsers = options.provider.supportedBrowser || [];
345
- if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
346
- throw new Error(`${name}Browser "${browser}" is not supported by the browser provider "${options.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`);
347
- }
348
- if (typeof options.provider.factory !== "function") {
349
- throw new TypeError(`The "${name}" browser provider does not provide a "factory" function. Received ${typeof options.provider.factory}.`);
350
- }
351
- return options.provider.factory(project);
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
+ }
502
+ ];
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
+ };
352
529
  }
353
- function slash(path) {
354
- 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
+ };
355
541
  }
356
-
357
- async function resolveOrchestrator(globalServer, url, res) {
358
- let sessionId = url.searchParams.get("sessionId");
359
- // it's possible to open the page without a context
360
- if (!sessionId) {
361
- const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
362
- sessionId = contexts.at(-1) ?? "none";
363
- }
364
- // it's ok to not have a session here, especially in the preview provider
365
- // because the user could refresh the page which would remove the session id from the url
366
- const session = globalServer.vitest._browserSessions.getSession(sessionId);
367
- const browserProject = session?.project.browser || [...globalServer.children][0];
368
- if (!browserProject) {
369
- return;
370
- }
371
- // ignore unknown pages
372
- if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
373
- return;
374
- }
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
+ }
375
689
  const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
376
690
  const injector = replacer(injectorJs, {
377
691
  __VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider?.name || "preview"),
@@ -514,36 +828,58 @@ function createTesterMiddleware(browserServer) {
514
828
  };
515
829
  }
516
830
 
517
- const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
518
- 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";
519
837
  const __dirname = dirname(fileURLToPath(import.meta.url));
520
838
  function BrowserContext(globalServer) {
521
839
  return {
522
840
  name: "vitest:browser:virtual-module:context",
523
841
  enforce: "pre",
524
- resolveId(id) {
842
+ resolveId(id, importer) {
525
843
  if (id === ID_CONTEXT) {
526
844
  return VIRTUAL_ID_CONTEXT;
527
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
+ }
528
855
  },
529
856
  load(id) {
530
857
  if (id === VIRTUAL_ID_CONTEXT) {
531
858
  return generateContextFile.call(this, globalServer);
532
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
+ }
533
869
  }
534
870
  };
535
871
  }
536
872
  async function generateContextFile(globalServer) {
537
873
  const commands = Object.keys(globalServer.commands);
538
- const provider = [...globalServer.children][0].provider || { name: "preview" };
539
- const providerName = provider.name;
874
+ const provider = [...globalServer.children][0].provider;
875
+ const providerName = provider?.name || "preview";
540
876
  const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
541
877
  return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
542
878
  }).join("\n");
543
- const userEventNonProviderImport = await getUserEventImport(providerName, this.resolve.bind(this));
879
+ const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
544
880
  const distContextPath = slash$1(`/@fs/${resolve(__dirname, "context.js")}`);
545
881
  return `
546
- import { page, createUserEvent, cdp, locators } from '${distContextPath}'
882
+ import { page, createUserEvent, cdp, locators, utils } from '${distContextPath}'
547
883
  ${userEventNonProviderImport}
548
884
 
549
885
  export const server = {
@@ -558,16 +894,17 @@ export const server = {
558
894
  }
559
895
  export const commands = server.commands
560
896
  export const userEvent = createUserEvent(_userEventSetup)
561
- export { page, cdp, locators }
897
+ export { page, cdp, locators, utils }
562
898
  `;
563
899
  }
564
900
  async function getUserEventImport(provider, resolve) {
565
- if (provider !== "preview") {
901
+ if (!provider || provider.name !== "preview") {
566
902
  return "const _userEventSetup = undefined";
567
903
  }
568
- const resolved = await resolve("@testing-library/user-event", __dirname);
904
+ const previewDistRoot = provider.distRoot;
905
+ const resolved = await resolve("@testing-library/user-event", previewDistRoot);
569
906
  if (!resolved) {
570
- throw new Error(`Failed to resolve user-event package from ${__dirname}`);
907
+ throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
571
908
  }
572
909
  return `\
573
910
  import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
@@ -728,9 +1065,9 @@ var BrowserPlugin = (parentServer, base = "/") => {
728
1065
  ];
729
1066
  const exclude = [
730
1067
  "vitest",
1068
+ "vitest/browser",
731
1069
  "vitest/internal/browser",
732
1070
  "vitest/runners",
733
- "@vitest/browser",
734
1071
  "@vitest/browser/client",
735
1072
  "@vitest/utils",
736
1073
  "@vitest/utils/source-map",
@@ -772,10 +1109,12 @@ var BrowserPlugin = (parentServer, base = "/") => {
772
1109
  const include = [
773
1110
  "vitest > expect-type",
774
1111
  "vitest > @vitest/snapshot > magic-string",
775
- "vitest > @vitest/expect > chai",
776
- "@vitest/browser > @testing-library/user-event",
777
- "@vitest/browser > @testing-library/dom"
1112
+ "vitest > @vitest/expect > chai"
778
1113
  ];
1114
+ const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
1115
+ if (provider?.name === "preview") {
1116
+ include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
1117
+ }
779
1118
  const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
780
1119
  const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
781
1120
  if (svelte) {
@@ -989,14 +1328,14 @@ body {
989
1328
  },
990
1329
  injectTo: "head"
991
1330
  },
992
- parentServer.locatorsUrl ? {
1331
+ ...parentServer.initScripts.map((script) => ({
993
1332
  tag: "script",
994
1333
  attrs: {
995
1334
  type: "module",
996
- src: parentServer.locatorsUrl
1335
+ src: join("/@fs/", script)
997
1336
  },
998
1337
  injectTo: "head"
999
- } : null,
1338
+ })),
1000
1339
  ...testerTags
1001
1340
  ].filter((s) => s != null);
1002
1341
  }
@@ -1110,114 +1449,6 @@ class BrowserServerCDPHandler {
1110
1449
  }
1111
1450
  }
1112
1451
 
1113
- const clear = async (context, selector) => {
1114
- if (context.provider instanceof PlaywrightBrowserProvider) {
1115
- const { iframe } = context;
1116
- const element = iframe.locator(selector);
1117
- await element.clear();
1118
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1119
- const browser = context.browser;
1120
- const element = await browser.$(selector);
1121
- await element.clearValue();
1122
- } else {
1123
- throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1124
- }
1125
- };
1126
-
1127
- const click = async (context, selector, options = {}) => {
1128
- const provider = context.provider;
1129
- if (provider instanceof PlaywrightBrowserProvider) {
1130
- const tester = context.iframe;
1131
- await tester.locator(selector).click(options);
1132
- } else if (provider instanceof WebdriverBrowserProvider) {
1133
- const browser = context.browser;
1134
- await browser.$(selector).click(options);
1135
- } else {
1136
- throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
1137
- }
1138
- };
1139
- const dblClick = async (context, selector, options = {}) => {
1140
- const provider = context.provider;
1141
- if (provider instanceof PlaywrightBrowserProvider) {
1142
- const tester = context.iframe;
1143
- await tester.locator(selector).dblclick(options);
1144
- } else if (provider instanceof WebdriverBrowserProvider) {
1145
- const browser = context.browser;
1146
- await browser.$(selector).doubleClick();
1147
- } else {
1148
- throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
1149
- }
1150
- };
1151
- const tripleClick = async (context, selector, options = {}) => {
1152
- const provider = context.provider;
1153
- if (provider instanceof PlaywrightBrowserProvider) {
1154
- const tester = context.iframe;
1155
- await tester.locator(selector).click({
1156
- ...options,
1157
- clickCount: 3
1158
- });
1159
- } else if (provider instanceof WebdriverBrowserProvider) {
1160
- const browser = context.browser;
1161
- await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
1162
- } else {
1163
- throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
1164
- }
1165
- };
1166
-
1167
- const dragAndDrop = async (context, source, target, options_) => {
1168
- if (context.provider instanceof PlaywrightBrowserProvider) {
1169
- const frame = await context.frame();
1170
- await frame.dragAndDrop(source, target, options_);
1171
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1172
- const $source = context.browser.$(source);
1173
- const $target = context.browser.$(target);
1174
- const options = options_ || {};
1175
- const duration = options.duration ?? 10;
1176
- // https://github.com/webdriverio/webdriverio/issues/8022#issuecomment-1700919670
1177
- await context.browser.action("pointer").move({
1178
- duration: 0,
1179
- origin: $source,
1180
- x: options.sourceX ?? 0,
1181
- y: options.sourceY ?? 0
1182
- }).down({ button: 0 }).move({
1183
- duration: 0,
1184
- origin: "pointer",
1185
- x: 0,
1186
- y: 0
1187
- }).pause(duration).move({
1188
- duration: 0,
1189
- origin: $target,
1190
- x: options.targetX ?? 0,
1191
- y: options.targetY ?? 0
1192
- }).move({
1193
- duration: 0,
1194
- origin: "pointer",
1195
- x: 1,
1196
- y: 0
1197
- }).move({
1198
- duration: 0,
1199
- origin: "pointer",
1200
- x: -1,
1201
- y: 0
1202
- }).up({ button: 0 }).perform();
1203
- } else {
1204
- throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
1205
- }
1206
- };
1207
-
1208
- const fill = async (context, selector, text, options = {}) => {
1209
- if (context.provider instanceof PlaywrightBrowserProvider) {
1210
- const { iframe } = context;
1211
- const element = iframe.locator(selector);
1212
- await element.fill(text, options);
1213
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1214
- const browser = context.browser;
1215
- await browser.$(selector).setValue(text);
1216
- } else {
1217
- throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`);
1218
- }
1219
- };
1220
-
1221
1452
  const types = {
1222
1453
  'application/andrew-inset': ['ez'],
1223
1454
  'application/appinstaller': ['appinstaller'],
@@ -1668,796 +1899,55 @@ class Mime {
1668
1899
  }
1669
1900
  _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
1670
1901
 
1671
- var mime = new Mime(types)._freeze();
1672
-
1673
- function assertFileAccess(path, project) {
1674
- if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
1675
- throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
1676
- }
1677
- }
1678
- const readFile = async ({ project }, path, options = {}) => {
1679
- const filepath = resolve$1(project.config.root, path);
1680
- assertFileAccess(filepath, project);
1681
- // never return a Buffer
1682
- if (typeof options === "object" && !options.encoding) {
1683
- options.encoding = "utf-8";
1684
- }
1685
- return promises.readFile(filepath, options);
1686
- };
1687
- const writeFile = async ({ project }, path, data, options) => {
1688
- const filepath = resolve$1(project.config.root, path);
1689
- assertFileAccess(filepath, project);
1690
- const dir = dirname$1(filepath);
1691
- if (!fs.existsSync(dir)) {
1692
- await promises.mkdir(dir, { recursive: true });
1693
- }
1694
- await promises.writeFile(filepath, data, options);
1695
- };
1696
- const removeFile = async ({ project }, path) => {
1697
- const filepath = resolve$1(project.config.root, path);
1698
- assertFileAccess(filepath, project);
1699
- await promises.rm(filepath);
1700
- };
1701
- const _fileInfo = async ({ project }, path, encoding) => {
1702
- const filepath = resolve$1(project.config.root, path);
1703
- assertFileAccess(filepath, project);
1704
- const content = await promises.readFile(filepath, encoding || "base64");
1705
- return {
1706
- content,
1707
- basename: basename$1(filepath),
1708
- mime: mime.getType(filepath)
1709
- };
1710
- };
1711
-
1712
- const hover = async (context, selector, options = {}) => {
1713
- if (context.provider instanceof PlaywrightBrowserProvider) {
1714
- await context.iframe.locator(selector).hover(options);
1715
- } else if (context.provider instanceof WebdriverBrowserProvider) {
1716
- const browser = context.browser;
1717
- await browser.$(selector).moveTo(options);
1718
- } else {
1719
- throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
1720
- }
1721
- };
1722
-
1723
- var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
1724
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
1725
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
1726
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
1727
- DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
1728
- return DOM_KEY_LOCATION;
1729
- }({});
1730
-
1731
- /**
1732
- * Mapping for a default US-104-QWERTY keyboard
1733
- */ const defaultKeyMap = [
1734
- // alphanumeric block - writing system
1735
- ...'0123456789'.split('').map((c)=>({
1736
- code: `Digit${c}`,
1737
- key: c
1738
- })),
1739
- ...')!@#$%^&*('.split('').map((c, i)=>({
1740
- code: `Digit${i}`,
1741
- key: c,
1742
- shiftKey: true
1743
- })),
1744
- ...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
1745
- code: `Key${c.toUpperCase()}`,
1746
- key: c
1747
- })),
1748
- ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
1749
- code: `Key${c}`,
1750
- key: c,
1751
- shiftKey: true
1752
- })),
1753
- {
1754
- code: 'BracketLeft',
1755
- key: '['
1756
- },
1757
- {
1758
- code: 'BracketLeft',
1759
- key: '{',
1760
- shiftKey: true
1761
- },
1762
- {
1763
- code: 'BracketRight',
1764
- key: ']'
1765
- },
1766
- {
1767
- code: 'BracketRight',
1768
- key: '}',
1769
- shiftKey: true
1770
- },
1771
- // alphanumeric block - functional
1772
- {
1773
- code: 'Space',
1774
- key: ' '
1775
- },
1776
- {
1777
- code: 'AltLeft',
1778
- key: 'Alt',
1779
- location: DOM_KEY_LOCATION.LEFT
1780
- },
1781
- {
1782
- code: 'AltRight',
1783
- key: 'Alt',
1784
- location: DOM_KEY_LOCATION.RIGHT
1785
- },
1786
- {
1787
- code: 'ShiftLeft',
1788
- key: 'Shift',
1789
- location: DOM_KEY_LOCATION.LEFT
1790
- },
1791
- {
1792
- code: 'ShiftRight',
1793
- key: 'Shift',
1794
- location: DOM_KEY_LOCATION.RIGHT
1795
- },
1796
- {
1797
- code: 'ControlLeft',
1798
- key: 'Control',
1799
- location: DOM_KEY_LOCATION.LEFT
1800
- },
1801
- {
1802
- code: 'ControlRight',
1803
- key: 'Control',
1804
- location: DOM_KEY_LOCATION.RIGHT
1805
- },
1806
- {
1807
- code: 'MetaLeft',
1808
- key: 'Meta',
1809
- location: DOM_KEY_LOCATION.LEFT
1810
- },
1811
- {
1812
- code: 'MetaRight',
1813
- key: 'Meta',
1814
- location: DOM_KEY_LOCATION.RIGHT
1815
- },
1816
- {
1817
- code: 'OSLeft',
1818
- key: 'OS',
1819
- location: DOM_KEY_LOCATION.LEFT
1820
- },
1821
- {
1822
- code: 'OSRight',
1823
- key: 'OS',
1824
- location: DOM_KEY_LOCATION.RIGHT
1825
- },
1826
- {
1827
- code: 'ContextMenu',
1828
- key: 'ContextMenu'
1829
- },
1830
- {
1831
- code: 'Tab',
1832
- key: 'Tab'
1833
- },
1834
- {
1835
- code: 'CapsLock',
1836
- key: 'CapsLock'
1837
- },
1838
- {
1839
- code: 'Backspace',
1840
- key: 'Backspace'
1841
- },
1842
- {
1843
- code: 'Enter',
1844
- key: 'Enter'
1845
- },
1846
- // function
1847
- {
1848
- code: 'Escape',
1849
- key: 'Escape'
1850
- },
1851
- // arrows
1852
- {
1853
- code: 'ArrowUp',
1854
- key: 'ArrowUp'
1855
- },
1856
- {
1857
- code: 'ArrowDown',
1858
- key: 'ArrowDown'
1859
- },
1860
- {
1861
- code: 'ArrowLeft',
1862
- key: 'ArrowLeft'
1863
- },
1864
- {
1865
- code: 'ArrowRight',
1866
- key: 'ArrowRight'
1867
- },
1868
- // control pad
1869
- {
1870
- code: 'Home',
1871
- key: 'Home'
1872
- },
1873
- {
1874
- code: 'End',
1875
- key: 'End'
1876
- },
1877
- {
1878
- code: 'Delete',
1879
- key: 'Delete'
1880
- },
1881
- {
1882
- code: 'PageUp',
1883
- key: 'PageUp'
1884
- },
1885
- {
1886
- code: 'PageDown',
1887
- key: 'PageDown'
1888
- },
1889
- // Special keys that are not part of a default US-layout but included for specific behavior
1890
- {
1891
- code: 'Fn',
1892
- key: 'Fn'
1893
- },
1894
- {
1895
- code: 'Symbol',
1896
- key: 'Symbol'
1897
- },
1898
- {
1899
- code: 'AltRight',
1900
- key: 'AltGraph'
1901
- }
1902
- ];
1903
-
1904
- var bracketDict = /*#__PURE__*/ function(bracketDict) {
1905
- bracketDict["{"] = "}";
1906
- bracketDict["["] = "]";
1907
- return bracketDict;
1908
- }(bracketDict || {});
1909
- /**
1910
- * Read the next key definition from user input
1911
- *
1912
- * Describe key per `{descriptor}` or `[descriptor]`.
1913
- * Everything else will be interpreted as a single character as descriptor - e.g. `a`.
1914
- * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
1915
- * A previously pressed key can be released per `{/descriptor}`.
1916
- * Keeping the key pressed can be written as `{descriptor>}`.
1917
- * When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
1918
- * You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
1919
- */ function readNextDescriptor(text, context) {
1920
- let pos = 0;
1921
- const startBracket = text[pos] in bracketDict ? text[pos] : '';
1922
- pos += startBracket.length;
1923
- const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
1924
- const type = isEscapedChar ? '' : startBracket;
1925
- return {
1926
- type,
1927
- ...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
1928
- };
1929
- }
1930
- function readPrintableChar(text, pos, context) {
1931
- const descriptor = text[pos];
1932
- assertDescriptor(descriptor, text, pos);
1933
- pos += descriptor.length;
1934
- return {
1935
- consumedLength: pos,
1936
- descriptor,
1937
- releasePrevious: false,
1938
- releaseSelf: true,
1939
- repeat: 1
1940
- };
1941
- }
1942
- function readTag(text, pos, startBracket, context) {
1943
- var _text_slice_match, _text_slice_match1;
1944
- const releasePreviousModifier = text[pos] === '/' ? '/' : '';
1945
- pos += releasePreviousModifier.length;
1946
- const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
1947
- pos += Number(escapedDescriptor);
1948
- 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];
1949
- assertDescriptor(descriptor, text, pos);
1950
- pos += descriptor.length;
1951
- var _text_slice_match_;
1952
- 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_ : '';
1953
- pos += repeatModifier.length;
1954
- const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
1955
- pos += releaseSelfModifier.length;
1956
- const expectedEndBracket = bracketDict[startBracket];
1957
- const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
1958
- if (!endBracket) {
1959
- throw new Error(getErrorMessage([
1960
- !repeatModifier && 'repeat modifier',
1961
- !releaseSelfModifier && 'release modifier',
1962
- `"${expectedEndBracket}"`
1963
- ].filter(Boolean).join(' or '), text[pos], text));
1964
- }
1965
- pos += endBracket.length;
1966
- return {
1967
- consumedLength: pos,
1968
- descriptor,
1969
- releasePrevious: !!releasePreviousModifier,
1970
- repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
1971
- releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
1972
- };
1973
- }
1974
- function assertDescriptor(descriptor, text, pos, context) {
1975
- if (!descriptor) {
1976
- throw new Error(getErrorMessage('key descriptor', text[pos], text));
1977
- }
1978
- }
1979
- function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
1980
- if (releaseSelfModifier) {
1981
- return releaseSelfModifier === '/';
1982
- }
1983
- if (repeatModifier) {
1984
- return false;
1985
- }
1986
- }
1987
- function getErrorMessage(expected, found, text, context) {
1988
- return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
1989
- See ${`https://testing-library.com/docs/user-event/keyboard`}
1990
- for more information about how userEvent parses your input.`;
1991
- }
1992
-
1993
- /**
1994
- * Parse key definitions per `keyboardMap`
1995
- *
1996
- * Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
1997
- * Everything else will be interpreted as a typed character - e.g. `a`.
1998
- * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
1999
- * Keeping the key pressed can be written as `{key>}`.
2000
- * When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
2001
- * You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
2002
- */ function parseKeyDef(keyboardMap, text) {
2003
- const defs = [];
2004
- do {
2005
- const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
2006
- var _keyboardMap_find;
2007
- const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
2008
- if (type === '[') {
2009
- var _def_code;
2010
- return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
2011
- } else if (type === '{') {
2012
- var _def_key;
2013
- return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
2014
- }
2015
- return def.key === descriptor;
2016
- })) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
2017
- key: 'Unknown',
2018
- code: 'Unknown',
2019
- [type === '[' ? 'code' : 'key']: descriptor
2020
- };
2021
- defs.push({
2022
- keyDef,
2023
- releasePrevious,
2024
- releaseSelf,
2025
- repeat
2026
- });
2027
- text = text.slice(consumedLength);
2028
- }while (text)
2029
- return defs;
2030
- }
2031
-
2032
- const keyboard = async (context, text, state) => {
2033
- if (context.provider instanceof PlaywrightBrowserProvider) {
2034
- const frame = await context.frame();
2035
- await frame.evaluate(focusIframe);
2036
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2037
- await context.browser.execute(focusIframe);
2038
- }
2039
- const pressed = new Set(state.unreleased);
2040
- await keyboardImplementation(pressed, context.provider, context.sessionId, text, async () => {
2041
- if (context.provider instanceof PlaywrightBrowserProvider) {
2042
- const frame = await context.frame();
2043
- await frame.evaluate(selectAll);
2044
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2045
- await context.browser.execute(selectAll);
2046
- } else {
2047
- throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
2048
- }
2049
- }, true);
2050
- return { unreleased: Array.from(pressed) };
2051
- };
2052
- const keyboardCleanup = async (context, state) => {
2053
- const { provider, sessionId } = context;
2054
- if (!state.unreleased) {
2055
- return;
2056
- }
2057
- if (provider instanceof PlaywrightBrowserProvider) {
2058
- const page = provider.getPage(sessionId);
2059
- for (const key of state.unreleased) {
2060
- await page.keyboard.up(key);
2061
- }
2062
- } else if (provider instanceof WebdriverBrowserProvider) {
2063
- const keyboard = provider.browser.action("key");
2064
- for (const key of state.unreleased) {
2065
- keyboard.up(key);
2066
- }
2067
- await keyboard.perform();
2068
- } else {
2069
- throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`);
2070
- }
2071
- };
2072
- // fallback to insertText for non US key
2073
- // https://github.com/microsoft/playwright/blob/50775698ae13642742f2a1e8983d1d686d7f192d/packages/playwright-core/src/server/input.ts#L95
2074
- const VALID_KEYS = new Set([
2075
- "Escape",
2076
- "F1",
2077
- "F2",
2078
- "F3",
2079
- "F4",
2080
- "F5",
2081
- "F6",
2082
- "F7",
2083
- "F8",
2084
- "F9",
2085
- "F10",
2086
- "F11",
2087
- "F12",
2088
- "Backquote",
2089
- "`",
2090
- "~",
2091
- "Digit1",
2092
- "1",
2093
- "!",
2094
- "Digit2",
2095
- "2",
2096
- "@",
2097
- "Digit3",
2098
- "3",
2099
- "#",
2100
- "Digit4",
2101
- "4",
2102
- "$",
2103
- "Digit5",
2104
- "5",
2105
- "%",
2106
- "Digit6",
2107
- "6",
2108
- "^",
2109
- "Digit7",
2110
- "7",
2111
- "&",
2112
- "Digit8",
2113
- "8",
2114
- "*",
2115
- "Digit9",
2116
- "9",
2117
- "(",
2118
- "Digit0",
2119
- "0",
2120
- ")",
2121
- "Minus",
2122
- "-",
2123
- "_",
2124
- "Equal",
2125
- "=",
2126
- "+",
2127
- "Backslash",
2128
- "\\",
2129
- "|",
2130
- "Backspace",
2131
- "Tab",
2132
- "KeyQ",
2133
- "q",
2134
- "Q",
2135
- "KeyW",
2136
- "w",
2137
- "W",
2138
- "KeyE",
2139
- "e",
2140
- "E",
2141
- "KeyR",
2142
- "r",
2143
- "R",
2144
- "KeyT",
2145
- "t",
2146
- "T",
2147
- "KeyY",
2148
- "y",
2149
- "Y",
2150
- "KeyU",
2151
- "u",
2152
- "U",
2153
- "KeyI",
2154
- "i",
2155
- "I",
2156
- "KeyO",
2157
- "o",
2158
- "O",
2159
- "KeyP",
2160
- "p",
2161
- "P",
2162
- "BracketLeft",
2163
- "[",
2164
- "{",
2165
- "BracketRight",
2166
- "]",
2167
- "}",
2168
- "CapsLock",
2169
- "KeyA",
2170
- "a",
2171
- "A",
2172
- "KeyS",
2173
- "s",
2174
- "S",
2175
- "KeyD",
2176
- "d",
2177
- "D",
2178
- "KeyF",
2179
- "f",
2180
- "F",
2181
- "KeyG",
2182
- "g",
2183
- "G",
2184
- "KeyH",
2185
- "h",
2186
- "H",
2187
- "KeyJ",
2188
- "j",
2189
- "J",
2190
- "KeyK",
2191
- "k",
2192
- "K",
2193
- "KeyL",
2194
- "l",
2195
- "L",
2196
- "Semicolon",
2197
- ";",
2198
- ":",
2199
- "Quote",
2200
- "'",
2201
- "\"",
2202
- "Enter",
2203
- "\n",
2204
- "\r",
2205
- "ShiftLeft",
2206
- "Shift",
2207
- "KeyZ",
2208
- "z",
2209
- "Z",
2210
- "KeyX",
2211
- "x",
2212
- "X",
2213
- "KeyC",
2214
- "c",
2215
- "C",
2216
- "KeyV",
2217
- "v",
2218
- "V",
2219
- "KeyB",
2220
- "b",
2221
- "B",
2222
- "KeyN",
2223
- "n",
2224
- "N",
2225
- "KeyM",
2226
- "m",
2227
- "M",
2228
- "Comma",
2229
- ",",
2230
- "<",
2231
- "Period",
2232
- ".",
2233
- ">",
2234
- "Slash",
2235
- "/",
2236
- "?",
2237
- "ShiftRight",
2238
- "ControlLeft",
2239
- "Control",
2240
- "MetaLeft",
2241
- "Meta",
2242
- "AltLeft",
2243
- "Alt",
2244
- "Space",
2245
- " ",
2246
- "AltRight",
2247
- "AltGraph",
2248
- "MetaRight",
2249
- "ContextMenu",
2250
- "ControlRight",
2251
- "PrintScreen",
2252
- "ScrollLock",
2253
- "Pause",
2254
- "PageUp",
2255
- "PageDown",
2256
- "Insert",
2257
- "Delete",
2258
- "Home",
2259
- "End",
2260
- "ArrowLeft",
2261
- "ArrowUp",
2262
- "ArrowRight",
2263
- "ArrowDown",
2264
- "NumLock",
2265
- "NumpadDivide",
2266
- "NumpadMultiply",
2267
- "NumpadSubtract",
2268
- "Numpad7",
2269
- "Numpad8",
2270
- "Numpad9",
2271
- "Numpad4",
2272
- "Numpad5",
2273
- "Numpad6",
2274
- "NumpadAdd",
2275
- "Numpad1",
2276
- "Numpad2",
2277
- "Numpad3",
2278
- "Numpad0",
2279
- "NumpadDecimal",
2280
- "NumpadEnter",
2281
- "ControlOrMeta"
2282
- ]);
2283
- async function keyboardImplementation(pressed, provider, sessionId, text, selectAll, skipRelease) {
2284
- if (provider instanceof PlaywrightBrowserProvider) {
2285
- const page = provider.getPage(sessionId);
2286
- const actions = parseKeyDef(defaultKeyMap, text);
2287
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2288
- const key = keyDef.key;
2289
- // TODO: instead of calling down/up for each key, join non special
2290
- // together, and call `type` once for all non special keys,
2291
- // and then `press` for special keys
2292
- if (pressed.has(key)) {
2293
- if (VALID_KEYS.has(key)) {
2294
- await page.keyboard.up(key);
2295
- }
2296
- pressed.delete(key);
2297
- }
2298
- if (!releasePrevious) {
2299
- if (key === "selectall") {
2300
- await selectAll();
2301
- continue;
2302
- }
2303
- for (let i = 1; i <= repeat; i++) {
2304
- if (VALID_KEYS.has(key)) {
2305
- await page.keyboard.down(key);
2306
- } else {
2307
- await page.keyboard.insertText(key);
2308
- }
2309
- }
2310
- if (releaseSelf) {
2311
- if (VALID_KEYS.has(key)) {
2312
- await page.keyboard.up(key);
2313
- }
2314
- } else {
2315
- pressed.add(key);
2316
- }
2317
- }
2318
- }
2319
- if (!skipRelease && pressed.size) {
2320
- for (const key of pressed) {
2321
- if (VALID_KEYS.has(key)) {
2322
- await page.keyboard.up(key);
2323
- }
2324
- }
2325
- }
2326
- } else if (provider instanceof WebdriverBrowserProvider) {
2327
- const { Key } = await import('webdriverio');
2328
- const browser = provider.browser;
2329
- const actions = parseKeyDef(defaultKeyMap, text);
2330
- let keyboard = browser.action("key");
2331
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
2332
- let key = keyDef.key;
2333
- const special = Key[key];
2334
- if (special) {
2335
- key = special;
2336
- }
2337
- if (pressed.has(key)) {
2338
- keyboard.up(key);
2339
- pressed.delete(key);
2340
- }
2341
- if (!releasePrevious) {
2342
- if (key === "selectall") {
2343
- await keyboard.perform();
2344
- keyboard = browser.action("key");
2345
- await selectAll();
2346
- continue;
2347
- }
2348
- for (let i = 1; i <= repeat; i++) {
2349
- keyboard.down(key);
2350
- }
2351
- if (releaseSelf) {
2352
- keyboard.up(key);
2353
- } else {
2354
- pressed.add(key);
2355
- }
2356
- }
2357
- }
2358
- // seems like webdriverio doesn't release keys automatically if skipRelease is true and all events are keyUp
2359
- const allRelease = keyboard.toJSON().actions.every((action) => action.type === "keyUp");
2360
- await keyboard.perform(allRelease ? false : skipRelease);
1902
+ var mime = new Mime(types)._freeze();
1903
+
1904
+ function assertFileAccess(path, project) {
1905
+ if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
1906
+ throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
2361
1907
  }
2362
- return { pressed };
2363
1908
  }
2364
- function focusIframe() {
2365
- if (!document.activeElement || document.activeElement.ownerDocument !== document || document.activeElement === document.body) {
2366
- window.focus();
1909
+ const readFile = async ({ project }, path, options = {}) => {
1910
+ const filepath = resolve$1(project.config.root, path);
1911
+ assertFileAccess(filepath, project);
1912
+ // never return a Buffer
1913
+ if (typeof options === "object" && !options.encoding) {
1914
+ options.encoding = "utf-8";
2367
1915
  }
2368
- }
2369
- function selectAll() {
2370
- const element = document.activeElement;
2371
- if (element && typeof element.select === "function") {
2372
- element.select();
1916
+ return promises.readFile(filepath, options);
1917
+ };
1918
+ const writeFile = async ({ project }, path, data, options) => {
1919
+ const filepath = resolve$1(project.config.root, path);
1920
+ assertFileAccess(filepath, project);
1921
+ const dir = dirname$1(filepath);
1922
+ if (!fs.existsSync(dir)) {
1923
+ await promises.mkdir(dir, { recursive: true });
2373
1924
  }
2374
- }
1925
+ await promises.writeFile(filepath, data, options);
1926
+ };
1927
+ const removeFile = async ({ project }, path) => {
1928
+ const filepath = resolve$1(project.config.root, path);
1929
+ assertFileAccess(filepath, project);
1930
+ await promises.rm(filepath);
1931
+ };
1932
+ const _fileInfo = async ({ project }, path, encoding) => {
1933
+ const filepath = resolve$1(project.config.root, path);
1934
+ assertFileAccess(filepath, project);
1935
+ const content = await promises.readFile(filepath, encoding || "base64");
1936
+ return {
1937
+ content,
1938
+ basename: basename$1(filepath),
1939
+ mime: mime.getType(filepath)
1940
+ };
1941
+ };
2375
1942
 
2376
1943
  const screenshot = async (context, name, options = {}) => {
2377
1944
  options.save ??= true;
2378
1945
  if (!options.save) {
2379
1946
  options.base64 = true;
2380
1947
  }
2381
- const { buffer, path } = await takeScreenshot(context, name, options);
1948
+ const { buffer, path } = await context.triggerCommand("__vitest_takeScreenshot", name, options);
2382
1949
  return returnResult(options, path, buffer);
2383
1950
  };
2384
- /**
2385
- * Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
2386
- *
2387
- * **Note**: the returned `path` indicates where the screenshot *might* be found.
2388
- * It is not guaranteed to exist, especially if `options.save` is `false`.
2389
- *
2390
- * @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
2391
- */
2392
- async function takeScreenshot(context, name, options) {
2393
- if (!context.testPath) {
2394
- throw new Error(`Cannot take a screenshot without a test path`);
2395
- }
2396
- const path = resolveScreenshotPath(context.testPath, name, context.project.config, options.path);
2397
- // playwright does not need a screenshot path if we don't intend to save it
2398
- let savePath;
2399
- if (options.save) {
2400
- savePath = normalize(path);
2401
- await mkdir(dirname(savePath), { recursive: true });
2402
- }
2403
- if (context.provider instanceof PlaywrightBrowserProvider) {
2404
- const mask = options.mask?.map((selector) => context.iframe.locator(selector));
2405
- if (options.element) {
2406
- const { element: selector,...config } = options;
2407
- const element = context.iframe.locator(selector);
2408
- const buffer = await element.screenshot({
2409
- ...config,
2410
- mask,
2411
- path: savePath
2412
- });
2413
- return {
2414
- buffer,
2415
- path
2416
- };
2417
- }
2418
- const buffer = await context.iframe.locator("body").screenshot({
2419
- ...options,
2420
- mask,
2421
- path: savePath
2422
- });
2423
- return {
2424
- buffer,
2425
- path
2426
- };
2427
- }
2428
- if (context.provider instanceof WebdriverBrowserProvider) {
2429
- // webdriverio needs a path, so if one is not already set we create a temporary one
2430
- if (savePath === undefined) {
2431
- savePath = resolve(context.project.tmpDir, nanoid$1());
2432
- await mkdir(context.project.tmpDir, { recursive: true });
2433
- }
2434
- const page = context.provider.browser;
2435
- const element = !options.element ? await page.$("body") : await page.$(`${options.element}`);
2436
- // webdriverio expects the path to contain the extension and only works with PNG files
2437
- const savePathWithExtension = savePath.endsWith(".png") ? savePath : `${savePath}.png`;
2438
- // there seems to be a bug in webdriverio, `X:/` gets appended to cwd, so we convert to `X:\`
2439
- const buffer = await element.saveScreenshot(normalize$1(savePathWithExtension));
2440
- if (!options.save) {
2441
- await rm(savePathWithExtension, { force: true });
2442
- }
2443
- return {
2444
- buffer,
2445
- path
2446
- };
2447
- }
2448
- throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
2449
- }
2450
- function resolveScreenshotPath(testPath, name, config, customPath) {
2451
- if (customPath) {
2452
- return resolve(dirname(testPath), customPath);
2453
- }
2454
- const dir = dirname(testPath);
2455
- const base = basename(testPath);
2456
- if (config.browser.screenshotDirectory) {
2457
- return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
2458
- }
2459
- return resolve(dir, "__screenshots__", base, name);
2460
- }
2461
1951
  function returnResult(options, path, buffer) {
2462
1952
  if (!options.save) {
2463
1953
  return buffer.toString("base64");
@@ -2685,7 +2175,7 @@ function sanitizeArg(input) {
2685
2175
  * @returns `Promise` resolving to the decoded screenshot data
2686
2176
  */
2687
2177
  function takeDecodedScreenshot({ codec, context, element, name, screenshotOptions }) {
2688
- return takeScreenshot(context, name, {
2178
+ return context.triggerCommand("__vitest_takeScreenshot", name, {
2689
2179
  ...screenshotOptions,
2690
2180
  save: false,
2691
2181
  element
@@ -2866,248 +2356,13 @@ async function getStableScreenshots({ codec, context, comparator, comparatorOpti
2866
2356
  };
2867
2357
  }
2868
2358
 
2869
- const selectOptions = async (context, selector, userValues, options = {}) => {
2870
- if (context.provider instanceof PlaywrightBrowserProvider) {
2871
- const value = userValues;
2872
- const { iframe } = context;
2873
- const selectElement = iframe.locator(selector);
2874
- const values = await Promise.all(value.map(async (v) => {
2875
- if (typeof v === "string") {
2876
- return v;
2877
- }
2878
- const elementHandler = await iframe.locator(v.element).elementHandle();
2879
- if (!elementHandler) {
2880
- throw new Error(`Element not found: ${v.element}`);
2881
- }
2882
- return elementHandler;
2883
- }));
2884
- await selectElement.selectOption(values, options);
2885
- } else if (context.provider instanceof WebdriverBrowserProvider) {
2886
- const values = userValues;
2887
- if (!values.length) {
2888
- return;
2889
- }
2890
- const browser = context.browser;
2891
- if (values.length === 1 && "index" in values[0]) {
2892
- const selectElement = browser.$(selector);
2893
- await selectElement.selectByIndex(values[0].index);
2894
- } else {
2895
- throw new Error("Provider \"webdriverio\" doesn't support selecting multiple values at once");
2896
- }
2897
- } else {
2898
- throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
2899
- }
2900
- };
2901
-
2902
- const tab = async (context, options = {}) => {
2903
- const provider = context.provider;
2904
- if (provider instanceof PlaywrightBrowserProvider) {
2905
- const page = context.page;
2906
- await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
2907
- return;
2908
- }
2909
- if (provider instanceof WebdriverBrowserProvider) {
2910
- const { Key } = await import('webdriverio');
2911
- const browser = context.browser;
2912
- await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
2913
- return;
2914
- }
2915
- throw new Error(`Provider "${provider.name}" doesn't support tab command`);
2916
- };
2917
-
2918
- const startTracing = async ({ context, project, provider, sessionId }) => {
2919
- if (provider instanceof PlaywrightBrowserProvider) {
2920
- if (provider.tracingContexts.has(sessionId)) {
2921
- return;
2922
- }
2923
- provider.tracingContexts.add(sessionId);
2924
- const options = project.config.browser.trace;
2925
- await context.tracing.start({
2926
- screenshots: options.screenshots ?? true,
2927
- snapshots: options.snapshots ?? true,
2928
- sources: false
2929
- }).catch(() => {
2930
- provider.tracingContexts.delete(sessionId);
2931
- });
2932
- return;
2933
- }
2934
- throw new TypeError(`The ${provider.name} provider does not support tracing.`);
2935
- };
2936
- const startChunkTrace = async (command, { name, title }) => {
2937
- const { provider, sessionId, testPath, context } = command;
2938
- if (!testPath) {
2939
- throw new Error(`stopChunkTrace cannot be called outside of the test file.`);
2940
- }
2941
- if (provider instanceof PlaywrightBrowserProvider) {
2942
- if (!provider.tracingContexts.has(sessionId)) {
2943
- await startTracing(command);
2944
- }
2945
- const path = resolveTracesPath(command, name);
2946
- provider.pendingTraces.set(path, sessionId);
2947
- await context.tracing.startChunk({
2948
- name,
2949
- title
2950
- });
2951
- return;
2952
- }
2953
- throw new TypeError(`The ${provider.name} provider does not support tracing.`);
2954
- };
2955
- const stopChunkTrace = async (context, { name }) => {
2956
- if (context.provider instanceof PlaywrightBrowserProvider) {
2957
- const path = resolveTracesPath(context, name);
2958
- context.provider.pendingTraces.delete(path);
2959
- await context.context.tracing.stopChunk({ path });
2960
- return { tracePath: path };
2961
- }
2962
- throw new TypeError(`The ${context.provider.name} provider does not support tracing.`);
2963
- };
2964
- function resolveTracesPath({ testPath, project }, name) {
2965
- if (!testPath) {
2966
- throw new Error(`This command can only be called inside a test file.`);
2967
- }
2968
- const options = project.config.browser.trace;
2969
- const sanitizedName = `${project.name.replace(/[^a-z0-9]/gi, "-")}-${name}.trace.zip`;
2970
- if (options.tracesDir) {
2971
- return resolve(options.tracesDir, sanitizedName);
2972
- }
2973
- const dir = dirname(testPath);
2974
- const base = basename(testPath);
2975
- return resolve(dir, "__traces__", base, `${project.name.replace(/[^a-z0-9]/gi, "-")}-${name}.trace.zip`);
2976
- }
2977
- const deleteTracing = async (context, { traces }) => {
2978
- if (!context.testPath) {
2979
- throw new Error(`stopChunkTrace cannot be called outside of the test file.`);
2980
- }
2981
- if (context.provider instanceof PlaywrightBrowserProvider) {
2982
- return Promise.all(traces.map((trace) => unlink(trace).catch((err) => {
2983
- if (err.code === "ENOENT") {
2984
- // Ignore the error if the file doesn't exist
2985
- return;
2986
- }
2987
- // Re-throw other errors
2988
- throw err;
2989
- })));
2990
- }
2991
- throw new Error(`provider ${context.provider.name} is not supported`);
2992
- };
2993
- const annotateTraces = async ({ project }, { testId, traces }) => {
2994
- const vitest = project.vitest;
2995
- await Promise.all(traces.map((trace) => {
2996
- const entity = vitest.state.getReportedEntityById(testId);
2997
- return vitest._testRun.annotate(testId, {
2998
- message: relative(project.config.root, trace),
2999
- type: "traces",
3000
- attachment: {
3001
- path: trace,
3002
- contentType: "application/octet-stream"
3003
- },
3004
- location: entity?.location ? {
3005
- file: entity.module.moduleId,
3006
- line: entity.location.line,
3007
- column: entity.location.column
3008
- } : undefined
3009
- });
3010
- }));
3011
- };
3012
-
3013
- const type = async (context, selector, text, options = {}) => {
3014
- const { skipClick = false, skipAutoClose = false } = options;
3015
- const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
3016
- if (context.provider instanceof PlaywrightBrowserProvider) {
3017
- const { iframe } = context;
3018
- const element = iframe.locator(selector);
3019
- if (!skipClick) {
3020
- await element.focus();
3021
- }
3022
- await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => element.selectText(), skipAutoClose);
3023
- } else if (context.provider instanceof WebdriverBrowserProvider) {
3024
- const browser = context.browser;
3025
- const element = browser.$(selector);
3026
- if (!skipClick && !await element.isFocused()) {
3027
- await element.click();
3028
- }
3029
- await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => browser.execute(() => {
3030
- const element = document.activeElement;
3031
- if (element && typeof element.select === "function") {
3032
- element.select();
3033
- }
3034
- }), skipAutoClose);
3035
- } else {
3036
- throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
3037
- }
3038
- return { unreleased: Array.from(unreleased) };
3039
- };
3040
-
3041
- const upload = async (context, selector, files, options) => {
3042
- const testPath = context.testPath;
3043
- if (!testPath) {
3044
- throw new Error(`Cannot upload files outside of a test`);
3045
- }
3046
- const root = context.project.config.root;
3047
- if (context.provider instanceof PlaywrightBrowserProvider) {
3048
- const { iframe } = context;
3049
- const playwrightFiles = files.map((file) => {
3050
- if (typeof file === "string") {
3051
- return resolve(root, file);
3052
- }
3053
- return {
3054
- name: file.name,
3055
- mimeType: file.mimeType,
3056
- buffer: Buffer.from(file.base64, "base64")
3057
- };
3058
- });
3059
- await iframe.locator(selector).setInputFiles(playwrightFiles, options);
3060
- } else if (context.provider instanceof WebdriverBrowserProvider) {
3061
- for (const file of files) {
3062
- if (typeof file !== "string") {
3063
- throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`);
3064
- }
3065
- }
3066
- const element = context.browser.$(selector);
3067
- for (const file of files) {
3068
- const filepath = resolve(root, file);
3069
- const remoteFilePath = await context.browser.uploadFile(filepath);
3070
- await element.addValue(remoteFilePath);
3071
- }
3072
- } else {
3073
- throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`);
3074
- }
3075
- };
3076
-
3077
- const viewport = async (context, options) => {
3078
- if (context.provider instanceof WebdriverBrowserProvider) {
3079
- await context.provider.setViewport(options);
3080
- } else {
3081
- throw new TypeError(`Provider ${context.provider.name} doesn't support "viewport" command`);
3082
- }
3083
- };
3084
-
3085
2359
  var builtinCommands = {
3086
2360
  readFile,
3087
2361
  removeFile,
3088
2362
  writeFile,
3089
2363
  __vitest_fileInfo: _fileInfo,
3090
- __vitest_upload: upload,
3091
- __vitest_click: click,
3092
- __vitest_dblClick: dblClick,
3093
- __vitest_tripleClick: tripleClick,
3094
2364
  __vitest_screenshot: screenshot,
3095
- __vitest_type: type,
3096
- __vitest_clear: clear,
3097
- __vitest_fill: fill,
3098
- __vitest_tab: tab,
3099
- __vitest_keyboard: keyboard,
3100
- __vitest_selectOptions: selectOptions,
3101
- __vitest_dragAndDrop: dragAndDrop,
3102
- __vitest_hover: hover,
3103
- __vitest_cleanup: keyboardCleanup,
3104
- __vitest_viewport: viewport,
3105
- __vitest_screenshotMatcher: screenshotMatcher,
3106
- __vitest_deleteTracing: deleteTracing,
3107
- __vitest_startChunkTrace: startChunkTrace,
3108
- __vitest_startTracing: startTracing,
3109
- __vitest_stopChunkTrace: stopChunkTrace,
3110
- __vitest_annotateTraces: annotateTraces
2365
+ __vitest_screenshotMatcher: screenshotMatcher
3111
2366
  };
3112
2367
 
3113
2368
  class BrowserServerState {
@@ -3141,6 +2396,22 @@ class ProjectBrowser {
3141
2396
  get vite() {
3142
2397
  return this.parent.vite;
3143
2398
  }
2399
+ commands = {};
2400
+ registerCommand(name, cb) {
2401
+ if (!/^[a-z_$][\w$]*$/i.test(name)) {
2402
+ throw new Error(`Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`);
2403
+ }
2404
+ this.commands[name] = cb;
2405
+ }
2406
+ triggerCommand = ((name, context, ...args) => {
2407
+ if (name in this.commands) {
2408
+ return this.commands[name](context, ...args);
2409
+ }
2410
+ if (name in this.parent.commands) {
2411
+ return this.parent.commands[name](context, ...args);
2412
+ }
2413
+ throw new Error(`Provider ${this.provider.name} does not support command "${name}".`);
2414
+ });
3144
2415
  wrapSerializedConfig() {
3145
2416
  const config = wrapConfig(this.project.serializedConfig);
3146
2417
  config.env ??= {};
@@ -3152,6 +2423,16 @@ class ProjectBrowser {
3152
2423
  return;
3153
2424
  }
3154
2425
  this.provider = await getBrowserProvider(project.config.browser, project);
2426
+ if (this.provider.initScripts) {
2427
+ this.parent.initScripts = this.provider.initScripts;
2428
+ // make sure the script can be imported
2429
+ this.provider.initScripts.forEach((script) => {
2430
+ const allow = this.parent.vite.config.server.fs.allow;
2431
+ if (!allow.includes(script)) {
2432
+ allow.push(script);
2433
+ }
2434
+ });
2435
+ }
3155
2436
  }
3156
2437
  parseErrorStacktrace(e, options = {}) {
3157
2438
  return this.parent.parseErrorStacktrace(e, options);
@@ -3184,6 +2465,7 @@ class ParentBrowserProject {
3184
2465
  locatorsUrl;
3185
2466
  matchersUrl;
3186
2467
  stateJs;
2468
+ initScripts = [];
3187
2469
  commands = {};
3188
2470
  children = new Set();
3189
2471
  vitest;
@@ -3255,15 +2537,6 @@ class ParentBrowserProject {
3255
2537
  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);
3256
2538
  this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
3257
2539
  this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
3258
- const builtinProviders = [
3259
- "playwright",
3260
- "webdriverio",
3261
- "preview"
3262
- ];
3263
- const providerName = project.config.browser.provider?.name || "preview";
3264
- if (builtinProviders.includes(providerName)) {
3265
- this.locatorsUrl = join("/@fs/", distRoot, "locators", `${providerName}.js`);
3266
- }
3267
2540
  this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
3268
2541
  this.stateJs = readFile$1(resolve(distRoot, "state.js"), "utf-8").then((js) => this.stateJs = js);
3269
2542
  }
@@ -3550,7 +2823,7 @@ function nanoid(size = 21) {
3550
2823
  return id;
3551
2824
  }
3552
2825
 
3553
- const debug$1 = createDebugger("vitest:browser:api");
2826
+ const debug = createDebugger("vitest:browser:api");
3554
2827
  const BROWSER_API_PATH = "/__vitest_browser_api__";
3555
2828
  function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3556
2829
  const vite = globalServer.vite;
@@ -3598,9 +2871,9 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3598
2871
  const state = project.browser.state;
3599
2872
  const clients = type === "tester" ? state.testers : state.orchestrators;
3600
2873
  clients.set(rpcId, rpc);
3601
- debug$1?.("[%s] Browser API connected to %s", rpcId, type);
2874
+ debug?.("[%s] Browser API connected to %s", rpcId, type);
3602
2875
  ws.on("close", () => {
3603
- debug$1?.("[%s] Browser API disconnected from %s", rpcId, type);
2876
+ debug?.("[%s] Browser API disconnected from %s", rpcId, type);
3604
2877
  clients.delete(rpcId);
3605
2878
  globalServer.removeCDPHandler(rpcId);
3606
2879
  if (type === "orchestrator") {
@@ -3725,23 +2998,22 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3725
2998
  }
3726
2999
  },
3727
3000
  async triggerCommand(sessionId, command, testPath, payload) {
3728
- debug$1?.("[%s] Triggering command \"%s\"", sessionId, command);
3001
+ debug?.("[%s] Triggering command \"%s\"", sessionId, command);
3729
3002
  const provider = project.browser.provider;
3730
3003
  if (!provider) {
3731
3004
  throw new Error("Commands are only available for browser tests.");
3732
3005
  }
3733
- const commands = globalServer.commands;
3734
- if (!commands || !commands[command]) {
3735
- throw new Error(`Unknown command "${command}".`);
3736
- }
3737
3006
  const context = Object.assign({
3738
3007
  testPath,
3739
3008
  project,
3740
3009
  provider,
3741
3010
  contextId: sessionId,
3742
- sessionId
3011
+ sessionId,
3012
+ triggerCommand: (name, ...args) => {
3013
+ return project.browser.triggerCommand(name, context, ...args);
3014
+ }
3743
3015
  }, provider.getCommandsContext(sessionId));
3744
- return await commands[command](context, ...payload);
3016
+ return await project.browser.triggerCommand(command, context, ...payload);
3745
3017
  },
3746
3018
  resolveMock(rawId, importer, options) {
3747
3019
  return mockResolver.resolveMock(rawId, importer, options);
@@ -3847,276 +3119,12 @@ function stringifyReplace(key, value) {
3847
3119
  }
3848
3120
  }
3849
3121
 
3850
- const debug = createDebugger("vitest:browser:pool");
3851
- function createBrowserPool(vitest) {
3852
- const providers = new Set();
3853
- const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
3854
- const threadsCount = vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
3855
- const projectPools = new WeakMap();
3856
- const ensurePool = (project) => {
3857
- if (projectPools.has(project)) {
3858
- return projectPools.get(project);
3859
- }
3860
- debug?.("creating pool for project %s", project.name);
3861
- const resolvedUrls = project.browser.vite.resolvedUrls;
3862
- const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
3863
- if (!origin) {
3864
- throw new Error(`Can't find browser origin URL for project "${project.name}"`);
3865
- }
3866
- const pool = new BrowserPool(project, {
3867
- maxWorkers: getThreadsCount(project),
3868
- origin
3869
- });
3870
- projectPools.set(project, pool);
3871
- vitest.onCancel(() => {
3872
- pool.cancel();
3873
- });
3874
- return pool;
3875
- };
3876
- const runWorkspaceTests = async (method, specs) => {
3877
- const groupedFiles = new Map();
3878
- for (const { project, moduleId } of specs) {
3879
- const files = groupedFiles.get(project) || [];
3880
- files.push(moduleId);
3881
- groupedFiles.set(project, files);
3882
- }
3883
- let isCancelled = false;
3884
- vitest.onCancel(() => {
3885
- isCancelled = true;
3886
- });
3887
- const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
3888
- await project._initBrowserProvider();
3889
- if (!project.browser) {
3890
- 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.`);
3891
- }
3892
- if (isCancelled) {
3893
- return;
3894
- }
3895
- debug?.("provider is ready for %s project", project.name);
3896
- const pool = ensurePool(project);
3897
- vitest.state.clearFiles(project, files);
3898
- providers.add(project.browser.provider);
3899
- return {
3900
- pool,
3901
- provider: project.browser.provider,
3902
- runTests: () => pool.runTests(method, files)
3903
- };
3904
- }));
3905
- if (isCancelled) {
3906
- return;
3907
- }
3908
- const parallelPools = [];
3909
- const nonParallelPools = [];
3910
- for (const pool of initialisedPools) {
3911
- if (!pool) {
3912
- // this means it was cancelled
3913
- return;
3914
- }
3915
- if (pool.provider.mocker && pool.provider.supportsParallelism) {
3916
- parallelPools.push(pool.runTests);
3917
- } else {
3918
- nonParallelPools.push(pool.runTests);
3919
- }
3920
- }
3921
- await Promise.all(parallelPools.map((runTests) => runTests()));
3922
- for (const runTests of nonParallelPools) {
3923
- if (isCancelled) {
3924
- return;
3925
- }
3926
- await runTests();
3927
- }
3928
- };
3929
- function getThreadsCount(project) {
3930
- const config = project.config.browser;
3931
- if (!config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism) {
3932
- return 1;
3933
- }
3934
- if (project.config.maxWorkers) {
3935
- return project.config.maxWorkers;
3936
- }
3937
- return threadsCount;
3938
- }
3939
- return {
3940
- name: "browser",
3941
- async close() {
3942
- await Promise.all([...providers].map((provider) => provider.close()));
3943
- vitest._browserSessions.sessionIds.clear();
3944
- providers.clear();
3945
- vitest.projects.forEach((project) => {
3946
- project.browser?.state.orchestrators.forEach((orchestrator) => {
3947
- orchestrator.$close();
3948
- });
3949
- });
3950
- debug?.("browser pool closed all providers");
3951
- },
3952
- runTests: (files) => runWorkspaceTests("run", files),
3953
- collectTests: (files) => runWorkspaceTests("collect", files)
3954
- };
3955
- }
3956
- function escapePathToRegexp(path) {
3957
- return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
3958
- }
3959
- class BrowserPool {
3960
- _queue = [];
3961
- _promise;
3962
- _providedContext;
3963
- readySessions = new Set();
3964
- constructor(project, options) {
3965
- this.project = project;
3966
- this.options = options;
3967
- }
3968
- cancel() {
3969
- this._queue = [];
3970
- }
3971
- reject(error) {
3972
- this._promise?.reject(error);
3973
- this._promise = undefined;
3974
- this.cancel();
3975
- }
3976
- get orchestrators() {
3977
- return this.project.browser.state.orchestrators;
3978
- }
3979
- async runTests(method, files) {
3980
- this._promise ??= createDefer();
3981
- if (!files.length) {
3982
- debug?.("no tests found, finishing test run immediately");
3983
- this._promise.resolve();
3984
- return this._promise;
3985
- }
3986
- this._providedContext = stringify(this.project.getProvidedContext());
3987
- this._queue.push(...files);
3988
- this.readySessions.forEach((sessionId) => {
3989
- if (this._queue.length) {
3990
- this.readySessions.delete(sessionId);
3991
- this.runNextTest(method, sessionId);
3992
- }
3993
- });
3994
- if (this.orchestrators.size >= this.options.maxWorkers) {
3995
- debug?.("all orchestrators are ready, not creating more");
3996
- return this._promise;
3997
- }
3998
- // open the minimum amount of tabs
3999
- // if there is only 1 file running, we don't need 8 tabs running
4000
- const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length);
4001
- const promises = [];
4002
- for (let i = 0; i < workerCount; i++) {
4003
- const sessionId = crypto.randomUUID();
4004
- this.project.vitest._browserSessions.sessionIds.add(sessionId);
4005
- const project = this.project.name;
4006
- debug?.("[%s] creating session for %s", sessionId, project);
4007
- const page = this.openPage(sessionId).then(() => {
4008
- // start running tests on the page when it's ready
4009
- this.runNextTest(method, sessionId);
4010
- });
4011
- promises.push(page);
4012
- }
4013
- await Promise.all(promises);
4014
- debug?.("all sessions are created");
4015
- return this._promise;
4016
- }
4017
- async openPage(sessionId) {
4018
- const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
4019
- const browser = this.project.browser;
4020
- const url = new URL("/__vitest_test__/", this.options.origin);
4021
- url.searchParams.set("sessionId", sessionId);
4022
- const pagePromise = browser.provider.openPage(sessionId, url.toString());
4023
- await Promise.all([sessionPromise, pagePromise]);
4024
- }
4025
- getOrchestrator(sessionId) {
4026
- const orchestrator = this.orchestrators.get(sessionId);
4027
- if (!orchestrator) {
4028
- throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
4029
- }
4030
- return orchestrator;
4031
- }
4032
- finishSession(sessionId) {
4033
- this.readySessions.add(sessionId);
4034
- // the last worker finished running tests
4035
- if (this.readySessions.size === this.orchestrators.size) {
4036
- this._promise?.resolve();
4037
- this._promise = undefined;
4038
- debug?.("[%s] all tests finished running", sessionId);
4039
- } else {
4040
- debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
4041
- }
4042
- }
4043
- runNextTest(method, sessionId) {
4044
- const file = this._queue.shift();
4045
- if (!file) {
4046
- debug?.("[%s] no more tests to run", sessionId);
4047
- const isolate = this.project.config.browser.isolate;
4048
- // we don't need to cleanup testers if isolation is enabled,
4049
- // because cleanup is done at the end of every test
4050
- if (isolate) {
4051
- this.finishSession(sessionId);
4052
- return;
4053
- }
4054
- // we need to cleanup testers first because there is only
4055
- // one iframe and it does the cleanup only after everything is completed
4056
- const orchestrator = this.getOrchestrator(sessionId);
4057
- orchestrator.cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
4058
- return;
4059
- }
4060
- if (!this._promise) {
4061
- throw new Error(`Unexpected empty queue`);
4062
- }
4063
- const startTime = performance.now();
4064
- const orchestrator = this.getOrchestrator(sessionId);
4065
- debug?.("[%s] run test %s", sessionId, file);
4066
- this.setBreakpoint(sessionId, file).then(() => {
4067
- // this starts running tests inside the orchestrator
4068
- orchestrator.createTesters({
4069
- method,
4070
- files: [file],
4071
- providedContext: this._providedContext || "[{}]",
4072
- startTime
4073
- }).then(() => {
4074
- debug?.("[%s] test %s finished running", sessionId, file);
4075
- this.runNextTest(method, sessionId);
4076
- }).catch((error) => {
4077
- // if user cancels the test run manually, ignore the error and exit gracefully
4078
- if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
4079
- this.cancel();
4080
- this._promise?.resolve();
4081
- this._promise = undefined;
4082
- debug?.("[%s] browser connection was closed", sessionId);
4083
- return;
4084
- }
4085
- debug?.("[%s] error during %s test run: %s", sessionId, file, error);
4086
- this.reject(error);
4087
- });
4088
- }).catch((err) => this.reject(err));
4089
- }
4090
- async setBreakpoint(sessionId, file) {
4091
- if (!this.project.config.inspector.waitForDebugger) {
4092
- return;
4093
- }
4094
- const provider = this.project.browser.provider;
4095
- const browser = this.project.config.browser.name;
4096
- if (shouldIgnoreDebugger(provider.name, browser)) {
4097
- debug?.("[$s] ignoring debugger in %s browser because it is not supported", sessionId, browser);
4098
- return;
4099
- }
4100
- if (!provider.getCDPSession) {
4101
- throw new Error("Unable to set breakpoint, CDP not supported");
4102
- }
4103
- debug?.("[%s] set breakpoint for %s", sessionId, file);
4104
- const session = await provider.getCDPSession(sessionId);
4105
- await session.send("Debugger.enable", {});
4106
- await session.send("Debugger.setBreakpointByUrl", {
4107
- lineNumber: 0,
4108
- urlRegex: escapePathToRegexp(file)
4109
- });
4110
- }
4111
- }
4112
- function shouldIgnoreDebugger(provider, browser) {
4113
- if (provider === "webdriverio") {
4114
- return browser !== "chrome" && browser !== "edge";
4115
- }
4116
- return browser !== "chromium";
3122
+ function defineBrowserCommand(fn) {
3123
+ return fn;
4117
3124
  }
4118
-
4119
- async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
3125
+ const createBrowserServer = async (options) => {
3126
+ const project = options.project;
3127
+ const configFile = project.vite.config.configFile;
4120
3128
  if (project.vitest.version !== version) {
4121
3129
  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."));
4122
3130
  }
@@ -4125,6 +3133,7 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
4125
3133
  const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
4126
3134
  const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
4127
3135
  const mockerRegistry = new MockerRegistry();
3136
+ let cacheDir;
4128
3137
  const vite = await createViteServer({
4129
3138
  ...project.options,
4130
3139
  base: "/",
@@ -4148,16 +3157,35 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
4148
3157
  },
4149
3158
  cacheDir: project.vite.config.cacheDir,
4150
3159
  plugins: [
4151
- ...prePlugins,
3160
+ {
3161
+ name: "vitest-internal:browser-cacheDir",
3162
+ configResolved(config) {
3163
+ cacheDir = config.cacheDir;
3164
+ }
3165
+ },
3166
+ ...options.mocksPlugins({ filter(id) {
3167
+ if (id.includes(distRoot) || id.includes(cacheDir)) {
3168
+ return false;
3169
+ }
3170
+ return true;
3171
+ } }),
3172
+ options.metaEnvReplacer(),
4152
3173
  ...project.options?.plugins || [],
4153
3174
  BrowserPlugin(server),
4154
3175
  interceptorPlugin({ registry: mockerRegistry }),
4155
- ...postPlugins
3176
+ options.coveragePlugin()
4156
3177
  ]
4157
3178
  });
4158
3179
  await vite.listen();
4159
3180
  setupBrowserRpc(server, mockerRegistry);
4160
3181
  return server;
3182
+ };
3183
+ function defineBrowserProvider(options) {
3184
+ return {
3185
+ ...options,
3186
+ options: options.options || {},
3187
+ serverFactory: createBrowserServer
3188
+ };
4161
3189
  }
4162
3190
 
4163
- export { createBrowserPool, createBrowserServer, distRoot };
3191
+ export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };