@vitest/browser 4.0.0-beta.11 → 4.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
@@ -4,13 +4,13 @@ import c from 'tinyrainbow';
4
4
  import { isValidApiRequest, isFileServingAllowed, distDir, resolveApiServerConfig, resolveFsAllow, rolldownVersion, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
5
5
  import fs, { readFileSync, lstatSync, createReadStream, promises, existsSync } from 'node:fs';
6
6
  import { createRequire } from 'node:module';
7
- import { slash as slash$1, toArray, deepMerge, createDefer } from '@vitest/utils/helpers';
7
+ import { slash as slash$1, toArray, nanoid as nanoid$1, deepMerge, createDefer } from '@vitest/utils/helpers';
8
8
  import MagicString from 'magic-string';
9
9
  import sirv from 'sirv';
10
10
  import { coverageConfigDefaults } from 'vitest/config';
11
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 } from 'node:fs/promises';
13
+ import { mkdir, rm, readFile as readFile$1, writeFile as writeFile$1, unlink } from 'node:fs/promises';
14
14
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
15
15
  import { PlaywrightBrowserProvider } from './providers/playwright.js';
16
16
  import { WebdriverBrowserProvider } from './providers/webdriverio.js';
@@ -22,7 +22,7 @@ import pm from 'pixelmatch';
22
22
  import { WebSocketServer } from 'ws';
23
23
  import { performance } from 'node:perf_hooks';
24
24
 
25
- var version = "4.0.0-beta.11";
25
+ var version = "4.0.0-beta.13";
26
26
 
27
27
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
28
28
  function normalizeWindowsPath(input = "") {
@@ -339,7 +339,7 @@ async function getBrowserProvider(options, project) {
339
339
  if (!(name in providers)) {
340
340
  throw new Error(`Unknown browser provider "${name}". Available providers: ${Object.keys(providers).join(", ")}.`);
341
341
  }
342
- return providers[name]().factory(project);
342
+ return providers[name](options.provider?.options).factory(project);
343
343
  }
344
344
  const supportedBrowsers = options.provider.supportedBrowser || [];
345
345
  if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
@@ -773,8 +773,6 @@ var BrowserPlugin = (parentServer, base = "/") => {
773
773
  "vitest > expect-type",
774
774
  "vitest > @vitest/snapshot > magic-string",
775
775
  "vitest > @vitest/expect > chai",
776
- "vitest > @vitest/expect > chai > loupe",
777
- "vitest > @vitest/utils > loupe",
778
776
  "@vitest/browser > @testing-library/user-event",
779
777
  "@vitest/browser > @testing-library/dom"
780
778
  ];
@@ -2396,8 +2394,12 @@ async function takeScreenshot(context, name, options) {
2396
2394
  throw new Error(`Cannot take a screenshot without a test path`);
2397
2395
  }
2398
2396
  const path = resolveScreenshotPath(context.testPath, name, context.project.config, options.path);
2399
- const savePath = normalize$1(path);
2400
- await mkdir(dirname(path), { recursive: true });
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
+ }
2401
2403
  if (context.provider instanceof PlaywrightBrowserProvider) {
2402
2404
  const mask = options.mask?.map((selector) => context.iframe.locator(selector));
2403
2405
  if (options.element) {
@@ -2406,7 +2408,7 @@ async function takeScreenshot(context, name, options) {
2406
2408
  const buffer = await element.screenshot({
2407
2409
  ...config,
2408
2410
  mask,
2409
- path: options.save ? savePath : undefined
2411
+ path: savePath
2410
2412
  });
2411
2413
  return {
2412
2414
  buffer,
@@ -2416,7 +2418,7 @@ async function takeScreenshot(context, name, options) {
2416
2418
  const buffer = await context.iframe.locator("body").screenshot({
2417
2419
  ...options,
2418
2420
  mask,
2419
- path: options.save ? savePath : undefined
2421
+ path: savePath
2420
2422
  });
2421
2423
  return {
2422
2424
  buffer,
@@ -2424,11 +2426,17 @@ async function takeScreenshot(context, name, options) {
2424
2426
  };
2425
2427
  }
2426
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
+ }
2427
2434
  const page = context.provider.browser;
2428
2435
  const element = !options.element ? await page.$("body") : await page.$(`${options.element}`);
2429
2436
  // webdriverio expects the path to contain the extension and only works with PNG files
2430
2437
  const savePathWithExtension = savePath.endsWith(".png") ? savePath : `${savePath}.png`;
2431
- const buffer = await element.saveScreenshot(savePathWithExtension);
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));
2432
2440
  if (!options.save) {
2433
2441
  await rm(savePathWithExtension, { force: true });
2434
2442
  }
@@ -2907,6 +2915,101 @@ const tab = async (context, options = {}) => {
2907
2915
  throw new Error(`Provider "${provider.name}" doesn't support tab command`);
2908
2916
  };
2909
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
+
2910
3013
  const type = async (context, selector, text, options = {}) => {
2911
3014
  const { skipClick = false, skipAutoClose = false } = options;
2912
3015
  const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
@@ -2999,7 +3102,12 @@ var builtinCommands = {
2999
3102
  __vitest_hover: hover,
3000
3103
  __vitest_cleanup: keyboardCleanup,
3001
3104
  __vitest_viewport: viewport,
3002
- __vitest_screenshotMatcher: screenshotMatcher
3105
+ __vitest_screenshotMatcher: screenshotMatcher,
3106
+ __vitest_deleteTracing: deleteTracing,
3107
+ __vitest_startChunkTrace: startChunkTrace,
3108
+ __vitest_startTracing: startTracing,
3109
+ __vitest_stopChunkTrace: stopChunkTrace,
3110
+ __vitest_annotateTraces: annotateTraces
3003
3111
  };
3004
3112
 
3005
3113
  class BrowserServerState {
@@ -3984,6 +4092,11 @@ class BrowserPool {
3984
4092
  return;
3985
4093
  }
3986
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
+ }
3987
4100
  if (!provider.getCDPSession) {
3988
4101
  throw new Error("Unable to set breakpoint, CDP not supported");
3989
4102
  }
@@ -3996,6 +4109,12 @@ class BrowserPool {
3996
4109
  });
3997
4110
  }
3998
4111
  }
4112
+ function shouldIgnoreDebugger(provider, browser) {
4113
+ if (provider === "webdriverio") {
4114
+ return browser !== "chrome" && browser !== "edge";
4115
+ }
4116
+ return browser !== "chromium";
4117
+ }
3999
4118
 
4000
4119
  async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
4001
4120
  if (project.vitest.version !== version) {
@@ -1 +1 @@
1
- import"@vitest/browser/context";import"../public-utils-Kx5DUGWa.js";export{L as Locator,s as selectorEngine}from"../index-DDlvjJVO.js";import"vitest/internal/browser";
1
+ import"@vitest/browser/context";import"../public-utils-B6exS8fl.js";export{L as Locator,s as selectorEngine}from"../index-BPDFwkoW.js";import"vitest/internal/browser";
@@ -1 +1 @@
1
- import{page,server}from"@vitest/browser/context";import{g as getByTitleSelector,a as getByTextSelector,b as getByPlaceholderSelector,c as getByAltTextSelector,d as getByTestIdSelector,e as getByRoleSelector,f as getByLabelSelector}from"../public-utils-Kx5DUGWa.js";import{s as selectorEngine,L as Locator,p as processTimeoutOptions,g as getIframeScale}from"../index-DDlvjJVO.js";import"vitest/internal/browser";page.extend({getByLabelText(e,_){return new PlaywrightLocator(getByLabelSelector(e,_))},getByRole(e,_){return new PlaywrightLocator(getByRoleSelector(e,_))},getByTestId(e){return new PlaywrightLocator(getByTestIdSelector(server.config.browser.locators.testIdAttribute,e))},getByAltText(e,_){return new PlaywrightLocator(getByAltTextSelector(e,_))},getByPlaceholder(e,_){return new PlaywrightLocator(getByPlaceholderSelector(e,_))},getByText(e,_){return new PlaywrightLocator(getByTextSelector(e,_))},getByTitle(e,_){return new PlaywrightLocator(getByTitleSelector(e,_))},_createLocator(e){return new PlaywrightLocator(e)},elementLocator(e){return new PlaywrightLocator(selectorEngine.generateSelectorSimple(e),e)},frameLocator(e){return new PlaywrightLocator(`${e.selector} >> internal:control=enter-frame`)}});class PlaywrightLocator extends Locator{constructor(e,_){super(),this.selector=e,this._container=_}click(e){return super.click(processTimeoutOptions(processClickOptions(e)))}dblClick(e){return super.dblClick(processTimeoutOptions(processClickOptions(e)))}tripleClick(e){return super.tripleClick(processTimeoutOptions(processClickOptions(e)))}selectOptions(e,_){return super.selectOptions(e,processTimeoutOptions(_))}clear(e){return super.clear(processTimeoutOptions(e))}hover(e){return super.hover(processTimeoutOptions(processHoverOptions(e)))}upload(e,_){return super.upload(e,processTimeoutOptions(_))}fill(e,_){return super.fill(e,processTimeoutOptions(_))}dropTo(e,_){return super.dropTo(e,processTimeoutOptions(processDragAndDropOptions(_)))}locator(e){return new PlaywrightLocator(`${this.selector} >> ${e}`,this._container)}elementLocator(e){return new PlaywrightLocator(selectorEngine.generateSelectorSimple(e),e)}}function processDragAndDropOptions(e){if(!e)return e;let _=e;return _.sourcePosition&&=processPlaywrightPosition(_.sourcePosition),_.targetPosition&&=processPlaywrightPosition(_.targetPosition),e}function processHoverOptions(e){if(!e)return e;let _=e;return _.position&&=processPlaywrightPosition(_.position),e}function processClickOptions(e){if(!e)return e;let _=e;return _.position&&=processPlaywrightPosition(_.position),_}function processPlaywrightPosition(e){let _=getIframeScale();return e.x!=null&&(e.x*=_),e.y!=null&&(e.y*=_),e}
1
+ import{page,server}from"@vitest/browser/context";import{g as getByTitleSelector,a as getByTextSelector,b as getByPlaceholderSelector,c as getByAltTextSelector,d as getByTestIdSelector,e as getByRoleSelector,f as getByLabelSelector}from"../public-utils-B6exS8fl.js";import{s as selectorEngine,L as Locator,p as processTimeoutOptions,g as getIframeScale}from"../index-BPDFwkoW.js";import"vitest/internal/browser";page.extend({getByLabelText(e,_){return new PlaywrightLocator(getByLabelSelector(e,_))},getByRole(e,_){return new PlaywrightLocator(getByRoleSelector(e,_))},getByTestId(e){return new PlaywrightLocator(getByTestIdSelector(server.config.browser.locators.testIdAttribute,e))},getByAltText(e,_){return new PlaywrightLocator(getByAltTextSelector(e,_))},getByPlaceholder(e,_){return new PlaywrightLocator(getByPlaceholderSelector(e,_))},getByText(e,_){return new PlaywrightLocator(getByTextSelector(e,_))},getByTitle(e,_){return new PlaywrightLocator(getByTitleSelector(e,_))},_createLocator(e){return new PlaywrightLocator(e)},elementLocator(e){return new PlaywrightLocator(selectorEngine.generateSelectorSimple(e),e)},frameLocator(e){return new PlaywrightLocator(`${e.selector} >> internal:control=enter-frame`)}});class PlaywrightLocator extends Locator{constructor(e,_){super(),this.selector=e,this._container=_}click(e){return super.click(processTimeoutOptions(processClickOptions(e)))}dblClick(e){return super.dblClick(processTimeoutOptions(processClickOptions(e)))}tripleClick(e){return super.tripleClick(processTimeoutOptions(processClickOptions(e)))}selectOptions(e,_){return super.selectOptions(e,processTimeoutOptions(_))}clear(e){return super.clear(processTimeoutOptions(e))}hover(e){return super.hover(processTimeoutOptions(processHoverOptions(e)))}upload(e,_){return super.upload(e,processTimeoutOptions(_))}fill(e,_){return super.fill(e,processTimeoutOptions(_))}dropTo(e,_){return super.dropTo(e,processTimeoutOptions(processDragAndDropOptions(_)))}locator(e){return new PlaywrightLocator(`${this.selector} >> ${e}`,this._container)}elementLocator(e){return new PlaywrightLocator(selectorEngine.generateSelectorSimple(e),e)}}function processDragAndDropOptions(e){if(!e)return e;let _=e;return _.sourcePosition&&=processPlaywrightPosition(_.sourcePosition),_.targetPosition&&=processPlaywrightPosition(_.targetPosition),e}function processHoverOptions(e){if(!e)return e;let _=e;return _.position&&=processPlaywrightPosition(_.position),e}function processClickOptions(e){if(!e)return e;let _=e;return _.position&&=processPlaywrightPosition(_.position),_}function processPlaywrightPosition(e){let _=getIframeScale();return e.x!=null&&(e.x*=_),e.y!=null&&(e.y*=_),e}
@@ -1 +1 @@
1
- import{page,server,userEvent}from"@vitest/browser/context";import{g as getByTitleSelector,a as getByTextSelector,b as getByPlaceholderSelector,c as getByAltTextSelector,d as getByTestIdSelector,e as getByRoleSelector,f as getByLabelSelector,h as getElementError}from"../public-utils-Kx5DUGWa.js";import{s as selectorEngine,L as Locator,c as convertElementToCssSelector}from"../index-DDlvjJVO.js";import"vitest/internal/browser";page.extend({getByLabelText(e,m){return new PreviewLocator(getByLabelSelector(e,m))},getByRole(e,m){return new PreviewLocator(getByRoleSelector(e,m))},getByTestId(e){return new PreviewLocator(getByTestIdSelector(server.config.browser.locators.testIdAttribute,e))},getByAltText(e,m){return new PreviewLocator(getByAltTextSelector(e,m))},getByPlaceholder(e,m){return new PreviewLocator(getByPlaceholderSelector(e,m))},getByText(e,m){return new PreviewLocator(getByTextSelector(e,m))},getByTitle(e,m){return new PreviewLocator(getByTitleSelector(e,m))},_createLocator(e){return new PreviewLocator(e)},elementLocator(e){return new PreviewLocator(selectorEngine.generateSelectorSimple(e),e)}});class PreviewLocator extends Locator{constructor(e,m){super(),this._pwSelector=e,this._container=m}get selector(){let e=this.elements().map(e=>convertElementToCssSelector(e));if(!e.length)throw getElementError(this._pwSelector,this._container||document.body);return e.join(`, `)}click(){return userEvent.click(this.element())}dblClick(){return userEvent.dblClick(this.element())}tripleClick(){return userEvent.tripleClick(this.element())}hover(){return userEvent.hover(this.element())}unhover(){return userEvent.unhover(this.element())}async fill(e){return userEvent.fill(this.element(),e)}async upload(e){return userEvent.upload(this.element(),e)}selectOptions(e){return userEvent.selectOptions(this.element(),e)}clear(){return userEvent.clear(this.element())}locator(e){return new PreviewLocator(`${this._pwSelector} >> ${e}`,this._container)}elementLocator(e){return new PreviewLocator(selectorEngine.generateSelectorSimple(e),e)}}
1
+ import{page,server,userEvent}from"@vitest/browser/context";import{g as getByTitleSelector,a as getByTextSelector,b as getByPlaceholderSelector,c as getByAltTextSelector,d as getByTestIdSelector,e as getByRoleSelector,f as getByLabelSelector,h as getElementError}from"../public-utils-B6exS8fl.js";import{s as selectorEngine,L as Locator,c as convertElementToCssSelector}from"../index-BPDFwkoW.js";import"vitest/internal/browser";page.extend({getByLabelText(e,m){return new PreviewLocator(getByLabelSelector(e,m))},getByRole(e,m){return new PreviewLocator(getByRoleSelector(e,m))},getByTestId(e){return new PreviewLocator(getByTestIdSelector(server.config.browser.locators.testIdAttribute,e))},getByAltText(e,m){return new PreviewLocator(getByAltTextSelector(e,m))},getByPlaceholder(e,m){return new PreviewLocator(getByPlaceholderSelector(e,m))},getByText(e,m){return new PreviewLocator(getByTextSelector(e,m))},getByTitle(e,m){return new PreviewLocator(getByTitleSelector(e,m))},_createLocator(e){return new PreviewLocator(e)},elementLocator(e){return new PreviewLocator(selectorEngine.generateSelectorSimple(e),e)}});class PreviewLocator extends Locator{constructor(e,m){super(),this._pwSelector=e,this._container=m}get selector(){let e=this.elements().map(e=>convertElementToCssSelector(e));if(!e.length)throw getElementError(this._pwSelector,this._container||document.body);return e.join(`, `)}click(){return userEvent.click(this.element())}dblClick(){return userEvent.dblClick(this.element())}tripleClick(){return userEvent.tripleClick(this.element())}hover(){return userEvent.hover(this.element())}unhover(){return userEvent.unhover(this.element())}async fill(e){return userEvent.fill(this.element(),e)}async upload(e){return userEvent.upload(this.element(),e)}selectOptions(e){return userEvent.selectOptions(this.element(),e)}clear(){return userEvent.clear(this.element())}locator(e){return new PreviewLocator(`${this._pwSelector} >> ${e}`,this._container)}elementLocator(e){return new PreviewLocator(selectorEngine.generateSelectorSimple(e),e)}}
@@ -1 +1 @@
1
- import{page,server}from"@vitest/browser/context";import{g as getByTitleSelector,a as getByTextSelector,b as getByPlaceholderSelector,c as getByAltTextSelector,d as getByTestIdSelector,e as getByRoleSelector,f as getByLabelSelector,h as getElementError}from"../public-utils-Kx5DUGWa.js";import{s as selectorEngine,L as Locator,c as convertElementToCssSelector,a as getBrowserState,g as getIframeScale}from"../index-DDlvjJVO.js";import"vitest/internal/browser";page.extend({getByLabelText(e,g){return new WebdriverIOLocator(getByLabelSelector(e,g))},getByRole(e,g){return new WebdriverIOLocator(getByRoleSelector(e,g))},getByTestId(e){return new WebdriverIOLocator(getByTestIdSelector(server.config.browser.locators.testIdAttribute,e))},getByAltText(e,g){return new WebdriverIOLocator(getByAltTextSelector(e,g))},getByPlaceholder(e,g){return new WebdriverIOLocator(getByPlaceholderSelector(e,g))},getByText(e,g){return new WebdriverIOLocator(getByTextSelector(e,g))},getByTitle(e,g){return new WebdriverIOLocator(getByTitleSelector(e,g))},_createLocator(e){return new WebdriverIOLocator(e)},elementLocator(e){return new WebdriverIOLocator(selectorEngine.generateSelectorSimple(e))}});class WebdriverIOLocator extends Locator{constructor(e,g){super(),this._pwSelector=e,this._container=g}get selector(){let e=this.elements().map(e=>convertElementToCssSelector(e));if(!e.length)throw getElementError(this._pwSelector,this._container||document.body);let g=!1,_=e.map(e=>e.startsWith(`>>>`)?(g=!0,e.slice(3)):e);return(g?`>>>`:``)+_.join(`, `)}click(e){return super.click(processClickOptions(e))}dblClick(e){return super.dblClick(processClickOptions(e))}tripleClick(e){return super.tripleClick(processClickOptions(e))}selectOptions(e,g){let _=getWebdriverioSelectOptions(this.element(),e);return this.triggerCommand(`__vitest_selectOptions`,this.selector,_,g)}hover(e){return super.hover(processHoverOptions(e))}dropTo(e,g){return super.dropTo(e,processDragAndDropOptions(g))}locator(e){return new WebdriverIOLocator(`${this._pwSelector} >> ${e}`,this._container)}elementLocator(e){return new WebdriverIOLocator(selectorEngine.generateSelectorSimple(e),e)}}function getWebdriverioSelectOptions(e,g){let _=[...e.querySelectorAll(`option`)],v=Array.isArray(g)?g:[g];if(!v.length)return[];if(v.length>1)throw Error(`Provider "webdriverio" doesn't support selecting multiple values at once`);let y=v[0];if(typeof y!=`string`){let e=`element`in y?y.element():y,g=_.indexOf(e);if(g===-1)throw Error(`The element ${selectorEngine.previewNode(e)} was not found in the "select" options.`);return[{index:g}]}let b=_.findIndex(e=>e.value===y);if(b!==-1)return[{index:b}];let x=_.findIndex(e=>e.textContent?.trim()===y||e.ariaLabel===y);if(x===-1)throw Error(`The option "${y}" was not found in the "select" options.`);return[{index:x}]}function processClickOptions(e){if(!e||!getBrowserState().config.browser.ui)return e;let g=e;if(g.x!=null||g.y!=null){let e={};g.x!=null&&(g.x=scaleCoordinate(g.x,e)),g.y!=null&&(g.y=scaleCoordinate(g.y,e))}return e}function processHoverOptions(e){if(!e||!getBrowserState().config.browser.ui)return e;let g=e,_={};return g.xOffset!=null&&(g.xOffset=scaleCoordinate(g.xOffset,_)),g.yOffset!=null&&(g.yOffset=scaleCoordinate(g.yOffset,_)),e}function processDragAndDropOptions(e){if(!e||!getBrowserState().config.browser.ui)return e;let g={},_=e;return _.sourceX!=null&&(_.sourceX=scaleCoordinate(_.sourceX,g)),_.sourceY!=null&&(_.sourceY=scaleCoordinate(_.sourceY,g)),_.targetX!=null&&(_.targetX=scaleCoordinate(_.targetX,g)),_.targetY!=null&&(_.targetY=scaleCoordinate(_.targetY,g)),e}function scaleCoordinate(e,g){return Math.round(e*getCachedScale(g))}function getCachedScale(e){return e.scale??=getIframeScale()}
1
+ import{page,server}from"@vitest/browser/context";import{g as getByTitleSelector,a as getByTextSelector,b as getByPlaceholderSelector,c as getByAltTextSelector,d as getByTestIdSelector,e as getByRoleSelector,f as getByLabelSelector,h as getElementError}from"../public-utils-B6exS8fl.js";import{s as selectorEngine,L as Locator,c as convertElementToCssSelector,a as getBrowserState,g as getIframeScale}from"../index-BPDFwkoW.js";import"vitest/internal/browser";page.extend({getByLabelText(e,g){return new WebdriverIOLocator(getByLabelSelector(e,g))},getByRole(e,g){return new WebdriverIOLocator(getByRoleSelector(e,g))},getByTestId(e){return new WebdriverIOLocator(getByTestIdSelector(server.config.browser.locators.testIdAttribute,e))},getByAltText(e,g){return new WebdriverIOLocator(getByAltTextSelector(e,g))},getByPlaceholder(e,g){return new WebdriverIOLocator(getByPlaceholderSelector(e,g))},getByText(e,g){return new WebdriverIOLocator(getByTextSelector(e,g))},getByTitle(e,g){return new WebdriverIOLocator(getByTitleSelector(e,g))},_createLocator(e){return new WebdriverIOLocator(e)},elementLocator(e){return new WebdriverIOLocator(selectorEngine.generateSelectorSimple(e))}});class WebdriverIOLocator extends Locator{constructor(e,g){super(),this._pwSelector=e,this._container=g}get selector(){let e=this.elements().map(e=>convertElementToCssSelector(e));if(!e.length)throw getElementError(this._pwSelector,this._container||document.body);let g=!1,_=e.map(e=>e.startsWith(`>>>`)?(g=!0,e.slice(3)):e);return(g?`>>>`:``)+_.join(`, `)}click(e){return super.click(processClickOptions(e))}dblClick(e){return super.dblClick(processClickOptions(e))}tripleClick(e){return super.tripleClick(processClickOptions(e))}selectOptions(e,g){let _=getWebdriverioSelectOptions(this.element(),e);return this.triggerCommand(`__vitest_selectOptions`,this.selector,_,g)}hover(e){return super.hover(processHoverOptions(e))}dropTo(e,g){return super.dropTo(e,processDragAndDropOptions(g))}locator(e){return new WebdriverIOLocator(`${this._pwSelector} >> ${e}`,this._container)}elementLocator(e){return new WebdriverIOLocator(selectorEngine.generateSelectorSimple(e),e)}}function getWebdriverioSelectOptions(e,g){let _=[...e.querySelectorAll(`option`)],v=Array.isArray(g)?g:[g];if(!v.length)return[];if(v.length>1)throw Error(`Provider "webdriverio" doesn't support selecting multiple values at once`);let y=v[0];if(typeof y!=`string`){let e=`element`in y?y.element():y,g=_.indexOf(e);if(g===-1)throw Error(`The element ${selectorEngine.previewNode(e)} was not found in the "select" options.`);return[{index:g}]}let b=_.findIndex(e=>e.value===y);if(b!==-1)return[{index:b}];let x=_.findIndex(e=>e.textContent?.trim()===y||e.ariaLabel===y);if(x===-1)throw Error(`The option "${y}" was not found in the "select" options.`);return[{index:x}]}function processClickOptions(e){if(!e||!getBrowserState().config.browser.ui)return e;let g=e;if(g.x!=null||g.y!=null){let e={};g.x!=null&&(g.x=scaleCoordinate(g.x,e)),g.y!=null&&(g.y=scaleCoordinate(g.y,e))}return e}function processHoverOptions(e){if(!e||!getBrowserState().config.browser.ui)return e;let g=e,_={};return g.xOffset!=null&&(g.xOffset=scaleCoordinate(g.xOffset,_)),g.yOffset!=null&&(g.yOffset=scaleCoordinate(g.yOffset,_)),e}function processDragAndDropOptions(e){if(!e||!getBrowserState().config.browser.ui)return e;let g={},_=e;return _.sourceX!=null&&(_.sourceX=scaleCoordinate(_.sourceX,g)),_.sourceY!=null&&(_.sourceY=scaleCoordinate(_.sourceY,g)),_.targetX!=null&&(_.targetX=scaleCoordinate(_.targetX,g)),_.targetY!=null&&(_.targetY=scaleCoordinate(_.targetY,g)),e}function scaleCoordinate(e,g){return Math.round(e*getCachedScale(g))}function getCachedScale(e){return e.scale??=getIframeScale()}
@@ -10,7 +10,7 @@ interface PlaywrightProviderOptions {
10
10
  * The options passed down to [`playwright.connect`](https://playwright.dev/docs/api/class-browsertype#browser-type-launch) method.
11
11
  * @see {@link https://playwright.dev/docs/api/class-browsertype#browser-type-launch}
12
12
  */
13
- launchOptions?: LaunchOptions;
13
+ launchOptions?: Omit<LaunchOptions, "tracesDir">;
14
14
  /**
15
15
  * The options passed down to [`playwright.connect`](https://playwright.dev/docs/api/class-browsertype#browser-type-connect) method.
16
16
  *
@@ -31,7 +31,7 @@ interface PlaywrightProviderOptions {
31
31
  */
32
32
  actionTimeout?: number;
33
33
  }
34
- declare function playwright(options?: PlaywrightProviderOptions): BrowserProviderOption;
34
+ declare function playwright(options?: PlaywrightProviderOptions): BrowserProviderOption<PlaywrightProviderOptions>;
35
35
  declare class PlaywrightBrowserProvider implements BrowserProvider {
36
36
  private project;
37
37
  private options;
@@ -44,6 +44,8 @@ declare class PlaywrightBrowserProvider implements BrowserProvider {
44
44
  browserName: PlaywrightBrowser;
45
45
  private browserPromise;
46
46
  private closing;
47
+ tracingContexts: Set<string>;
48
+ pendingTraces: Map<string, string>;
47
49
  constructor(project: TestProject, options: PlaywrightProviderOptions);
48
50
  private openBrowser;
49
51
  private createMocker;
@@ -12,6 +12,7 @@ function playwright(options = {}) {
12
12
  return {
13
13
  name: "playwright",
14
14
  supportedBrowser: playwrightBrowsers,
15
+ options,
15
16
  factory(project) {
16
17
  return new PlaywrightBrowserProvider(project, options);
17
18
  },
@@ -28,11 +29,27 @@ class PlaywrightBrowserProvider {
28
29
  browserName;
29
30
  browserPromise = null;
30
31
  closing = false;
32
+ tracingContexts = new Set();
33
+ pendingTraces = new Map();
31
34
  constructor(project, options) {
32
35
  this.project = project;
33
36
  this.options = options;
34
37
  this.browserName = project.config.browser.name;
35
38
  this.mocker = this.createMocker();
39
+ // make sure the traces are finished if the test hangs
40
+ process.on("SIGTERM", () => {
41
+ if (!this.browser) {
42
+ return;
43
+ }
44
+ const promises = [];
45
+ for (const [trace, contextId] of this.pendingTraces.entries()) {
46
+ promises.push((() => {
47
+ const context = this.contexts.get(contextId);
48
+ return context?.tracing.stopChunk({ path: trace });
49
+ })());
50
+ }
51
+ return Promise.allSettled(promises);
52
+ });
36
53
  }
37
54
  async openBrowser() {
38
55
  await this._throwIfClosing();
@@ -62,10 +79,14 @@ class PlaywrightBrowserProvider {
62
79
  ...this.options.launchOptions,
63
80
  headless: options.headless
64
81
  };
65
- if (this.project.config.inspector.enabled) {
82
+ if (typeof options.trace === "object" && options.trace.tracesDir) {
83
+ launchOptions.tracesDir = options.trace?.tracesDir;
84
+ }
85
+ const inspector = this.project.vitest.config.inspector;
86
+ if (inspector.enabled) {
66
87
  // NodeJS equivalent defaults: https://nodejs.org/en/learn/getting-started/debugging#enable-inspector
67
- const port = this.project.config.inspector.port || 9229;
68
- const host = this.project.config.inspector.host || "127.0.0.1";
88
+ const port = inspector.port || 9229;
89
+ const host = inspector.host || "127.0.0.1";
69
90
  launchOptions.args ||= [];
70
91
  launchOptions.args.push(`--remote-debugging-port=${port}`);
71
92
  launchOptions.args.push(`--remote-debugging-address=${host}`);
@@ -1,6 +1,7 @@
1
1
  function preview() {
2
2
  return {
3
3
  name: "preview",
4
+ options: {},
4
5
  factory(project) {
5
6
  return new PreviewBrowserProvider(project);
6
7
  },
@@ -1,9 +1,9 @@
1
1
  import { ScreenshotMatcherOptions, ScreenshotComparatorRegistry } from '@vitest/browser/context';
2
- import { BrowserProviderOption, BrowserProvider, TestProject } from 'vitest/node';
2
+ import { BrowserProviderOption, BrowserProvider, TestProject, CDPSession } from 'vitest/node';
3
3
  import { ClickOptions, DragAndDropOptions, remote } from 'webdriverio';
4
4
 
5
5
  interface WebdriverProviderOptions extends Partial<Parameters<typeof remote>[0]> {}
6
- declare function webdriverio(options?: WebdriverProviderOptions): BrowserProviderOption;
6
+ declare function webdriverio(options?: WebdriverProviderOptions): BrowserProviderOption<WebdriverProviderOptions>;
7
7
  declare class WebdriverBrowserProvider implements BrowserProvider {
8
8
  name: "webdriverio";
9
9
  supportsParallelism: boolean;
@@ -31,6 +31,7 @@ declare class WebdriverBrowserProvider implements BrowserProvider {
31
31
  openPage(sessionId: string, url: string): Promise<void>;
32
32
  private _throwIfClosing;
33
33
  close(): Promise<void>;
34
+ getCDPSession(_sessionId: string): Promise<CDPSession>;
34
35
  }
35
36
  declare module "vitest/node" {
36
37
  interface UserEventClickOptions extends ClickOptions {}
@@ -11,6 +11,7 @@ function webdriverio(options = {}) {
11
11
  return {
12
12
  name: "webdriverio",
13
13
  supportedBrowser: webdriverBrowsers,
14
+ options,
14
15
  factory(project) {
15
16
  return new WebdriverBrowserProvider(project, options);
16
17
  },
@@ -135,6 +136,19 @@ class WebdriverBrowserProvider {
135
136
  capabilities[key] ??= {};
136
137
  capabilities[key].args = args;
137
138
  }
139
+ const inspector = this.project.vitest.config.inspector;
140
+ if (inspector.enabled && (browser === "chrome" || browser === "edge")) {
141
+ const key = browser === "chrome" ? "goog:chromeOptions" : "ms:edgeOptions";
142
+ const args = capabilities[key]?.args || [];
143
+ // NodeJS equivalent defaults: https://nodejs.org/en/learn/getting-started/debugging#enable-inspector
144
+ const port = inspector.port || 9229;
145
+ const host = inspector.host || "127.0.0.1";
146
+ args.push(`--remote-debugging-port=${port}`);
147
+ args.push(`--remote-debugging-address=${host}`);
148
+ this.project.vitest.logger.log(`Debugger listening on ws://${host}:${port}`);
149
+ capabilities[key] ??= {};
150
+ capabilities[key].args = args;
151
+ }
138
152
  return capabilities;
139
153
  }
140
154
  async openPage(sessionId, url) {
@@ -166,6 +180,27 @@ class WebdriverBrowserProvider {
166
180
  browser.sessionId = undefined;
167
181
  this.browser = null;
168
182
  }
183
+ async getCDPSession(_sessionId) {
184
+ return {
185
+ send: (method, params) => {
186
+ if (!this.browser) {
187
+ throw new Error(`The environment was torn down.`);
188
+ }
189
+ return this.browser.sendCommandAndGetResult(method, params ?? {}).catch((error) => {
190
+ return Promise.reject(new Error(`Failed to execute "${method}" command.`, { cause: error }));
191
+ });
192
+ },
193
+ on: () => {
194
+ throw new Error(`webdriverio provider doesn't support cdp.on()`);
195
+ },
196
+ once: () => {
197
+ throw new Error(`webdriverio provider doesn't support cdp.once()`);
198
+ },
199
+ off: () => {
200
+ throw new Error(`webdriverio provider doesn't support cdp.off()`);
201
+ }
202
+ };
203
+ }
169
204
  }
170
205
 
171
206
  export { WebdriverBrowserProvider, webdriverio };