@vitest/browser 2.0.0-beta.11 → 2.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { createDebugger, isFileServingAllowed as isFileServingAllowed$1, getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir, createServer } from 'vitest/node';
2
- import fs, { existsSync, readdirSync, promises, readFileSync } from 'node:fs';
2
+ import fs, { existsSync, readdirSync, readFileSync, promises } from 'node:fs';
3
3
  import { WebSocketServer } from 'ws';
4
4
  import { isFileServingAllowed } from 'vite';
5
5
  import { builtinModules, createRequire } from 'node:module';
6
6
  import { readFile as readFile$1, mkdir } from 'node:fs/promises';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { createDefer, slash, toArray } from '@vitest/utils';
9
+ import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
9
10
  import sirv from 'sirv';
10
11
  import { defaultBrowserPort, coverageConfigDefaults } from 'vitest/config';
11
- import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-BRud6NtS.js';
12
+ import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-BdVqnfdE.js';
12
13
  import { resolve as resolve$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
13
14
  import MagicString from 'magic-string';
14
15
  import { esmWalker } from '@vitest/utils/ast';
@@ -406,7 +407,9 @@ const stringify = (value, replacer, space) => {
406
407
  async function resolveMock(project, rawId, importer, hasFactory) {
407
408
  const { id, fsPath, external } = await resolveId(project, rawId, importer);
408
409
  if (hasFactory) {
409
- return { type: "factory", resolvedId: id };
410
+ const needsInteropMap = viteDepsInteropMap(project.browser.vite.config);
411
+ const needsInterop = needsInteropMap?.get(fsPath) ?? false;
412
+ return { type: "factory", resolvedId: id, needsInterop };
410
413
  }
411
414
  const mockPath = resolveMockPath(project.config.root, fsPath, external);
412
415
  return {
@@ -497,6 +500,29 @@ const postfixRE = /[?#].*$/;
497
500
  function cleanUrl(url) {
498
501
  return url.replace(postfixRE, "");
499
502
  }
503
+ const metadata = /* @__PURE__ */ new WeakMap();
504
+ function viteDepsInteropMap(config) {
505
+ if (metadata.has(config)) {
506
+ return metadata.get(config);
507
+ }
508
+ const cacheDirPath = getDepsCacheDir(config);
509
+ const metadataPath = resolve(cacheDirPath, "_metadata.json");
510
+ if (!existsSync(metadataPath)) {
511
+ return null;
512
+ }
513
+ const { optimized } = JSON.parse(readFileSync(metadataPath, "utf-8"));
514
+ const needsInteropMap = /* @__PURE__ */ new Map();
515
+ for (const name in optimized) {
516
+ const dep = optimized[name];
517
+ const file = resolve(cacheDirPath, dep.file);
518
+ needsInteropMap.set(file, dep.needsInterop);
519
+ }
520
+ metadata.set(config, needsInteropMap);
521
+ return needsInteropMap;
522
+ }
523
+ function getDepsCacheDir(config) {
524
+ return resolve(config.cacheDir, "deps");
525
+ }
500
526
 
501
527
  const debug$1 = createDebugger("vitest:browser:api");
502
528
  const BROWSER_API_PATH = "/__vitest_browser_api__";
@@ -517,7 +543,7 @@ function setupBrowserRpc(server) {
517
543
  const sessionId = searchParams.get("sessionId") ?? "0";
518
544
  wss.handleUpgrade(request, socket, head, (ws) => {
519
545
  wss.emit("connection", ws, request);
520
- const rpc = setupClient(ws);
546
+ const rpc = setupClient(sessionId, ws);
521
547
  const state = server.state;
522
548
  const clients = type === "tester" ? state.testers : state.orchestrators;
523
549
  clients.set(sessionId, rpc);
@@ -525,6 +551,7 @@ function setupBrowserRpc(server) {
525
551
  ws.on("close", () => {
526
552
  debug$1?.("[%s] Browser API disconnected from %s", sessionId, type);
527
553
  clients.delete(sessionId);
554
+ server.state.removeCDPHandler(sessionId);
528
555
  });
529
556
  });
530
557
  });
@@ -535,7 +562,7 @@ function setupBrowserRpc(server) {
535
562
  );
536
563
  }
537
564
  }
538
- function setupClient(ws) {
565
+ function setupClient(sessionId, ws) {
539
566
  const rpc = createBirpc(
540
567
  {
541
568
  async onUnhandledError(error, type) {
@@ -653,12 +680,21 @@ function setupBrowserRpc(server) {
653
680
  moduleGraph.invalidateModule(module, /* @__PURE__ */ new Set(), Date.now(), true);
654
681
  }
655
682
  });
683
+ },
684
+ // CDP
685
+ async sendCdpEvent(contextId, event, payload) {
686
+ const cdp = await server.ensureCDPHandler(contextId, sessionId);
687
+ return cdp.send(event, payload);
688
+ },
689
+ async trackCdpEvent(contextId, type, event, listenerId) {
690
+ const cdp = await server.ensureCDPHandler(contextId, sessionId);
691
+ cdp[type](event, listenerId);
656
692
  }
657
693
  },
658
694
  {
659
695
  post: (msg) => ws.send(msg),
660
696
  on: (fn) => ws.on("message", fn),
661
- eventNames: ["onCancel"],
697
+ eventNames: ["onCancel", "cdpEvent"],
662
698
  serialize: (data) => stringify(data, stringifyReplace),
663
699
  deserialize: parse,
664
700
  onTimeoutError(functionName) {
@@ -696,14 +732,16 @@ function stringifyReplace(key, value) {
696
732
  class BrowserServerState {
697
733
  orchestrators = /* @__PURE__ */ new Map();
698
734
  testers = /* @__PURE__ */ new Map();
735
+ cdps = /* @__PURE__ */ new Map();
699
736
  contexts = /* @__PURE__ */ new Map();
700
737
  getContext(contextId) {
701
738
  return this.contexts.get(contextId);
702
739
  }
703
- createAsyncContext(contextId, files) {
740
+ createAsyncContext(method, contextId, files) {
704
741
  const defer = createDefer();
705
742
  this.contexts.set(contextId, {
706
743
  files,
744
+ method,
707
745
  resolve: () => {
708
746
  defer.resolve();
709
747
  this.contexts.delete(contextId);
@@ -712,6 +750,9 @@ class BrowserServerState {
712
750
  });
713
751
  return defer;
714
752
  }
753
+ async removeCDPHandler(sessionId) {
754
+ this.cdps.delete(sessionId);
755
+ }
715
756
  }
716
757
 
717
758
  function replacer(code, values) {
@@ -743,10 +784,71 @@ async function getBrowserProvider(options, project) {
743
784
  return customProviderModule.default;
744
785
  }
745
786
 
787
+ class BrowserServerCDPHandler {
788
+ constructor(session, tester) {
789
+ this.session = session;
790
+ this.tester = tester;
791
+ }
792
+ listenerIds = {};
793
+ listeners = {};
794
+ send(method, params) {
795
+ return this.session.send(method, params);
796
+ }
797
+ on(event, id, once = false) {
798
+ if (!this.listenerIds[event]) {
799
+ this.listenerIds[event] = [];
800
+ }
801
+ this.listenerIds[event].push(id);
802
+ if (!this.listeners[event]) {
803
+ this.listeners[event] = (payload) => {
804
+ this.tester.cdpEvent(
805
+ event,
806
+ payload
807
+ );
808
+ if (once) {
809
+ this.off(event, id);
810
+ }
811
+ };
812
+ this.session.on(event, this.listeners[event]);
813
+ }
814
+ }
815
+ off(event, id) {
816
+ if (!this.listenerIds[event]) {
817
+ this.listenerIds[event] = [];
818
+ }
819
+ this.listenerIds[event] = this.listenerIds[event].filter((l) => l !== id);
820
+ if (!this.listenerIds[event].length) {
821
+ this.session.off(event, this.listeners[event]);
822
+ delete this.listeners[event];
823
+ }
824
+ }
825
+ once(event, listener) {
826
+ this.on(event, listener, true);
827
+ }
828
+ }
829
+
746
830
  class BrowserServer {
747
831
  constructor(project, base) {
748
832
  this.project = project;
749
833
  this.base = base;
834
+ this.stackTraceOptions = {
835
+ frameFilter: project.config.onStackTrace,
836
+ getSourceMap: (id) => {
837
+ const result = this.vite.moduleGraph.getModuleById(id)?.transformResult;
838
+ return result?.map;
839
+ },
840
+ getFileName: (id) => {
841
+ const mod = this.vite.moduleGraph.getModuleById(id);
842
+ if (mod?.file) {
843
+ return mod.file;
844
+ }
845
+ const modUrl = this.vite.moduleGraph.urlToModuleMap.get(id);
846
+ if (modUrl?.file) {
847
+ return modUrl.file;
848
+ }
849
+ return id;
850
+ }
851
+ };
750
852
  this.state = new BrowserServerState();
751
853
  const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
752
854
  const distRoot = resolve(pkgRoot, "dist");
@@ -783,6 +885,7 @@ class BrowserServer {
783
885
  state;
784
886
  provider;
785
887
  vite;
888
+ stackTraceOptions;
786
889
  setServer(server) {
787
890
  this.vite = server;
788
891
  }
@@ -837,6 +940,47 @@ class BrowserServer {
837
940
  options: providerOptions
838
941
  });
839
942
  }
943
+ parseErrorStacktrace(e, options = {}) {
944
+ return parseErrorStacktrace(e, {
945
+ ...this.stackTraceOptions,
946
+ ...options
947
+ });
948
+ }
949
+ parseStacktrace(trace, options = {}) {
950
+ return parseStacktrace(trace, {
951
+ ...this.stackTraceOptions,
952
+ ...options
953
+ });
954
+ }
955
+ cdpSessions = /* @__PURE__ */ new Map();
956
+ async ensureCDPHandler(contextId, sessionId) {
957
+ const cachedHandler = this.state.cdps.get(sessionId);
958
+ if (cachedHandler) {
959
+ return cachedHandler;
960
+ }
961
+ const provider = this.provider;
962
+ if (!provider.getCDPSession) {
963
+ throw new Error(`CDP is not supported by the provider "${provider.name}".`);
964
+ }
965
+ const promise = this.cdpSessions.get(sessionId) ?? await (async () => {
966
+ const promise2 = provider.getCDPSession(contextId).finally(() => {
967
+ this.cdpSessions.delete(sessionId);
968
+ });
969
+ this.cdpSessions.set(sessionId, promise2);
970
+ return promise2;
971
+ })();
972
+ const session = await promise;
973
+ const rpc = this.state.testers.get(sessionId);
974
+ if (!rpc) {
975
+ throw new Error(`Tester RPC "${sessionId}" was not established.`);
976
+ }
977
+ const handler = new BrowserServerCDPHandler(session, rpc);
978
+ this.state.cdps.set(
979
+ sessionId,
980
+ handler
981
+ );
982
+ return handler;
983
+ }
840
984
  async close() {
841
985
  await this.vite.close();
842
986
  }
@@ -849,35 +993,49 @@ function wrapConfig(config) {
849
993
  };
850
994
  }
851
995
 
852
- const click = async (context, xpath, options = {}) => {
996
+ const click = async (context, selector, options = {}) => {
853
997
  const provider = context.provider;
854
998
  if (provider instanceof PlaywrightBrowserProvider) {
855
- const tester = context.frame;
856
- await tester.locator(`xpath=${xpath}`).click({
999
+ const tester = context.iframe;
1000
+ await tester.locator(`css=${selector}`).click({
857
1001
  timeout: 1e3,
858
1002
  ...options
859
1003
  });
860
1004
  } else if (provider instanceof WebdriverBrowserProvider) {
861
1005
  const browser = context.browser;
862
- const markedXpath = `//${xpath}`;
863
- await browser.$(markedXpath).click(options);
1006
+ await browser.$(selector).click(options);
864
1007
  } else {
865
1008
  throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
866
1009
  }
867
1010
  };
868
- const dblClick = async (context, xpath, options = {}) => {
1011
+ const dblClick = async (context, selector, options = {}) => {
869
1012
  const provider = context.provider;
870
1013
  if (provider instanceof PlaywrightBrowserProvider) {
871
- const tester = context.frame;
872
- await tester.locator(`xpath=${xpath}`).dblclick(options);
1014
+ const tester = context.iframe;
1015
+ await tester.locator(`css=${selector}`).dblclick(options);
873
1016
  } else if (provider instanceof WebdriverBrowserProvider) {
874
1017
  const browser = context.browser;
875
- const markedXpath = `//${xpath}`;
876
- await browser.$(markedXpath).doubleClick();
1018
+ await browser.$(selector).doubleClick();
877
1019
  } else {
878
1020
  throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
879
1021
  }
880
1022
  };
1023
+ const tripleClick = async (context, selector, options = {}) => {
1024
+ const provider = context.provider;
1025
+ if (provider instanceof PlaywrightBrowserProvider) {
1026
+ const tester = context.iframe;
1027
+ await tester.locator(`css=${selector}`).click({
1028
+ timeout: 1e3,
1029
+ ...options,
1030
+ clickCount: 3
1031
+ });
1032
+ } else if (provider instanceof WebdriverBrowserProvider) {
1033
+ const browser = context.browser;
1034
+ await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: await browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
1035
+ } else {
1036
+ throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
1037
+ }
1038
+ };
881
1039
 
882
1040
  var clickableInputTypes;
883
1041
  (function(clickableInputTypes) {
@@ -1250,7 +1408,8 @@ const keyboard = async (context, text) => {
1250
1408
  }
1251
1409
  }
1252
1410
  if (context.provider instanceof PlaywrightBrowserProvider) {
1253
- await context.frame.evaluate(focusIframe);
1411
+ const frame = await context.frame();
1412
+ await frame.evaluate(focusIframe);
1254
1413
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1255
1414
  await context.browser.execute(focusIframe);
1256
1415
  }
@@ -1266,7 +1425,8 @@ const keyboard = async (context, text) => {
1266
1425
  }
1267
1426
  }
1268
1427
  if (context.provider instanceof PlaywrightBrowserProvider) {
1269
- await context.frame.evaluate(selectAll);
1428
+ const frame = await context.frame();
1429
+ await frame.evaluate(selectAll);
1270
1430
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1271
1431
  await context.browser.execute(selectAll);
1272
1432
  } else {
@@ -1347,11 +1507,11 @@ async function keyboardImplementation(provider, contextId, text, selectAll, skip
1347
1507
  };
1348
1508
  }
1349
1509
 
1350
- const type = async (context, xpath, text, options = {}) => {
1510
+ const type = async (context, selector, text, options = {}) => {
1351
1511
  const { skipClick = false, skipAutoClose = false } = options;
1352
1512
  if (context.provider instanceof PlaywrightBrowserProvider) {
1353
- const { frame } = context;
1354
- const element = frame.locator(`xpath=${xpath}`);
1513
+ const { iframe } = context;
1514
+ const element = iframe.locator(`css=${selector}`);
1355
1515
  if (!skipClick) {
1356
1516
  await element.focus();
1357
1517
  }
@@ -1364,8 +1524,7 @@ const type = async (context, xpath, text, options = {}) => {
1364
1524
  );
1365
1525
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1366
1526
  const browser = context.browser;
1367
- const markedXpath = `//${xpath}`;
1368
- const element = browser.$(markedXpath);
1527
+ const element = browser.$(selector);
1369
1528
  if (!skipClick && !await element.isFocused()) {
1370
1529
  await element.click();
1371
1530
  }
@@ -1386,47 +1545,45 @@ const type = async (context, xpath, text, options = {}) => {
1386
1545
  }
1387
1546
  };
1388
1547
 
1389
- const clear = async (context, xpath) => {
1548
+ const clear = async (context, selector) => {
1390
1549
  if (context.provider instanceof PlaywrightBrowserProvider) {
1391
- const { frame } = context;
1392
- const element = frame.locator(`xpath=${xpath}`);
1550
+ const { iframe } = context;
1551
+ const element = iframe.locator(`css=${selector}`);
1393
1552
  await element.clear({
1394
1553
  timeout: 1e3
1395
1554
  });
1396
1555
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1397
1556
  const browser = context.browser;
1398
- const markedXpath = `//${xpath}`;
1399
- const element = await browser.$(markedXpath);
1557
+ const element = await browser.$(selector);
1400
1558
  await element.clearValue();
1401
1559
  } else {
1402
1560
  throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1403
1561
  }
1404
1562
  };
1405
1563
 
1406
- const fill = async (context, xpath, text, options = {}) => {
1564
+ const fill = async (context, selector, text, options = {}) => {
1407
1565
  if (context.provider instanceof PlaywrightBrowserProvider) {
1408
- const { frame } = context;
1409
- const element = frame.locator(`xpath=${xpath}`);
1566
+ const { iframe } = context;
1567
+ const element = iframe.locator(`css=${selector}`);
1410
1568
  await element.fill(text, { timeout: 1e3, ...options });
1411
1569
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1412
1570
  const browser = context.browser;
1413
- const markedXpath = `//${xpath}`;
1414
- await browser.$(markedXpath).setValue(text);
1571
+ await browser.$(selector).setValue(text);
1415
1572
  } else {
1416
1573
  throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1417
1574
  }
1418
1575
  };
1419
1576
 
1420
- const selectOptions = async (context, xpath, userValues, options = {}) => {
1577
+ const selectOptions = async (context, selector, userValues, options = {}) => {
1421
1578
  if (context.provider instanceof PlaywrightBrowserProvider) {
1422
1579
  const value = userValues;
1423
- const { frame } = context;
1424
- const selectElement = frame.locator(`xpath=${xpath}`);
1580
+ const { iframe } = context;
1581
+ const selectElement = iframe.locator(`css=${selector}`);
1425
1582
  const values = await Promise.all(value.map(async (v) => {
1426
1583
  if (typeof v === "string") {
1427
1584
  return v;
1428
1585
  }
1429
- const elementHandler = await frame.locator(`xpath=${v.element}`).elementHandle();
1586
+ const elementHandler = await iframe.locator(`css=${v.element}`).elementHandle();
1430
1587
  if (!elementHandler) {
1431
1588
  throw new Error(`Element not found: ${v.element}`);
1432
1589
  }
@@ -1441,10 +1598,9 @@ const selectOptions = async (context, xpath, userValues, options = {}) => {
1441
1598
  if (!values.length) {
1442
1599
  return;
1443
1600
  }
1444
- const markedXpath = `//${xpath}`;
1445
1601
  const browser = context.browser;
1446
1602
  if (values.length === 1 && "index" in values[0]) {
1447
- const selectElement = browser.$(markedXpath);
1603
+ const selectElement = browser.$(selector);
1448
1604
  await selectElement.selectByIndex(values[0].index);
1449
1605
  } else {
1450
1606
  throw new Error(`Provider "webdriverio" doesn't support selecting multiple values at once`);
@@ -1472,19 +1628,18 @@ const tab = async (context, options = {}) => {
1472
1628
 
1473
1629
  const dragAndDrop = async (context, source, target, options) => {
1474
1630
  if (context.provider instanceof PlaywrightBrowserProvider) {
1475
- await context.frame.dragAndDrop(
1476
- `xpath=${source}`,
1477
- `xpath=${target}`,
1631
+ const frame = await context.frame();
1632
+ await frame.dragAndDrop(
1633
+ `css=${source}`,
1634
+ `css=${target}`,
1478
1635
  {
1479
1636
  timeout: 1e3,
1480
1637
  ...options
1481
1638
  }
1482
1639
  );
1483
1640
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1484
- const sourceXpath = `//${source}`;
1485
- const targetXpath = `//${target}`;
1486
- const $source = context.browser.$(sourceXpath);
1487
- const $target = context.browser.$(targetXpath);
1641
+ const $source = context.browser.$(source);
1642
+ const $target = context.browser.$(target);
1488
1643
  const duration = options?.duration ?? 10;
1489
1644
  await context.browser.action("pointer").move({ duration: 0, origin: $source, x: 0, y: 0 }).down({ button: 0 }).move({ duration: 0, origin: "pointer", x: 0, y: 0 }).pause(duration).move({ duration: 0, origin: $target, x: 0, y: 0 }).move({ duration: 0, origin: "pointer", x: 1, y: 0 }).move({ duration: 0, origin: "pointer", x: -1, y: 0 }).up({ button: 0 }).perform();
1490
1645
  } else {
@@ -1492,16 +1647,15 @@ const dragAndDrop = async (context, source, target, options) => {
1492
1647
  }
1493
1648
  };
1494
1649
 
1495
- const hover = async (context, xpath, options = {}) => {
1650
+ const hover = async (context, selector, options = {}) => {
1496
1651
  if (context.provider instanceof PlaywrightBrowserProvider) {
1497
- await context.frame.locator(`xpath=${xpath}`).hover({
1652
+ await context.iframe.locator(`css=${selector}`).hover({
1498
1653
  timeout: 1e3,
1499
1654
  ...options
1500
1655
  });
1501
1656
  } else if (context.provider instanceof WebdriverBrowserProvider) {
1502
1657
  const browser = context.browser;
1503
- const markedXpath = `//${xpath}`;
1504
- await browser.$(markedXpath).moveTo(options);
1658
+ await browser.$(selector).moveTo(options);
1505
1659
  } else {
1506
1660
  throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
1507
1661
  }
@@ -1550,26 +1704,31 @@ const screenshot = async (context, name, options = {}) => {
1550
1704
  await mkdir(dirname(path), { recursive: true });
1551
1705
  if (context.provider instanceof PlaywrightBrowserProvider) {
1552
1706
  if (options.element) {
1553
- const { element: elementXpath, ...config } = options;
1554
- const iframe = context.frame;
1555
- const element = iframe.locator(`xpath=${elementXpath}`);
1556
- await element.screenshot({ ...config, path: savePath });
1557
- } else {
1558
- await context.frame.locator("body").screenshot({ ...options, path: savePath });
1707
+ const { element: css, ...config } = options;
1708
+ const element = context.iframe.locator(`css=${css}`);
1709
+ const buffer2 = await element.screenshot({
1710
+ timeout: 1e3,
1711
+ ...config,
1712
+ path: savePath
1713
+ });
1714
+ return returnResult(options, path, buffer2);
1559
1715
  }
1560
- return path;
1716
+ const buffer = await context.iframe.locator("body").screenshot({
1717
+ ...options,
1718
+ path: savePath
1719
+ });
1720
+ return returnResult(options, path, buffer);
1561
1721
  }
1562
1722
  if (context.provider instanceof WebdriverBrowserProvider) {
1563
1723
  const page = context.provider.browser;
1564
1724
  if (!options.element) {
1565
1725
  const body = await page.$("body");
1566
- await body.saveScreenshot(savePath);
1567
- return path;
1726
+ const buffer2 = await body.saveScreenshot(savePath);
1727
+ return returnResult(options, path, buffer2);
1568
1728
  }
1569
- const xpath = `//${options.element}`;
1570
- const element = await page.$(xpath);
1571
- await element.saveScreenshot(savePath);
1572
- return path;
1729
+ const element = await page.$(`${options.element}`);
1730
+ const buffer = await element.saveScreenshot(savePath);
1731
+ return returnResult(options, path, buffer);
1573
1732
  }
1574
1733
  throw new Error(
1575
1734
  `Provider "${context.provider.name}" does not support screenshots`
@@ -1588,6 +1747,12 @@ function resolveScreenshotPath(testPath, name, config) {
1588
1747
  }
1589
1748
  return resolve(dir, "__screenshots__", base, name);
1590
1749
  }
1750
+ function returnResult(options, path, buffer) {
1751
+ if (options.base64) {
1752
+ return { path, base64: buffer.toString("base64") };
1753
+ }
1754
+ return path;
1755
+ }
1591
1756
 
1592
1757
  var builtinCommands = {
1593
1758
  readFile,
@@ -1595,6 +1760,7 @@ var builtinCommands = {
1595
1760
  writeFile,
1596
1761
  __vitest_click: click,
1597
1762
  __vitest_dblClick: dblClick,
1763
+ __vitest_tripleClick: tripleClick,
1598
1764
  __vitest_screenshot: screenshot,
1599
1765
  __vitest_type: type,
1600
1766
  __vitest_clear: clear,
@@ -1650,7 +1816,7 @@ async function generateContextFile(server) {
1650
1816
  );
1651
1817
  const distContextPath = slash(`/@fs/${resolve(__dirname, "context.js")}`);
1652
1818
  return `
1653
- import { page, userEvent as __userEvent_CDP__ } from '${distContextPath}'
1819
+ import { page, userEvent as __userEvent_CDP__, cdp } from '${distContextPath}'
1654
1820
  ${userEventNonProviderImport}
1655
1821
  const filepath = () => ${filepathCode}
1656
1822
  const rpc = () => __vitest_worker__.rpc
@@ -1667,7 +1833,7 @@ export const server = {
1667
1833
  }
1668
1834
  export const commands = server.commands
1669
1835
  export const userEvent = ${getUserEvent(provider)}
1670
- export { page }
1836
+ export { page, cdp }
1671
1837
  `;
1672
1838
  }
1673
1839
  function getUserEvent(provider) {
@@ -1713,11 +1879,13 @@ ${err.message}`);
1713
1879
  onImportMeta() {
1714
1880
  },
1715
1881
  onDynamicImport(node) {
1716
- const replace = "__vitest_browser_runner__.wrapModule(() => import(";
1882
+ const replaceString = "__vitest_browser_runner__.wrapModule(() => import(";
1883
+ const importSubstring = code.substring(node.start, node.end);
1884
+ const hasIgnore = importSubstring.includes("/* @vite-ignore */");
1717
1885
  s.overwrite(
1718
1886
  node.start,
1719
1887
  node.source.start,
1720
- replace
1888
+ replaceString + (hasIgnore ? "/* @vite-ignore */ " : "")
1721
1889
  );
1722
1890
  s.overwrite(node.end - 1, node.end, "))");
1723
1891
  }
@@ -1807,7 +1975,9 @@ async function resolveTester(server, url, res) {
1807
1975
  const testFiles = await project.globTestFiles();
1808
1976
  const tests = testFile === "__vitest_all__" || !testFiles.includes(testFile) ? "__vitest_browser_runner__.files" : JSON.stringify([testFile]);
1809
1977
  const iframeId = JSON.stringify(testFile);
1810
- const files = state.getContext(contextId)?.files ?? [];
1978
+ const context = state.getContext(contextId);
1979
+ const files = context?.files ?? [];
1980
+ const method = context?.method ?? "run";
1811
1981
  const injectorJs = typeof server.injectorJs === "string" ? server.injectorJs : await server.injectorJs;
1812
1982
  const config = server.getSerializableConfig();
1813
1983
  const injector = replacer(injectorJs, {
@@ -1839,7 +2009,7 @@ async function resolveTester(server, url, res) {
1839
2009
  __VITEST_APPEND__: `<script type="module">
1840
2010
  __vitest_browser_runner__.runningFiles = ${tests}
1841
2011
  __vitest_browser_runner__.iframeId = ${iframeId}
1842
- __vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
2012
+ __vitest_browser_runner__.${method === "run" ? "runTests" : "collectTests"}(__vitest_browser_runner__.runningFiles)
1843
2013
  <\/script>`
1844
2014
  });
1845
2015
  }
@@ -1848,6 +2018,7 @@ var BrowserPlugin = (browserServer, base = "/") => {
1848
2018
  const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
1849
2019
  const distRoot = resolve(pkgRoot, "dist");
1850
2020
  const project = browserServer.project;
2021
+ let loupePath;
1851
2022
  return [
1852
2023
  {
1853
2024
  enforce: "pre",
@@ -1926,16 +2097,48 @@ var BrowserPlugin = (browserServer, base = "/") => {
1926
2097
  (file) => getFilePoolName(project, file) === "browser"
1927
2098
  );
1928
2099
  const setupFiles = toArray(project.config.setupFiles);
2100
+ const define = {};
2101
+ for (const env in project.config.env || {}) {
2102
+ const stringValue = JSON.stringify(project.config.env[env]);
2103
+ define[`process.env.${env}`] = stringValue;
2104
+ define[`import.meta.env.${env}`] = stringValue;
2105
+ }
2106
+ const entries = [
2107
+ ...browserTestFiles,
2108
+ ...setupFiles,
2109
+ resolve(distDir, "index.js"),
2110
+ resolve(distDir, "browser.js"),
2111
+ resolve(distDir, "runners.js"),
2112
+ resolve(distDir, "utils.js"),
2113
+ ...project.config.snapshotSerializers || []
2114
+ ];
2115
+ if (project.config.diff) {
2116
+ entries.push(project.config.diff);
2117
+ }
2118
+ if (project.ctx.coverageProvider) {
2119
+ const coverage = project.ctx.config.coverage;
2120
+ const provider = coverage.provider;
2121
+ if (provider === "v8") {
2122
+ const path = tryResolve("@vitest/coverage-v8", [project.ctx.config.root]);
2123
+ if (path) {
2124
+ entries.push(path);
2125
+ }
2126
+ } else if (provider === "istanbul") {
2127
+ const path = tryResolve("@vitest/coverage-istanbul", [project.ctx.config.root]);
2128
+ if (path) {
2129
+ entries.push(path);
2130
+ }
2131
+ } else if (provider === "custom" && coverage.customProviderModule) {
2132
+ entries.push(coverage.customProviderModule);
2133
+ }
2134
+ }
1929
2135
  return {
2136
+ define,
2137
+ resolve: {
2138
+ dedupe: ["vitest"]
2139
+ },
1930
2140
  optimizeDeps: {
1931
- entries: [
1932
- ...browserTestFiles,
1933
- ...setupFiles,
1934
- resolve(distDir, "index.js"),
1935
- resolve(distDir, "browser.js"),
1936
- resolve(distDir, "runners.js"),
1937
- resolve(distDir, "utils.js")
1938
- ],
2141
+ entries,
1939
2142
  exclude: [
1940
2143
  "vitest",
1941
2144
  "vitest/utils",
@@ -1964,8 +2167,9 @@ var BrowserPlugin = (browserServer, base = "/") => {
1964
2167
  "vitest > pretty-format > ansi-regex",
1965
2168
  "vitest > chai",
1966
2169
  "vitest > chai > loupe",
1967
- "vitest > @vitest/runner > p-limit",
2170
+ "vitest > @vitest/runner > pretty-format",
1968
2171
  "vitest > @vitest/utils > diff-sequences",
2172
+ "vitest > @vitest/utils > loupe",
1969
2173
  "@vitest/browser > @testing-library/user-event",
1970
2174
  "@vitest/browser > @testing-library/dom"
1971
2175
  ]
@@ -1992,7 +2196,7 @@ var BrowserPlugin = (browserServer, base = "/") => {
1992
2196
  if (rawId.startsWith("/__virtual_vitest__")) {
1993
2197
  const url = new URL(rawId, "http://localhost");
1994
2198
  if (!url.searchParams.has("id")) {
1995
- throw new TypeError(`Invalid virtual module id: ${rawId}, requires "id" query.`);
2199
+ return;
1996
2200
  }
1997
2201
  const id = decodeURIComponent(url.searchParams.get("id"));
1998
2202
  const resolved = await this.resolve(id, distRoot, {
@@ -2013,6 +2217,15 @@ var BrowserPlugin = (browserServer, base = "/") => {
2013
2217
  if (id.startsWith("/__vitest_browser__/") || id.startsWith("/__vitest__/")) {
2014
2218
  return resolve(distRoot, "client", id.slice(1));
2015
2219
  }
2220
+ },
2221
+ configResolved(config) {
2222
+ loupePath = resolve(config.cacheDir, "deps/loupe.js");
2223
+ },
2224
+ transform(code, id) {
2225
+ if (id.startsWith(loupePath)) {
2226
+ const utilRequire = "nodeUtil = require_util();";
2227
+ return code.replace(utilRequire, " ".repeat(utilRequire.length));
2228
+ }
2016
2229
  }
2017
2230
  },
2018
2231
  BrowserContext(browserServer),
@@ -2022,10 +2235,8 @@ var BrowserPlugin = (browserServer, base = "/") => {
2022
2235
  enforce: "post",
2023
2236
  async config(viteConfig) {
2024
2237
  // Enables using ignore hint for coverage providers with @preserve keyword
2025
- if (viteConfig.esbuild !== false) {
2026
- viteConfig.esbuild ||= {};
2027
- viteConfig.esbuild.legalComments = "inline";
2028
- }
2238
+ viteConfig.esbuild ||= {};
2239
+ viteConfig.esbuild.legalComments = "inline";
2029
2240
  const server = resolveApiServerConfig(
2030
2241
  viteConfig.test?.browser || {},
2031
2242
  defaultBrowserPort
@@ -2055,18 +2266,31 @@ var BrowserPlugin = (browserServer, base = "/") => {
2055
2266
  },
2056
2267
  // TODO: remove this when @testing-library/vue supports ESM
2057
2268
  {
2058
- name: "vitest:browser:support-vue-testing-library",
2269
+ name: "vitest:browser:support-testing-library",
2059
2270
  config() {
2060
2271
  return {
2272
+ define: {
2273
+ // testing-library/preact
2274
+ "process.env.PTL_SKIP_AUTO_CLEANUP": !!process.env.PTL_SKIP_AUTO_CLEANUP,
2275
+ // testing-library/react
2276
+ "process.env.RTL_SKIP_AUTO_CLEANUP": !!process.env.RTL_SKIP_AUTO_CLEANUP,
2277
+ "process.env?.RTL_SKIP_AUTO_CLEANUP": !!process.env.RTL_SKIP_AUTO_CLEANUP,
2278
+ // testing-library/svelte, testing-library/solid
2279
+ "process.env.STL_SKIP_AUTO_CLEANUP": !!process.env.STL_SKIP_AUTO_CLEANUP,
2280
+ // testing-library/vue
2281
+ "process.env.VTL_SKIP_AUTO_CLEANUP": !!process.env.VTL_SKIP_AUTO_CLEANUP,
2282
+ // dom.debug()
2283
+ "process.env.DEBUG_PRINT_LIMIT": process.env.DEBUG_PRINT_LIMIT || 7e3
2284
+ },
2061
2285
  optimizeDeps: {
2062
2286
  esbuildOptions: {
2063
2287
  plugins: [
2064
2288
  {
2065
2289
  name: "test-utils-rewrite",
2066
2290
  setup(build) {
2067
- const _require = createRequire(import.meta.url);
2068
2291
  build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
2069
- const resolved = _require.resolve(args.path, {
2292
+ const _require2 = getRequire();
2293
+ const resolved = _require2.resolve(args.path, {
2070
2294
  paths: [args.importer]
2071
2295
  });
2072
2296
  return { path: resolved };
@@ -2081,6 +2305,21 @@ var BrowserPlugin = (browserServer, base = "/") => {
2081
2305
  }
2082
2306
  ];
2083
2307
  };
2308
+ function tryResolve(path, paths) {
2309
+ try {
2310
+ const _require2 = getRequire();
2311
+ return _require2.resolve(path, { paths });
2312
+ } catch {
2313
+ return void 0;
2314
+ }
2315
+ }
2316
+ let _require;
2317
+ function getRequire() {
2318
+ if (!_require) {
2319
+ _require = createRequire(import.meta.url);
2320
+ }
2321
+ return _require;
2322
+ }
2084
2323
  function resolveCoverageFolder(project) {
2085
2324
  const options = project.ctx.config;
2086
2325
  const htmlReporter = options.coverage?.enabled ? toArray(options.coverage.reporter).find((reporter) => {
@@ -2106,11 +2345,11 @@ function resolveCoverageFolder(project) {
2106
2345
  const debug = createDebugger("vitest:browser:pool");
2107
2346
  function createBrowserPool(ctx) {
2108
2347
  const providers = /* @__PURE__ */ new Set();
2109
- const waitForTests = async (contextId, project, files) => {
2110
- const context = project.browser.state.createAsyncContext(contextId, files);
2348
+ const waitForTests = async (method, contextId, project, files) => {
2349
+ const context = project.browser.state.createAsyncContext(method, contextId, files);
2111
2350
  return await context;
2112
2351
  };
2113
- const runTests = async (project, files) => {
2352
+ const executeTests = async (method, project, files) => {
2114
2353
  ctx.state.clearFiles(project, files);
2115
2354
  const browser = project.browser;
2116
2355
  const threadsCount = getThreadsCount(project);
@@ -2146,12 +2385,12 @@ function createBrowserPool(ctx) {
2146
2385
  contextId,
2147
2386
  [...files2.map((f) => relative(project.config.root, f))].join(", ")
2148
2387
  );
2149
- const promise = waitForTests(contextId, project, files2);
2388
+ const promise = waitForTests(method, contextId, project, files2);
2150
2389
  promises.push(promise);
2151
2390
  orchestrator.createTesters(files2);
2152
2391
  } else {
2153
2392
  const contextId = crypto.randomUUID();
2154
- const waitPromise = waitForTests(contextId, project, files2);
2393
+ const waitPromise = waitForTests(method, contextId, project, files2);
2155
2394
  debug?.(
2156
2395
  "Opening a new context %s for files: %s",
2157
2396
  contextId,
@@ -2165,15 +2404,22 @@ function createBrowserPool(ctx) {
2165
2404
  });
2166
2405
  await Promise.all(promises);
2167
2406
  };
2168
- const runWorkspaceTests = async (specs) => {
2407
+ const runWorkspaceTests = async (method, specs) => {
2169
2408
  const groupedFiles = /* @__PURE__ */ new Map();
2170
2409
  for (const [project, file] of specs) {
2171
2410
  const files = groupedFiles.get(project) || [];
2172
2411
  files.push(file);
2173
2412
  groupedFiles.set(project, files);
2174
2413
  }
2414
+ let isCancelled = false;
2415
+ ctx.onCancel(() => {
2416
+ isCancelled = true;
2417
+ });
2175
2418
  for (const [project, files] of groupedFiles.entries()) {
2176
- await runTests(project, files);
2419
+ if (isCancelled) {
2420
+ break;
2421
+ }
2422
+ await executeTests(method, project, files);
2177
2423
  }
2178
2424
  };
2179
2425
  const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
@@ -2193,7 +2439,8 @@ function createBrowserPool(ctx) {
2193
2439
  await Promise.all([...providers].map((provider) => provider.close()));
2194
2440
  providers.clear();
2195
2441
  },
2196
- runTests: runWorkspaceTests
2442
+ runTests: (files) => runWorkspaceTests("run", files),
2443
+ collectTests: (files) => runWorkspaceTests("collect", files)
2197
2444
  };
2198
2445
  }
2199
2446
 
@@ -2206,14 +2453,13 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
2206
2453
  ...project.options,
2207
2454
  // spread project config inlined in root workspace config
2208
2455
  base: "/",
2209
- logLevel: "error",
2456
+ logLevel: process.env.VITEST_BROWSER_DEBUG ?? "info",
2210
2457
  mode: project.config.mode,
2211
2458
  configFile: configPath,
2212
2459
  // watch is handled by Vitest
2213
2460
  server: {
2214
2461
  hmr: false,
2215
- watch: null,
2216
- preTransformRequests: false
2462
+ watch: null
2217
2463
  },
2218
2464
  plugins: [
2219
2465
  ...prePlugins,