@vitest/browser 4.0.0-beta.1 → 4.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -15
  3. package/context.d.ts +153 -3
  4. package/dist/client/.vite/manifest.json +6 -6
  5. package/dist/client/__vitest__/assets/{index-KbpJLW--.css → index-CCvbyxW7.css} +1 -1
  6. package/dist/client/__vitest__/assets/index-CYIziQD6.js +53 -0
  7. package/dist/client/__vitest__/index.html +2 -2
  8. package/dist/client/__vitest_browser__/orchestrator-DOxlOAkk.js +293 -0
  9. package/dist/client/__vitest_browser__/tester-B7fynsGK.js +2090 -0
  10. package/dist/client/__vitest_browser__/{utils-Owv5OOOf.js → utils-CPmDBIKG.js} +3 -3
  11. package/dist/client/error-catcher.js +7 -3
  12. package/dist/client/esm-client-injector.js +1 -0
  13. package/dist/client/orchestrator.html +2 -2
  14. package/dist/client/tester/tester.html +2 -2
  15. package/dist/client.js +24 -8
  16. package/dist/context.js +29 -22
  17. package/dist/expect-element.js +10 -8
  18. package/dist/index-CwoiDq7G.js +6 -0
  19. package/dist/index-DDlvjJVO.js +1 -0
  20. package/dist/index.d.ts +16 -10
  21. package/dist/index.js +555 -104
  22. package/dist/locators/index.d.ts +8 -7
  23. package/dist/locators/index.js +1 -1
  24. package/dist/locators/playwright.js +1 -1
  25. package/dist/locators/preview.js +1 -1
  26. package/dist/locators/webdriverio.js +1 -1
  27. package/dist/providers/playwright.d.ts +103 -0
  28. package/dist/{webdriver-KA1WiV0q.js → providers/playwright.js} +37 -180
  29. package/dist/providers/preview.d.ts +16 -0
  30. package/dist/{providers.js → providers/preview.js} +17 -21
  31. package/dist/providers/webdriverio.d.ts +50 -0
  32. package/dist/providers/webdriverio.js +171 -0
  33. package/dist/shared/screenshotMatcher/types.d.ts +16 -0
  34. package/dist/state.js +4 -3
  35. package/dist/types.d.ts +5 -7
  36. package/jest-dom.d.ts +95 -1
  37. package/package.json +22 -32
  38. package/utils.d.ts +1 -1
  39. package/dist/client/__vitest__/assets/index-BjtzXzAw.js +0 -58
  40. package/dist/client/__vitest_browser__/orchestrator-CQgVbcQq.js +0 -3213
  41. package/dist/client/__vitest_browser__/tester-BScMoGFI.js +0 -3560
  42. package/dist/index-W1MM53zC.js +0 -1
  43. package/providers/playwright.d.ts +0 -81
  44. package/providers/webdriverio.d.ts +0 -22
@@ -0,0 +1,171 @@
1
+ import { createDebugger } from 'vitest/node';
2
+
3
+ const debug = createDebugger("vitest:browser:wdio");
4
+ const webdriverBrowsers = [
5
+ "firefox",
6
+ "chrome",
7
+ "edge",
8
+ "safari"
9
+ ];
10
+ function webdriverio(options = {}) {
11
+ return {
12
+ name: "webdriverio",
13
+ supportedBrowser: webdriverBrowsers,
14
+ factory(project) {
15
+ return new WebdriverBrowserProvider(project, options);
16
+ },
17
+ _cli: true
18
+ };
19
+ }
20
+ class WebdriverBrowserProvider {
21
+ name = "webdriverio";
22
+ supportsParallelism = false;
23
+ browser = null;
24
+ browserName;
25
+ project;
26
+ options;
27
+ closing = false;
28
+ iframeSwitched = false;
29
+ topLevelContext;
30
+ getSupportedBrowsers() {
31
+ return webdriverBrowsers;
32
+ }
33
+ constructor(project, options) {
34
+ // increase shutdown timeout because WDIO takes some extra time to kill the driver
35
+ if (!project.vitest.state._data.timeoutIncreased) {
36
+ project.vitest.state._data.timeoutIncreased = true;
37
+ project.vitest.config.teardownTimeout += 1e4;
38
+ }
39
+ this.closing = false;
40
+ this.project = project;
41
+ this.browserName = project.config.browser.name;
42
+ this.options = options;
43
+ }
44
+ isIframeSwitched() {
45
+ return this.iframeSwitched;
46
+ }
47
+ async switchToTestFrame() {
48
+ const browser = this.browser;
49
+ // support wdio@9
50
+ if (browser.switchFrame) {
51
+ await browser.switchFrame(browser.$("iframe[data-vitest]"));
52
+ } else {
53
+ const iframe = await browser.findElement("css selector", "iframe[data-vitest]");
54
+ await browser.switchToFrame(iframe);
55
+ }
56
+ this.iframeSwitched = true;
57
+ }
58
+ async switchToMainFrame() {
59
+ const page = this.browser;
60
+ if (page.switchFrame) {
61
+ await page.switchFrame(null);
62
+ } else {
63
+ await page.switchToParentFrame();
64
+ }
65
+ this.iframeSwitched = false;
66
+ }
67
+ async setViewport(options) {
68
+ if (this.topLevelContext == null || !this.browser) {
69
+ throw new Error(`The browser has no open pages.`);
70
+ }
71
+ await this.browser.send({
72
+ method: "browsingContext.setViewport",
73
+ params: {
74
+ context: this.topLevelContext,
75
+ devicePixelRatio: 1,
76
+ viewport: options
77
+ }
78
+ });
79
+ }
80
+ getCommandsContext() {
81
+ return { browser: this.browser };
82
+ }
83
+ async openBrowser() {
84
+ await this._throwIfClosing("opening the browser");
85
+ if (this.browser) {
86
+ debug?.("[%s] the browser is already opened, reusing it", this.browserName);
87
+ return this.browser;
88
+ }
89
+ const options = this.project.config.browser;
90
+ if (this.browserName === "safari") {
91
+ if (options.headless) {
92
+ throw new Error("You've enabled headless mode for Safari but it doesn't currently support it.");
93
+ }
94
+ }
95
+ const { remote } = await import('webdriverio');
96
+ const remoteOptions = {
97
+ logLevel: "silent",
98
+ ...this.options,
99
+ capabilities: this.buildCapabilities()
100
+ };
101
+ debug?.("[%s] opening the browser with options: %O", this.browserName, remoteOptions);
102
+ // TODO: close everything, if browser is closed from the outside
103
+ this.browser = await remote(remoteOptions);
104
+ await this._throwIfClosing();
105
+ return this.browser;
106
+ }
107
+ buildCapabilities() {
108
+ const capabilities = {
109
+ ...this.options?.capabilities,
110
+ browserName: this.browserName
111
+ };
112
+ const headlessMap = {
113
+ chrome: ["goog:chromeOptions", ["headless", "disable-gpu"]],
114
+ firefox: ["moz:firefoxOptions", ["-headless"]],
115
+ edge: ["ms:edgeOptions", ["--headless"]]
116
+ };
117
+ const options = this.project.config.browser;
118
+ const browser = this.browserName;
119
+ if (browser !== "safari" && options.headless) {
120
+ const [key, args] = headlessMap[browser];
121
+ const currentValues = (this.options?.capabilities)?.[key] || {};
122
+ const newArgs = [...currentValues.args || [], ...args];
123
+ capabilities[key] = {
124
+ ...currentValues,
125
+ args: newArgs
126
+ };
127
+ }
128
+ // start Vitest UI maximized only on supported browsers
129
+ if (options.ui && (browser === "chrome" || browser === "edge")) {
130
+ const key = browser === "chrome" ? "goog:chromeOptions" : "ms:edgeOptions";
131
+ const args = capabilities[key]?.args || [];
132
+ if (!args.includes("--start-maximized") && !args.includes("--start-fullscreen")) {
133
+ args.push("--start-maximized");
134
+ }
135
+ capabilities[key] ??= {};
136
+ capabilities[key].args = args;
137
+ }
138
+ return capabilities;
139
+ }
140
+ async openPage(sessionId, url) {
141
+ await this._throwIfClosing("creating the browser");
142
+ debug?.("[%s][%s] creating the browser page for %s", sessionId, this.browserName, url);
143
+ const browserInstance = await this.openBrowser();
144
+ debug?.("[%s][%s] browser page is created, opening %s", sessionId, this.browserName, url);
145
+ await browserInstance.url(url);
146
+ this.topLevelContext = await browserInstance.getWindowHandle();
147
+ await this._throwIfClosing("opening the url");
148
+ }
149
+ async _throwIfClosing(action) {
150
+ if (this.closing) {
151
+ debug?.(`[%s] provider was closed, cannot perform the action${action ? ` ${action}` : ""}`, this.browserName);
152
+ await (this.browser?.sessionId ? this.browser?.deleteSession?.() : null);
153
+ throw new Error(`[vitest] The provider was closed.`);
154
+ }
155
+ }
156
+ async close() {
157
+ debug?.("[%s] closing provider", this.browserName);
158
+ this.closing = true;
159
+ const browser = this.browser;
160
+ const sessionId = browser?.sessionId;
161
+ if (!browser || !sessionId) {
162
+ return;
163
+ }
164
+ // https://github.com/webdriverio/webdriverio/blob/ab1a2e82b13a9c7d0e275ae87e7357e1b047d8d3/packages/wdio-runner/src/index.ts#L486
165
+ await browser.deleteSession();
166
+ browser.sessionId = undefined;
167
+ this.browser = null;
168
+ }
169
+ }
170
+
171
+ export { WebdriverBrowserProvider, webdriverio };
@@ -0,0 +1,16 @@
1
+ import type { ScreenshotComparatorRegistry, ScreenshotMatcherOptions } from "../../../context.js";
2
+ export type ScreenshotMatcherArguments<ComparatorName extends keyof ScreenshotComparatorRegistry = keyof ScreenshotComparatorRegistry> = [name: string, testName: string, options: ScreenshotMatcherOptions<ComparatorName> & {
3
+ element: string;
4
+ screenshotOptions?: ScreenshotMatcherOptions<ComparatorName>["screenshotOptions"] & {
5
+ mask?: readonly string[];
6
+ };
7
+ }];
8
+ export type ScreenshotMatcherOutput = Promise<{
9
+ pass: false;
10
+ reference: string | null;
11
+ actual: string | null;
12
+ diff: string | null;
13
+ message: string;
14
+ } | {
15
+ pass: true;
16
+ }>;
package/dist/state.js CHANGED
@@ -12,7 +12,6 @@
12
12
  const state = {
13
13
  ctx: {
14
14
  pool: "browser",
15
- worker: "./browser.js",
16
15
  workerId: 1,
17
16
  config,
18
17
  projectName: config.name || "",
@@ -28,14 +27,16 @@
28
27
  config,
29
28
  environment: {
30
29
  name: "browser",
31
- transformMode: "web",
30
+ viteEnvironment: "client",
32
31
  setup() {
33
32
  throw new Error("Not called in the browser");
34
33
  }
35
34
  },
36
35
  onCleanup: (fn) => getBrowserState().cleanups.push(fn),
37
- moduleCache: getBrowserState().moduleCache,
36
+ evaluatedModules: getBrowserState().evaluatedModules,
37
+ resolvingModules: getBrowserState().resolvingModules,
38
38
  moduleExecutionInfo: new Map(),
39
+ metaEnv: null,
39
40
  rpc: null,
40
41
  durations: {
41
42
  environment: 0,
package/dist/types.d.ts CHANGED
@@ -22,24 +22,22 @@ export interface WebSocketBrowserHandlers {
22
22
  resolveId: (id: string, importer?: string) => Promise<ServerIdResolution | null>;
23
23
  triggerCommand: <T>(sessionId: string, command: string, testPath: string | undefined, payload: unknown[]) => Promise<T>;
24
24
  resolveMock: (id: string, importer: string, options: {
25
- mock: "spy" | "factory" | "auto"
25
+ mock: "spy" | "factory" | "auto";
26
26
  }) => Promise<ServerMockResolution>;
27
27
  invalidate: (ids: string[]) => void;
28
28
  getBrowserFileSourceMap: (id: string) => SourceMap | null | {
29
- mappings: ""
29
+ mappings: "";
30
30
  } | undefined;
31
31
  wdioSwitchContext: (direction: "iframe" | "parent") => void;
32
32
  registerMock: (sessionId: string, mock: MockedModuleSerialized) => void;
33
33
  unregisterMock: (sessionId: string, id: string) => void;
34
34
  clearMocks: (sessionId: string) => void;
35
- // cdp
36
35
  sendCdpEvent: (sessionId: string, event: string, payload?: Record<string, unknown>) => unknown;
37
36
  trackCdpEvent: (sessionId: string, type: "on" | "once" | "off", event: string, listenerId: string) => void;
38
37
  }
39
38
  export type Awaitable<T> = T | PromiseLike<T>;
40
39
  export interface WebSocketEvents {
41
40
  onCollected?: (files: RunnerTestFile[]) => Awaitable<void>;
42
- onFinished?: (files: File[], errors: unknown[], coverage?: unknown) => Awaitable<void>;
43
41
  onTaskUpdate?: (packs: TaskResultPack[]) => Awaitable<void>;
44
42
  onUserConsoleLog?: (log: UserConsoleLog) => Awaitable<void>;
45
43
  onPathsCollected?: (paths?: string[]) => Awaitable<void>;
@@ -52,9 +50,9 @@ export interface WebSocketBrowserEvents {
52
50
  cleanupTesters: () => Promise<void>;
53
51
  cdpEvent: (event: string, payload: unknown) => void;
54
52
  resolveManualMock: (url: string) => Promise<{
55
- url: string
56
- keys: string[]
57
- responseId: string
53
+ url: string;
54
+ keys: string[];
55
+ responseId: string;
58
56
  }>;
59
57
  }
60
58
  export type WebSocketBrowserRPC = BirpcReturn<WebSocketBrowserEvents, WebSocketBrowserHandlers>;
package/jest-dom.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // Disable automatic exports.
2
2
 
3
3
  import { ARIARole } from './aria-role.ts'
4
+ import { ScreenshotComparatorRegistry, ScreenshotMatcherOptions } from './context.js'
4
5
 
5
6
  export interface TestingLibraryMatchers<E, R> {
6
7
  /**
@@ -14,6 +15,52 @@ export interface TestingLibraryMatchers<E, R> {
14
15
  * @see https://vitest.dev/guide/browser/assertion-api#tobeinthedocument
15
16
  */
16
17
  toBeInTheDocument(): R
18
+ /**
19
+ * @description
20
+ * Assert whether an element is within the viewport or not.
21
+ *
22
+ * An element is considered to be in the viewport if any part of it intersects with the current viewport bounds.
23
+ * This matcher calculates the intersection ratio between the element and the viewport, similar to the
24
+ * IntersectionObserver API.
25
+ *
26
+ * The element must be in the document and have visible dimensions. Elements with display: none or
27
+ * visibility: hidden are considered not in viewport.
28
+ * @example
29
+ * <div
30
+ * data-testid="visible-element"
31
+ * style="position: absolute; top: 10px; left: 10px; width: 50px; height: 50px;"
32
+ * >
33
+ * Visible Element
34
+ * </div>
35
+ *
36
+ * <div
37
+ * data-testid="hidden-element"
38
+ * style="position: fixed; top: -100px; left: 10px; width: 50px; height: 50px;"
39
+ * >
40
+ * Hidden Element
41
+ * </div>
42
+ *
43
+ * <div
44
+ * data-testid="large-element"
45
+ * style="height: 400vh;"
46
+ * >
47
+ * Large Element
48
+ * </div>
49
+ *
50
+ * // Check if any part of element is in viewport
51
+ * await expect.element(page.getByTestId('visible-element')).toBeInViewport()
52
+ *
53
+ * // Check if element is outside viewport
54
+ * await expect.element(page.getByTestId('hidden-element')).not.toBeInViewport()
55
+ *
56
+ * // Check if at least 50% of element is visible
57
+ * await expect.element(page.getByTestId('large-element')).toBeInViewport({ ratio: 0.5 })
58
+ *
59
+ * // Check if element is completely visible
60
+ * await expect.element(page.getByTestId('visible-element')).toBeInViewport({ ratio: 1 })
61
+ * @see https://vitest.dev/guide/browser/assertion-api#tobeinviewport
62
+ */
63
+ toBeInViewport(options?: { ratio?: number }): R
17
64
  /**
18
65
  * @description
19
66
  * This allows you to check if an element is currently visible to the user.
@@ -580,7 +627,7 @@ export interface TestingLibraryMatchers<E, R> {
580
627
  * other element that contains text, such as a paragraph, span, div etc.
581
628
  *
582
629
  * NOTE: the expected selection is a string, it does not allow to check for
583
- * selection range indeces.
630
+ * selection range indices.
584
631
  *
585
632
  * @example
586
633
  * <div>
@@ -627,4 +674,51 @@ export interface TestingLibraryMatchers<E, R> {
627
674
  * @see https://vitest.dev/guide/browser/assertion-api#tohaveselection
628
675
  */
629
676
  toHaveSelection(selection?: string): R
677
+
678
+ /**
679
+ * @description
680
+ * This assertion allows you to perform visual regression testing by comparing
681
+ * screenshots of elements or pages against stored reference images.
682
+ *
683
+ * When differences are detected beyond the configured threshold, the test fails.
684
+ * To help identify the changes, the assertion generates:
685
+ *
686
+ * - The actual screenshot captured during the test
687
+ * - The expected reference screenshot
688
+ * - A diff image highlighting the differences (when possible)
689
+ *
690
+ * @example
691
+ * <button data-testid="button">Fancy Button</button>
692
+ *
693
+ * // basic usage, auto-generates screenshot name
694
+ * await expect.element(getByTestId('button')).toMatchScreenshot()
695
+ *
696
+ * // with custom name
697
+ * await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button')
698
+ *
699
+ * // with options
700
+ * await expect.element(getByTestId('button')).toMatchScreenshot({
701
+ * comparatorName: 'pixelmatch',
702
+ * comparatorOptions: {
703
+ * allowedMismatchedPixelRatio: 0.01,
704
+ * },
705
+ * })
706
+ *
707
+ * // with both name and options
708
+ * await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button', {
709
+ * comparatorName: 'pixelmatch',
710
+ * comparatorOptions: {
711
+ * allowedMismatchedPixelRatio: 0.01,
712
+ * },
713
+ * })
714
+ *
715
+ * @see https://vitest.dev/guide/browser/assertion-api#tomatchscreenshot
716
+ */
717
+ toMatchScreenshot<ComparatorName extends keyof ScreenshotComparatorRegistry>(
718
+ options?: ScreenshotMatcherOptions<ComparatorName>,
719
+ ): Promise<R>
720
+ toMatchScreenshot<ComparatorName extends keyof ScreenshotComparatorRegistry>(
721
+ name?: string,
722
+ options?: ScreenshotMatcherOptions<ComparatorName>,
723
+ ): Promise<R>
630
724
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/browser",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.1",
4
+ "version": "4.0.0-beta.11",
5
5
  "description": "Browser running for Vitest",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -20,9 +20,9 @@
20
20
  "types": "./dist/index.d.ts",
21
21
  "default": "./dist/index.js"
22
22
  },
23
- "./providers": {
24
- "types": "./providers.d.ts",
25
- "default": "./dist/providers.js"
23
+ "./providers/*": {
24
+ "types": "./dist/providers/*.d.ts",
25
+ "default": "./dist/providers/*.js"
26
26
  },
27
27
  "./context": {
28
28
  "types": "./context.d.ts",
@@ -35,14 +35,6 @@
35
35
  "types": "./matchers.d.ts",
36
36
  "default": "./dummy.js"
37
37
  },
38
- "./providers/webdriverio": {
39
- "types": "./providers/webdriverio.d.ts",
40
- "default": "./dummy.js"
41
- },
42
- "./providers/playwright": {
43
- "types": "./providers/playwright.d.ts",
44
- "default": "./dummy.js"
45
- },
46
38
  "./locator": {
47
39
  "types": "./dist/locators/index.d.ts",
48
40
  "default": "./dist/locators/index.js"
@@ -66,7 +58,7 @@
66
58
  "peerDependencies": {
67
59
  "playwright": "*",
68
60
  "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0",
69
- "vitest": "4.0.0-beta.1"
61
+ "vitest": "4.0.0-beta.11"
70
62
  },
71
63
  "peerDependenciesMeta": {
72
64
  "playwright": {
@@ -80,33 +72,31 @@
80
72
  }
81
73
  },
82
74
  "dependencies": {
83
- "@testing-library/dom": "^10.4.0",
75
+ "@testing-library/dom": "^10.4.1",
84
76
  "@testing-library/user-event": "^14.6.1",
85
- "magic-string": "^0.30.17",
86
- "sirv": "^3.0.1",
87
- "tinyrainbow": "^2.0.0",
88
- "ws": "^8.18.2",
89
- "@vitest/mocker": "4.0.0-beta.1",
90
- "@vitest/utils": "4.0.0-beta.1"
77
+ "magic-string": "^0.30.19",
78
+ "pixelmatch": "7.1.0",
79
+ "pngjs": "^7.0.0",
80
+ "sirv": "^3.0.2",
81
+ "tinyrainbow": "^3.0.3",
82
+ "ws": "^8.18.3",
83
+ "@vitest/mocker": "4.0.0-beta.11",
84
+ "@vitest/utils": "4.0.0-beta.11"
91
85
  },
92
86
  "devDependencies": {
87
+ "@types/pngjs": "^6.0.5",
93
88
  "@types/ws": "^8.18.1",
94
- "@wdio/protocols": "^9.15.0",
95
- "@wdio/types": "^9.15.0",
96
- "birpc": "2.4.0",
89
+ "@wdio/types": "^9.19.2",
90
+ "birpc": "^2.5.0",
97
91
  "flatted": "^3.3.3",
98
92
  "ivya": "^1.7.0",
99
93
  "mime": "^4.0.7",
100
94
  "pathe": "^2.0.3",
101
- "periscopic": "^4.0.2",
102
- "playwright": "^1.53.0",
103
- "playwright-core": "^1.53.0",
104
- "safaridriver": "^1.0.0",
105
- "webdriverio": "^9.15.0",
106
- "@vitest/ui": "4.0.0-beta.1",
107
- "@vitest/runner": "4.0.0-beta.1",
108
- "vitest": "4.0.0-beta.1",
109
- "@vitest/ws-client": "4.0.0-beta.1"
95
+ "playwright": "^1.55.0",
96
+ "playwright-core": "^1.55.0",
97
+ "webdriverio": "^9.19.2",
98
+ "@vitest/runner": "4.0.0-beta.11",
99
+ "vitest": "4.0.0-beta.11"
110
100
  },
111
101
  "scripts": {
112
102
  "typecheck": "tsc -p ./src/client/tsconfig.json --noEmit",
package/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // should be in sync with tester/public-utils.ts
2
- // we cannot bundle it because vitest depend on the @vitest/browser and vise versa
2
+ // we cannot bundle it because vitest depend on the @vitest/browser and vice versa
3
3
  // fortunately, the file is quite small
4
4
 
5
5
  import { LocatorSelectors, Locator } from '@vitest/browser/context'