@vitest/browser 2.0.0-beta.6 → 2.0.0-beta.7

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.
@@ -17,7 +17,7 @@
17
17
  })()
18
18
  </script>
19
19
  <!-- !LOAD_METADATA! -->
20
- <script type="module" crossorigin src="./assets/index-T2_JR9hd.js"></script>
20
+ <script type="module" crossorigin src="./assets/index-BNIf8EuF.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="./assets/index-DrQA2UkS.css">
22
22
  </head>
23
23
  <body>
@@ -66,10 +66,12 @@ async function getContainer(config) {
66
66
  }
67
67
  return document.querySelector("#vitest-tester");
68
68
  }
69
+ const runningFiles = /* @__PURE__ */ new Set();
69
70
  client.ws.addEventListener("open", async () => {
70
71
  const testFiles = getBrowserState().files;
71
72
  debug("test files", testFiles.join(", "));
72
- const runningFiles = new Set(testFiles);
73
+ runningFiles.clear();
74
+ testFiles.forEach((file) => runningFiles.add(file));
73
75
  channel.addEventListener("message", async (e) => {
74
76
  var _a;
75
77
  debug("channel event", JSON.stringify(e.data));
@@ -101,14 +103,14 @@ client.ws.addEventListener("open", async () => {
101
103
  }
102
104
  await done();
103
105
  } else {
104
- const iframeId = filenames.length > 1 ? ID_ALL : filenames[0];
106
+ const iframeId = e.data.id;
105
107
  (_a = iframes.get(iframeId)) == null ? void 0 : _a.remove();
106
108
  iframes.delete(iframeId);
107
109
  }
108
110
  break;
109
111
  }
110
112
  case "error": {
111
- const iframeId = e.data.files.length > 1 ? ID_ALL : e.data.files[0];
113
+ const iframeId = e.data.id;
112
114
  iframes.delete(iframeId);
113
115
  await client.rpc.onUnhandledError(e.data.error, e.data.errorType);
114
116
  if (iframeId === ID_ALL)
@@ -132,6 +134,8 @@ client.ws.addEventListener("open", async () => {
132
134
  await createTesters(testFiles);
133
135
  });
134
136
  async function createTesters(testFiles) {
137
+ runningFiles.clear();
138
+ testFiles.forEach((file) => runningFiles.add(file));
135
139
  const config = getConfig();
136
140
  const container = await getContainer(config);
137
141
  if (config.browser.ui) {
@@ -139,6 +143,8 @@ async function createTesters(testFiles) {
139
143
  container.textContent = "";
140
144
  }
141
145
  const { width, height } = config.browser.viewport;
146
+ iframes.forEach((iframe) => iframe.remove());
147
+ iframes.clear();
142
148
  if (config.isolate === false) {
143
149
  const iframe = createIframe(
144
150
  container,
@@ -492,7 +492,13 @@ function serializeError(unhandledError) {
492
492
  }
493
493
  async function defaultErrorReport(type, unhandledError) {
494
494
  const error = serializeError(unhandledError);
495
- channel.postMessage({ type: "error", files: getBrowserState().runningFiles, error, errorType: type });
495
+ channel.postMessage({
496
+ type: "error",
497
+ files: getBrowserState().runningFiles,
498
+ error,
499
+ errorType: type,
500
+ id: getBrowserState().iframeId
501
+ });
496
502
  }
497
503
  function catchWindowErrors(cb) {
498
504
  let userErrorListenerCount = 0;
@@ -647,7 +653,11 @@ async function prepareTestEnvironment(files) {
647
653
  };
648
654
  }
649
655
  function done(files) {
650
- channel.postMessage({ type: "done", filenames: files });
656
+ channel.postMessage({
657
+ type: "done",
658
+ filenames: files,
659
+ id: getBrowserState().iframeId
660
+ });
651
661
  }
652
662
  async function runTests(files) {
653
663
  await client.waitForConnection();
@@ -25,7 +25,7 @@
25
25
  </style>
26
26
  <script>{__VITEST_INJECTOR__}</script>
27
27
  {__VITEST_SCRIPTS__}
28
- <script type="module" crossorigin src="/__vitest_browser__/orchestrator-D24IzA4b.js"></script>
28
+ <script type="module" crossorigin src="/__vitest_browser__/orchestrator-CTfJ7g35.js"></script>
29
29
  <link rel="modulepreload" crossorigin href="/__vitest_browser__/rpc-DBukiZYG.js">
30
30
  </head>
31
31
  <body>
@@ -18,7 +18,7 @@
18
18
  </style>
19
19
  <script>{__VITEST_INJECTOR__}</script>
20
20
  {__VITEST_SCRIPTS__}
21
- <script type="module" crossorigin src="/__vitest_browser__/tester-C3Mchfpf.js"></script>
21
+ <script type="module" crossorigin src="/__vitest_browser__/tester-Bo1gw1oi.js"></script>
22
22
  <link rel="modulepreload" crossorigin href="/__vitest_browser__/rpc-DBukiZYG.js">
23
23
  </head>
24
24
  <body style="width: 100%; height: 100%; transform: scale(1); transform-origin: left top;">
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { fileURLToPath } from 'node:url';
2
2
  import { readFile as readFile$1 } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
3
4
  import sirv from 'sirv';
4
5
  import { coverageConfigDefaults } from 'vitest/config';
5
6
  import { slash } from '@vitest/utils';
6
7
  import fs, { promises } from 'node:fs';
7
- import { resolve as resolve$1, dirname } from 'node:path';
8
+ import { resolve as resolve$1, dirname as dirname$1 } from 'node:path';
8
9
  import { isFileServingAllowed } from 'vitest/node';
9
10
  import MagicString from 'magic-string';
10
11
  import { esmWalker } from '@vitest/utils/ast';
@@ -154,32 +155,56 @@ function normalizeString(path, allowAboveRoot) {
154
155
  const isAbsolute = function(p) {
155
156
  return _IS_ABSOLUTE_RE.test(p);
156
157
  };
158
+ const dirname = function(p) {
159
+ const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
160
+ if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
161
+ segments[0] += "/";
162
+ }
163
+ return segments.join("/") || (isAbsolute(p) ? "/" : ".");
164
+ };
157
165
  const basename = function(p, extension) {
158
166
  const lastSegment = normalizeWindowsPath(p).split("/").pop();
159
167
  return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
160
168
  };
161
169
 
170
+ const click = async ({ provider }, element, options = {}) => {
171
+ if (provider.name === "playwright") {
172
+ const page = provider.page;
173
+ await page.frameLocator("iframe[data-vitest]").locator(`xpath=${element}`).click(options);
174
+ return;
175
+ }
176
+ if (provider.name === "webdriverio") {
177
+ const page = provider.browser;
178
+ const frame = await page.findElement("css selector", "iframe[data-vitest]");
179
+ await page.switchToFrame(frame);
180
+ const xpath = `//${element}`;
181
+ await (await page.$(xpath)).click(options);
182
+ return;
183
+ }
184
+ throw new Error(`Provider "${provider.name}" doesn't support click command`);
185
+ };
186
+
162
187
  function assertFileAccess(path, project) {
163
188
  if (!isFileServingAllowed(path, project.server) && !isFileServingAllowed(path, project.ctx.server))
164
189
  throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
165
190
  }
166
191
  const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => {
167
- const filepath = resolve$1(dirname(testPath), path);
192
+ const filepath = resolve$1(dirname$1(testPath), path);
168
193
  assertFileAccess(filepath, project);
169
194
  if (typeof options === "object" && !options.encoding)
170
195
  options.encoding = "utf-8";
171
196
  return promises.readFile(filepath, options);
172
197
  };
173
198
  const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => {
174
- const filepath = resolve$1(dirname(testPath), path);
199
+ const filepath = resolve$1(dirname$1(testPath), path);
175
200
  assertFileAccess(filepath, project);
176
- const dir = dirname(filepath);
201
+ const dir = dirname$1(filepath);
177
202
  if (!fs.existsSync(dir))
178
203
  await promises.mkdir(dir, { recursive: true });
179
204
  await promises.writeFile(filepath, data, options);
180
205
  };
181
206
  const removeFile = async ({ project, testPath = process.cwd() }, path) => {
182
- const filepath = resolve$1(dirname(testPath), path);
207
+ const filepath = resolve$1(dirname$1(testPath), path);
183
208
  assertFileAccess(filepath, project);
184
209
  await promises.rm(filepath);
185
210
  };
@@ -255,11 +280,13 @@ var builtinCommands = {
255
280
  readFile,
256
281
  removeFile,
257
282
  writeFile,
258
- sendKeys
283
+ sendKeys,
284
+ __vitest_click: click
259
285
  };
260
286
 
261
287
  const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
262
288
  const ID_CONTEXT = "@vitest/browser/context";
289
+ const __dirname = dirname(fileURLToPath(import.meta.url));
263
290
  function BrowserContext(project) {
264
291
  project.config.browser.commands ??= {};
265
292
  for (const [name, command] of Object.entries(builtinCommands))
@@ -277,24 +304,28 @@ function BrowserContext(project) {
277
304
  },
278
305
  load(id) {
279
306
  if (id === VIRTUAL_ID_CONTEXT)
280
- return generateContextFile(project);
307
+ return generateContextFile.call(this, project);
281
308
  }
282
309
  };
283
310
  }
284
- function generateContextFile(project) {
311
+ async function generateContextFile(project) {
285
312
  const commands = Object.keys(project.config.browser.commands ?? {});
286
313
  const filepathCode = "__vitest_worker__.filepath || __vitest_worker__.current?.file?.filepath || undefined";
314
+ const provider = project.browserProvider;
287
315
  const commandsCode = commands.map((command) => {
288
- return ` ["${command}"]: (...args) => rpc().triggerCommand("${command}", ${filepathCode}, args),`;
316
+ return ` ["${command}"]: (...args) => rpc().triggerCommand("${command}", filepath(), args),`;
289
317
  }).join("\n");
318
+ const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
290
319
  return `
320
+ ${userEventNonProviderImport}
321
+ const filepath = () => ${filepathCode}
291
322
  const rpc = () => __vitest_worker__.rpc
292
323
  const channel = new BroadcastChannel('vitest')
293
324
 
294
325
  export const server = {
295
326
  platform: ${JSON.stringify(process.platform)},
296
327
  version: ${JSON.stringify(process.version)},
297
- provider: ${JSON.stringify(project.browserProvider.name)},
328
+ provider: ${JSON.stringify(provider.name)},
298
329
  browser: ${JSON.stringify(project.config.browser.name)},
299
330
  commands: {
300
331
  ${commandsCode}
@@ -320,10 +351,56 @@ export const page = {
320
351
  }
321
352
  })
322
353
  })
354
+ },
355
+ }
356
+
357
+ export const userEvent = ${getUserEventScript(project)}
358
+
359
+ function convertElementToXPath(element) {
360
+ if (!element || !(element instanceof Element)) {
361
+ // TODO: better error message
362
+ throw new Error('Expected element to be an instance of Element')
363
+ }
364
+ return getPathTo(element)
365
+ }
366
+
367
+ function getPathTo(element) {
368
+ if (element.id !== '')
369
+ return \`id("\${element.id}")\`
370
+
371
+ if (!element.parentNode || element === document.documentElement)
372
+ return element.tagName
373
+
374
+ let ix = 0
375
+ const siblings = element.parentNode.childNodes
376
+ for (let i = 0; i < siblings.length; i++) {
377
+ const sibling = siblings[i]
378
+ if (sibling === element)
379
+ return \`\${getPathTo(element.parentNode)}/\${element.tagName}[\${ix + 1}]\`
380
+ if (sibling.nodeType === 1 && sibling.tagName === element.tagName)
381
+ ix++
323
382
  }
324
383
  }
325
384
  `;
326
385
  }
386
+ async function getUserEventImport(provider, resolve) {
387
+ if (provider.name !== "preview")
388
+ return "";
389
+ const resolved = await resolve("@testing-library/user-event", __dirname);
390
+ if (!resolved)
391
+ throw new Error(`Failed to resolve user-event package from ${__dirname}`);
392
+ return `import { userEvent as __vitest_user_event__ } from '${slash(`/@fs/${resolved.id}`)}'`;
393
+ }
394
+ function getUserEventScript(project) {
395
+ if (project.browserProvider?.name === "preview")
396
+ return `__vitest_user_event__`;
397
+ return `{
398
+ async click(element, options) {
399
+ const xpath = convertElementToXPath(element)
400
+ return rpc().triggerCommand('__vitest_click', filepath(), options ? [xpath, options] : [xpath]);
401
+ },
402
+ }`;
403
+ }
327
404
 
328
405
  function automockModule(code, parse) {
329
406
  const ast = parse(code);
@@ -598,8 +675,9 @@ var index = (project, base = "/") => {
598
675
  return;
599
676
  }
600
677
  const decodedTestFile = decodeURIComponent(url.pathname.slice(testerPrefix.length));
601
- const tests = decodedTestFile === "__vitest_all__" || !files.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
602
- const iframeId = decodedTestFile === "__vitest_all__" ? '"__vitest_all__"' : JSON.stringify(decodedTestFile);
678
+ const testFiles = await project.globTestFiles();
679
+ const tests = decodedTestFile === "__vitest_all__" || !testFiles.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
680
+ const iframeId = JSON.stringify(decodedTestFile);
603
681
  if (!testerScripts)
604
682
  testerScripts = await formatScripts(project.config.browser.testerScripts, server);
605
683
  const html = replacer(await testerHtml, {
@@ -643,25 +721,8 @@ var index = (project, base = "/") => {
643
721
  name: "vitest:browser:tests",
644
722
  enforce: "pre",
645
723
  async config() {
646
- const {
647
- include,
648
- exclude,
649
- includeSource,
650
- dir,
651
- root
652
- } = project.config;
653
- const projectRoot = dir || root;
654
- const entries = await project.globAllTestFiles(include, exclude, includeSource, projectRoot);
655
724
  return {
656
725
  optimizeDeps: {
657
- entries: [
658
- ...entries,
659
- "vitest",
660
- "vitest/utils",
661
- "vitest/browser",
662
- "vitest/runners",
663
- "@vitest/utils"
664
- ],
665
726
  exclude: [
666
727
  "vitest",
667
728
  "vitest/utils",
@@ -671,6 +732,7 @@ var index = (project, base = "/") => {
671
732
  "std-env",
672
733
  "tinybench",
673
734
  "tinyspy",
735
+ "pathe",
674
736
  // loupe is manually transformed
675
737
  "loupe"
676
738
  ],
@@ -678,11 +740,14 @@ var index = (project, base = "/") => {
678
740
  "vitest > @vitest/utils > pretty-format",
679
741
  "vitest > @vitest/snapshot > pretty-format",
680
742
  "vitest > @vitest/snapshot > magic-string",
681
- "vitest > diff-sequences",
682
743
  "vitest > pretty-format",
683
744
  "vitest > pretty-format > ansi-styles",
684
745
  "vitest > pretty-format > ansi-regex",
685
- "vitest > chai"
746
+ "vitest > chai",
747
+ "vitest > @vitest/runner > p-limit",
748
+ "vitest > @vitest/utils > diff-sequences",
749
+ "@vitest/browser > @testing-library/user-event",
750
+ "@vitest/browser > @testing-library/dom"
686
751
  ]
687
752
  }
688
753
  };
@@ -708,7 +773,31 @@ export default globalThis.loupe`;
708
773
  }
709
774
  },
710
775
  BrowserContext(project),
711
- DynamicImport()
776
+ DynamicImport(),
777
+ // TODO: remove this when @testing-library/vue supports ESM
778
+ {
779
+ name: "vitest:browser:support-vue-testing-library",
780
+ config() {
781
+ return {
782
+ optimizeDeps: {
783
+ esbuildOptions: {
784
+ plugins: [
785
+ {
786
+ name: "test-utils-rewrite",
787
+ setup(build) {
788
+ const _require = createRequire(import.meta.url);
789
+ build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
790
+ const resolved = _require.resolve(args.path, { paths: [args.importer] });
791
+ return { path: resolved };
792
+ });
793
+ }
794
+ }
795
+ ]
796
+ }
797
+ }
798
+ };
799
+ }
800
+ }
712
801
  ];
713
802
  };
714
803
  function resolveCoverageFolder(project) {
package/dist/providers.js CHANGED
@@ -104,8 +104,8 @@ class WebdriverBrowserProvider {
104
104
  }
105
105
  }
106
106
 
107
- class NoneBrowserProvider {
108
- name = "none";
107
+ class PreviewBrowserProvider {
108
+ name = "preview";
109
109
  ctx;
110
110
  open = false;
111
111
  getSupportedBrowsers() {
@@ -118,7 +118,7 @@ class NoneBrowserProvider {
118
118
  this.ctx = ctx;
119
119
  this.open = false;
120
120
  if (ctx.config.browser.headless)
121
- throw new Error(`You've enabled headless mode for "none" provider but it doesn't support it.`);
121
+ throw new Error(`You've enabled headless mode for "preview" provider but it doesn't support it. Use "playwright" or "webdriverio" instead: https://vitest.dev/guide/browser#configuration`);
122
122
  }
123
123
  async openPage(_url) {
124
124
  this.open = true;
@@ -136,6 +136,6 @@ class NoneBrowserProvider {
136
136
 
137
137
  const webdriverio = WebdriverBrowserProvider;
138
138
  const playwright = PlaywrightBrowserProvider;
139
- const none = NoneBrowserProvider;
139
+ const preview = PreviewBrowserProvider;
140
140
 
141
- export { none, playwright, webdriverio };
141
+ export { playwright, preview, webdriverio };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/browser",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.6",
4
+ "version": "2.0.0-beta.7",
5
5
  "description": "Browser running for Vitest",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -47,7 +47,7 @@
47
47
  "peerDependencies": {
48
48
  "playwright": "*",
49
49
  "webdriverio": "*",
50
- "vitest": "2.0.0-beta.6"
50
+ "vitest": "2.0.0-beta.7"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "playwright": {
@@ -61,9 +61,11 @@
61
61
  }
62
62
  },
63
63
  "dependencies": {
64
+ "@testing-library/dom": "^9.3.3",
65
+ "@testing-library/user-event": "^14.5.2",
64
66
  "magic-string": "^0.30.10",
65
67
  "sirv": "^2.0.4",
66
- "@vitest/utils": "2.0.0-beta.6"
68
+ "@vitest/utils": "2.0.0-beta.7"
67
69
  },
68
70
  "devDependencies": {
69
71
  "@types/ws": "^8.5.10",
@@ -75,10 +77,10 @@
75
77
  "playwright-core": "^1.44.0",
76
78
  "safaridriver": "^0.1.2",
77
79
  "webdriverio": "^8.36.1",
78
- "@vitest/ui": "2.0.0-beta.6",
79
- "@vitest/runner": "2.0.0-beta.6",
80
- "@vitest/ws-client": "2.0.0-beta.6",
81
- "vitest": "2.0.0-beta.6"
80
+ "@vitest/ws-client": "2.0.0-beta.7",
81
+ "vitest": "2.0.0-beta.7",
82
+ "@vitest/runner": "2.0.0-beta.7",
83
+ "@vitest/ui": "2.0.0-beta.7"
82
84
  },
83
85
  "scripts": {
84
86
  "build": "rimraf dist && pnpm build:node && pnpm build:client",
package/providers.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { BrowserProvider } from 'vitest/node'
1
+ import type { BrowserProviderModule } from 'vitest/node'
2
2
 
3
- declare const webdriverio: BrowserProvider
4
- declare const playwright: BrowserProvider
5
- declare const none: BrowserProvider
3
+ declare const webdriverio: BrowserProviderModule
4
+ declare const playwright: BrowserProviderModule
5
+ declare const preview: BrowserProviderModule
6
6
 
7
- export { webdriverio, playwright, none }
7
+ export { webdriverio, playwright, preview }