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.
Files changed (105) hide show
  1. package/README.md +3 -3
  2. package/ThirdPartyNotices.txt +3 -3
  3. package/lib/agents/generateAgents.js +263 -0
  4. package/lib/agents/generator.md +102 -0
  5. package/lib/agents/healer.md +78 -0
  6. package/lib/agents/planner.md +135 -0
  7. package/lib/common/config.js +1 -1
  8. package/lib/common/expectBundle.js +3 -0
  9. package/lib/common/expectBundleImpl.js +51 -51
  10. package/lib/index.js +7 -8
  11. package/lib/isomorphic/testServerConnection.js +0 -7
  12. package/lib/isomorphic/testTree.js +35 -8
  13. package/lib/matchers/expect.js +8 -21
  14. package/lib/matchers/matcherHint.js +42 -18
  15. package/lib/matchers/matchers.js +12 -6
  16. package/lib/matchers/toBeTruthy.js +16 -14
  17. package/lib/matchers/toEqual.js +18 -13
  18. package/lib/matchers/toHaveURL.js +12 -27
  19. package/lib/matchers/toMatchAriaSnapshot.js +26 -30
  20. package/lib/matchers/toMatchSnapshot.js +15 -12
  21. package/lib/matchers/toMatchText.js +29 -35
  22. package/lib/mcp/browser/actions.d.js +16 -0
  23. package/lib/mcp/browser/browserContextFactory.js +296 -0
  24. package/lib/mcp/browser/browserServerBackend.js +76 -0
  25. package/lib/mcp/browser/codegen.js +66 -0
  26. package/lib/mcp/browser/config.js +383 -0
  27. package/lib/mcp/browser/context.js +284 -0
  28. package/lib/mcp/browser/response.js +228 -0
  29. package/lib/mcp/browser/sessionLog.js +160 -0
  30. package/lib/mcp/browser/tab.js +277 -0
  31. package/lib/mcp/browser/tools/common.js +63 -0
  32. package/lib/mcp/browser/tools/console.js +44 -0
  33. package/lib/mcp/browser/tools/dialogs.js +60 -0
  34. package/lib/mcp/browser/tools/evaluate.js +70 -0
  35. package/lib/mcp/browser/tools/files.js +58 -0
  36. package/lib/mcp/browser/tools/form.js +74 -0
  37. package/lib/mcp/browser/tools/install.js +69 -0
  38. package/lib/mcp/browser/tools/keyboard.js +85 -0
  39. package/lib/mcp/browser/tools/mouse.js +107 -0
  40. package/lib/mcp/browser/tools/navigate.js +62 -0
  41. package/lib/mcp/browser/tools/network.js +54 -0
  42. package/lib/mcp/browser/tools/pdf.js +59 -0
  43. package/lib/mcp/browser/tools/screenshot.js +88 -0
  44. package/lib/mcp/browser/tools/snapshot.js +182 -0
  45. package/lib/mcp/browser/tools/tabs.js +67 -0
  46. package/lib/mcp/browser/tools/tool.js +49 -0
  47. package/lib/mcp/browser/tools/tracing.js +74 -0
  48. package/lib/mcp/browser/tools/utils.js +100 -0
  49. package/lib/mcp/browser/tools/verify.js +154 -0
  50. package/lib/mcp/browser/tools/wait.js +63 -0
  51. package/lib/mcp/browser/tools.js +80 -0
  52. package/lib/mcp/browser/watchdog.js +44 -0
  53. package/lib/mcp/config.d.js +16 -0
  54. package/lib/mcp/extension/cdpRelay.js +351 -0
  55. package/lib/mcp/extension/extensionContextFactory.js +75 -0
  56. package/lib/mcp/extension/protocol.js +28 -0
  57. package/lib/mcp/index.js +61 -0
  58. package/lib/mcp/{tool.js → log.js} +12 -18
  59. package/lib/mcp/program.js +96 -0
  60. package/lib/mcp/{bundle.js → sdk/bundle.js} +24 -2
  61. package/lib/mcp/{exports.js → sdk/exports.js} +12 -10
  62. package/lib/mcp/{transport.js → sdk/http.js} +79 -60
  63. package/lib/mcp/sdk/mdb.js +208 -0
  64. package/lib/mcp/{proxyBackend.js → sdk/proxyBackend.js} +18 -13
  65. package/lib/mcp/sdk/server.js +190 -0
  66. package/lib/mcp/sdk/tool.js +51 -0
  67. package/lib/mcp/test/browserBackend.js +98 -0
  68. package/lib/mcp/test/generatorTools.js +122 -0
  69. package/lib/mcp/test/plannerTools.js +46 -0
  70. package/lib/mcp/test/seed.js +72 -0
  71. package/lib/mcp/test/streams.js +39 -0
  72. package/lib/mcp/test/testBackend.js +97 -0
  73. package/lib/mcp/test/testContext.js +176 -0
  74. package/lib/mcp/test/testTool.js +30 -0
  75. package/lib/mcp/test/testTools.js +115 -0
  76. package/lib/mcpBundleImpl.js +14 -67
  77. package/lib/plugins/webServerPlugin.js +2 -0
  78. package/lib/program.js +68 -0
  79. package/lib/reporters/base.js +15 -17
  80. package/lib/reporters/html.js +39 -26
  81. package/lib/reporters/list.js +8 -4
  82. package/lib/reporters/listModeReporter.js +6 -3
  83. package/lib/reporters/merge.js +3 -1
  84. package/lib/reporters/teleEmitter.js +3 -1
  85. package/lib/runner/dispatcher.js +9 -23
  86. package/lib/runner/failureTracker.js +12 -16
  87. package/lib/runner/loadUtils.js +39 -3
  88. package/lib/runner/projectUtils.js +8 -2
  89. package/lib/runner/tasks.js +18 -7
  90. package/lib/runner/testRunner.js +16 -28
  91. package/lib/runner/testServer.js +17 -23
  92. package/lib/runner/watchMode.js +1 -53
  93. package/lib/runner/workerHost.js +8 -10
  94. package/lib/transform/babelBundleImpl.js +10 -10
  95. package/lib/transform/compilationCache.js +22 -5
  96. package/lib/util.js +12 -16
  97. package/lib/utilsBundleImpl.js +1 -1
  98. package/lib/worker/fixtureRunner.js +15 -7
  99. package/lib/worker/testInfo.js +9 -24
  100. package/lib/worker/workerMain.js +12 -8
  101. package/package.json +7 -3
  102. package/types/test.d.ts +17 -8
  103. package/types/testReporter.d.ts +1 -1
  104. package/lib/mcp/server.js +0 -118
  105. /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
- if (zone && zone.category === "expect") {
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._onDidFinishTestFunction = () => this.didFinishTestFunction();
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
- const fileItem = this._fileItem(fileSuite.location.file.split(pathSeparator), true);
128
- visitSuite(projectSuite.project(), fileSuite, fileItem);
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
  }
@@ -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 = (isAsync, e) => {
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 (!isAsync || !expectError) {
247
- if (this._info.isSoft)
248
- testInfo._failWithError(error);
249
- else
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.bind(null, true));
258
+ return result.then(finalizer).catch(reportStepError);
272
259
  finalizer();
273
260
  return result;
274
261
  } catch (e) {
275
- void reportStepError(false, e);
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
- isJestError: () => isJestError,
23
- kNoElementsFoundError: () => kNoElementsFoundError,
24
- matcherHint: () => matcherHint
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
- const kNoElementsFoundError = "<element(s) not found>";
29
- function matcherHint(state, locator, matcherName, expression, actual, matcherOptions, timeout, expectedReceivedString, preventExtraStatIndent = false) {
30
- let header = state.utils.matcherHint(matcherName, expression, actual, matcherOptions).replace(/ \/\/ deep equality/, "") + " failed\n\n";
31
- const extraSpace = preventExtraStatIndent ? "" : " ";
32
- if (locator)
33
- header += `Locator: ${extraSpace}${String(locator)}
34
- `;
35
- if (expectedReceivedString)
36
- header += `${expectedReceivedString}
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 (timeout)
39
- header += `Timeout: ${extraSpace}${timeout}ms
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
- return header;
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
- isJestError,
61
- kNoElementsFoundError,
62
- matcherHint
84
+ callLogText,
85
+ formatMatcherMessage,
86
+ isJestError
63
87
  });
@@ -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, true);
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, true);
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, { receiverLabel: "page", ...options });
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, { receiverLabel: "page", ...options });
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 = () => this.utils.matcherHint(matcherName, void 0, "", { isNot: this.isNot }) + (0, import_util.callLogText)(log) + (text === null ? "" : `
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, receiver, receiverType, expected, arg, query, options = {}) {
27
- (0, import_util.expectTypes)(receiver, [receiverType], matcherName);
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: ${notFound ? import_matcherHint.kNoElementsFoundError : expected}`;
42
+ printedReceived = errorMessage ? "" : `Received: ${expected}`;
48
43
  } else {
49
44
  printedExpected = `Expected: ${expected}`;
50
- printedReceived = `Received: ${notFound ? import_matcherHint.kNoElementsFoundError : received}`;
45
+ printedReceived = errorMessage ? "" : `Received: ${received}`;
51
46
  }
52
47
  const message = () => {
53
- const header = (0, import_matcherHint.matcherHint)(this, receiver, matcherName, "locator", arg, matcherOptions, timedOut ? timeout : void 0, `${printedExpected}
54
- ${printedReceived}`);
55
- const logText = (0, import_util.callLogText)(log);
56
- return `${header}${logText}`;
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,
@@ -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, receiver, receiverType, query, expected, options = {}, messagePreventExtraStatIndent) {
30
- (0, import_util.expectTypes)(receiver, [receiverType], matcherName);
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
- const details = printedDiff || `${printedExpected}
77
- ${printedReceived}`;
78
- const header = (0, import_matcherHint.matcherHint)(this, receiver, matcherName, "locator", void 0, matcherOptions, timedOut ? timeout : void 0, details, messagePreventExtraStatIndent);
79
- return `${header}${(0, import_util.callLogText)(log)}`;
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, expression, expected, received, pass, didTimeout, timeout) {
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 string: ${(0, import_expectBundle.printReceived)(receivedString)}`;
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
- const resultDetails = printedDiff ? printedDiff : printedExpected + "\n" + printedReceived;
112
- return messagePrefix + resultDetails;
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(receiver, expectedParam, options = {}) {
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 receiver._expect("to.match.aria", { expectedValue: expected, isNot: this.isNot, timeout });
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
- if (pass) {
96
- const receivedString = notFound ? receivedText : (0, import_expect.printReceivedStringContainExpectedSubstring)(receivedText, receivedText.indexOf(expected), expected.length);
97
- const expectedReceivedString = `Expected: not ${this.utils.printExpected(expected)}
98
- Received: ${receivedString}`;
99
- return matcherHintWithExpect(expectedReceivedString) + (0, import_util.callLogText)(log);
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
- const labelExpected = `Expected`;
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) {