playwright 1.54.1 → 1.56.1
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 +3 -3
- package/ThirdPartyNotices.txt +2727 -434
- package/lib/agents/generateAgents.js +263 -0
- package/lib/agents/generator.md +102 -0
- package/lib/agents/healer.md +78 -0
- package/lib/agents/planner.md +135 -0
- package/lib/common/config.js +3 -1
- package/lib/common/configLoader.js +2 -1
- package/lib/common/expectBundle.js +3 -0
- package/lib/common/expectBundleImpl.js +51 -51
- package/lib/common/fixtures.js +1 -1
- package/lib/common/suiteUtils.js +0 -9
- package/lib/index.js +127 -115
- package/lib/isomorphic/testTree.js +35 -8
- package/lib/matchers/expect.js +6 -7
- package/lib/matchers/matcherHint.js +43 -15
- package/lib/matchers/matchers.js +10 -4
- package/lib/matchers/toBeTruthy.js +16 -14
- package/lib/matchers/toEqual.js +18 -13
- package/lib/matchers/toHaveURL.js +12 -27
- package/lib/matchers/toMatchAriaSnapshot.js +26 -31
- package/lib/matchers/toMatchSnapshot.js +15 -12
- package/lib/matchers/toMatchText.js +29 -35
- package/lib/mcp/browser/actions.d.js +16 -0
- package/lib/mcp/browser/browserContextFactory.js +296 -0
- package/lib/mcp/browser/browserServerBackend.js +76 -0
- package/lib/mcp/browser/codegen.js +66 -0
- package/lib/mcp/browser/config.js +383 -0
- package/lib/mcp/browser/context.js +284 -0
- package/lib/mcp/browser/response.js +228 -0
- package/lib/mcp/browser/sessionLog.js +160 -0
- package/lib/mcp/browser/tab.js +277 -0
- package/lib/mcp/browser/tools/common.js +63 -0
- package/lib/mcp/browser/tools/console.js +44 -0
- package/lib/mcp/browser/tools/dialogs.js +60 -0
- package/lib/mcp/browser/tools/evaluate.js +70 -0
- package/lib/mcp/browser/tools/files.js +58 -0
- package/lib/mcp/browser/tools/form.js +74 -0
- package/lib/mcp/browser/tools/install.js +69 -0
- package/lib/mcp/browser/tools/keyboard.js +85 -0
- package/lib/mcp/browser/tools/mouse.js +107 -0
- package/lib/mcp/browser/tools/navigate.js +62 -0
- package/lib/mcp/browser/tools/network.js +54 -0
- package/lib/mcp/browser/tools/pdf.js +59 -0
- package/lib/mcp/browser/tools/screenshot.js +88 -0
- package/lib/mcp/browser/tools/snapshot.js +182 -0
- package/lib/mcp/browser/tools/tabs.js +67 -0
- package/lib/mcp/browser/tools/tool.js +49 -0
- package/lib/mcp/browser/tools/tracing.js +74 -0
- package/lib/mcp/browser/tools/utils.js +100 -0
- package/lib/mcp/browser/tools/verify.js +154 -0
- package/lib/mcp/browser/tools/wait.js +63 -0
- package/lib/mcp/browser/tools.js +80 -0
- package/lib/mcp/browser/watchdog.js +44 -0
- package/lib/mcp/config.d.js +16 -0
- package/lib/mcp/extension/cdpRelay.js +351 -0
- package/lib/mcp/extension/extensionContextFactory.js +75 -0
- package/lib/mcp/extension/protocol.js +28 -0
- package/lib/mcp/index.js +61 -0
- package/lib/mcp/log.js +35 -0
- package/lib/mcp/program.js +96 -0
- package/lib/mcp/sdk/bundle.js +81 -0
- package/lib/mcp/sdk/exports.js +32 -0
- package/lib/mcp/sdk/http.js +180 -0
- package/lib/mcp/sdk/inProcessTransport.js +71 -0
- package/lib/mcp/sdk/mdb.js +208 -0
- package/lib/mcp/sdk/proxyBackend.js +128 -0
- package/lib/mcp/sdk/server.js +190 -0
- package/lib/mcp/sdk/tool.js +51 -0
- package/lib/mcp/test/browserBackend.js +98 -0
- package/lib/mcp/test/generatorTools.js +122 -0
- package/lib/mcp/test/plannerTools.js +46 -0
- package/lib/mcp/test/seed.js +72 -0
- package/lib/mcp/test/streams.js +39 -0
- package/lib/mcp/test/testBackend.js +97 -0
- package/lib/mcp/test/testContext.js +176 -0
- package/lib/mcp/test/testTool.js +30 -0
- package/lib/mcp/test/testTools.js +115 -0
- package/lib/mcpBundleImpl.js +41 -0
- package/lib/plugins/webServerPlugin.js +2 -0
- package/lib/program.js +77 -57
- package/lib/reporters/base.js +34 -29
- package/lib/reporters/dot.js +11 -11
- package/lib/reporters/github.js +2 -1
- package/lib/reporters/html.js +58 -41
- package/lib/reporters/internalReporter.js +2 -1
- package/lib/reporters/line.js +15 -15
- package/lib/reporters/list.js +24 -19
- package/lib/reporters/listModeReporter.js +69 -0
- package/lib/reporters/markdown.js +3 -3
- package/lib/reporters/merge.js +3 -1
- package/lib/reporters/teleEmitter.js +3 -1
- package/lib/runner/dispatcher.js +9 -2
- package/lib/runner/failureTracker.js +12 -2
- package/lib/runner/lastRun.js +7 -4
- package/lib/runner/loadUtils.js +46 -12
- package/lib/runner/projectUtils.js +8 -2
- package/lib/runner/reporters.js +7 -32
- package/lib/runner/tasks.js +20 -10
- package/lib/runner/testRunner.js +390 -0
- package/lib/runner/testServer.js +57 -276
- package/lib/runner/watchMode.js +5 -1
- package/lib/runner/workerHost.js +8 -6
- package/lib/transform/babelBundleImpl.js +179 -195
- package/lib/transform/compilationCache.js +22 -5
- package/lib/transform/transform.js +1 -1
- package/lib/util.js +12 -35
- package/lib/utilsBundleImpl.js +1 -1
- package/lib/worker/fixtureRunner.js +7 -2
- package/lib/worker/testInfo.js +76 -45
- package/lib/worker/testTracing.js +8 -7
- package/lib/worker/workerMain.js +12 -3
- package/package.json +10 -2
- package/types/test.d.ts +63 -44
- package/types/testReporter.d.ts +1 -1
- package/lib/runner/runner.js +0 -110
package/lib/common/fixtures.js
CHANGED
|
@@ -90,7 +90,7 @@ class FixturePool {
|
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
92
92
|
} else if (previous) {
|
|
93
|
-
options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle
|
|
93
|
+
options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle };
|
|
94
94
|
} else if (!options) {
|
|
95
95
|
options = { auto: false, scope: "test", option: false, timeout: void 0 };
|
|
96
96
|
}
|
package/lib/common/suiteUtils.js
CHANGED
|
@@ -31,10 +31,8 @@ __export(suiteUtils_exports, {
|
|
|
31
31
|
applyRepeatEachIndex: () => applyRepeatEachIndex,
|
|
32
32
|
bindFileSuiteToProject: () => bindFileSuiteToProject,
|
|
33
33
|
filterByFocusedLine: () => filterByFocusedLine,
|
|
34
|
-
filterByTestIds: () => filterByTestIds,
|
|
35
34
|
filterOnly: () => filterOnly,
|
|
36
35
|
filterSuite: () => filterSuite,
|
|
37
|
-
filterSuiteWithOnlySemantics: () => filterSuiteWithOnlySemantics,
|
|
38
36
|
filterTestsRemoveEmptySuites: () => filterTestsRemoveEmptySuites
|
|
39
37
|
});
|
|
40
38
|
module.exports = __toCommonJS(suiteUtils_exports);
|
|
@@ -127,11 +125,6 @@ function filterByFocusedLine(suite, focusedTestFileLines) {
|
|
|
127
125
|
const testFilter = (test) => testFileLineMatches(test.location.file, test.location.line, test.location.column);
|
|
128
126
|
return filterSuite(suite, suiteFilter, testFilter);
|
|
129
127
|
}
|
|
130
|
-
function filterByTestIds(suite, testIdMatcher) {
|
|
131
|
-
if (!testIdMatcher)
|
|
132
|
-
return;
|
|
133
|
-
filterTestsRemoveEmptySuites(suite, (test) => testIdMatcher(test.id));
|
|
134
|
-
}
|
|
135
128
|
function createFileMatcherFromFilter(filter) {
|
|
136
129
|
const fileMatcher = (0, import_util.createFileMatcher)(filter.re || filter.exact || "");
|
|
137
130
|
return (testFileName, testLine, testColumn) => fileMatcher(testFileName) && (filter.line === testLine || filter.line === null) && (filter.column === testColumn || filter.column === null);
|
|
@@ -141,9 +134,7 @@ function createFileMatcherFromFilter(filter) {
|
|
|
141
134
|
applyRepeatEachIndex,
|
|
142
135
|
bindFileSuiteToProject,
|
|
143
136
|
filterByFocusedLine,
|
|
144
|
-
filterByTestIds,
|
|
145
137
|
filterOnly,
|
|
146
138
|
filterSuite,
|
|
147
|
-
filterSuiteWithOnlySemantics,
|
|
148
139
|
filterTestsRemoveEmptySuites
|
|
149
140
|
});
|
package/lib/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var playwrightLibrary = __toESM(require("playwright-core"));
|
|
|
42
42
|
var import_utils = require("playwright-core/lib/utils");
|
|
43
43
|
var import_globals = require("./common/globals");
|
|
44
44
|
var import_testType = require("./common/testType");
|
|
45
|
-
var
|
|
45
|
+
var import_browserBackend = require("./mcp/test/browserBackend");
|
|
46
46
|
var import_expect = require("./matchers/expect");
|
|
47
47
|
var import_configLoader = require("./common/configLoader");
|
|
48
48
|
var import_testType2 = require("./common/testType");
|
|
@@ -61,20 +61,20 @@ if (process["__pw_initiator__"]) {
|
|
|
61
61
|
process["__pw_initiator__"] = new Error().stack;
|
|
62
62
|
}
|
|
63
63
|
const playwrightFixtures = {
|
|
64
|
-
defaultBrowserType: ["chromium", { scope: "worker", option: true }],
|
|
65
|
-
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true }],
|
|
64
|
+
defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
|
|
65
|
+
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
|
|
66
66
|
playwright: [async ({}, use) => {
|
|
67
67
|
await use(require("playwright-core"));
|
|
68
68
|
}, { scope: "worker", box: true }],
|
|
69
|
-
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true }],
|
|
70
|
-
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true }],
|
|
71
|
-
launchOptions: [{}, { scope: "worker", option: true }],
|
|
69
|
+
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true, box: true }],
|
|
70
|
+
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true, box: true }],
|
|
71
|
+
launchOptions: [{}, { scope: "worker", option: true, box: true }],
|
|
72
72
|
connectOptions: [async ({ _optionConnectOptions }, use) => {
|
|
73
73
|
await use(connectOptionsFromEnv() || _optionConnectOptions);
|
|
74
|
-
}, { scope: "worker", option: true }],
|
|
75
|
-
screenshot: ["off", { scope: "worker", option: true }],
|
|
76
|
-
video: ["off", { scope: "worker", option: true }],
|
|
77
|
-
trace: ["off", { scope: "worker", option: true }],
|
|
74
|
+
}, { scope: "worker", option: true, box: true }],
|
|
75
|
+
screenshot: ["off", { scope: "worker", option: true, box: true }],
|
|
76
|
+
video: ["off", { scope: "worker", option: true, box: true }],
|
|
77
|
+
trace: ["off", { scope: "worker", option: true, box: true }],
|
|
78
78
|
_browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
|
|
79
79
|
const options = {
|
|
80
80
|
handleSIGINT: false,
|
|
@@ -103,45 +103,41 @@ const playwrightFixtures = {
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
await use(browser2);
|
|
106
|
-
await browser2.
|
|
107
|
-
await browser2.close({ reason: "Test ended." });
|
|
108
|
-
}, { internal: true });
|
|
106
|
+
await browser2.close({ reason: "Test ended." });
|
|
109
107
|
return;
|
|
110
108
|
}
|
|
111
109
|
const browser = await playwright[browserName].launch();
|
|
112
110
|
await use(browser);
|
|
113
|
-
await browser.
|
|
114
|
-
await browser.close({ reason: "Test ended." });
|
|
115
|
-
}, { internal: true });
|
|
111
|
+
await browser.close({ reason: "Test ended." });
|
|
116
112
|
}, { scope: "worker", timeout: 0 }],
|
|
117
|
-
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
|
|
118
|
-
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true }],
|
|
119
|
-
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true }],
|
|
120
|
-
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true }],
|
|
121
|
-
extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true }],
|
|
122
|
-
geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true }],
|
|
123
|
-
hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true }],
|
|
124
|
-
httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true }],
|
|
125
|
-
ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true }],
|
|
126
|
-
isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true }],
|
|
127
|
-
javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true }],
|
|
128
|
-
locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true }],
|
|
129
|
-
offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true }],
|
|
130
|
-
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
|
|
131
|
-
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
|
|
132
|
-
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
|
|
133
|
-
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true }],
|
|
134
|
-
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
|
|
135
|
-
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }],
|
|
136
|
-
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
|
|
137
|
-
actionTimeout: [0, { option: true }],
|
|
138
|
-
testIdAttribute: ["data-testid", { option: true }],
|
|
139
|
-
navigationTimeout: [0, { option: true }],
|
|
113
|
+
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
|
|
114
|
+
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
|
|
115
|
+
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
|
|
116
|
+
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
|
|
117
|
+
extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true, box: true }],
|
|
118
|
+
geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true, box: true }],
|
|
119
|
+
hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true, box: true }],
|
|
120
|
+
httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true, box: true }],
|
|
121
|
+
ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true, box: true }],
|
|
122
|
+
isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true, box: true }],
|
|
123
|
+
javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true, box: true }],
|
|
124
|
+
locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true, box: true }],
|
|
125
|
+
offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true, box: true }],
|
|
126
|
+
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true, box: true }],
|
|
127
|
+
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true, box: true }],
|
|
128
|
+
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true, box: true }],
|
|
129
|
+
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true, box: true }],
|
|
130
|
+
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true, box: true }],
|
|
131
|
+
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true, box: true }],
|
|
132
|
+
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true, box: true }],
|
|
133
|
+
actionTimeout: [0, { option: true, box: true }],
|
|
134
|
+
testIdAttribute: ["data-testid", { option: true, box: true }],
|
|
135
|
+
navigationTimeout: [0, { option: true, box: true }],
|
|
140
136
|
baseURL: [async ({}, use) => {
|
|
141
137
|
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
|
|
142
|
-
}, { option: true }],
|
|
143
|
-
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true }],
|
|
144
|
-
contextOptions: [{}, { option: true }],
|
|
138
|
+
}, { option: true, box: true }],
|
|
139
|
+
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
|
|
140
|
+
contextOptions: [{}, { option: true, box: true }],
|
|
145
141
|
_combinedContextOptions: [async ({
|
|
146
142
|
acceptDownloads,
|
|
147
143
|
bypassCSP,
|
|
@@ -221,10 +217,10 @@ const playwrightFixtures = {
|
|
|
221
217
|
if (testIdAttribute)
|
|
222
218
|
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
|
223
219
|
testInfo.snapshotSuffix = process.platform;
|
|
224
|
-
if ((0, import_utils.debugMode)())
|
|
220
|
+
if ((0, import_utils.debugMode)() === "inspector")
|
|
225
221
|
testInfo._setDebugMode();
|
|
226
222
|
playwright._defaultContextOptions = _combinedContextOptions;
|
|
227
|
-
playwright._defaultContextTimeout = actionTimeout || 0;
|
|
223
|
+
playwright._defaultContextTimeout = testInfo._pauseOnError() ? 5e3 : actionTimeout || 0;
|
|
228
224
|
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
|
|
229
225
|
await use();
|
|
230
226
|
playwright._defaultContextOptions = void 0;
|
|
@@ -242,11 +238,12 @@ const playwrightFixtures = {
|
|
|
242
238
|
if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
|
|
243
239
|
return;
|
|
244
240
|
const zone = (0, import_utils.currentZone)().data("stepZone");
|
|
245
|
-
|
|
241
|
+
const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
|
|
242
|
+
if (zone && zone.category === "expect" && isExpectCall) {
|
|
246
243
|
if (zone.apiName)
|
|
247
244
|
data.apiName = zone.apiName;
|
|
248
245
|
if (zone.title)
|
|
249
|
-
data.title =
|
|
246
|
+
data.title = zone.title;
|
|
250
247
|
data.stepId = zone.stepId;
|
|
251
248
|
return;
|
|
252
249
|
}
|
|
@@ -255,7 +252,8 @@ const playwrightFixtures = {
|
|
|
255
252
|
category: "pw:api",
|
|
256
253
|
title: renderTitle(channel.type, channel.method, channel.params, data.title),
|
|
257
254
|
apiName: data.apiName,
|
|
258
|
-
params: channel.params
|
|
255
|
+
params: channel.params,
|
|
256
|
+
group: (0, import_utils.getActionGroup)({ type: channel.type, method: channel.method })
|
|
259
257
|
}, tracingGroupSteps[tracingGroupSteps.length - 1]);
|
|
260
258
|
data.userData = step;
|
|
261
259
|
data.stepId = step.stepId;
|
|
@@ -310,6 +308,7 @@ const playwrightFixtures = {
|
|
|
310
308
|
const videoMode = normalizeVideoMode(video);
|
|
311
309
|
const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
|
|
312
310
|
const contexts = /* @__PURE__ */ new Map();
|
|
311
|
+
let counter = 0;
|
|
313
312
|
await use(async (options) => {
|
|
314
313
|
const hook = testInfoImpl._currentHookType();
|
|
315
314
|
if (hook === "beforeAll" || hook === "afterAll") {
|
|
@@ -326,10 +325,6 @@ const playwrightFixtures = {
|
|
|
326
325
|
}
|
|
327
326
|
} : {};
|
|
328
327
|
const context = await browser.newContext({ ...videoOptions, ...options });
|
|
329
|
-
const contextData = { pagesWithVideo: [] };
|
|
330
|
-
contexts.set(context, contextData);
|
|
331
|
-
if (captureVideo)
|
|
332
|
-
context.on("page", (page) => contextData.pagesWithVideo.push(page));
|
|
333
328
|
if (process.env.PW_CLOCK === "frozen") {
|
|
334
329
|
await context._wrapApiCall(async () => {
|
|
335
330
|
await context.clock.install({ time: 0 });
|
|
@@ -340,33 +335,39 @@ const playwrightFixtures = {
|
|
|
340
335
|
await context.clock.install({ time: 0 });
|
|
341
336
|
}, { internal: true });
|
|
342
337
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
338
|
+
let closed = false;
|
|
339
|
+
const close = async () => {
|
|
340
|
+
if (closed)
|
|
341
|
+
return;
|
|
342
|
+
closed = true;
|
|
343
|
+
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
|
349
344
|
await context.close({ reason: closeReason });
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
345
|
+
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
|
346
|
+
const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
|
|
347
|
+
if (preserveVideo) {
|
|
348
|
+
const { pagesWithVideo: pagesForVideo } = contexts.get(context);
|
|
349
|
+
const videos = pagesForVideo.map((p) => p.video()).filter((video2) => !!video2);
|
|
350
|
+
await Promise.all(videos.map(async (v) => {
|
|
351
|
+
try {
|
|
352
|
+
const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`);
|
|
353
|
+
++counter;
|
|
354
|
+
await v.saveAs(savedPath);
|
|
355
|
+
testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" });
|
|
356
|
+
} catch (e) {
|
|
357
|
+
}
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
const contextData = { close, pagesWithVideo: [] };
|
|
362
|
+
if (captureVideo)
|
|
363
|
+
context.on("page", (page) => contextData.pagesWithVideo.push(page));
|
|
364
|
+
contexts.set(context, contextData);
|
|
365
|
+
return { context, close };
|
|
366
|
+
});
|
|
367
|
+
await Promise.all([...contexts.values()].map((data) => data.close()));
|
|
367
368
|
}, { scope: "test", title: "context", box: true }],
|
|
368
|
-
_optionContextReuseMode: ["none", { scope: "worker", option: true }],
|
|
369
|
-
_optionConnectOptions: [void 0, { scope: "worker", option: true }],
|
|
369
|
+
_optionContextReuseMode: ["none", { scope: "worker", option: true, box: true }],
|
|
370
|
+
_optionConnectOptions: [void 0, { scope: "worker", option: true, box: true }],
|
|
370
371
|
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
|
371
372
|
let mode = _optionContextReuseMode;
|
|
372
373
|
if (process.env.PW_TEST_REUSE_CONTEXT)
|
|
@@ -374,17 +375,21 @@ const playwrightFixtures = {
|
|
|
374
375
|
const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
|
|
375
376
|
await use(reuse);
|
|
376
377
|
}, { scope: "worker", title: "context", box: true }],
|
|
377
|
-
context: async ({
|
|
378
|
-
|
|
378
|
+
context: async ({ browser, _reuseContext, _contextFactory }, use, testInfo) => {
|
|
379
|
+
const browserImpl = browser;
|
|
380
|
+
attachConnectedHeaderIfNeeded(testInfo, browserImpl);
|
|
379
381
|
if (!_reuseContext) {
|
|
380
|
-
|
|
382
|
+
const { context: context2, close } = await _contextFactory();
|
|
383
|
+
testInfo._onDidFinishTestFunctions.unshift(() => (0, import_browserBackend.runBrowserBackendAtEnd)(context2, testInfo.errors[0]?.message));
|
|
384
|
+
await use(context2);
|
|
385
|
+
await close();
|
|
381
386
|
return;
|
|
382
387
|
}
|
|
383
|
-
const
|
|
384
|
-
|
|
388
|
+
const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
|
|
389
|
+
testInfo._onDidFinishTestFunctions.unshift(() => (0, import_browserBackend.runBrowserBackendAtEnd)(context, testInfo.errors[0]?.message));
|
|
385
390
|
await use(context);
|
|
386
391
|
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
|
387
|
-
await
|
|
392
|
+
await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
|
|
388
393
|
},
|
|
389
394
|
page: async ({ context, _reuseContext }, use) => {
|
|
390
395
|
if (!_reuseContext) {
|
|
@@ -548,22 +553,24 @@ class ArtifactsRecorder {
|
|
|
548
553
|
const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
|
|
549
554
|
this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
|
|
550
555
|
this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
|
|
551
|
-
await page.
|
|
556
|
+
await page._wrapApiCall(async () => {
|
|
557
|
+
await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" });
|
|
558
|
+
}, { internal: true });
|
|
552
559
|
});
|
|
553
560
|
}
|
|
554
561
|
async willStartTest(testInfo) {
|
|
555
562
|
this._testInfo = testInfo;
|
|
556
|
-
testInfo.
|
|
563
|
+
testInfo._onDidFinishTestFunctions.push(() => this.didFinishTestFunction());
|
|
557
564
|
this._screenshotRecorder.fixOrdinal();
|
|
558
565
|
await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
|
|
559
566
|
const existingApiRequests = Array.from(this._playwright.request._contexts);
|
|
560
567
|
await Promise.all(existingApiRequests.map((c) => this.didCreateRequestContext(c)));
|
|
561
568
|
}
|
|
562
569
|
async didCreateBrowserContext(context) {
|
|
563
|
-
await this._startTraceChunkOnContextCreation(context.tracing);
|
|
570
|
+
await this._startTraceChunkOnContextCreation(context, context.tracing);
|
|
564
571
|
}
|
|
565
572
|
async willCloseBrowserContext(context) {
|
|
566
|
-
await this._stopTracing(context.tracing);
|
|
573
|
+
await this._stopTracing(context, context.tracing);
|
|
567
574
|
await this._screenshotRecorder.captureTemporary(context);
|
|
568
575
|
await this._takePageSnapshot(context);
|
|
569
576
|
}
|
|
@@ -575,18 +582,20 @@ class ArtifactsRecorder {
|
|
|
575
582
|
if (this._pageSnapshot)
|
|
576
583
|
return;
|
|
577
584
|
const page = context.pages()[0];
|
|
585
|
+
if (!page)
|
|
586
|
+
return;
|
|
578
587
|
try {
|
|
579
|
-
|
|
588
|
+
await page._wrapApiCall(async () => {
|
|
589
|
+
this._pageSnapshot = await page._snapshotForAI({ timeout: 5e3 });
|
|
590
|
+
}, { internal: true });
|
|
580
591
|
} catch {
|
|
581
592
|
}
|
|
582
593
|
}
|
|
583
594
|
async didCreateRequestContext(context) {
|
|
584
|
-
|
|
585
|
-
await this._startTraceChunkOnContextCreation(tracing2);
|
|
595
|
+
await this._startTraceChunkOnContextCreation(context, context._tracing);
|
|
586
596
|
}
|
|
587
597
|
async willCloseRequestContext(context) {
|
|
588
|
-
|
|
589
|
-
await this._stopTracing(tracing2);
|
|
598
|
+
await this._stopTracing(context, context._tracing);
|
|
590
599
|
}
|
|
591
600
|
async didFinishTestFunction() {
|
|
592
601
|
await this._screenshotRecorder.maybeCapture();
|
|
@@ -596,10 +605,9 @@ class ArtifactsRecorder {
|
|
|
596
605
|
const leftoverContexts = this._playwright._allContexts();
|
|
597
606
|
const leftoverApiRequests = Array.from(this._playwright.request._contexts);
|
|
598
607
|
await Promise.all(leftoverContexts.map(async (context2) => {
|
|
599
|
-
await this._stopTracing(context2.tracing);
|
|
608
|
+
await this._stopTracing(context2, context2.tracing);
|
|
600
609
|
}).concat(leftoverApiRequests.map(async (context2) => {
|
|
601
|
-
|
|
602
|
-
await this._stopTracing(tracing2);
|
|
610
|
+
await this._stopTracing(context2, context2._tracing);
|
|
603
611
|
})));
|
|
604
612
|
await this._screenshotRecorder.persistTemporary();
|
|
605
613
|
const context = leftoverContexts[0];
|
|
@@ -622,30 +630,34 @@ class ArtifactsRecorder {
|
|
|
622
630
|
}, void 0);
|
|
623
631
|
}
|
|
624
632
|
}
|
|
625
|
-
async _startTraceChunkOnContextCreation(tracing2) {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
+
async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
|
|
634
|
+
await channelOwner._wrapApiCall(async () => {
|
|
635
|
+
const options = this._testInfo._tracing.traceOptions();
|
|
636
|
+
if (options) {
|
|
637
|
+
const title = this._testInfo._tracing.traceTitle();
|
|
638
|
+
const name = this._testInfo._tracing.generateNextTraceRecordingName();
|
|
639
|
+
if (!tracing2[kTracingStarted]) {
|
|
640
|
+
await tracing2.start({ ...options, title, name });
|
|
641
|
+
tracing2[kTracingStarted] = true;
|
|
642
|
+
} else {
|
|
643
|
+
await tracing2.startChunk({ title, name });
|
|
644
|
+
}
|
|
633
645
|
} else {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
tracing2[kTracingStarted] = false;
|
|
639
|
-
await tracing2.stop();
|
|
646
|
+
if (tracing2[kTracingStarted]) {
|
|
647
|
+
tracing2[kTracingStarted] = false;
|
|
648
|
+
await tracing2.stop();
|
|
649
|
+
}
|
|
640
650
|
}
|
|
641
|
-
}
|
|
651
|
+
}, { internal: true });
|
|
642
652
|
}
|
|
643
|
-
async _stopTracing(tracing2) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
653
|
+
async _stopTracing(channelOwner, tracing2) {
|
|
654
|
+
await channelOwner._wrapApiCall(async () => {
|
|
655
|
+
if (tracing2[this._startedCollectingArtifacts])
|
|
656
|
+
return;
|
|
657
|
+
tracing2[this._startedCollectingArtifacts] = true;
|
|
658
|
+
if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted])
|
|
659
|
+
await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() });
|
|
660
|
+
}, { internal: true });
|
|
649
661
|
}
|
|
650
662
|
}
|
|
651
663
|
function renderTitle(type, method, params, title) {
|
|
@@ -25,7 +25,7 @@ __export(testTree_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(testTree_exports);
|
|
27
27
|
class TestTree {
|
|
28
|
-
constructor(rootFolder, rootSuite, loadErrors, projectFilters, pathSeparator) {
|
|
28
|
+
constructor(rootFolder, rootSuite, loadErrors, projectFilters, pathSeparator, hideFiles) {
|
|
29
29
|
this._treeItemById = /* @__PURE__ */ new Map();
|
|
30
30
|
this._treeItemByTestId = /* @__PURE__ */ new Map();
|
|
31
31
|
const filterProjects = projectFilters && [...projectFilters.values()].some(Boolean);
|
|
@@ -43,10 +43,10 @@ class TestTree {
|
|
|
43
43
|
hasLoadErrors: false
|
|
44
44
|
};
|
|
45
45
|
this._treeItemById.set(rootFolder, this.rootItem);
|
|
46
|
-
const visitSuite = (project, parentSuite, parentGroup) => {
|
|
47
|
-
for (const suite of parentSuite.suites) {
|
|
46
|
+
const visitSuite = (project, parentSuite, parentGroup, mode) => {
|
|
47
|
+
for (const suite of mode === "tests" ? [] : parentSuite.suites) {
|
|
48
48
|
if (!suite.title) {
|
|
49
|
-
visitSuite(project, suite, parentGroup);
|
|
49
|
+
visitSuite(project, suite, parentGroup, "all");
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
52
|
let group = parentGroup.children.find((item) => item.kind === "group" && item.title === suite.title);
|
|
@@ -66,9 +66,9 @@ class TestTree {
|
|
|
66
66
|
};
|
|
67
67
|
this._addChild(parentGroup, group);
|
|
68
68
|
}
|
|
69
|
-
visitSuite(project, suite, group);
|
|
69
|
+
visitSuite(project, suite, group, "all");
|
|
70
70
|
}
|
|
71
|
-
for (const test of parentSuite.tests) {
|
|
71
|
+
for (const test of mode === "suites" ? [] : parentSuite.tests) {
|
|
72
72
|
const title = test.title;
|
|
73
73
|
let testCaseItem = parentGroup.children.find((t) => t.kind !== "group" && t.title === title);
|
|
74
74
|
if (!testCaseItem) {
|
|
@@ -124,8 +124,16 @@ class TestTree {
|
|
|
124
124
|
if (filterProjects && !projectFilters.get(projectSuite.title))
|
|
125
125
|
continue;
|
|
126
126
|
for (const fileSuite of projectSuite.suites) {
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
if (hideFiles) {
|
|
128
|
+
visitSuite(projectSuite.project(), fileSuite, this.rootItem, "suites");
|
|
129
|
+
if (fileSuite.tests.length) {
|
|
130
|
+
const defaultDescribeItem = this._defaultDescribeItem();
|
|
131
|
+
visitSuite(projectSuite.project(), fileSuite, defaultDescribeItem, "tests");
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
const fileItem = this._fileItem(fileSuite.location.file.split(pathSeparator), true);
|
|
135
|
+
visitSuite(projectSuite.project(), fileSuite, fileItem, "all");
|
|
136
|
+
}
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
for (const loadError of loadErrors) {
|
|
@@ -192,6 +200,25 @@ class TestTree {
|
|
|
192
200
|
this._addChild(parentFileItem, fileItem);
|
|
193
201
|
return fileItem;
|
|
194
202
|
}
|
|
203
|
+
_defaultDescribeItem() {
|
|
204
|
+
let defaultDescribeItem = this._treeItemById.get("<anonymous>");
|
|
205
|
+
if (!defaultDescribeItem) {
|
|
206
|
+
defaultDescribeItem = {
|
|
207
|
+
kind: "group",
|
|
208
|
+
subKind: "describe",
|
|
209
|
+
id: "<anonymous>",
|
|
210
|
+
title: "<anonymous>",
|
|
211
|
+
location: { file: "", line: 0, column: 0 },
|
|
212
|
+
duration: 0,
|
|
213
|
+
parent: this.rootItem,
|
|
214
|
+
children: [],
|
|
215
|
+
status: "none",
|
|
216
|
+
hasLoadErrors: false
|
|
217
|
+
};
|
|
218
|
+
this._addChild(this.rootItem, defaultDescribeItem);
|
|
219
|
+
}
|
|
220
|
+
return defaultDescribeItem;
|
|
221
|
+
}
|
|
195
222
|
sortAndPropagateStatus() {
|
|
196
223
|
sortAndPropagateStatus(this.rootItem);
|
|
197
224
|
}
|
package/lib/matchers/expect.js
CHANGED
|
@@ -222,26 +222,25 @@ class ExpectMetaInfoProxyHandler {
|
|
|
222
222
|
const customMessage = this._info.message || "";
|
|
223
223
|
const argsSuffix = computeArgsSuffix(matcherName, args);
|
|
224
224
|
const defaultTitle = `${this._info.poll ? "poll " : ""}${this._info.isSoft ? "soft " : ""}${this._info.isNot ? "not " : ""}${matcherName}${argsSuffix}`;
|
|
225
|
-
const title = customMessage || defaultTitle
|
|
225
|
+
const title = customMessage || `Expect ${(0, import_utils.escapeWithQuotes)(defaultTitle, '"')}`;
|
|
226
226
|
const apiName = `expect${this._info.poll ? ".poll " : ""}${this._info.isSoft ? ".soft " : ""}${this._info.isNot ? ".not" : ""}.${matcherName}${argsSuffix}`;
|
|
227
227
|
const stackFrames = (0, import_util.filteredStackTrace)((0, import_utils.captureRawStack)());
|
|
228
|
-
const category = matcherName === "toPass" || this._info.poll ? "test.step" : "expect";
|
|
229
|
-
const formattedTitle = category === "expect" ? title : `Expect "${title}"`;
|
|
230
228
|
const stepInfo = {
|
|
231
|
-
category,
|
|
229
|
+
category: "expect",
|
|
232
230
|
apiName,
|
|
233
|
-
title
|
|
231
|
+
title,
|
|
234
232
|
params: args[0] ? { expected: args[0] } : void 0,
|
|
235
233
|
infectParentStepsWithError: this._info.isSoft
|
|
236
234
|
};
|
|
237
235
|
const step = testInfo._addStep(stepInfo);
|
|
238
236
|
const reportStepError = (e) => {
|
|
239
237
|
const jestError = (0, import_matcherHint.isJestError)(e) ? e : null;
|
|
240
|
-
const
|
|
238
|
+
const expectError = jestError ? new import_matcherHint.ExpectError(jestError, customMessage, stackFrames) : void 0;
|
|
241
239
|
if (jestError?.matcherResult.suggestedRebaseline) {
|
|
242
240
|
step.complete({ suggestedRebaseline: jestError?.matcherResult.suggestedRebaseline });
|
|
243
241
|
return;
|
|
244
242
|
}
|
|
243
|
+
const error = expectError ?? e;
|
|
245
244
|
step.complete({ error });
|
|
246
245
|
if (this._info.isSoft)
|
|
247
246
|
testInfo._failWithError(error);
|
|
@@ -260,7 +259,7 @@ class ExpectMetaInfoProxyHandler {
|
|
|
260
259
|
finalizer();
|
|
261
260
|
return result;
|
|
262
261
|
} catch (e) {
|
|
263
|
-
reportStepError(e);
|
|
262
|
+
void reportStepError(e);
|
|
264
263
|
}
|
|
265
264
|
};
|
|
266
265
|
}
|
|
@@ -19,22 +19,42 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var matcherHint_exports = {};
|
|
20
20
|
__export(matcherHint_exports, {
|
|
21
21
|
ExpectError: () => ExpectError,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
callLogText: () => callLogText,
|
|
23
|
+
formatMatcherMessage: () => formatMatcherMessage,
|
|
24
|
+
isJestError: () => isJestError
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(matcherHint_exports);
|
|
27
27
|
var import_utils = require("playwright-core/lib/utils");
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
var import_expectBundle = require("../common/expectBundle");
|
|
29
|
+
function formatMatcherMessage(state, details) {
|
|
30
|
+
const receiver = details.receiver ?? (details.locator ? "locator" : "page");
|
|
31
|
+
let message = (0, import_expectBundle.DIM_COLOR)("expect(") + (0, import_expectBundle.RECEIVED_COLOR)(receiver) + (0, import_expectBundle.DIM_COLOR)(")" + (state.promise ? "." + state.promise : "") + (state.isNot ? ".not" : "") + ".") + details.matcherName + (0, import_expectBundle.DIM_COLOR)("(") + (0, import_expectBundle.EXPECTED_COLOR)(details.expectation) + (0, import_expectBundle.DIM_COLOR)(")") + " failed\n\n";
|
|
32
|
+
const diffLines = details.printedDiff?.split("\n");
|
|
33
|
+
if (diffLines?.length === 2) {
|
|
34
|
+
details.printedExpected = diffLines[0];
|
|
35
|
+
details.printedReceived = diffLines[1];
|
|
36
|
+
details.printedDiff = void 0;
|
|
37
|
+
}
|
|
38
|
+
const align = !details.errorMessage && details.printedExpected?.startsWith("Expected:") && (!details.printedReceived || details.printedReceived.startsWith("Received:"));
|
|
39
|
+
if (details.locator)
|
|
40
|
+
message += `Locator: ${align ? " " : ""}${String(details.locator)}
|
|
41
|
+
`;
|
|
42
|
+
if (details.printedExpected)
|
|
43
|
+
message += details.printedExpected + "\n";
|
|
44
|
+
if (details.printedReceived)
|
|
45
|
+
message += details.printedReceived + "\n";
|
|
46
|
+
if (details.timedOut && details.timeout)
|
|
47
|
+
message += `Timeout: ${align ? " " : ""}${details.timeout}ms
|
|
36
48
|
`;
|
|
37
|
-
|
|
49
|
+
if (details.printedDiff)
|
|
50
|
+
message += details.printedDiff + "\n";
|
|
51
|
+
if (details.errorMessage) {
|
|
52
|
+
message += details.errorMessage;
|
|
53
|
+
if (!details.errorMessage.endsWith("\n"))
|
|
54
|
+
message += "\n";
|
|
55
|
+
}
|
|
56
|
+
message += callLogText(details.log);
|
|
57
|
+
return message;
|
|
38
58
|
}
|
|
39
59
|
class ExpectError extends Error {
|
|
40
60
|
constructor(jestError, customMessage, stackFrames) {
|
|
@@ -50,10 +70,18 @@ class ExpectError extends Error {
|
|
|
50
70
|
function isJestError(e) {
|
|
51
71
|
return e instanceof Error && "matcherResult" in e;
|
|
52
72
|
}
|
|
73
|
+
const callLogText = (log) => {
|
|
74
|
+
if (!log || !log.some((l) => !!l))
|
|
75
|
+
return "";
|
|
76
|
+
return `
|
|
77
|
+
Call log:
|
|
78
|
+
${(0, import_expectBundle.DIM_COLOR)(log.join("\n"))}
|
|
79
|
+
`;
|
|
80
|
+
};
|
|
53
81
|
// Annotate the CommonJS export names for ESM import in node:
|
|
54
82
|
0 && (module.exports = {
|
|
55
83
|
ExpectError,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
callLogText,
|
|
85
|
+
formatMatcherMessage,
|
|
86
|
+
isJestError
|
|
59
87
|
});
|