playwright 1.56.0-alpha-2025-09-10 → 1.56.0-alpha-2025-09-11
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 +2 -2
- package/lib/agents/fixer.md +5 -5
- package/lib/agents/generator.md +10 -10
- package/lib/agents/planner.md +9 -9
- package/lib/common/expectBundle.js +3 -0
- package/lib/common/expectBundleImpl.js +45 -45
- package/lib/index.js +2 -1
- package/lib/matchers/expect.js +1 -2
- package/lib/matchers/matcherHint.js +42 -15
- package/lib/matchers/matchers.js +12 -6
- package/lib/matchers/toBeTruthy.js +16 -13
- package/lib/matchers/toEqual.js +16 -14
- package/lib/matchers/toHaveURL.js +12 -27
- package/lib/matchers/toMatchAriaSnapshot.js +26 -27
- package/lib/matchers/toMatchSnapshot.js +5 -7
- package/lib/matchers/toMatchText.js +30 -33
- package/lib/mcp/browser/browserServerBackend.js +0 -1
- package/lib/mcp/browser/config.js +2 -0
- package/lib/mcp/browser/context.js +0 -1
- package/lib/mcp/browser/tab.js +6 -6
- package/lib/mcp/browser/tools/files.js +3 -2
- package/lib/mcp/browser/tools/snapshot.js +16 -1
- package/lib/mcp/browser/tools/tool.js +1 -1
- package/lib/mcp/browser/tools/verify.js +4 -4
- package/lib/mcp/browser/tools.js +4 -4
- package/lib/mcp/test/browserBackend.js +19 -42
- package/lib/mcp/test/testBackend.js +2 -4
- package/lib/util.js +0 -10
- package/package.json +2 -2
- package/lib/mcp/test/browserTool.js +0 -30
- package/lib/mcp/test/browserTools.js +0 -120
package/lib/index.js
CHANGED
|
@@ -240,7 +240,8 @@ const playwrightFixtures = {
|
|
|
240
240
|
if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
|
|
241
241
|
return;
|
|
242
242
|
const zone = (0, import_utils.currentZone)().data("stepZone");
|
|
243
|
-
|
|
243
|
+
const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
|
|
244
|
+
if (zone && zone.category === "expect" && isExpectCall) {
|
|
244
245
|
if (zone.apiName)
|
|
245
246
|
data.apiName = zone.apiName;
|
|
246
247
|
if (zone.title)
|
package/lib/matchers/expect.js
CHANGED
|
@@ -225,9 +225,8 @@ 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,
|
|
@@ -19,24 +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
|
-
|
|
22
|
+
callLogText: () => callLogText,
|
|
23
|
+
formatMatcherMessage: () => formatMatcherMessage,
|
|
24
|
+
isJestError: () => isJestError
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(matcherHint_exports);
|
|
26
27
|
var import_utils = require("playwright-core/lib/utils");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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)}
|
|
35
41
|
`;
|
|
36
|
-
if (
|
|
37
|
-
|
|
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
|
|
38
48
|
`;
|
|
39
|
-
|
|
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;
|
|
40
58
|
}
|
|
41
59
|
class ExpectError extends Error {
|
|
42
60
|
constructor(jestError, customMessage, stackFrames) {
|
|
@@ -52,9 +70,18 @@ class ExpectError extends Error {
|
|
|
52
70
|
function isJestError(e) {
|
|
53
71
|
return e instanceof Error && "matcherResult" in e;
|
|
54
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
|
+
};
|
|
55
81
|
// Annotate the CommonJS export names for ESM import in node:
|
|
56
82
|
0 && (module.exports = {
|
|
57
83
|
ExpectError,
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
callLogText,
|
|
85
|
+
formatMatcherMessage,
|
|
86
|
+
isJestError
|
|
60
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();
|
|
@@ -24,12 +24,8 @@ module.exports = __toCommonJS(toBeTruthy_exports);
|
|
|
24
24
|
var import_util = require("../util");
|
|
25
25
|
var import_matcherHint = require("./matcherHint");
|
|
26
26
|
var import_browserBackend = require("../mcp/test/browserBackend");
|
|
27
|
-
async function toBeTruthy(matcherName,
|
|
28
|
-
(0, import_util.expectTypes)(
|
|
29
|
-
const matcherOptions = {
|
|
30
|
-
isNot: this.isNot,
|
|
31
|
-
promise: this.promise
|
|
32
|
-
};
|
|
27
|
+
async function toBeTruthy(matcherName, locator, receiverType, expected, arg, query, options = {}) {
|
|
28
|
+
(0, import_util.expectTypes)(locator, [receiverType], matcherName);
|
|
33
29
|
const timeout = options.timeout ?? this.timeout;
|
|
34
30
|
const { matches: pass, log, timedOut, received, errorMessage } = await query(!!this.isNot, timeout);
|
|
35
31
|
if (pass === !this.isNot) {
|
|
@@ -44,18 +40,25 @@ async function toBeTruthy(matcherName, receiver, receiverType, expected, arg, qu
|
|
|
44
40
|
let printedExpected;
|
|
45
41
|
if (pass) {
|
|
46
42
|
printedExpected = `Expected: not ${expected}`;
|
|
47
|
-
printedReceived = errorMessage
|
|
43
|
+
printedReceived = errorMessage ? "" : `Received: ${expected}`;
|
|
48
44
|
} else {
|
|
49
45
|
printedExpected = `Expected: ${expected}`;
|
|
50
|
-
printedReceived = errorMessage
|
|
46
|
+
printedReceived = errorMessage ? "" : `Received: ${received}`;
|
|
51
47
|
}
|
|
52
48
|
const message = () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
return (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
50
|
+
matcherName,
|
|
51
|
+
expectation: arg,
|
|
52
|
+
locator,
|
|
53
|
+
timeout,
|
|
54
|
+
timedOut,
|
|
55
|
+
printedExpected,
|
|
56
|
+
printedReceived,
|
|
57
|
+
errorMessage,
|
|
58
|
+
log
|
|
59
|
+
});
|
|
57
60
|
};
|
|
58
|
-
await (0, import_browserBackend.runBrowserBackendOnError)(
|
|
61
|
+
await (0, import_browserBackend.runBrowserBackendOnError)(locator.page(), message);
|
|
59
62
|
return {
|
|
60
63
|
message,
|
|
61
64
|
pass,
|
package/lib/matchers/toEqual.js
CHANGED
|
@@ -27,13 +27,8 @@ var import_matcherHint = require("./matcherHint");
|
|
|
27
27
|
var import_browserBackend = require("../mcp/test/browserBackend");
|
|
28
28
|
const EXPECTED_LABEL = "Expected";
|
|
29
29
|
const RECEIVED_LABEL = "Received";
|
|
30
|
-
async function toEqual(matcherName,
|
|
31
|
-
(0, import_util.expectTypes)(
|
|
32
|
-
const matcherOptions = {
|
|
33
|
-
comment: options.contains ? "" : "deep equality",
|
|
34
|
-
isNot: this.isNot,
|
|
35
|
-
promise: this.promise
|
|
36
|
-
};
|
|
30
|
+
async function toEqual(matcherName, locator, receiverType, query, expected, options = {}) {
|
|
31
|
+
(0, import_util.expectTypes)(locator, [receiverType], matcherName);
|
|
37
32
|
const timeout = options.timeout ?? this.timeout;
|
|
38
33
|
const { matches: pass, received, log, timedOut, errorMessage } = await query(!!this.isNot, timeout);
|
|
39
34
|
if (pass === !this.isNot) {
|
|
@@ -49,10 +44,9 @@ async function toEqual(matcherName, receiver, receiverType, query, expected, opt
|
|
|
49
44
|
let printedDiff;
|
|
50
45
|
if (pass) {
|
|
51
46
|
printedExpected = `Expected: not ${this.utils.printExpected(expected)}`;
|
|
52
|
-
printedReceived = errorMessage
|
|
47
|
+
printedReceived = errorMessage ? "" : `Received: ${this.utils.printReceived(received)}`;
|
|
53
48
|
} else if (errorMessage) {
|
|
54
49
|
printedExpected = `Expected: ${this.utils.printExpected(expected)}`;
|
|
55
|
-
printedReceived = errorMessage;
|
|
56
50
|
} else if (Array.isArray(expected) && Array.isArray(received)) {
|
|
57
51
|
const normalizedExpected = expected.map((exp, index) => {
|
|
58
52
|
const rec = received[index];
|
|
@@ -77,12 +71,20 @@ async function toEqual(matcherName, receiver, receiverType, query, expected, opt
|
|
|
77
71
|
);
|
|
78
72
|
}
|
|
79
73
|
const message = () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
return (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
75
|
+
matcherName,
|
|
76
|
+
expectation: "expected",
|
|
77
|
+
locator,
|
|
78
|
+
timeout,
|
|
79
|
+
timedOut,
|
|
80
|
+
printedExpected,
|
|
81
|
+
printedReceived,
|
|
82
|
+
printedDiff,
|
|
83
|
+
errorMessage,
|
|
84
|
+
log
|
|
85
|
+
});
|
|
84
86
|
};
|
|
85
|
-
await (0, import_browserBackend.runBrowserBackendOnError)(
|
|
87
|
+
await (0, import_browserBackend.runBrowserBackendOnError)(locator.page(), message);
|
|
86
88
|
return {
|
|
87
89
|
actual: received,
|
|
88
90
|
expected,
|
|
@@ -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 = {
|
|
@@ -38,7 +38,7 @@ var import_matcherHint = require("./matcherHint");
|
|
|
38
38
|
var import_util = require("../util");
|
|
39
39
|
var import_expect = require("./expect");
|
|
40
40
|
var import_globals = require("../common/globals");
|
|
41
|
-
async function toMatchAriaSnapshot(
|
|
41
|
+
async function toMatchAriaSnapshot(locator, expectedParam, options = {}) {
|
|
42
42
|
const matcherName = "toMatchAriaSnapshot";
|
|
43
43
|
const testInfo = (0, import_globals.currentTestInfo)();
|
|
44
44
|
if (!testInfo)
|
|
@@ -46,10 +46,6 @@ async function toMatchAriaSnapshot(receiver, expectedParam, options = {}) {
|
|
|
46
46
|
if (testInfo._projectInternal.ignoreSnapshots)
|
|
47
47
|
return { pass: !this.isNot, message: () => "", name: "toMatchAriaSnapshot", expected: "" };
|
|
48
48
|
const updateSnapshots = testInfo.config.updateSnapshots;
|
|
49
|
-
const matcherOptions = {
|
|
50
|
-
isNot: this.isNot,
|
|
51
|
-
promise: this.promise
|
|
52
|
-
};
|
|
53
49
|
let expected;
|
|
54
50
|
let timeout;
|
|
55
51
|
let expectedPath;
|
|
@@ -74,33 +70,36 @@ async function toMatchAriaSnapshot(receiver, expectedParam, options = {}) {
|
|
|
74
70
|
}
|
|
75
71
|
}
|
|
76
72
|
expected = unshift(expected);
|
|
77
|
-
const { matches: pass, received, log, timedOut, errorMessage } = await
|
|
73
|
+
const { matches: pass, received, log, timedOut, errorMessage } = await locator._expect("to.match.aria", { expectedValue: expected, isNot: this.isNot, timeout });
|
|
78
74
|
const typedReceived = received;
|
|
79
|
-
const matcherHintWithExpect = (expectedReceivedString) => {
|
|
80
|
-
return (0, import_matcherHint.matcherHint)(this, receiver, matcherName, "locator", void 0, matcherOptions, timedOut ? timeout : void 0, expectedReceivedString);
|
|
81
|
-
};
|
|
82
|
-
if (errorMessage) {
|
|
83
|
-
return {
|
|
84
|
-
pass: this.isNot,
|
|
85
|
-
message: () => matcherHintWithExpect(`Expected: ${this.utils.printExpected(expected)}
|
|
86
|
-
${errorMessage}`) + (0, import_util.callLogText)(log),
|
|
87
|
-
name: "toMatchAriaSnapshot",
|
|
88
|
-
expected
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
const receivedText = typedReceived.raw;
|
|
92
75
|
const message = () => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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}`;
|
|
98
85
|
} else {
|
|
99
|
-
|
|
100
|
-
const expectedReceivedString = this.utils.printDiffOrStringify(expected, receivedText, labelExpected, "Received", false);
|
|
101
|
-
return matcherHintWithExpect(expectedReceivedString) + (0, import_util.callLogText)(log);
|
|
86
|
+
printedDiff = this.utils.printDiffOrStringify(expected, typedReceived.raw, "Expected", "Received", false);
|
|
102
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
|
+
});
|
|
103
100
|
};
|
|
101
|
+
if (errorMessage)
|
|
102
|
+
return { pass: this.isNot, message, name: "toMatchAriaSnapshot", expected };
|
|
104
103
|
if (!this.isNot) {
|
|
105
104
|
if (updateSnapshots === "all" || updateSnapshots === "changed" && pass === this.isNot || generateMissingBaseline) {
|
|
106
105
|
if (expectedPath) {
|
|
@@ -162,7 +162,7 @@ class SnapshotHelper {
|
|
|
162
162
|
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-diff"), contentType: this.mimeType, path: this.diffPath });
|
|
163
163
|
}
|
|
164
164
|
if (log?.length)
|
|
165
|
-
output.push((0,
|
|
165
|
+
output.push((0, import_matcherHint.callLogText)(log));
|
|
166
166
|
else
|
|
167
167
|
output.push("");
|
|
168
168
|
return this.createMatcherResult(output.join("\n"), false, log);
|
|
@@ -216,8 +216,7 @@ function toMatchSnapshot(received, nameOrOptions = {}, optOptions = {}) {
|
|
|
216
216
|
const result = helper.comparator(received, expected, helper.options);
|
|
217
217
|
if (!result)
|
|
218
218
|
return helper.handleMatching();
|
|
219
|
-
const
|
|
220
|
-
const header = (0, import_matcherHint.matcherHint)(this, void 0, "toMatchSnapshot", receiver, void 0, void 0, void 0);
|
|
219
|
+
const header = (0, import_matcherHint.formatMatcherMessage)(this, { matcherName: "toMatchSnapshot", receiver: (0, import_utils.isString)(received) ? "string" : "Buffer", expectation: "expected" });
|
|
221
220
|
return helper.handleDifferent(received, expected, void 0, result.diff, header, result.errorMessage, void 0, this._stepInfo);
|
|
222
221
|
}
|
|
223
222
|
function toHaveScreenshotStepTitle(nameOrOptions = {}, optOptions = {}) {
|
|
@@ -271,11 +270,10 @@ async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions =
|
|
|
271
270
|
}
|
|
272
271
|
if (helper.updateSnapshots === "none" && !hasSnapshot)
|
|
273
272
|
return helper.createMatcherResult(`A snapshot doesn't exist at ${helper.expectedPath}.`, false);
|
|
274
|
-
const receiver = locator ? "locator" : "page";
|
|
275
273
|
if (!hasSnapshot) {
|
|
276
274
|
const { actual: actual2, previous: previous2, diff: diff2, errorMessage: errorMessage2, log: log2, timedOut: timedOut2 } = await page._expectScreenshot(expectScreenshotOptions);
|
|
277
275
|
if (errorMessage2) {
|
|
278
|
-
const header2 = (0, import_matcherHint.
|
|
276
|
+
const header2 = (0, import_matcherHint.formatMatcherMessage)(this, { matcherName: "toHaveScreenshot", locator, expectation: "expected", timeout, timedOut: timedOut2 });
|
|
279
277
|
return helper.handleDifferent(actual2, void 0, previous2, diff2, header2, errorMessage2, log2, this._stepInfo);
|
|
280
278
|
}
|
|
281
279
|
return helper.handleMissing(actual2, this._stepInfo);
|
|
@@ -299,11 +297,11 @@ async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions =
|
|
|
299
297
|
if (helper.updateSnapshots === "changed" || helper.updateSnapshots === "all") {
|
|
300
298
|
if (actual)
|
|
301
299
|
return writeFiles(actual);
|
|
302
|
-
let header2 = (0, import_matcherHint.
|
|
300
|
+
let header2 = (0, import_matcherHint.formatMatcherMessage)(this, { matcherName: "toHaveScreenshot", locator, expectation: "expected", timeout, timedOut });
|
|
303
301
|
header2 += " Failed to re-generate expected.\n";
|
|
304
302
|
return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header2, errorMessage, log, this._stepInfo);
|
|
305
303
|
}
|
|
306
|
-
const header = (0, import_matcherHint.
|
|
304
|
+
const header = (0, import_matcherHint.formatMatcherMessage)(this, { matcherName: "toHaveScreenshot", locator, expectation: "expected", timeout, timedOut });
|
|
307
305
|
return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header, errorMessage, log, this._stepInfo);
|
|
308
306
|
}
|
|
309
307
|
function writeFileSync(aPath, content) {
|
|
@@ -21,7 +21,6 @@ __export(toMatchText_exports, {
|
|
|
21
21
|
toMatchText: () => toMatchText
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(toMatchText_exports);
|
|
24
|
-
var import_utils = require("playwright-core/lib/utils");
|
|
25
24
|
var import_util = require("../util");
|
|
26
25
|
var import_expect = require("./expect");
|
|
27
26
|
var import_matcherHint = require("./matcherHint");
|
|
@@ -29,16 +28,11 @@ var import_expectBundle = require("../common/expectBundle");
|
|
|
29
28
|
var import_browserBackend = require("../mcp/test/browserBackend");
|
|
30
29
|
async function toMatchText(matcherName, receiver, receiverType, query, expected, options = {}) {
|
|
31
30
|
(0, import_util.expectTypes)(receiver, [receiverType], matcherName);
|
|
32
|
-
const
|
|
33
|
-
isNot: this.isNot,
|
|
34
|
-
promise: this.promise
|
|
35
|
-
};
|
|
31
|
+
const locator = receiverType === "Locator" ? receiver : void 0;
|
|
36
32
|
if (!(typeof expected === "string") && !(expected && typeof expected.test === "function")) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.utils.printWithType("Expected", expected, this.utils.printExpected)
|
|
41
|
-
].join("\n\n"));
|
|
33
|
+
const errorMessage2 = `Error: ${(0, import_expectBundle.EXPECTED_COLOR)("expected")} value must be a string or regular expression
|
|
34
|
+
${this.utils.printWithType("Expected", expected, this.utils.printExpected)}`;
|
|
35
|
+
throw new Error((0, import_matcherHint.formatMatcherMessage)(this, { locator, matcherName, expectation: "expected", errorMessage: errorMessage2 }));
|
|
42
36
|
}
|
|
43
37
|
const timeout = options.timeout ?? this.timeout;
|
|
44
38
|
const { matches: pass, received, log, timedOut, errorMessage } = await query(!!this.isNot, timeout);
|
|
@@ -50,45 +44,48 @@ async function toMatchText(matcherName, receiver, receiverType, query, expected,
|
|
|
50
44
|
expected
|
|
51
45
|
};
|
|
52
46
|
}
|
|
53
|
-
const
|
|
47
|
+
const expectedSuffix = typeof expected === "string" ? options.matchSubstring ? " substring" : "" : " pattern";
|
|
48
|
+
const receivedSuffix = typeof expected === "string" ? options.matchSubstring ? " string" : "" : " string";
|
|
54
49
|
const receivedString = received || "";
|
|
55
50
|
let printedReceived;
|
|
56
51
|
let printedExpected;
|
|
57
52
|
let printedDiff;
|
|
58
53
|
if (pass) {
|
|
59
54
|
if (typeof expected === "string") {
|
|
60
|
-
printedExpected = `Expected
|
|
61
|
-
if (errorMessage) {
|
|
62
|
-
printedReceived = errorMessage;
|
|
63
|
-
} else {
|
|
55
|
+
printedExpected = `Expected${expectedSuffix}: not ${this.utils.printExpected(expected)}`;
|
|
56
|
+
if (!errorMessage) {
|
|
64
57
|
const formattedReceived = (0, import_expect.printReceivedStringContainExpectedSubstring)(receivedString, receivedString.indexOf(expected), expected.length);
|
|
65
|
-
printedReceived = `Received
|
|
58
|
+
printedReceived = `Received${receivedSuffix}: ${formattedReceived}`;
|
|
66
59
|
}
|
|
67
60
|
} else {
|
|
68
|
-
printedExpected = `Expected
|
|
69
|
-
if (errorMessage) {
|
|
70
|
-
printedReceived = errorMessage;
|
|
71
|
-
} else {
|
|
61
|
+
printedExpected = `Expected${expectedSuffix}: not ${this.utils.printExpected(expected)}`;
|
|
62
|
+
if (!errorMessage) {
|
|
72
63
|
const formattedReceived = (0, import_expect.printReceivedStringContainExpectedResult)(receivedString, typeof expected.exec === "function" ? expected.exec(receivedString) : null);
|
|
73
|
-
printedReceived = `Received
|
|
64
|
+
printedReceived = `Received${receivedSuffix}: ${formattedReceived}`;
|
|
74
65
|
}
|
|
75
66
|
}
|
|
76
67
|
} else {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
} else {
|
|
82
|
-
printedDiff = this.utils.printDiffOrStringify(expected, receivedString, labelExpected, "Received string", false);
|
|
83
|
-
}
|
|
68
|
+
if (errorMessage)
|
|
69
|
+
printedExpected = `Expected${expectedSuffix}: ${this.utils.printExpected(expected)}`;
|
|
70
|
+
else
|
|
71
|
+
printedDiff = this.utils.printDiffOrStringify(expected, receivedString, `Expected${expectedSuffix}`, `Received${receivedSuffix}`, false);
|
|
84
72
|
}
|
|
85
73
|
const message = () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
return (0, import_matcherHint.formatMatcherMessage)(this, {
|
|
75
|
+
matcherName,
|
|
76
|
+
expectation: "expected",
|
|
77
|
+
locator,
|
|
78
|
+
timeout,
|
|
79
|
+
timedOut,
|
|
80
|
+
printedExpected,
|
|
81
|
+
printedReceived,
|
|
82
|
+
printedDiff,
|
|
83
|
+
log,
|
|
84
|
+
errorMessage
|
|
85
|
+
});
|
|
89
86
|
};
|
|
90
|
-
if (
|
|
91
|
-
await (0, import_browserBackend.runBrowserBackendOnError)(
|
|
87
|
+
if (locator)
|
|
88
|
+
await (0, import_browserBackend.runBrowserBackendOnError)(locator.page(), message);
|
|
92
89
|
return {
|
|
93
90
|
name: matcherName,
|
|
94
91
|
expected,
|
|
@@ -43,7 +43,6 @@ class BrowserServerBackend {
|
|
|
43
43
|
}
|
|
44
44
|
this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, rootPath) : void 0;
|
|
45
45
|
this._context = new import_context.Context({
|
|
46
|
-
tools: this._tools,
|
|
47
46
|
config: this._config,
|
|
48
47
|
browserContextFactory: this._browserContextFactory,
|
|
49
48
|
sessionLog: this._sessionLog,
|
|
@@ -30,6 +30,7 @@ var config_exports = {};
|
|
|
30
30
|
__export(config_exports, {
|
|
31
31
|
commaSeparatedList: () => commaSeparatedList,
|
|
32
32
|
configFromCLIOptions: () => configFromCLIOptions,
|
|
33
|
+
defaultConfig: () => defaultConfig,
|
|
33
34
|
dotenvFileLoader: () => dotenvFileLoader,
|
|
34
35
|
headerParser: () => headerParser,
|
|
35
36
|
numberParser: () => numberParser,
|
|
@@ -307,6 +308,7 @@ function sanitizeForFilePath(s) {
|
|
|
307
308
|
0 && (module.exports = {
|
|
308
309
|
commaSeparatedList,
|
|
309
310
|
configFromCLIOptions,
|
|
311
|
+
defaultConfig,
|
|
310
312
|
dotenvFileLoader,
|
|
311
313
|
headerParser,
|
|
312
314
|
numberParser,
|
package/lib/mcp/browser/tab.js
CHANGED
|
@@ -51,7 +51,8 @@ class Tab extends import_events.EventEmitter {
|
|
|
51
51
|
this.setModalState({
|
|
52
52
|
type: "fileChooser",
|
|
53
53
|
description: "File chooser",
|
|
54
|
-
fileChooser: chooser
|
|
54
|
+
fileChooser: chooser,
|
|
55
|
+
clearedBy: "browser_file_upload"
|
|
55
56
|
});
|
|
56
57
|
});
|
|
57
58
|
page.on("dialog", (dialog) => this._dialogShown(dialog));
|
|
@@ -82,7 +83,8 @@ class Tab extends import_events.EventEmitter {
|
|
|
82
83
|
this.setModalState({
|
|
83
84
|
type: "dialog",
|
|
84
85
|
description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
|
|
85
|
-
dialog
|
|
86
|
+
dialog,
|
|
87
|
+
clearedBy: "browser_handle_dialog"
|
|
86
88
|
});
|
|
87
89
|
}
|
|
88
90
|
async _downloadStarted(download) {
|
|
@@ -241,10 +243,8 @@ function renderModalStates(context, modalStates) {
|
|
|
241
243
|
const result = ["### Modal state"];
|
|
242
244
|
if (modalStates.length === 0)
|
|
243
245
|
result.push("- There is no modal state present");
|
|
244
|
-
for (const state of modalStates)
|
|
245
|
-
|
|
246
|
-
result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
|
|
247
|
-
}
|
|
246
|
+
for (const state of modalStates)
|
|
247
|
+
result.push(`- [${state.description}]: can be handled by the "${state.clearedBy}" tool`);
|
|
248
248
|
return result;
|
|
249
249
|
}
|
|
250
250
|
const tabSymbol = Symbol("tabSymbol");
|