@vitest/browser-playwright 5.0.3 → 5.0.5
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.d.ts +7 -2
- package/dist/index.js +118 -24
- package/package.json +9 -7
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { CustomComparatorsRegistry } from '@vitest/browser';
|
|
|
2
2
|
export { defineBrowserCommand } from '@vitest/browser';
|
|
3
3
|
import { Page, Frame, FrameLocator, BrowserContext, CDPSession, Browser, LaunchOptions, ConnectOptions, BrowserContextOptions } from 'playwright';
|
|
4
4
|
import { ScreenshotMatcherOptions, ScreenshotComparatorRegistry, Locator } from 'vitest/browser';
|
|
5
|
-
import { BrowserProvider, BrowserModuleMocker, TestProject, CDPSession as CDPSession$1, BrowserProviderOption } from 'vitest/node';
|
|
5
|
+
import { BrowserProvider, BrowserModuleMocker, TestProject, CDPSession as CDPSession$1, BrowserProviderCloseOptions, BrowserProviderOption } from 'vitest/node';
|
|
6
6
|
|
|
7
7
|
declare const playwrightBrowsers: readonly ["firefox", "webkit", "chromium"];
|
|
8
8
|
type PlaywrightBrowser = (typeof playwrightBrowsers)[number];
|
|
@@ -57,13 +57,17 @@ declare class PlaywrightBrowserProvider implements BrowserProvider {
|
|
|
57
57
|
mocker: BrowserModuleMocker;
|
|
58
58
|
browserName: PlaywrightBrowser;
|
|
59
59
|
private browserPromise;
|
|
60
|
+
private launchOptionsJson;
|
|
60
61
|
private closing;
|
|
62
|
+
private armedContexts;
|
|
61
63
|
tracingContexts: Set<string>;
|
|
62
64
|
pendingTraces: Map<string, string>;
|
|
63
65
|
initScripts: string[];
|
|
64
66
|
constructor(project: TestProject, options: PlaywrightProviderOptions);
|
|
65
67
|
private onSIGTERM;
|
|
66
68
|
private openBrowser;
|
|
69
|
+
private armInterception;
|
|
70
|
+
private probeInterception;
|
|
67
71
|
private createMocker;
|
|
68
72
|
private createContext;
|
|
69
73
|
private getContextOptions;
|
|
@@ -80,7 +84,8 @@ declare class PlaywrightBrowserProvider implements BrowserProvider {
|
|
|
80
84
|
}): Promise<void>;
|
|
81
85
|
private _throwIfClosing;
|
|
82
86
|
getCDPSession(sessionid: string): Promise<CDPSession$1>;
|
|
83
|
-
close(): Promise<void>;
|
|
87
|
+
close(options?: BrowserProviderCloseOptions): Promise<void>;
|
|
88
|
+
private closeContexts;
|
|
84
89
|
}
|
|
85
90
|
declare module "vitest/node" {
|
|
86
91
|
interface BrowserCommandContext {
|
package/dist/index.js
CHANGED
|
@@ -875,18 +875,11 @@ function prewarmBrowser(project, options) {
|
|
|
875
875
|
if (warmBrowsers.has(project.config)) {
|
|
876
876
|
return;
|
|
877
877
|
}
|
|
878
|
-
|
|
879
|
-
if (!pending) {
|
|
880
|
-
const pendingBrowsers = new Set();
|
|
881
|
-
pending = pendingBrowsers;
|
|
882
|
-
pendingWarmBrowsers.set(project.vitest, pendingBrowsers);
|
|
883
|
-
// Browsers whose projects never initialize a provider (they have no test
|
|
884
|
-
// files to run) are cleaned up when Vitest closes.
|
|
885
|
-
project.vitest.onClose(() => closeWarmBrowsers(pendingBrowsers));
|
|
886
|
-
}
|
|
878
|
+
const pending = getPendingWarmBrowsers(project.vitest);
|
|
887
879
|
const launchOptions = resolveLaunchOptions(project.config.browser, project.vitest.config.inspector, options, browserName);
|
|
888
880
|
const entry = {
|
|
889
|
-
|
|
881
|
+
config: project.config,
|
|
882
|
+
launchOptionsJson: getWarmBrowserKey(browserName, launchOptions),
|
|
890
883
|
pending,
|
|
891
884
|
promise: (async () => {
|
|
892
885
|
debug?.("[%s] prewarming the browser", browserName);
|
|
@@ -905,6 +898,34 @@ function prewarmBrowser(project, options) {
|
|
|
905
898
|
pending.add(entry);
|
|
906
899
|
warmBrowsers.set(project.config, entry);
|
|
907
900
|
}
|
|
901
|
+
function getWarmBrowserKey(browserName, launchOptions) {
|
|
902
|
+
return JSON.stringify([browserName, launchOptions]);
|
|
903
|
+
}
|
|
904
|
+
function getPendingWarmBrowsers(vitest) {
|
|
905
|
+
let pending = pendingWarmBrowsers.get(vitest);
|
|
906
|
+
if (!pending) {
|
|
907
|
+
const pendingBrowsers = new Set();
|
|
908
|
+
pending = pendingBrowsers;
|
|
909
|
+
pendingWarmBrowsers.set(vitest, pendingBrowsers);
|
|
910
|
+
// Browsers whose projects never initialize a provider (they have no test
|
|
911
|
+
// files to run) are cleaned up when Vitest closes.
|
|
912
|
+
vitest.onClose(() => closeWarmBrowsers(pendingBrowsers));
|
|
913
|
+
}
|
|
914
|
+
return pending;
|
|
915
|
+
}
|
|
916
|
+
// the pool closes an idle instance to start another project; the browser
|
|
917
|
+
// process stays warm so the next project adopts it instead of launching one
|
|
918
|
+
function keepWarmBrowser(project, browser, launchOptionsJson) {
|
|
919
|
+
const pending = getPendingWarmBrowsers(project.vitest);
|
|
920
|
+
const entry = {
|
|
921
|
+
config: project.config,
|
|
922
|
+
launchOptionsJson,
|
|
923
|
+
pending,
|
|
924
|
+
promise: browser
|
|
925
|
+
};
|
|
926
|
+
pending.add(entry);
|
|
927
|
+
warmBrowsers.set(project.config, entry);
|
|
928
|
+
}
|
|
908
929
|
function takeWarmBrowser(config) {
|
|
909
930
|
const warm = warmBrowsers.get(config);
|
|
910
931
|
if (warm) {
|
|
@@ -913,6 +934,22 @@ function takeWarmBrowser(config) {
|
|
|
913
934
|
}
|
|
914
935
|
return warm;
|
|
915
936
|
}
|
|
937
|
+
// fewer browsers are prewarmed than there are projects and the pool starts
|
|
938
|
+
// projects in test order, so a browser prepared for another project is
|
|
939
|
+
// adopted when it is the same browser launched with the same options
|
|
940
|
+
function takeAnyWarmBrowser(vitest, launchOptionsJson) {
|
|
941
|
+
const pending = pendingWarmBrowsers.get(vitest);
|
|
942
|
+
if (!pending) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
for (const warm of pending) {
|
|
946
|
+
if (warm.launchOptionsJson === launchOptionsJson) {
|
|
947
|
+
pending.delete(warm);
|
|
948
|
+
warmBrowsers.delete(warm.config);
|
|
949
|
+
return warm;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
916
953
|
async function closeWarmBrowsers(pending) {
|
|
917
954
|
const closing = Array.from(pending, (warm) => warm.promise.then((browser) => browser.close()).catch(() => {}));
|
|
918
955
|
pending.clear();
|
|
@@ -955,7 +992,10 @@ class PlaywrightBrowserProvider {
|
|
|
955
992
|
mocker;
|
|
956
993
|
browserName;
|
|
957
994
|
browserPromise = null;
|
|
995
|
+
// set for a locally launched browser, which can be kept warm for another project
|
|
996
|
+
launchOptionsJson = null;
|
|
958
997
|
closing = false;
|
|
998
|
+
armedContexts = new WeakMap();
|
|
959
999
|
tracingContexts = new Set();
|
|
960
1000
|
pendingTraces = new Map();
|
|
961
1001
|
initScripts = [resolve(distRoot, "locators.js")];
|
|
@@ -1037,26 +1077,66 @@ class PlaywrightBrowserProvider {
|
|
|
1037
1077
|
});
|
|
1038
1078
|
this.browser = this.persistentContext.browser();
|
|
1039
1079
|
} else {
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1080
|
+
const launchOptionsJson = getWarmBrowserKey(this.browserName, launchOptions);
|
|
1081
|
+
let warm = takeWarmBrowser(this.project.config);
|
|
1082
|
+
if (warm && warm.launchOptionsJson !== launchOptionsJson) {
|
|
1083
|
+
debug?.("[%s] discarding the prewarmed browser, launch options changed", this.browserName);
|
|
1084
|
+
void warm.promise.then((browser) => browser.close()).catch(() => {});
|
|
1085
|
+
warm = undefined;
|
|
1086
|
+
}
|
|
1087
|
+
warm ??= takeAnyWarmBrowser(this.project.vitest, launchOptionsJson);
|
|
1088
|
+
if (warm) {
|
|
1042
1089
|
const browser = await warm.promise.catch(() => null);
|
|
1043
1090
|
if (browser?.isConnected()) {
|
|
1044
1091
|
debug?.("[%s] adopting the prewarmed browser", this.browserName);
|
|
1045
1092
|
this.browser = browser;
|
|
1046
1093
|
this.browserPromise = null;
|
|
1094
|
+
this.launchOptionsJson = launchOptionsJson;
|
|
1047
1095
|
return this.browser;
|
|
1048
1096
|
}
|
|
1049
|
-
} else if (warm) {
|
|
1050
|
-
debug?.("[%s] discarding the prewarmed browser, launch options changed", this.browserName);
|
|
1051
|
-
void warm.promise.then((browser) => browser.close()).catch(() => {});
|
|
1052
1097
|
}
|
|
1053
1098
|
this.browser = await playwright[this.browserName].launch(launchOptions);
|
|
1099
|
+
this.launchOptionsJson = launchOptionsJson;
|
|
1054
1100
|
}
|
|
1055
1101
|
this.browserPromise = null;
|
|
1056
1102
|
return this.browser;
|
|
1057
1103
|
})();
|
|
1058
1104
|
return this.browserPromise;
|
|
1059
1105
|
}
|
|
1106
|
+
armInterception(page) {
|
|
1107
|
+
if (this.browserName !== "chromium") {
|
|
1108
|
+
return Promise.resolve();
|
|
1109
|
+
}
|
|
1110
|
+
const context = page.context();
|
|
1111
|
+
const armed = this.armedContexts.get(context) ?? this.probeInterception(page).catch((error) => {
|
|
1112
|
+
this.armedContexts.delete(context);
|
|
1113
|
+
throw error;
|
|
1114
|
+
});
|
|
1115
|
+
this.armedContexts.set(context, armed);
|
|
1116
|
+
return armed;
|
|
1117
|
+
}
|
|
1118
|
+
async probeInterception(page) {
|
|
1119
|
+
const probeUrl = "/__vitest_interception_probe__";
|
|
1120
|
+
const probePattern = `**${probeUrl}`;
|
|
1121
|
+
const context = page.context();
|
|
1122
|
+
await context.route(probePattern, (route) => route.fulfill({
|
|
1123
|
+
status: 204,
|
|
1124
|
+
headers: { "x-vitest-probe": "1" }
|
|
1125
|
+
}));
|
|
1126
|
+
const deadline = Date.now() + 5e3;
|
|
1127
|
+
while (Date.now() < deadline) {
|
|
1128
|
+
const intercepted = await page.evaluate((url) => fetch(url, {
|
|
1129
|
+
cache: "no-store",
|
|
1130
|
+
signal: AbortSignal.timeout(1e3)
|
|
1131
|
+
}).then((r) => r.headers.has("x-vitest-probe"), () => false), probeUrl).catch(() => false);
|
|
1132
|
+
if (intercepted) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1136
|
+
}
|
|
1137
|
+
await context.unroute(probePattern);
|
|
1138
|
+
throw new Error(`Cannot verify that ${this.browserName} request interception is active after 5s; module mocks would not apply reliably (#8339)`);
|
|
1139
|
+
}
|
|
1060
1140
|
createMocker() {
|
|
1061
1141
|
const idPredicates = new Map();
|
|
1062
1142
|
const sessionIds = new Map();
|
|
@@ -1090,12 +1170,14 @@ class PlaywrightBrowserProvider {
|
|
|
1090
1170
|
predicate
|
|
1091
1171
|
};
|
|
1092
1172
|
}
|
|
1173
|
+
// the tester unmocks by path, but the route is registered by the full url
|
|
1093
1174
|
function predicateKey(sessionId, url) {
|
|
1094
|
-
return `${sessionId}:${url}`;
|
|
1175
|
+
return `${sessionId}:${new URL(url, "http://localhost").href}`;
|
|
1095
1176
|
}
|
|
1096
1177
|
return {
|
|
1097
1178
|
register: async (sessionId, module) => {
|
|
1098
1179
|
const page = this.getPage(sessionId);
|
|
1180
|
+
await this.armInterception(page);
|
|
1099
1181
|
const { url: moduleUrl, predicate } = createPredicate(module.url);
|
|
1100
1182
|
const key = predicateKey(sessionId, moduleUrl);
|
|
1101
1183
|
const existingPredicate = idPredicates.get(key);
|
|
@@ -1128,7 +1210,7 @@ class PlaywrightBrowserProvider {
|
|
|
1128
1210
|
request.searchParams.set("mock", module.type);
|
|
1129
1211
|
url = request.href.slice(request.origin.length);
|
|
1130
1212
|
}
|
|
1131
|
-
const result = await this.project.browser.vite.transformRequest(url).catch(() => null);
|
|
1213
|
+
const result = await this.project.browser.vite.transformRequest(decodeURI(url)).catch(() => null);
|
|
1132
1214
|
if (!result) {
|
|
1133
1215
|
return route.continue();
|
|
1134
1216
|
}
|
|
@@ -1290,10 +1372,6 @@ class PlaywrightBrowserProvider {
|
|
|
1290
1372
|
if (this.closing) {
|
|
1291
1373
|
debug?.("[%s] provider was closed, cannot perform the action on %s", this.browserName, String(disposable));
|
|
1292
1374
|
await disposable?.close();
|
|
1293
|
-
this.pages.clear();
|
|
1294
|
-
this.contexts.clear();
|
|
1295
|
-
this.browser = null;
|
|
1296
|
-
this.browserPromise = null;
|
|
1297
1375
|
throw new Error(`[vitest] The provider was closed.`);
|
|
1298
1376
|
}
|
|
1299
1377
|
}
|
|
@@ -1309,7 +1387,7 @@ class PlaywrightBrowserProvider {
|
|
|
1309
1387
|
detach: cdp.detach.bind(cdp)
|
|
1310
1388
|
};
|
|
1311
1389
|
}
|
|
1312
|
-
async close() {
|
|
1390
|
+
async close(options) {
|
|
1313
1391
|
process.off("SIGTERM", this.onSIGTERM);
|
|
1314
1392
|
debug?.("[%s] closing provider", this.browserName);
|
|
1315
1393
|
this.closing = true;
|
|
@@ -1324,6 +1402,24 @@ class PlaywrightBrowserProvider {
|
|
|
1324
1402
|
}
|
|
1325
1403
|
const browser = this.browser;
|
|
1326
1404
|
this.browser = null;
|
|
1405
|
+
const closed = this.closeContexts();
|
|
1406
|
+
if (options?.keepWarm && browser && this.launchOptionsJson && browser.isConnected()) {
|
|
1407
|
+
// registered before the contexts are closed, so the project that takes
|
|
1408
|
+
// over the pool slot finds the browser right away and waits for it
|
|
1409
|
+
const kept = closed.then(() => browser, async (error) => {
|
|
1410
|
+
await browser.close().catch(() => {});
|
|
1411
|
+
throw error;
|
|
1412
|
+
});
|
|
1413
|
+
keepWarmBrowser(this.project, kept, this.launchOptionsJson);
|
|
1414
|
+
await kept;
|
|
1415
|
+
debug?.("[%s] provider is closed, the browser is kept warm", this.browserName);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
await closed;
|
|
1419
|
+
await browser?.close();
|
|
1420
|
+
debug?.("[%s] provider is closed", this.browserName);
|
|
1421
|
+
}
|
|
1422
|
+
async closeContexts() {
|
|
1327
1423
|
await Promise.all(Array.from(this.pages.values(), (p) => p.close()));
|
|
1328
1424
|
this.pages.clear();
|
|
1329
1425
|
if (this.persistentContext) {
|
|
@@ -1332,8 +1428,6 @@ class PlaywrightBrowserProvider {
|
|
|
1332
1428
|
await Promise.all(Array.from(this.contexts.values(), (c) => c.close()));
|
|
1333
1429
|
}
|
|
1334
1430
|
this.contexts.clear();
|
|
1335
|
-
await browser?.close();
|
|
1336
|
-
debug?.("[%s] provider is closed", this.browserName);
|
|
1337
1431
|
}
|
|
1338
1432
|
}
|
|
1339
1433
|
function getHeaders(config) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest/browser-playwright",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.0.
|
|
4
|
+
"version": "5.0.5",
|
|
5
5
|
"description": "Browser running for Vitest using playwright",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://opencollective.com/vitest",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"playwright": "*",
|
|
46
|
-
"vitest": "5.0.
|
|
46
|
+
"vitest": "5.0.5"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"playwright": {
|
|
@@ -51,13 +51,15 @@
|
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@vitest/browser": "5.0.
|
|
55
|
-
"@vitest/mocker": "5.0.
|
|
56
|
-
"tinyrainbow": "^3.
|
|
54
|
+
"@vitest/browser": "5.0.5",
|
|
55
|
+
"@vitest/mocker": "5.0.5",
|
|
56
|
+
"tinyrainbow": "^3.2.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"
|
|
60
|
-
"
|
|
59
|
+
"pathe": "^2.0.3",
|
|
60
|
+
"playwright": "^1.64.0",
|
|
61
|
+
"vite": "8.0.11",
|
|
62
|
+
"vitest": "5.0.5"
|
|
61
63
|
},
|
|
62
64
|
"scripts": {
|
|
63
65
|
"build": "premove dist && pnpm rollup -c",
|