@vitest/browser-playwright 4.1.0-beta.4 → 4.1.0-beta.6
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/README.md +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +116 -43
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @vitest/browser-playwright
|
|
2
2
|
|
|
3
|
-
[](https://
|
|
3
|
+
[](https://npmx.dev/package/@vitest/browser-playwright)
|
|
4
4
|
|
|
5
5
|
Run your Vitest [browser tests](https://vitest.dev/guide/browser/) using [playwright](https://playwright.dev/docs/api/class-playwright) API. Note that Vitest does not use playwright as a test runner, but only as a browser provider.
|
|
6
6
|
|
package/dist/index.d.ts
CHANGED
|
@@ -102,7 +102,8 @@ type PWScreenshotOptions = NonNullable<Parameters<Page["screenshot"]>[0]>;
|
|
|
102
102
|
type PWSelectOptions = NonNullable<Parameters<Page["selectOption"]>[2]>;
|
|
103
103
|
type PWDragAndDropOptions = NonNullable<Parameters<Page["dragAndDrop"]>[2]>;
|
|
104
104
|
type PWSetInputFiles = NonNullable<Parameters<Page["setInputFiles"]>[2]>;
|
|
105
|
-
type PWCDPSession = CDPSession
|
|
105
|
+
type PWCDPSession = Pick<CDPSession, "send" | "on" | "off" | "once">;
|
|
106
|
+
|
|
106
107
|
declare module "vitest/browser" {
|
|
107
108
|
interface UserEventHoverOptions extends PWHoverOptions {}
|
|
108
109
|
interface UserEventClickOptions extends PWClickOptions {}
|
|
@@ -119,4 +120,4 @@ declare module "vitest/browser" {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
export { PlaywrightBrowserProvider, playwright };
|
|
122
|
-
export type { PlaywrightProviderOptions };
|
|
123
|
+
export type { PWCDPSession as CDPSession, PlaywrightProviderOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseKeyDef, resolveScreenshotPath, defineBrowserProvider } from '@vitest/browser';
|
|
1
|
+
import { asLocator, parseKeyDef, resolveScreenshotPath, defineBrowserProvider } from '@vitest/browser';
|
|
2
2
|
export { defineBrowserCommand } from '@vitest/browser';
|
|
3
3
|
import { createManualModuleSource } from '@vitest/mocker/node';
|
|
4
4
|
import c from 'tinyrainbow';
|
|
@@ -169,23 +169,28 @@ const basename = function(p, extension) {
|
|
|
169
169
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
+
// strip iframe locator part from the trace description e.g.
|
|
173
|
+
// - locator('[data-vitest="true"]').contentFrame().getByRole('button')
|
|
174
|
+
// ⇓
|
|
175
|
+
// - getByRole('button')
|
|
176
|
+
function getDescribedLocator(context, selector) {
|
|
177
|
+
const locator = context.iframe.locator(selector);
|
|
178
|
+
return typeof locator.describe === "function" ? locator.describe(asLocator("javascript", selector)) : locator;
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
const clear = async (context, selector) => {
|
|
173
|
-
const
|
|
174
|
-
const element = iframe.locator(selector);
|
|
182
|
+
const element = getDescribedLocator(context, selector);
|
|
175
183
|
await element.clear();
|
|
176
184
|
};
|
|
177
185
|
|
|
178
186
|
const click = async (context, selector, options = {}) => {
|
|
179
|
-
|
|
180
|
-
await tester.locator(selector).click(options);
|
|
187
|
+
await getDescribedLocator(context, selector).click(options);
|
|
181
188
|
};
|
|
182
189
|
const dblClick = async (context, selector, options = {}) => {
|
|
183
|
-
|
|
184
|
-
await tester.locator(selector).dblclick(options);
|
|
190
|
+
await getDescribedLocator(context, selector).dblclick(options);
|
|
185
191
|
};
|
|
186
192
|
const tripleClick = async (context, selector, options = {}) => {
|
|
187
|
-
|
|
188
|
-
await tester.locator(selector).click({
|
|
193
|
+
await getDescribedLocator(context, selector).click({
|
|
189
194
|
...options,
|
|
190
195
|
clickCount: 3
|
|
191
196
|
});
|
|
@@ -197,13 +202,12 @@ const dragAndDrop = async (context, source, target, options_) => {
|
|
|
197
202
|
};
|
|
198
203
|
|
|
199
204
|
const fill = async (context, selector, text, options = {}) => {
|
|
200
|
-
const
|
|
201
|
-
const element = iframe.locator(selector);
|
|
205
|
+
const element = getDescribedLocator(context, selector);
|
|
202
206
|
await element.fill(text, options);
|
|
203
207
|
};
|
|
204
208
|
|
|
205
209
|
const hover = async (context, selector, options = {}) => {
|
|
206
|
-
await context
|
|
210
|
+
await getDescribedLocator(context, selector).hover(options);
|
|
207
211
|
};
|
|
208
212
|
|
|
209
213
|
const keyboard = async (context, text, state) => {
|
|
@@ -512,10 +516,10 @@ async function takeScreenshot(context, name, options) {
|
|
|
512
516
|
savePath = normalize(path);
|
|
513
517
|
await mkdir(dirname(savePath), { recursive: true });
|
|
514
518
|
}
|
|
515
|
-
const mask = options.mask?.map((selector) => context
|
|
519
|
+
const mask = options.mask?.map((selector) => getDescribedLocator(context, selector));
|
|
516
520
|
if (options.element) {
|
|
517
521
|
const { element: selector, ...config } = options;
|
|
518
|
-
const element = context
|
|
522
|
+
const element = getDescribedLocator(context, selector);
|
|
519
523
|
const buffer = await element.screenshot({
|
|
520
524
|
...config,
|
|
521
525
|
mask,
|
|
@@ -526,7 +530,7 @@ async function takeScreenshot(context, name, options) {
|
|
|
526
530
|
path
|
|
527
531
|
};
|
|
528
532
|
}
|
|
529
|
-
const buffer = await context
|
|
533
|
+
const buffer = await getDescribedLocator(context, "body").screenshot({
|
|
530
534
|
...options,
|
|
531
535
|
mask,
|
|
532
536
|
path: savePath
|
|
@@ -539,13 +543,12 @@ async function takeScreenshot(context, name, options) {
|
|
|
539
543
|
|
|
540
544
|
const selectOptions = async (context, selector, userValues, options = {}) => {
|
|
541
545
|
const value = userValues;
|
|
542
|
-
const
|
|
543
|
-
const selectElement = iframe.locator(selector);
|
|
546
|
+
const selectElement = getDescribedLocator(context, selector);
|
|
544
547
|
const values = await Promise.all(value.map(async (v) => {
|
|
545
548
|
if (typeof v === "string") {
|
|
546
549
|
return v;
|
|
547
550
|
}
|
|
548
|
-
const elementHandler = await
|
|
551
|
+
const elementHandler = await getDescribedLocator(context, v.element).elementHandle();
|
|
549
552
|
if (!elementHandler) {
|
|
550
553
|
throw new Error(`Element not found: ${v.element}`);
|
|
551
554
|
}
|
|
@@ -569,7 +572,7 @@ const startTracing = async ({ context, project, provider, sessionId }) => {
|
|
|
569
572
|
await context.tracing.start({
|
|
570
573
|
screenshots: options.screenshots ?? true,
|
|
571
574
|
snapshots: options.snapshots ?? true,
|
|
572
|
-
sources:
|
|
575
|
+
sources: options.sources ?? true
|
|
573
576
|
}).catch(() => {
|
|
574
577
|
provider.tracingContexts.delete(sessionId);
|
|
575
578
|
});
|
|
@@ -605,6 +608,68 @@ const stopChunkTrace = async (context, { name }) => {
|
|
|
605
608
|
}
|
|
606
609
|
throw new TypeError(`The ${context.provider.name} provider does not support tracing.`);
|
|
607
610
|
};
|
|
611
|
+
const markTrace = async (context, payload) => {
|
|
612
|
+
if (isPlaywrightProvider(context.provider)) {
|
|
613
|
+
// skip if tracing is not active
|
|
614
|
+
// this is only safe guard and this isn't expected to happen since
|
|
615
|
+
// runner already checks if tracing is active before sending this command
|
|
616
|
+
if (!context.provider.tracingContexts.has(context.sessionId)) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const { name, selector, stack } = payload;
|
|
620
|
+
const location = parseLocation(context, stack);
|
|
621
|
+
// mark trace via group/groupEnd with dummy calls to force snapshot.
|
|
622
|
+
// https://github.com/microsoft/playwright/issues/39308
|
|
623
|
+
await context.context.tracing.group(name, { location });
|
|
624
|
+
try {
|
|
625
|
+
if (selector) {
|
|
626
|
+
const locator = getDescribedLocator(context, selector);
|
|
627
|
+
if (typeof locator._expect === "function") {
|
|
628
|
+
await locator._expect("to.be.attached", {
|
|
629
|
+
isNot: false,
|
|
630
|
+
timeout: 1
|
|
631
|
+
});
|
|
632
|
+
} else {
|
|
633
|
+
await context.page.evaluate(() => 0);
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
await context.page.evaluate(() => 0);
|
|
637
|
+
}
|
|
638
|
+
} catch {}
|
|
639
|
+
await context.context.tracing.groupEnd();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
throw new TypeError(`The ${context.provider.name} provider does not support tracing.`);
|
|
643
|
+
};
|
|
644
|
+
const groupTraceStart = async (context, payload) => {
|
|
645
|
+
if (isPlaywrightProvider(context.provider)) {
|
|
646
|
+
if (!context.provider.tracingContexts.has(context.sessionId)) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const { name, stack } = payload;
|
|
650
|
+
const location = parseLocation(context, stack);
|
|
651
|
+
await context.context.tracing.group(name, { location });
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
throw new TypeError(`The ${context.provider.name} provider does not support tracing.`);
|
|
655
|
+
};
|
|
656
|
+
const groupTraceEnd = async (context) => {
|
|
657
|
+
if (isPlaywrightProvider(context.provider)) {
|
|
658
|
+
if (!context.provider.tracingContexts.has(context.sessionId)) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
await context.context.tracing.groupEnd();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
throw new TypeError(`The ${context.provider.name} provider does not support tracing.`);
|
|
665
|
+
};
|
|
666
|
+
function parseLocation(context, stack) {
|
|
667
|
+
if (!stack) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const parsedStacks = context.project.browser.parseStacktrace(stack);
|
|
671
|
+
return parsedStacks[0];
|
|
672
|
+
}
|
|
608
673
|
function resolveTracesPath({ testPath, project }, name) {
|
|
609
674
|
if (!testPath) {
|
|
610
675
|
throw new Error(`This command can only be called inside a test file.`);
|
|
@@ -665,8 +730,7 @@ function isPlaywrightProvider(provider) {
|
|
|
665
730
|
const type = async (context, selector, text, options = {}) => {
|
|
666
731
|
const { skipClick = false, skipAutoClose = false } = options;
|
|
667
732
|
const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
|
|
668
|
-
const
|
|
669
|
-
const element = iframe.locator(selector);
|
|
733
|
+
const element = getDescribedLocator(context, selector);
|
|
670
734
|
if (!skipClick) {
|
|
671
735
|
await element.focus();
|
|
672
736
|
}
|
|
@@ -680,7 +744,6 @@ const upload = async (context, selector, files, options) => {
|
|
|
680
744
|
throw new Error(`Cannot upload files outside of a test`);
|
|
681
745
|
}
|
|
682
746
|
const root = context.project.config.root;
|
|
683
|
-
const { iframe } = context;
|
|
684
747
|
const playwrightFiles = files.map((file) => {
|
|
685
748
|
if (typeof file === "string") {
|
|
686
749
|
return resolve(root, file);
|
|
@@ -691,7 +754,7 @@ const upload = async (context, selector, files, options) => {
|
|
|
691
754
|
buffer: Buffer.from(file.base64, "base64")
|
|
692
755
|
};
|
|
693
756
|
});
|
|
694
|
-
await
|
|
757
|
+
await getDescribedLocator(context, selector).setInputFiles(playwrightFiles, options);
|
|
695
758
|
};
|
|
696
759
|
|
|
697
760
|
const wheel = async (context, selector, options) => {
|
|
@@ -724,7 +787,10 @@ var commands = {
|
|
|
724
787
|
__vitest_startChunkTrace: startChunkTrace,
|
|
725
788
|
__vitest_startTracing: startTracing,
|
|
726
789
|
__vitest_stopChunkTrace: stopChunkTrace,
|
|
727
|
-
__vitest_annotateTraces: annotateTraces
|
|
790
|
+
__vitest_annotateTraces: annotateTraces,
|
|
791
|
+
__vitest_markTrace: markTrace,
|
|
792
|
+
__vitest_groupTraceStart: groupTraceStart,
|
|
793
|
+
__vitest_groupTraceEnd: groupTraceEnd
|
|
728
794
|
};
|
|
729
795
|
|
|
730
796
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
@@ -799,17 +865,6 @@ class PlaywrightBrowserProvider {
|
|
|
799
865
|
this.browserPromise = (async () => {
|
|
800
866
|
const options = this.project.config.browser;
|
|
801
867
|
const playwright = await import('playwright');
|
|
802
|
-
if (this.options.connectOptions) {
|
|
803
|
-
if (this.options.launchOptions) {
|
|
804
|
-
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.
|
|
805
|
-
Ignoring ${c.bold(c.italic(c.yellow("launch")))} options and using ${c.bold(c.italic(c.yellow("connect")))} mode.
|
|
806
|
-
You probably want to remove one of the two options and keep only the one you want to use.`));
|
|
807
|
-
}
|
|
808
|
-
const browser = await playwright[this.browserName].connect(this.options.connectOptions.wsEndpoint, this.options.connectOptions);
|
|
809
|
-
this.browser = browser;
|
|
810
|
-
this.browserPromise = null;
|
|
811
|
-
return this.browser;
|
|
812
|
-
}
|
|
813
868
|
const launchOptions = {
|
|
814
869
|
...this.options.launchOptions,
|
|
815
870
|
headless: options.headless
|
|
@@ -824,8 +879,10 @@ class PlaywrightBrowserProvider {
|
|
|
824
879
|
const host = inspector.host || "127.0.0.1";
|
|
825
880
|
launchOptions.args ||= [];
|
|
826
881
|
launchOptions.args.push(`--remote-debugging-port=${port}`);
|
|
827
|
-
|
|
828
|
-
|
|
882
|
+
if (host !== "localhost" && host !== "127.0.0.1" && host !== "::1") {
|
|
883
|
+
this.project.vitest.logger.warn(`Custom inspector host "${host}" will be ignored. Chromium only allows remote debugging on localhost.`);
|
|
884
|
+
}
|
|
885
|
+
this.project.vitest.logger.log(`Debugger listening on ws://127.0.0.1:${port}`);
|
|
829
886
|
}
|
|
830
887
|
// start Vitest UI maximized only on supported browsers
|
|
831
888
|
if (this.project.config.browser.ui && this.browserName === "chromium") {
|
|
@@ -837,6 +894,23 @@ class PlaywrightBrowserProvider {
|
|
|
837
894
|
}
|
|
838
895
|
}
|
|
839
896
|
debug?.("[%s] initializing the browser with launch options: %O", this.browserName, launchOptions);
|
|
897
|
+
if (this.options.connectOptions) {
|
|
898
|
+
let { wsEndpoint, headers = {}, ...connectOptions } = this.options.connectOptions;
|
|
899
|
+
if ("x-playwright-launch-options" in headers) {
|
|
900
|
+
this.project.vitest.logger.warn(c.yellow("Detected \"x-playwright-launch-options\" in connectOptions.headers. Provider config launchOptions is ignored."));
|
|
901
|
+
} else {
|
|
902
|
+
headers = {
|
|
903
|
+
...headers,
|
|
904
|
+
"x-playwright-launch-options": JSON.stringify(launchOptions)
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
this.browser = await playwright[this.browserName].connect(wsEndpoint, {
|
|
908
|
+
...connectOptions,
|
|
909
|
+
headers
|
|
910
|
+
});
|
|
911
|
+
this.browserPromise = null;
|
|
912
|
+
return this.browser;
|
|
913
|
+
}
|
|
840
914
|
let persistentContextOption = this.options.persistentContext;
|
|
841
915
|
if (persistentContextOption && openBrowserOptions.parallel) {
|
|
842
916
|
persistentContextOption = false;
|
|
@@ -1082,18 +1156,17 @@ class PlaywrightBrowserProvider {
|
|
|
1082
1156
|
const page = this.getPage(sessionid);
|
|
1083
1157
|
const cdp = await page.context().newCDPSession(page);
|
|
1084
1158
|
return {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
return result;
|
|
1159
|
+
send(method, params) {
|
|
1160
|
+
return cdp.send(method, params);
|
|
1088
1161
|
},
|
|
1089
1162
|
on(event, listener) {
|
|
1090
|
-
cdp.on(event, listener);
|
|
1163
|
+
return cdp.on(event, listener);
|
|
1091
1164
|
},
|
|
1092
1165
|
off(event, listener) {
|
|
1093
|
-
cdp.off(event, listener);
|
|
1166
|
+
return cdp.off(event, listener);
|
|
1094
1167
|
},
|
|
1095
1168
|
once(event, listener) {
|
|
1096
|
-
cdp.once(event, listener);
|
|
1169
|
+
return cdp.once(event, listener);
|
|
1097
1170
|
}
|
|
1098
1171
|
};
|
|
1099
1172
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest/browser-playwright",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.1.0-beta.
|
|
4
|
+
"version": "4.1.0-beta.6",
|
|
5
5
|
"description": "Browser running for Vitest using playwright",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://opencollective.com/vitest",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"playwright": "*",
|
|
45
|
-
"vitest": "4.1.0-beta.
|
|
45
|
+
"vitest": "4.1.0-beta.6"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|
|
48
48
|
"playwright": {
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"tinyrainbow": "^3.0.3",
|
|
54
|
-
"@vitest/
|
|
55
|
-
"@vitest/
|
|
54
|
+
"@vitest/mocker": "4.1.0-beta.6",
|
|
55
|
+
"@vitest/browser": "4.1.0-beta.6"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"playwright": "^1.58.
|
|
59
|
-
"vitest": "4.1.0-beta.
|
|
58
|
+
"playwright": "^1.58.2",
|
|
59
|
+
"vitest": "4.1.0-beta.6"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "premove dist && pnpm rollup -c",
|