@vitest/browser 2.0.0-beta.5 → 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.
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}
@@ -308,7 +339,7 @@ export const page = {
308
339
  viewport(width, height) {
309
340
  const id = __vitest_browser_runner__.iframeId
310
341
  channel.postMessage({ type: 'viewport', width, height, id })
311
- return new Promise((resolve) => {
342
+ return new Promise((resolve, reject) => {
312
343
  channel.addEventListener('message', function handler(e) {
313
344
  if (e.data.type === 'viewport:done' && e.data.id === id) {
314
345
  channel.removeEventListener('message', handler)
@@ -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.5",
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.5"
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.5"
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/runner": "2.0.0-beta.5",
79
- "@vitest/ui": "2.0.0-beta.5",
80
- "@vitest/ws-client": "2.0.0-beta.5",
81
- "vitest": "2.0.0-beta.5"
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 }