@vitest/browser 3.2.4 → 4.0.0-beta.10
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/LICENSE +1 -1
- package/README.md +3 -15
- package/context.d.ts +153 -3
- package/dist/client/.vite/manifest.json +6 -6
- package/dist/client/__vitest__/assets/index-DFDJV2DF.js +53 -0
- package/dist/client/__vitest__/assets/{index-X8b7Z_4p.css → index-KbpJLW--.css} +1 -1
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/orchestrator-BXX6oamz.js +296 -0
- package/dist/client/__vitest_browser__/{tester-BYDMHqQ9.js → tester-CMhJ1E1W.js} +301 -580
- package/dist/client/__vitest_browser__/{utils-Owv5OOOf.js → utils-CPmDBIKG.js} +3 -3
- package/dist/client/error-catcher.js +7 -3
- package/dist/client/esm-client-injector.js +1 -0
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/tester.html +2 -2
- package/dist/client.js +24 -8
- package/dist/context.js +34 -24
- package/dist/expect-element.js +10 -8
- package/dist/index-CwoiDq7G.js +6 -0
- package/dist/index-DDlvjJVO.js +1 -0
- package/dist/index.d.ts +20 -15
- package/dist/index.js +550 -98
- package/dist/locators/index.d.ts +8 -7
- package/dist/locators/index.js +1 -1
- package/dist/locators/playwright.js +1 -1
- package/dist/locators/preview.js +1 -1
- package/dist/locators/webdriverio.js +1 -1
- package/dist/providers/playwright.d.ts +103 -0
- package/dist/{webdriver-KA1WiV0q.js → providers/playwright.js} +37 -180
- package/dist/providers/preview.d.ts +16 -0
- package/dist/{providers.js → providers/preview.js} +17 -21
- package/dist/providers/webdriverio.d.ts +50 -0
- package/dist/providers/webdriverio.js +171 -0
- package/dist/shared/screenshotMatcher/types.d.ts +16 -0
- package/dist/state.js +5 -2
- package/dist/types.d.ts +69 -0
- package/jest-dom.d.ts +95 -1
- package/package.json +22 -30
- package/utils.d.ts +1 -1
- package/dist/client/__vitest__/assets/index-D_ryMEPs.js +0 -58
- package/dist/client/__vitest_browser__/orchestrator-Bo1OwGWc.js +0 -3213
- package/dist/index-W1MM53zC.js +0 -1
- package/providers/playwright.d.ts +0 -81
- package/providers/webdriverio.d.ts +0 -22
package/dist/locators/index.d.ts
CHANGED
|
@@ -279,13 +279,13 @@ interface SelectorEngine {
|
|
|
279
279
|
queryAll: (root: SelectorRoot, selector: string | any) => Element[];
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
// we prefer using playwright locators because they are more powerful and support Shadow DOM
|
|
283
282
|
declare const selectorEngine: Ivya;
|
|
284
283
|
declare abstract class Locator {
|
|
285
284
|
abstract selector: string;
|
|
286
285
|
private _parsedSelector;
|
|
287
286
|
protected _container?: Element | undefined;
|
|
288
287
|
protected _pwSelector?: string | undefined;
|
|
288
|
+
constructor();
|
|
289
289
|
click(options?: UserEventClickOptions): Promise<void>;
|
|
290
290
|
dblClick(options?: UserEventClickOptions): Promise<void>;
|
|
291
291
|
tripleClick(options?: UserEventClickOptions): Promise<void>;
|
|
@@ -297,10 +297,10 @@ declare abstract class Locator {
|
|
|
297
297
|
dropTo(target: Locator, options?: UserEventDragAndDropOptions): Promise<void>;
|
|
298
298
|
selectOptions(value: HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[], options?: UserEventSelectOptions): Promise<void>;
|
|
299
299
|
screenshot(options: Omit<LocatorScreenshotOptions, "base64"> & {
|
|
300
|
-
base64: true
|
|
300
|
+
base64: true;
|
|
301
301
|
}): Promise<{
|
|
302
|
-
path: string
|
|
303
|
-
base64: string
|
|
302
|
+
path: string;
|
|
303
|
+
base64: string;
|
|
304
304
|
}>;
|
|
305
305
|
screenshot(options?: LocatorScreenshotOptions): Promise<string>;
|
|
306
306
|
protected abstract locator(selector: string): Locator;
|
|
@@ -315,9 +315,10 @@ declare abstract class Locator {
|
|
|
315
315
|
filter(filter: LocatorOptions): Locator;
|
|
316
316
|
and(locator: Locator): Locator;
|
|
317
317
|
or(locator: Locator): Locator;
|
|
318
|
-
query():
|
|
319
|
-
element():
|
|
320
|
-
elements():
|
|
318
|
+
query(): HTMLElement | SVGElement | null;
|
|
319
|
+
element(): HTMLElement | SVGElement;
|
|
320
|
+
elements(): (HTMLElement | SVGElement)[];
|
|
321
|
+
get length(): number;
|
|
321
322
|
all(): Locator[];
|
|
322
323
|
nth(index: number): Locator;
|
|
323
324
|
first(): Locator;
|
package/dist/locators/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"@vitest/browser/context";import"../public-utils-Kx5DUGWa.js";export{L as Locator,s as selectorEngine}from"../index-
|
|
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 +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-
|
|
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}
|
package/dist/locators/preview.js
CHANGED
|
@@ -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-
|
|
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 +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-
|
|
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()}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ScreenshotMatcherOptions, ScreenshotComparatorRegistry } from '@vitest/browser/context';
|
|
2
|
+
import { Page, Frame, FrameLocator, BrowserContext, LaunchOptions, ConnectOptions, BrowserContextOptions, Browser } from 'playwright';
|
|
3
|
+
import { Protocol } from 'playwright-core/types/protocol';
|
|
4
|
+
import { BrowserProviderOption, BrowserProvider, BrowserModuleMocker, TestProject, CDPSession } from 'vitest/node';
|
|
5
|
+
|
|
6
|
+
declare const playwrightBrowsers: readonly ["firefox", "webkit", "chromium"];
|
|
7
|
+
type PlaywrightBrowser = (typeof playwrightBrowsers)[number];
|
|
8
|
+
interface PlaywrightProviderOptions {
|
|
9
|
+
/**
|
|
10
|
+
* The options passed down to [`playwright.connect`](https://playwright.dev/docs/api/class-browsertype#browser-type-launch) method.
|
|
11
|
+
* @see {@link https://playwright.dev/docs/api/class-browsertype#browser-type-launch}
|
|
12
|
+
*/
|
|
13
|
+
launchOptions?: LaunchOptions;
|
|
14
|
+
/**
|
|
15
|
+
* The options passed down to [`playwright.connect`](https://playwright.dev/docs/api/class-browsertype#browser-type-connect) method.
|
|
16
|
+
*
|
|
17
|
+
* This is used only if you connect remotely to the playwright instance via a WebSocket connection.
|
|
18
|
+
* @see {@link https://playwright.dev/docs/api/class-browsertype#browser-type-connect}
|
|
19
|
+
*/
|
|
20
|
+
connectOptions?: ConnectOptions & {
|
|
21
|
+
wsEndpoint: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The options passed down to [`browser.newContext`](https://playwright.dev/docs/api/class-browser#browser-new-context) method.
|
|
25
|
+
* @see {@link https://playwright.dev/docs/api/class-browser#browser-new-context}
|
|
26
|
+
*/
|
|
27
|
+
contextOptions?: Omit<BrowserContextOptions, "ignoreHTTPSErrors" | "serviceWorkers">;
|
|
28
|
+
/**
|
|
29
|
+
* The maximum time in milliseconds to wait for `userEvent` action to complete.
|
|
30
|
+
* @default 0 (no timeout)
|
|
31
|
+
*/
|
|
32
|
+
actionTimeout?: number;
|
|
33
|
+
}
|
|
34
|
+
declare function playwright(options?: PlaywrightProviderOptions): BrowserProviderOption;
|
|
35
|
+
declare class PlaywrightBrowserProvider implements BrowserProvider {
|
|
36
|
+
private project;
|
|
37
|
+
private options;
|
|
38
|
+
name: "playwright";
|
|
39
|
+
supportsParallelism: boolean;
|
|
40
|
+
browser: Browser | null;
|
|
41
|
+
contexts: Map<string, BrowserContext>;
|
|
42
|
+
pages: Map<string, Page>;
|
|
43
|
+
mocker: BrowserModuleMocker;
|
|
44
|
+
browserName: PlaywrightBrowser;
|
|
45
|
+
private browserPromise;
|
|
46
|
+
private closing;
|
|
47
|
+
constructor(project: TestProject, options: PlaywrightProviderOptions);
|
|
48
|
+
private openBrowser;
|
|
49
|
+
private createMocker;
|
|
50
|
+
private createContext;
|
|
51
|
+
getPage(sessionId: string): Page;
|
|
52
|
+
getCommandsContext(sessionId: string): {
|
|
53
|
+
page: Page;
|
|
54
|
+
context: BrowserContext;
|
|
55
|
+
frame: () => Promise<Frame>;
|
|
56
|
+
readonly iframe: FrameLocator;
|
|
57
|
+
};
|
|
58
|
+
private openBrowserPage;
|
|
59
|
+
openPage(sessionId: string, url: string, beforeNavigate?: () => Promise<void>): Promise<void>;
|
|
60
|
+
private _throwIfClosing;
|
|
61
|
+
getCDPSession(sessionid: string): Promise<CDPSession>;
|
|
62
|
+
close(): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
declare module "vitest/node" {
|
|
65
|
+
interface BrowserCommandContext {
|
|
66
|
+
page: Page;
|
|
67
|
+
frame(): Promise<Frame>;
|
|
68
|
+
iframe: FrameLocator;
|
|
69
|
+
context: BrowserContext;
|
|
70
|
+
}
|
|
71
|
+
interface ToMatchScreenshotOptions extends Omit<ScreenshotMatcherOptions, "comparatorName" | "comparatorOptions"> {}
|
|
72
|
+
interface ToMatchScreenshotComparators extends ScreenshotComparatorRegistry {}
|
|
73
|
+
}
|
|
74
|
+
type PWHoverOptions = NonNullable<Parameters<Page["hover"]>[1]>;
|
|
75
|
+
type PWClickOptions = NonNullable<Parameters<Page["click"]>[1]>;
|
|
76
|
+
type PWDoubleClickOptions = NonNullable<Parameters<Page["dblclick"]>[1]>;
|
|
77
|
+
type PWFillOptions = NonNullable<Parameters<Page["fill"]>[2]>;
|
|
78
|
+
type PWScreenshotOptions = NonNullable<Parameters<Page["screenshot"]>[0]>;
|
|
79
|
+
type PWSelectOptions = NonNullable<Parameters<Page["selectOption"]>[2]>;
|
|
80
|
+
type PWDragAndDropOptions = NonNullable<Parameters<Page["dragAndDrop"]>[2]>;
|
|
81
|
+
type PWSetInputFiles = NonNullable<Parameters<Page["setInputFiles"]>[2]>;
|
|
82
|
+
declare module "@vitest/browser/context" {
|
|
83
|
+
interface UserEventHoverOptions extends PWHoverOptions {}
|
|
84
|
+
interface UserEventClickOptions extends PWClickOptions {}
|
|
85
|
+
interface UserEventDoubleClickOptions extends PWDoubleClickOptions {}
|
|
86
|
+
interface UserEventTripleClickOptions extends PWClickOptions {}
|
|
87
|
+
interface UserEventFillOptions extends PWFillOptions {}
|
|
88
|
+
interface UserEventSelectOptions extends PWSelectOptions {}
|
|
89
|
+
interface UserEventDragAndDropOptions extends PWDragAndDropOptions {}
|
|
90
|
+
interface UserEventUploadOptions extends PWSetInputFiles {}
|
|
91
|
+
interface ScreenshotOptions extends Omit<PWScreenshotOptions, "mask"> {
|
|
92
|
+
mask?: ReadonlyArray<Element | Locator> | undefined;
|
|
93
|
+
}
|
|
94
|
+
interface CDPSession {
|
|
95
|
+
send<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T]>;
|
|
96
|
+
on<T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void): this;
|
|
97
|
+
once<T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void): this;
|
|
98
|
+
off<T extends keyof Protocol.Events>(event: T, listener: (payload: Protocol.Events[T]) => void): this;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { PlaywrightBrowserProvider, playwright };
|
|
103
|
+
export type { PlaywrightProviderOptions };
|
|
@@ -2,60 +2,64 @@ import { createManualModuleSource } from '@vitest/mocker/node';
|
|
|
2
2
|
import c from 'tinyrainbow';
|
|
3
3
|
import { createDebugger, isCSSRequest } from 'vitest/node';
|
|
4
4
|
|
|
5
|
-
const debug
|
|
5
|
+
const debug = createDebugger("vitest:browser:playwright");
|
|
6
6
|
const playwrightBrowsers = [
|
|
7
7
|
"firefox",
|
|
8
8
|
"webkit",
|
|
9
9
|
"chromium"
|
|
10
10
|
];
|
|
11
|
+
function playwright(options = {}) {
|
|
12
|
+
return {
|
|
13
|
+
name: "playwright",
|
|
14
|
+
supportedBrowser: playwrightBrowsers,
|
|
15
|
+
factory(project) {
|
|
16
|
+
return new PlaywrightBrowserProvider(project, options);
|
|
17
|
+
},
|
|
18
|
+
_cli: true
|
|
19
|
+
};
|
|
20
|
+
}
|
|
11
21
|
class PlaywrightBrowserProvider {
|
|
12
22
|
name = "playwright";
|
|
13
23
|
supportsParallelism = true;
|
|
14
24
|
browser = null;
|
|
15
|
-
browserName;
|
|
16
|
-
project;
|
|
17
|
-
options;
|
|
18
25
|
contexts = new Map();
|
|
19
26
|
pages = new Map();
|
|
20
|
-
browserPromise = null;
|
|
21
27
|
mocker;
|
|
28
|
+
browserName;
|
|
29
|
+
browserPromise = null;
|
|
22
30
|
closing = false;
|
|
23
|
-
|
|
24
|
-
return playwrightBrowsers;
|
|
25
|
-
}
|
|
26
|
-
initialize(project, { browser, options }) {
|
|
27
|
-
this.closing = false;
|
|
31
|
+
constructor(project, options) {
|
|
28
32
|
this.project = project;
|
|
29
|
-
this.browserName = browser;
|
|
30
33
|
this.options = options;
|
|
34
|
+
this.browserName = project.config.browser.name;
|
|
31
35
|
this.mocker = this.createMocker();
|
|
32
36
|
}
|
|
33
37
|
async openBrowser() {
|
|
34
38
|
await this._throwIfClosing();
|
|
35
39
|
if (this.browserPromise) {
|
|
36
|
-
debug
|
|
40
|
+
debug?.("[%s] the browser is resolving, reusing the promise", this.browserName);
|
|
37
41
|
return this.browserPromise;
|
|
38
42
|
}
|
|
39
43
|
if (this.browser) {
|
|
40
|
-
debug
|
|
44
|
+
debug?.("[%s] the browser is resolved, reusing it", this.browserName);
|
|
41
45
|
return this.browser;
|
|
42
46
|
}
|
|
43
47
|
this.browserPromise = (async () => {
|
|
44
48
|
const options = this.project.config.browser;
|
|
45
49
|
const playwright = await import('playwright');
|
|
46
|
-
if (this.options
|
|
47
|
-
if (this.options.
|
|
50
|
+
if (this.options.connectOptions) {
|
|
51
|
+
if (this.options.launchOptions) {
|
|
48
52
|
this.project.vitest.logger.warn(c.yellow(`Found both ${c.bold(c.italic(c.yellow("connect")))} and ${c.bold(c.italic(c.yellow("launch")))} options in browser instance configuration.
|
|
49
53
|
Ignoring ${c.bold(c.italic(c.yellow("launch")))} options and using ${c.bold(c.italic(c.yellow("connect")))} mode.
|
|
50
54
|
You probably want to remove one of the two options and keep only the one you want to use.`));
|
|
51
55
|
}
|
|
52
|
-
const browser = await playwright[this.browserName].connect(this.options.
|
|
56
|
+
const browser = await playwright[this.browserName].connect(this.options.connectOptions.wsEndpoint, this.options.connectOptions);
|
|
53
57
|
this.browser = browser;
|
|
54
58
|
this.browserPromise = null;
|
|
55
59
|
return this.browser;
|
|
56
60
|
}
|
|
57
61
|
const launchOptions = {
|
|
58
|
-
...this.options
|
|
62
|
+
...this.options.launchOptions,
|
|
59
63
|
headless: options.headless
|
|
60
64
|
};
|
|
61
65
|
if (this.project.config.inspector.enabled) {
|
|
@@ -76,7 +80,7 @@ class PlaywrightBrowserProvider {
|
|
|
76
80
|
launchOptions.args.push("--start-maximized");
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
|
-
debug
|
|
83
|
+
debug?.("[%s] initializing the browser with launch options: %O", this.browserName, launchOptions);
|
|
80
84
|
this.browser = await playwright[this.browserName].launch(launchOptions);
|
|
81
85
|
this.browserPromise = null;
|
|
82
86
|
return this.browser;
|
|
@@ -200,12 +204,13 @@ class PlaywrightBrowserProvider {
|
|
|
200
204
|
async createContext(sessionId) {
|
|
201
205
|
await this._throwIfClosing();
|
|
202
206
|
if (this.contexts.has(sessionId)) {
|
|
203
|
-
debug
|
|
207
|
+
debug?.("[%s][%s] the context already exists, reusing it", sessionId, this.browserName);
|
|
204
208
|
return this.contexts.get(sessionId);
|
|
205
209
|
}
|
|
206
210
|
const browser = await this.openBrowser();
|
|
207
211
|
await this._throwIfClosing(browser);
|
|
208
|
-
const
|
|
212
|
+
const actionTimeout = this.options.actionTimeout;
|
|
213
|
+
const contextOptions = this.options.contextOptions ?? {};
|
|
209
214
|
const options = {
|
|
210
215
|
...contextOptions,
|
|
211
216
|
ignoreHTTPSErrors: true
|
|
@@ -215,10 +220,10 @@ class PlaywrightBrowserProvider {
|
|
|
215
220
|
}
|
|
216
221
|
const context = await browser.newContext(options);
|
|
217
222
|
await this._throwIfClosing(context);
|
|
218
|
-
if (actionTimeout) {
|
|
223
|
+
if (actionTimeout != null) {
|
|
219
224
|
context.setDefaultTimeout(actionTimeout);
|
|
220
225
|
}
|
|
221
|
-
debug
|
|
226
|
+
debug?.("[%s][%s] the context is ready", sessionId, this.browserName);
|
|
222
227
|
this.contexts.set(sessionId, context);
|
|
223
228
|
return context;
|
|
224
229
|
}
|
|
@@ -258,14 +263,14 @@ class PlaywrightBrowserProvider {
|
|
|
258
263
|
async openBrowserPage(sessionId) {
|
|
259
264
|
await this._throwIfClosing();
|
|
260
265
|
if (this.pages.has(sessionId)) {
|
|
261
|
-
debug
|
|
266
|
+
debug?.("[%s][%s] the page already exists, closing the old one", sessionId, this.browserName);
|
|
262
267
|
const page = this.pages.get(sessionId);
|
|
263
268
|
await page.close();
|
|
264
269
|
this.pages.delete(sessionId);
|
|
265
270
|
}
|
|
266
271
|
const context = await this.createContext(sessionId);
|
|
267
272
|
const page = await context.newPage();
|
|
268
|
-
debug
|
|
273
|
+
debug?.("[%s][%s] the page is ready", sessionId, this.browserName);
|
|
269
274
|
await this._throwIfClosing(page);
|
|
270
275
|
this.pages.set(sessionId, page);
|
|
271
276
|
if (process.env.VITEST_PW_DEBUG) {
|
|
@@ -276,16 +281,16 @@ class PlaywrightBrowserProvider {
|
|
|
276
281
|
return page;
|
|
277
282
|
}
|
|
278
283
|
async openPage(sessionId, url, beforeNavigate) {
|
|
279
|
-
debug
|
|
284
|
+
debug?.("[%s][%s] creating the browser page for %s", sessionId, this.browserName, url);
|
|
280
285
|
const browserPage = await this.openBrowserPage(sessionId);
|
|
281
286
|
await beforeNavigate?.();
|
|
282
|
-
debug
|
|
287
|
+
debug?.("[%s][%s] browser page is created, opening %s", sessionId, this.browserName, url);
|
|
283
288
|
await browserPage.goto(url, { timeout: 0 });
|
|
284
289
|
await this._throwIfClosing(browserPage);
|
|
285
290
|
}
|
|
286
291
|
async _throwIfClosing(disposable) {
|
|
287
292
|
if (this.closing) {
|
|
288
|
-
debug
|
|
293
|
+
debug?.("[%s] provider was closed, cannot perform the action on %s", this.browserName, String(disposable));
|
|
289
294
|
await disposable?.close();
|
|
290
295
|
this.pages.clear();
|
|
291
296
|
this.contexts.clear();
|
|
@@ -314,20 +319,20 @@ class PlaywrightBrowserProvider {
|
|
|
314
319
|
};
|
|
315
320
|
}
|
|
316
321
|
async close() {
|
|
317
|
-
debug
|
|
322
|
+
debug?.("[%s] closing provider", this.browserName);
|
|
318
323
|
this.closing = true;
|
|
319
|
-
const browser = this.browser;
|
|
320
|
-
this.browser = null;
|
|
321
324
|
if (this.browserPromise) {
|
|
322
325
|
await this.browserPromise;
|
|
323
326
|
this.browserPromise = null;
|
|
324
327
|
}
|
|
328
|
+
const browser = this.browser;
|
|
329
|
+
this.browser = null;
|
|
325
330
|
await Promise.all([...this.pages.values()].map((p) => p.close()));
|
|
326
331
|
this.pages.clear();
|
|
327
332
|
await Promise.all([...this.contexts.values()].map((c) => c.close()));
|
|
328
333
|
this.contexts.clear();
|
|
329
334
|
await browser?.close();
|
|
330
|
-
debug
|
|
335
|
+
debug?.("[%s] provider is closed", this.browserName);
|
|
331
336
|
}
|
|
332
337
|
}
|
|
333
338
|
function getHeaders(config) {
|
|
@@ -356,152 +361,4 @@ function isDirectCSSRequest(request) {
|
|
|
356
361
|
return isCSSRequest(request) && directRequestRE.test(request);
|
|
357
362
|
}
|
|
358
363
|
|
|
359
|
-
|
|
360
|
-
const webdriverBrowsers = [
|
|
361
|
-
"firefox",
|
|
362
|
-
"chrome",
|
|
363
|
-
"edge",
|
|
364
|
-
"safari"
|
|
365
|
-
];
|
|
366
|
-
class WebdriverBrowserProvider {
|
|
367
|
-
name = "webdriverio";
|
|
368
|
-
supportsParallelism = false;
|
|
369
|
-
browser = null;
|
|
370
|
-
browserName;
|
|
371
|
-
project;
|
|
372
|
-
options;
|
|
373
|
-
closing = false;
|
|
374
|
-
iframeSwitched = false;
|
|
375
|
-
topLevelContext;
|
|
376
|
-
getSupportedBrowsers() {
|
|
377
|
-
return webdriverBrowsers;
|
|
378
|
-
}
|
|
379
|
-
async initialize(ctx, { browser, options }) {
|
|
380
|
-
this.closing = false;
|
|
381
|
-
this.project = ctx;
|
|
382
|
-
this.browserName = browser;
|
|
383
|
-
this.options = options;
|
|
384
|
-
}
|
|
385
|
-
isIframeSwitched() {
|
|
386
|
-
return this.iframeSwitched;
|
|
387
|
-
}
|
|
388
|
-
async switchToTestFrame() {
|
|
389
|
-
const page = this.browser;
|
|
390
|
-
// support wdio@9
|
|
391
|
-
if (page.switchFrame) {
|
|
392
|
-
await page.switchFrame(page.$("iframe[data-vitest]"));
|
|
393
|
-
} else {
|
|
394
|
-
const iframe = await page.findElement("css selector", "iframe[data-vitest]");
|
|
395
|
-
await page.switchToFrame(iframe);
|
|
396
|
-
}
|
|
397
|
-
this.iframeSwitched = true;
|
|
398
|
-
}
|
|
399
|
-
async switchToMainFrame() {
|
|
400
|
-
const page = this.browser;
|
|
401
|
-
if (page.switchFrame) {
|
|
402
|
-
await page.switchFrame(null);
|
|
403
|
-
} else {
|
|
404
|
-
await page.switchToParentFrame();
|
|
405
|
-
}
|
|
406
|
-
this.iframeSwitched = false;
|
|
407
|
-
}
|
|
408
|
-
async setViewport(options) {
|
|
409
|
-
if (this.topLevelContext == null || !this.browser) {
|
|
410
|
-
throw new Error(`The browser has no open pages.`);
|
|
411
|
-
}
|
|
412
|
-
await this.browser.send({
|
|
413
|
-
method: "browsingContext.setViewport",
|
|
414
|
-
params: {
|
|
415
|
-
context: this.topLevelContext,
|
|
416
|
-
devicePixelRatio: 1,
|
|
417
|
-
viewport: options
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
getCommandsContext() {
|
|
422
|
-
return { browser: this.browser };
|
|
423
|
-
}
|
|
424
|
-
async openBrowser() {
|
|
425
|
-
await this._throwIfClosing("opening the browser");
|
|
426
|
-
if (this.browser) {
|
|
427
|
-
debug?.("[%s] the browser is already opened, reusing it", this.browserName);
|
|
428
|
-
return this.browser;
|
|
429
|
-
}
|
|
430
|
-
const options = this.project.config.browser;
|
|
431
|
-
if (this.browserName === "safari") {
|
|
432
|
-
if (options.headless) {
|
|
433
|
-
throw new Error("You've enabled headless mode for Safari but it doesn't currently support it.");
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const { remote } = await import('webdriverio');
|
|
437
|
-
const remoteOptions = {
|
|
438
|
-
...this.options,
|
|
439
|
-
logLevel: "error",
|
|
440
|
-
capabilities: this.buildCapabilities()
|
|
441
|
-
};
|
|
442
|
-
debug?.("[%s] opening the browser with options: %O", this.browserName, remoteOptions);
|
|
443
|
-
// TODO: close everything, if browser is closed from the outside
|
|
444
|
-
this.browser = await remote(remoteOptions);
|
|
445
|
-
await this._throwIfClosing();
|
|
446
|
-
return this.browser;
|
|
447
|
-
}
|
|
448
|
-
buildCapabilities() {
|
|
449
|
-
const capabilities = {
|
|
450
|
-
...this.options?.capabilities,
|
|
451
|
-
browserName: this.browserName
|
|
452
|
-
};
|
|
453
|
-
const headlessMap = {
|
|
454
|
-
chrome: ["goog:chromeOptions", ["headless", "disable-gpu"]],
|
|
455
|
-
firefox: ["moz:firefoxOptions", ["-headless"]],
|
|
456
|
-
edge: ["ms:edgeOptions", ["--headless"]]
|
|
457
|
-
};
|
|
458
|
-
const options = this.project.config.browser;
|
|
459
|
-
const browser = this.browserName;
|
|
460
|
-
if (browser !== "safari" && options.headless) {
|
|
461
|
-
const [key, args] = headlessMap[browser];
|
|
462
|
-
const currentValues = (this.options?.capabilities)?.[key] || {};
|
|
463
|
-
const newArgs = [...currentValues.args || [], ...args];
|
|
464
|
-
capabilities[key] = {
|
|
465
|
-
...currentValues,
|
|
466
|
-
args: newArgs
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
// start Vitest UI maximized only on supported browsers
|
|
470
|
-
if (options.ui && (browser === "chrome" || browser === "edge")) {
|
|
471
|
-
const key = browser === "chrome" ? "goog:chromeOptions" : "ms:edgeOptions";
|
|
472
|
-
const args = capabilities[key]?.args || [];
|
|
473
|
-
if (!args.includes("--start-maximized") && !args.includes("--start-fullscreen")) {
|
|
474
|
-
args.push("--start-maximized");
|
|
475
|
-
}
|
|
476
|
-
capabilities[key] ??= {};
|
|
477
|
-
capabilities[key].args = args;
|
|
478
|
-
}
|
|
479
|
-
return capabilities;
|
|
480
|
-
}
|
|
481
|
-
async openPage(sessionId, url) {
|
|
482
|
-
await this._throwIfClosing("creating the browser");
|
|
483
|
-
debug?.("[%s][%s] creating the browser page for %s", sessionId, this.browserName, url);
|
|
484
|
-
const browserInstance = await this.openBrowser();
|
|
485
|
-
debug?.("[%s][%s] browser page is created, opening %s", sessionId, this.browserName, url);
|
|
486
|
-
await browserInstance.url(url);
|
|
487
|
-
this.topLevelContext = await browserInstance.getWindowHandle();
|
|
488
|
-
await this._throwIfClosing("opening the url");
|
|
489
|
-
}
|
|
490
|
-
async _throwIfClosing(action) {
|
|
491
|
-
if (this.closing) {
|
|
492
|
-
debug?.(`[%s] provider was closed, cannot perform the action${action ? ` ${action}` : ""}`, this.browserName);
|
|
493
|
-
await (this.browser?.sessionId ? this.browser?.deleteSession?.() : null);
|
|
494
|
-
throw new Error(`[vitest] The provider was closed.`);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
async close() {
|
|
498
|
-
debug?.("[%s] closing provider", this.browserName);
|
|
499
|
-
this.closing = true;
|
|
500
|
-
await Promise.all([this.browser?.sessionId ? this.browser?.deleteSession?.() : null]);
|
|
501
|
-
// TODO: right now process can only exit with timeout, if we use browser
|
|
502
|
-
// needs investigating
|
|
503
|
-
process.exit();
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
export { PlaywrightBrowserProvider as P, WebdriverBrowserProvider as W };
|
|
364
|
+
export { PlaywrightBrowserProvider, playwright };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BrowserProviderOption, BrowserProvider, TestProject } from 'vitest/node';
|
|
2
|
+
|
|
3
|
+
declare function preview(): BrowserProviderOption;
|
|
4
|
+
declare class PreviewBrowserProvider implements BrowserProvider {
|
|
5
|
+
name: "preview";
|
|
6
|
+
supportsParallelism: boolean;
|
|
7
|
+
private project;
|
|
8
|
+
private open;
|
|
9
|
+
constructor(project: TestProject);
|
|
10
|
+
isOpen(): boolean;
|
|
11
|
+
getCommandsContext(): {};
|
|
12
|
+
openPage(_sessionId: string, url: string): Promise<void>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { PreviewBrowserProvider, preview };
|
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
function preview() {
|
|
2
|
+
return {
|
|
3
|
+
name: "preview",
|
|
4
|
+
factory(project) {
|
|
5
|
+
return new PreviewBrowserProvider(project);
|
|
6
|
+
},
|
|
7
|
+
_cli: true
|
|
8
|
+
};
|
|
9
|
+
}
|
|
6
10
|
class PreviewBrowserProvider {
|
|
7
11
|
name = "preview";
|
|
8
12
|
supportsParallelism = false;
|
|
9
13
|
project;
|
|
10
14
|
open = false;
|
|
11
|
-
|
|
12
|
-
// `none` is not restricted to certain browsers.
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
isOpen() {
|
|
16
|
-
return this.open;
|
|
17
|
-
}
|
|
18
|
-
getCommandsContext() {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
async initialize(project) {
|
|
15
|
+
constructor(project) {
|
|
22
16
|
this.project = project;
|
|
23
17
|
this.open = false;
|
|
24
18
|
if (project.config.browser.headless) {
|
|
@@ -26,6 +20,12 @@ class PreviewBrowserProvider {
|
|
|
26
20
|
}
|
|
27
21
|
project.vitest.logger.printBrowserBanner(project);
|
|
28
22
|
}
|
|
23
|
+
isOpen() {
|
|
24
|
+
return this.open;
|
|
25
|
+
}
|
|
26
|
+
getCommandsContext() {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
29
|
async openPage(_sessionId, url) {
|
|
30
30
|
this.open = true;
|
|
31
31
|
if (!this.project.browser) {
|
|
@@ -40,8 +40,4 @@ class PreviewBrowserProvider {
|
|
|
40
40
|
async close() {}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
const playwright = PlaywrightBrowserProvider;
|
|
45
|
-
const preview = PreviewBrowserProvider;
|
|
46
|
-
|
|
47
|
-
export { playwright, preview, webdriverio };
|
|
43
|
+
export { PreviewBrowserProvider, preview };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ScreenshotMatcherOptions, ScreenshotComparatorRegistry } from '@vitest/browser/context';
|
|
2
|
+
import { BrowserProviderOption, BrowserProvider, TestProject } from 'vitest/node';
|
|
3
|
+
import { ClickOptions, DragAndDropOptions, remote } from 'webdriverio';
|
|
4
|
+
|
|
5
|
+
interface WebdriverProviderOptions extends Partial<Parameters<typeof remote>[0]> {}
|
|
6
|
+
declare function webdriverio(options?: WebdriverProviderOptions): BrowserProviderOption;
|
|
7
|
+
declare class WebdriverBrowserProvider implements BrowserProvider {
|
|
8
|
+
name: "webdriverio";
|
|
9
|
+
supportsParallelism: boolean;
|
|
10
|
+
browser: WebdriverIO.Browser | null;
|
|
11
|
+
private browserName;
|
|
12
|
+
private project;
|
|
13
|
+
private options?;
|
|
14
|
+
private closing;
|
|
15
|
+
private iframeSwitched;
|
|
16
|
+
private topLevelContext;
|
|
17
|
+
getSupportedBrowsers(): readonly string[];
|
|
18
|
+
constructor(project: TestProject, options: WebdriverProviderOptions);
|
|
19
|
+
isIframeSwitched(): boolean;
|
|
20
|
+
switchToTestFrame(): Promise<void>;
|
|
21
|
+
switchToMainFrame(): Promise<void>;
|
|
22
|
+
setViewport(options: {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
getCommandsContext(): {
|
|
27
|
+
browser: WebdriverIO.Browser | null;
|
|
28
|
+
};
|
|
29
|
+
openBrowser(): Promise<WebdriverIO.Browser>;
|
|
30
|
+
private buildCapabilities;
|
|
31
|
+
openPage(sessionId: string, url: string): Promise<void>;
|
|
32
|
+
private _throwIfClosing;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
declare module "vitest/node" {
|
|
36
|
+
interface UserEventClickOptions extends ClickOptions {}
|
|
37
|
+
interface UserEventDragOptions extends DragAndDropOptions {
|
|
38
|
+
sourceX?: number;
|
|
39
|
+
sourceY?: number;
|
|
40
|
+
targetX?: number;
|
|
41
|
+
targetY?: number;
|
|
42
|
+
}
|
|
43
|
+
interface BrowserCommandContext {
|
|
44
|
+
browser: WebdriverIO.Browser;
|
|
45
|
+
}
|
|
46
|
+
interface ToMatchScreenshotOptions extends Omit<ScreenshotMatcherOptions, "comparatorName" | "comparatorOptions"> {}
|
|
47
|
+
interface ToMatchScreenshotComparators extends ScreenshotComparatorRegistry {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { WebdriverBrowserProvider, webdriverio };
|