playwright 1.55.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 +3 -3
- 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 +1 -1
- package/lib/common/expectBundle.js +3 -0
- package/lib/common/expectBundleImpl.js +51 -51
- package/lib/index.js +7 -8
- package/lib/isomorphic/testServerConnection.js +0 -7
- package/lib/isomorphic/testTree.js +35 -8
- package/lib/matchers/expect.js +8 -21
- package/lib/matchers/matcherHint.js +42 -18
- package/lib/matchers/matchers.js +12 -6
- 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 -30
- 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/{tool.js → log.js} +12 -18
- package/lib/mcp/program.js +96 -0
- package/lib/mcp/{bundle.js → sdk/bundle.js} +24 -2
- package/lib/mcp/{exports.js → sdk/exports.js} +12 -10
- package/lib/mcp/{transport.js → sdk/http.js} +79 -60
- package/lib/mcp/sdk/mdb.js +208 -0
- package/lib/mcp/{proxyBackend.js → sdk/proxyBackend.js} +18 -13
- 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 +14 -67
- package/lib/plugins/webServerPlugin.js +2 -0
- package/lib/program.js +68 -0
- package/lib/reporters/base.js +15 -17
- package/lib/reporters/html.js +39 -26
- package/lib/reporters/list.js +8 -4
- package/lib/reporters/listModeReporter.js +6 -3
- package/lib/reporters/merge.js +3 -1
- package/lib/reporters/teleEmitter.js +3 -1
- package/lib/runner/dispatcher.js +9 -23
- package/lib/runner/failureTracker.js +12 -16
- package/lib/runner/loadUtils.js +39 -3
- package/lib/runner/projectUtils.js +8 -2
- package/lib/runner/tasks.js +18 -7
- package/lib/runner/testRunner.js +16 -28
- package/lib/runner/testServer.js +17 -23
- package/lib/runner/watchMode.js +1 -53
- package/lib/runner/workerHost.js +8 -10
- package/lib/transform/babelBundleImpl.js +10 -10
- package/lib/transform/compilationCache.js +22 -5
- package/lib/util.js +12 -16
- package/lib/utilsBundleImpl.js +1 -1
- package/lib/worker/fixtureRunner.js +15 -7
- package/lib/worker/testInfo.js +9 -24
- package/lib/worker/workerMain.js +12 -8
- package/package.json +7 -3
- package/types/test.d.ts +17 -8
- package/types/testReporter.d.ts +1 -1
- package/lib/mcp/server.js +0 -118
- /package/lib/mcp/{inProcessTransport.js → sdk/inProcessTransport.js} +0 -0
package/lib/index.js
CHANGED
|
@@ -42,6 +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 import_browserBackend = require("./mcp/test/browserBackend");
|
|
45
46
|
var import_expect = require("./matchers/expect");
|
|
46
47
|
var import_configLoader = require("./common/configLoader");
|
|
47
48
|
var import_testType2 = require("./common/testType");
|
|
@@ -219,7 +220,7 @@ const playwrightFixtures = {
|
|
|
219
220
|
if ((0, import_utils.debugMode)() === "inspector")
|
|
220
221
|
testInfo._setDebugMode();
|
|
221
222
|
playwright._defaultContextOptions = _combinedContextOptions;
|
|
222
|
-
playwright._defaultContextTimeout = actionTimeout || 0;
|
|
223
|
+
playwright._defaultContextTimeout = testInfo._pauseOnError() ? 5e3 : actionTimeout || 0;
|
|
223
224
|
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
|
|
224
225
|
await use();
|
|
225
226
|
playwright._defaultContextOptions = void 0;
|
|
@@ -237,7 +238,8 @@ const playwrightFixtures = {
|
|
|
237
238
|
if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
|
|
238
239
|
return;
|
|
239
240
|
const zone = (0, import_utils.currentZone)().data("stepZone");
|
|
240
|
-
|
|
241
|
+
const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
|
|
242
|
+
if (zone && zone.category === "expect" && isExpectCall) {
|
|
241
243
|
if (zone.apiName)
|
|
242
244
|
data.apiName = zone.apiName;
|
|
243
245
|
if (zone.title)
|
|
@@ -258,11 +260,6 @@ const playwrightFixtures = {
|
|
|
258
260
|
if (data.apiName === "tracing.group")
|
|
259
261
|
tracingGroupSteps.push(step);
|
|
260
262
|
},
|
|
261
|
-
onApiCallRecovery: (data, error, recoveryHandlers) => {
|
|
262
|
-
const step = data.userData;
|
|
263
|
-
if (step)
|
|
264
|
-
recoveryHandlers.push(() => step.recoverFromStepError(error));
|
|
265
|
-
},
|
|
266
263
|
onApiCallEnd: (data) => {
|
|
267
264
|
if (data.apiName === "tracing.group")
|
|
268
265
|
return;
|
|
@@ -383,11 +380,13 @@ const playwrightFixtures = {
|
|
|
383
380
|
attachConnectedHeaderIfNeeded(testInfo, browserImpl);
|
|
384
381
|
if (!_reuseContext) {
|
|
385
382
|
const { context: context2, close } = await _contextFactory();
|
|
383
|
+
testInfo._onDidFinishTestFunctions.unshift(() => (0, import_browserBackend.runBrowserBackendAtEnd)(context2, testInfo.errors[0]?.message));
|
|
386
384
|
await use(context2);
|
|
387
385
|
await close();
|
|
388
386
|
return;
|
|
389
387
|
}
|
|
390
388
|
const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
|
|
389
|
+
testInfo._onDidFinishTestFunctions.unshift(() => (0, import_browserBackend.runBrowserBackendAtEnd)(context, testInfo.errors[0]?.message));
|
|
391
390
|
await use(context);
|
|
392
391
|
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
|
393
392
|
await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
|
|
@@ -561,7 +560,7 @@ class ArtifactsRecorder {
|
|
|
561
560
|
}
|
|
562
561
|
async willStartTest(testInfo) {
|
|
563
562
|
this._testInfo = testInfo;
|
|
564
|
-
testInfo.
|
|
563
|
+
testInfo._onDidFinishTestFunctions.push(() => this.didFinishTestFunction());
|
|
565
564
|
this._screenshotRecorder.fixOrdinal();
|
|
566
565
|
await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
|
|
567
566
|
const existingApiRequests = Array.from(this._playwright.request._contexts);
|
|
@@ -63,7 +63,6 @@ class TestServerConnection {
|
|
|
63
63
|
this._onStdioEmitter = new events.EventEmitter();
|
|
64
64
|
this._onTestFilesChangedEmitter = new events.EventEmitter();
|
|
65
65
|
this._onLoadTraceRequestedEmitter = new events.EventEmitter();
|
|
66
|
-
this._onRecoverFromStepErrorEmitter = new events.EventEmitter();
|
|
67
66
|
this._lastId = 0;
|
|
68
67
|
this._callbacks = /* @__PURE__ */ new Map();
|
|
69
68
|
this._isClosed = false;
|
|
@@ -72,7 +71,6 @@ class TestServerConnection {
|
|
|
72
71
|
this.onStdio = this._onStdioEmitter.event;
|
|
73
72
|
this.onTestFilesChanged = this._onTestFilesChangedEmitter.event;
|
|
74
73
|
this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event;
|
|
75
|
-
this.onRecoverFromStepError = this._onRecoverFromStepErrorEmitter.event;
|
|
76
74
|
this._transport = transport;
|
|
77
75
|
this._transport.onmessage((data) => {
|
|
78
76
|
const message = JSON.parse(data);
|
|
@@ -129,8 +127,6 @@ class TestServerConnection {
|
|
|
129
127
|
this._onTestFilesChangedEmitter.fire(params);
|
|
130
128
|
else if (method === "loadTraceRequested")
|
|
131
129
|
this._onLoadTraceRequestedEmitter.fire(params);
|
|
132
|
-
else if (method === "recoverFromStepError")
|
|
133
|
-
this._onRecoverFromStepErrorEmitter.fire(params);
|
|
134
130
|
}
|
|
135
131
|
async initialize(params) {
|
|
136
132
|
await this._sendMessage("initialize", params);
|
|
@@ -201,9 +197,6 @@ class TestServerConnection {
|
|
|
201
197
|
async closeGracefully(params) {
|
|
202
198
|
await this._sendMessage("closeGracefully", params);
|
|
203
199
|
}
|
|
204
|
-
async resumeAfterStepError(params) {
|
|
205
|
-
await this._sendMessage("resumeAfterStepError", params);
|
|
206
|
-
}
|
|
207
200
|
close() {
|
|
208
201
|
try {
|
|
209
202
|
this._transport.close();
|
|
@@ -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
|
@@ -225,16 +225,15 @@ class ExpectMetaInfoProxyHandler {
|
|
|
225
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
228
|
const stepInfo = {
|
|
230
|
-
category,
|
|
229
|
+
category: "expect",
|
|
231
230
|
apiName,
|
|
232
231
|
title,
|
|
233
232
|
params: args[0] ? { expected: args[0] } : void 0,
|
|
234
233
|
infectParentStepsWithError: this._info.isSoft
|
|
235
234
|
};
|
|
236
235
|
const step = testInfo._addStep(stepInfo);
|
|
237
|
-
const reportStepError = (
|
|
236
|
+
const reportStepError = (e) => {
|
|
238
237
|
const jestError = (0, import_matcherHint.isJestError)(e) ? e : null;
|
|
239
238
|
const expectError = jestError ? new import_matcherHint.ExpectError(jestError, customMessage, stackFrames) : void 0;
|
|
240
239
|
if (jestError?.matcherResult.suggestedRebaseline) {
|
|
@@ -243,22 +242,10 @@ class ExpectMetaInfoProxyHandler {
|
|
|
243
242
|
}
|
|
244
243
|
const error = expectError ?? e;
|
|
245
244
|
step.complete({ error });
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
throw error;
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
return (async () => {
|
|
254
|
-
const recoveryResult = await step.recoverFromStepError(expectError);
|
|
255
|
-
if (recoveryResult.status === "recovered")
|
|
256
|
-
return recoveryResult.value;
|
|
257
|
-
if (this._info.isSoft)
|
|
258
|
-
testInfo._failWithError(expectError);
|
|
259
|
-
else
|
|
260
|
-
throw expectError;
|
|
261
|
-
})();
|
|
245
|
+
if (this._info.isSoft)
|
|
246
|
+
testInfo._failWithError(error);
|
|
247
|
+
else
|
|
248
|
+
throw error;
|
|
262
249
|
};
|
|
263
250
|
const finalizer = () => {
|
|
264
251
|
step.complete({});
|
|
@@ -268,11 +255,11 @@ class ExpectMetaInfoProxyHandler {
|
|
|
268
255
|
const callback = () => matcher.call(target, ...args);
|
|
269
256
|
const result = (0, import_utils.currentZone)().with("stepZone", step).run(callback);
|
|
270
257
|
if (result instanceof Promise)
|
|
271
|
-
return result.then(finalizer).catch(reportStepError
|
|
258
|
+
return result.then(finalizer).catch(reportStepError);
|
|
272
259
|
finalizer();
|
|
273
260
|
return result;
|
|
274
261
|
} catch (e) {
|
|
275
|
-
void reportStepError(
|
|
262
|
+
void reportStepError(e);
|
|
276
263
|
}
|
|
277
264
|
};
|
|
278
265
|
}
|
|
@@ -19,26 +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
|
-
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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)}
|
|
37
41
|
`;
|
|
38
|
-
if (
|
|
39
|
-
|
|
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
|
|
40
48
|
`;
|
|
41
|
-
|
|
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;
|
|
42
58
|
}
|
|
43
59
|
class ExpectError extends Error {
|
|
44
60
|
constructor(jestError, customMessage, stackFrames) {
|
|
@@ -54,10 +70,18 @@ class ExpectError extends Error {
|
|
|
54
70
|
function isJestError(e) {
|
|
55
71
|
return e instanceof Error && "matcherResult" in e;
|
|
56
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
|
+
};
|
|
57
81
|
// Annotate the CommonJS export names for ESM import in node:
|
|
58
82
|
0 && (module.exports = {
|
|
59
83
|
ExpectError,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
callLogText,
|
|
85
|
+
formatMatcherMessage,
|
|
86
|
+
isJestError
|
|
63
87
|
});
|
package/lib/matchers/matchers.js
CHANGED
|
@@ -59,6 +59,7 @@ var import_toMatchText = require("./toMatchText");
|
|
|
59
59
|
var import_config = require("../common/config");
|
|
60
60
|
var import_globals = require("../common/globals");
|
|
61
61
|
var import_testInfo = require("../worker/testInfo");
|
|
62
|
+
var import_matcherHint = require("./matcherHint");
|
|
62
63
|
function toBeAttached(locator, options) {
|
|
63
64
|
const attached = !options || options.attached === void 0 || options.attached;
|
|
64
65
|
const expected = attached ? "attached" : "detached";
|
|
@@ -146,7 +147,7 @@ function toContainText(locator, expected, options = {}) {
|
|
|
146
147
|
return import_toMatchText.toMatchText.call(this, "toContainText", locator, "Locator", async (isNot, timeout) => {
|
|
147
148
|
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
|
148
149
|
return await locator._expect("to.have.text", { expectedText, isNot, useInnerText: options.useInnerText, timeout });
|
|
149
|
-
}, expected, options);
|
|
150
|
+
}, expected, { ...options, matchSubstring: true });
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
function toHaveAccessibleDescription(locator, expected, options) {
|
|
@@ -189,7 +190,7 @@ function toHaveClass(locator, expected, options) {
|
|
|
189
190
|
return import_toEqual.toEqual.call(this, "toHaveClass", locator, "Locator", async (isNot, timeout) => {
|
|
190
191
|
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected);
|
|
191
192
|
return await locator._expect("to.have.class.array", { expectedText, isNot, timeout });
|
|
192
|
-
}, expected, options
|
|
193
|
+
}, expected, options);
|
|
193
194
|
} else {
|
|
194
195
|
return import_toMatchText.toMatchText.call(this, "toHaveClass", locator, "Locator", async (isNot, timeout) => {
|
|
195
196
|
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
|
@@ -204,7 +205,7 @@ function toContainClass(locator, expected, options) {
|
|
|
204
205
|
return import_toEqual.toEqual.call(this, "toContainClass", locator, "Locator", async (isNot, timeout) => {
|
|
205
206
|
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected);
|
|
206
207
|
return await locator._expect("to.contain.class.array", { expectedText, isNot, timeout });
|
|
207
|
-
}, expected, options
|
|
208
|
+
}, expected, options);
|
|
208
209
|
} else {
|
|
209
210
|
if ((0, import_utils.isRegExp)(expected))
|
|
210
211
|
throw new Error(`"expected" argument in toContainClass cannot be a RegExp value`);
|
|
@@ -273,7 +274,7 @@ function toHaveTitle(page, expected, options = {}) {
|
|
|
273
274
|
return import_toMatchText.toMatchText.call(this, "toHaveTitle", page, "Page", async (isNot, timeout) => {
|
|
274
275
|
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { normalizeWhiteSpace: true });
|
|
275
276
|
return await page.mainFrame()._expect("to.have.title", { expectedText, isNot, timeout });
|
|
276
|
-
}, expected,
|
|
277
|
+
}, expected, options);
|
|
277
278
|
}
|
|
278
279
|
function toHaveURL(page, expected, options) {
|
|
279
280
|
if (typeof expected === "function")
|
|
@@ -283,7 +284,7 @@ function toHaveURL(page, expected, options) {
|
|
|
283
284
|
return import_toMatchText.toMatchText.call(this, "toHaveURL", page, "Page", async (isNot, timeout) => {
|
|
284
285
|
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { ignoreCase: options?.ignoreCase });
|
|
285
286
|
return await page.mainFrame()._expect("to.have.url", { expectedText, isNot, timeout });
|
|
286
|
-
}, expected,
|
|
287
|
+
}, expected, options);
|
|
287
288
|
}
|
|
288
289
|
async function toBeOK(response) {
|
|
289
290
|
const matcherName = "toBeOK";
|
|
@@ -294,7 +295,12 @@ async function toBeOK(response) {
|
|
|
294
295
|
response._fetchLog(),
|
|
295
296
|
isTextEncoding ? response.text() : null
|
|
296
297
|
]) : [];
|
|
297
|
-
const message = () =>
|
|
298
|
+
const message = () => (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
299
|
+
matcherName,
|
|
300
|
+
receiver: "response",
|
|
301
|
+
expectation: "",
|
|
302
|
+
log
|
|
303
|
+
}) + (text === null ? "" : `
|
|
298
304
|
Response text:
|
|
299
305
|
${import_utils2.colors.dim(text?.substring(0, 1e3) || "")}`);
|
|
300
306
|
const pass = response.ok();
|
|
@@ -23,14 +23,10 @@ __export(toBeTruthy_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(toBeTruthy_exports);
|
|
24
24
|
var import_util = require("../util");
|
|
25
25
|
var import_matcherHint = require("./matcherHint");
|
|
26
|
-
async function toBeTruthy(matcherName,
|
|
27
|
-
(0, import_util.expectTypes)(
|
|
28
|
-
const matcherOptions = {
|
|
29
|
-
isNot: this.isNot,
|
|
30
|
-
promise: this.promise
|
|
31
|
-
};
|
|
26
|
+
async function toBeTruthy(matcherName, locator, receiverType, expected, arg, query, options = {}) {
|
|
27
|
+
(0, import_util.expectTypes)(locator, [receiverType], matcherName);
|
|
32
28
|
const timeout = options.timeout ?? this.timeout;
|
|
33
|
-
const { matches: pass, log, timedOut, received } = await query(!!this.isNot, timeout);
|
|
29
|
+
const { matches: pass, log, timedOut, received, errorMessage } = await query(!!this.isNot, timeout);
|
|
34
30
|
if (pass === !this.isNot) {
|
|
35
31
|
return {
|
|
36
32
|
name: matcherName,
|
|
@@ -39,21 +35,27 @@ async function toBeTruthy(matcherName, receiver, receiverType, expected, arg, qu
|
|
|
39
35
|
expected
|
|
40
36
|
};
|
|
41
37
|
}
|
|
42
|
-
const notFound = received === import_matcherHint.kNoElementsFoundError ? received : void 0;
|
|
43
38
|
let printedReceived;
|
|
44
39
|
let printedExpected;
|
|
45
40
|
if (pass) {
|
|
46
41
|
printedExpected = `Expected: not ${expected}`;
|
|
47
|
-
printedReceived = `Received: ${
|
|
42
|
+
printedReceived = errorMessage ? "" : `Received: ${expected}`;
|
|
48
43
|
} else {
|
|
49
44
|
printedExpected = `Expected: ${expected}`;
|
|
50
|
-
printedReceived = `Received: ${
|
|
45
|
+
printedReceived = errorMessage ? "" : `Received: ${received}`;
|
|
51
46
|
}
|
|
52
47
|
const message = () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
return (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
49
|
+
matcherName,
|
|
50
|
+
expectation: arg,
|
|
51
|
+
locator,
|
|
52
|
+
timeout,
|
|
53
|
+
timedOut,
|
|
54
|
+
printedExpected,
|
|
55
|
+
printedReceived,
|
|
56
|
+
errorMessage,
|
|
57
|
+
log
|
|
58
|
+
});
|
|
57
59
|
};
|
|
58
60
|
return {
|
|
59
61
|
message,
|
package/lib/matchers/toEqual.js
CHANGED
|
@@ -26,15 +26,10 @@ var import_util = require("../util");
|
|
|
26
26
|
var import_matcherHint = require("./matcherHint");
|
|
27
27
|
const EXPECTED_LABEL = "Expected";
|
|
28
28
|
const RECEIVED_LABEL = "Received";
|
|
29
|
-
async function toEqual(matcherName,
|
|
30
|
-
(0, import_util.expectTypes)(
|
|
31
|
-
const matcherOptions = {
|
|
32
|
-
comment: options.contains ? "" : "deep equality",
|
|
33
|
-
isNot: this.isNot,
|
|
34
|
-
promise: this.promise
|
|
35
|
-
};
|
|
29
|
+
async function toEqual(matcherName, locator, receiverType, query, expected, options = {}) {
|
|
30
|
+
(0, import_util.expectTypes)(locator, [receiverType], matcherName);
|
|
36
31
|
const timeout = options.timeout ?? this.timeout;
|
|
37
|
-
const { matches: pass, received, log, timedOut } = await query(!!this.isNot, timeout);
|
|
32
|
+
const { matches: pass, received, log, timedOut, errorMessage } = await query(!!this.isNot, timeout);
|
|
38
33
|
if (pass === !this.isNot) {
|
|
39
34
|
return {
|
|
40
35
|
name: matcherName,
|
|
@@ -48,7 +43,9 @@ async function toEqual(matcherName, receiver, receiverType, query, expected, opt
|
|
|
48
43
|
let printedDiff;
|
|
49
44
|
if (pass) {
|
|
50
45
|
printedExpected = `Expected: not ${this.utils.printExpected(expected)}`;
|
|
51
|
-
printedReceived = `Received: ${this.utils.printReceived(received)}`;
|
|
46
|
+
printedReceived = errorMessage ? "" : `Received: ${this.utils.printReceived(received)}`;
|
|
47
|
+
} else if (errorMessage) {
|
|
48
|
+
printedExpected = `Expected: ${this.utils.printExpected(expected)}`;
|
|
52
49
|
} else if (Array.isArray(expected) && Array.isArray(received)) {
|
|
53
50
|
const normalizedExpected = expected.map((exp, index) => {
|
|
54
51
|
const rec = received[index];
|
|
@@ -73,10 +70,18 @@ async function toEqual(matcherName, receiver, receiverType, query, expected, opt
|
|
|
73
70
|
);
|
|
74
71
|
}
|
|
75
72
|
const message = () => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
return (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
74
|
+
matcherName,
|
|
75
|
+
expectation: "expected",
|
|
76
|
+
locator,
|
|
77
|
+
timeout,
|
|
78
|
+
timedOut,
|
|
79
|
+
printedExpected,
|
|
80
|
+
printedReceived,
|
|
81
|
+
printedDiff,
|
|
82
|
+
errorMessage,
|
|
83
|
+
log
|
|
84
|
+
});
|
|
80
85
|
};
|
|
81
86
|
return {
|
|
82
87
|
actual: received,
|
|
@@ -22,27 +22,11 @@ __export(toHaveURL_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(toHaveURL_exports);
|
|
24
24
|
var import_utils = require("playwright-core/lib/utils");
|
|
25
|
-
var import_utils2 = require("playwright-core/lib/utils");
|
|
26
25
|
var import_expect = require("./expect");
|
|
27
26
|
var import_matcherHint = require("./matcherHint");
|
|
28
27
|
var import_expectBundle = require("../common/expectBundle");
|
|
29
28
|
async function toHaveURLWithPredicate(page, expected, options) {
|
|
30
29
|
const matcherName = "toHaveURL";
|
|
31
|
-
const expression = "page";
|
|
32
|
-
const matcherOptions = {
|
|
33
|
-
isNot: this.isNot,
|
|
34
|
-
promise: this.promise
|
|
35
|
-
};
|
|
36
|
-
if (typeof expected !== "function") {
|
|
37
|
-
throw new Error(
|
|
38
|
-
[
|
|
39
|
-
// Always display `expected` in expectation place
|
|
40
|
-
(0, import_matcherHint.matcherHint)(this, void 0, matcherName, expression, void 0, matcherOptions, void 0, void 0, true),
|
|
41
|
-
`${import_utils2.colors.bold("Matcher error")}: ${(0, import_expectBundle.EXPECTED_COLOR)("expected")} value must be a string, regular expression, or predicate`,
|
|
42
|
-
this.utils.printWithType("Expected", expected, this.utils.printExpected)
|
|
43
|
-
].join("\n\n")
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
30
|
const timeout = options?.timeout ?? this.timeout;
|
|
47
31
|
const baseURL = page.context()._options.baseURL;
|
|
48
32
|
let conditionSucceeded = false;
|
|
@@ -74,7 +58,6 @@ async function toHaveURLWithPredicate(page, expected, options) {
|
|
|
74
58
|
message: () => toHaveURLMessage(
|
|
75
59
|
this,
|
|
76
60
|
matcherName,
|
|
77
|
-
expression,
|
|
78
61
|
expected,
|
|
79
62
|
lastCheckedURLString,
|
|
80
63
|
this.isNot,
|
|
@@ -85,19 +68,14 @@ async function toHaveURLWithPredicate(page, expected, options) {
|
|
|
85
68
|
timeout
|
|
86
69
|
};
|
|
87
70
|
}
|
|
88
|
-
function toHaveURLMessage(state, matcherName,
|
|
89
|
-
const matcherOptions = {
|
|
90
|
-
isNot: state.isNot,
|
|
91
|
-
promise: state.promise
|
|
92
|
-
};
|
|
71
|
+
function toHaveURLMessage(state, matcherName, expected, received, pass, timedOut, timeout) {
|
|
93
72
|
const receivedString = received || "";
|
|
94
|
-
const messagePrefix = (0, import_matcherHint.matcherHint)(state, void 0, matcherName, expression, void 0, matcherOptions, didTimeout ? timeout : void 0, void 0, true);
|
|
95
73
|
let printedReceived;
|
|
96
74
|
let printedExpected;
|
|
97
75
|
let printedDiff;
|
|
98
76
|
if (typeof expected === "function") {
|
|
99
|
-
printedExpected = `Expected predicate to ${!state.isNot ? "succeed" : "fail"}`;
|
|
100
|
-
printedReceived = `Received
|
|
77
|
+
printedExpected = `Expected: predicate to ${!state.isNot ? "succeed" : "fail"}`;
|
|
78
|
+
printedReceived = `Received: ${(0, import_expectBundle.printReceived)(receivedString)}`;
|
|
101
79
|
} else {
|
|
102
80
|
if (pass) {
|
|
103
81
|
printedExpected = `Expected pattern: not ${state.utils.printExpected(expected)}`;
|
|
@@ -108,8 +86,15 @@ function toHaveURLMessage(state, matcherName, expression, expected, received, pa
|
|
|
108
86
|
printedDiff = state.utils.printDiffOrStringify(expected, receivedString, labelExpected, "Received string", false);
|
|
109
87
|
}
|
|
110
88
|
}
|
|
111
|
-
|
|
112
|
-
|
|
89
|
+
return (0, import_matcherHint.formatMatcherMessage)(state, {
|
|
90
|
+
matcherName,
|
|
91
|
+
expectation: "expected",
|
|
92
|
+
timeout,
|
|
93
|
+
timedOut,
|
|
94
|
+
printedExpected,
|
|
95
|
+
printedReceived,
|
|
96
|
+
printedDiff
|
|
97
|
+
});
|
|
113
98
|
}
|
|
114
99
|
// Annotate the CommonJS export names for ESM import in node:
|
|
115
100
|
0 && (module.exports = {
|
|
@@ -35,11 +35,10 @@ var import_fs = __toESM(require("fs"));
|
|
|
35
35
|
var import_path = __toESM(require("path"));
|
|
36
36
|
var import_utils = require("playwright-core/lib/utils");
|
|
37
37
|
var import_matcherHint = require("./matcherHint");
|
|
38
|
-
var import_expectBundle = require("../common/expectBundle");
|
|
39
38
|
var import_util = require("../util");
|
|
40
39
|
var import_expect = require("./expect");
|
|
41
40
|
var import_globals = require("../common/globals");
|
|
42
|
-
async function toMatchAriaSnapshot(
|
|
41
|
+
async function toMatchAriaSnapshot(locator, expectedParam, options = {}) {
|
|
43
42
|
const matcherName = "toMatchAriaSnapshot";
|
|
44
43
|
const testInfo = (0, import_globals.currentTestInfo)();
|
|
45
44
|
if (!testInfo)
|
|
@@ -47,10 +46,6 @@ async function toMatchAriaSnapshot(receiver, expectedParam, options = {}) {
|
|
|
47
46
|
if (testInfo._projectInternal.ignoreSnapshots)
|
|
48
47
|
return { pass: !this.isNot, message: () => "", name: "toMatchAriaSnapshot", expected: "" };
|
|
49
48
|
const updateSnapshots = testInfo.config.updateSnapshots;
|
|
50
|
-
const matcherOptions = {
|
|
51
|
-
isNot: this.isNot,
|
|
52
|
-
promise: this.promise
|
|
53
|
-
};
|
|
54
49
|
let expected;
|
|
55
50
|
let timeout;
|
|
56
51
|
let expectedPath;
|
|
@@ -75,35 +70,36 @@ async function toMatchAriaSnapshot(receiver, expectedParam, options = {}) {
|
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
72
|
expected = unshift(expected);
|
|
78
|
-
const { matches: pass, received, log, timedOut } = await
|
|
73
|
+
const { matches: pass, received, log, timedOut, errorMessage } = await locator._expect("to.match.aria", { expectedValue: expected, isNot: this.isNot, timeout });
|
|
79
74
|
const typedReceived = received;
|
|
80
|
-
const matcherHintWithExpect = (expectedReceivedString) => {
|
|
81
|
-
return (0, import_matcherHint.matcherHint)(this, receiver, matcherName, "locator", void 0, matcherOptions, timedOut ? timeout : void 0, expectedReceivedString);
|
|
82
|
-
};
|
|
83
|
-
const notFound = typedReceived === import_matcherHint.kNoElementsFoundError;
|
|
84
|
-
if (notFound) {
|
|
85
|
-
return {
|
|
86
|
-
pass: this.isNot,
|
|
87
|
-
message: () => matcherHintWithExpect(`Expected: ${this.utils.printExpected(expected)}
|
|
88
|
-
Received: ${(0, import_expectBundle.EXPECTED_COLOR)("<element not found>")}`) + (0, import_util.callLogText)(log),
|
|
89
|
-
name: "toMatchAriaSnapshot",
|
|
90
|
-
expected
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
const receivedText = typedReceived.raw;
|
|
94
75
|
const message = () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
76
|
+
let printedExpected;
|
|
77
|
+
let printedReceived;
|
|
78
|
+
let printedDiff;
|
|
79
|
+
if (errorMessage) {
|
|
80
|
+
printedExpected = `Expected: ${this.isNot ? "not " : ""}${this.utils.printExpected(expected)}`;
|
|
81
|
+
} else if (pass) {
|
|
82
|
+
const receivedString = (0, import_expect.printReceivedStringContainExpectedSubstring)(typedReceived.raw, typedReceived.raw.indexOf(expected), expected.length);
|
|
83
|
+
printedExpected = `Expected: not ${this.utils.printExpected(expected)}`;
|
|
84
|
+
printedReceived = `Received: ${receivedString}`;
|
|
100
85
|
} else {
|
|
101
|
-
|
|
102
|
-
const expectedReceivedString = notFound ? `${labelExpected}: ${this.utils.printExpected(expected)}
|
|
103
|
-
Received: ${receivedText}` : this.utils.printDiffOrStringify(expected, receivedText, labelExpected, "Received", false);
|
|
104
|
-
return matcherHintWithExpect(expectedReceivedString) + (0, import_util.callLogText)(log);
|
|
86
|
+
printedDiff = this.utils.printDiffOrStringify(expected, typedReceived.raw, "Expected", "Received", false);
|
|
105
87
|
}
|
|
88
|
+
return (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
89
|
+
matcherName,
|
|
90
|
+
expectation: "expected",
|
|
91
|
+
locator,
|
|
92
|
+
timeout,
|
|
93
|
+
timedOut,
|
|
94
|
+
printedExpected,
|
|
95
|
+
printedReceived,
|
|
96
|
+
printedDiff,
|
|
97
|
+
errorMessage,
|
|
98
|
+
log
|
|
99
|
+
});
|
|
106
100
|
};
|
|
101
|
+
if (errorMessage)
|
|
102
|
+
return { pass: this.isNot, message, name: "toMatchAriaSnapshot", expected };
|
|
107
103
|
if (!this.isNot) {
|
|
108
104
|
if (updateSnapshots === "all" || updateSnapshots === "changed" && pass === this.isNot || generateMissingBaseline) {
|
|
109
105
|
if (expectedPath) {
|