playwright 1.58.0-alpha-2025-12-05 → 1.58.0-alpha-2025-12-07

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/index.js CHANGED
@@ -14,4 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- module.exports = require('playwright-core');
17
+ const playwright = require('playwright-core');
18
+ const { decoratePage } = require('./lib/agents/pageDecorator');
19
+ decoratePage(playwright);
20
+ module.exports = playwright;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var pageDecorator_exports = {};
20
+ __export(pageDecorator_exports, {
21
+ decoratePage: () => decoratePage
22
+ });
23
+ module.exports = __toCommonJS(pageDecorator_exports);
24
+ var import_utilsBundle = require("playwright-core/lib/utilsBundle");
25
+ var import_mcpBundle = require("playwright-core/lib/mcpBundle");
26
+ var import_browserContextFactory = require("../mcp/browser/browserContextFactory");
27
+ var import_browserServerBackend = require("../mcp/browser/browserServerBackend");
28
+ var import_config = require("../mcp/browser/config");
29
+ var import_server = require("../mcp/sdk/server");
30
+ function decoratePage(playwright) {
31
+ playwright._instrumentation.addListener({
32
+ onPage: (page) => {
33
+ page.perform = pagePerform.bind(null, page);
34
+ page.extract = pageExtract.bind(null, page);
35
+ }
36
+ });
37
+ }
38
+ async function pagePerform(page, userTask, options = {}) {
39
+ const resultSchema = {
40
+ type: "object",
41
+ properties: {
42
+ code: { type: "string" }
43
+ },
44
+ required: ["code"]
45
+ };
46
+ await perform(page, userTask, resultSchema, options);
47
+ }
48
+ async function pageExtract(page, query, schema, options = {}) {
49
+ const task = `
50
+ ### Instructions
51
+ Extract the following information from the page. Do not perform any actions, just extract the information.
52
+
53
+ ### Query
54
+ ${query}`;
55
+ return await perform(page, task, (0, import_mcpBundle.zodToJsonSchema)(schema), options);
56
+ }
57
+ async function perform(page, userTask, resultSchema, options = {}) {
58
+ const context = page.context();
59
+ if (!context._options.agent)
60
+ throw new Error(`page.perform() and page.extract() require the agent to be set on the browser context`);
61
+ const { full } = await page._snapshotForAI();
62
+ const backend = new import_browserServerBackend.BrowserServerBackend(import_config.defaultConfig, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
63
+ const client = await (0, import_server.wrapInClient)(backend, { name: "Internal", version: "0.0.0" });
64
+ const callTool = async (params) => {
65
+ return await client.callTool(params);
66
+ };
67
+ const loop = new import_mcpBundle.Loop(context._options.agent.provider, {
68
+ model: context._options.agent.model,
69
+ summarize: true,
70
+ debug: import_utilsBundle.debug,
71
+ callTool,
72
+ tools: await backend.listTools(),
73
+ ...options
74
+ });
75
+ const task = `${userTask}
76
+
77
+ ### Page snapshot
78
+ ${full}
79
+ `;
80
+ try {
81
+ return await loop.run(task, {
82
+ resultSchema
83
+ });
84
+ } finally {
85
+ await client.close();
86
+ }
87
+ }
88
+ // Annotate the CommonJS export names for ESM import in node:
89
+ 0 && (module.exports = {
90
+ decoratePage
91
+ });
package/lib/index.js CHANGED
@@ -33,7 +33,6 @@ __export(index_exports, {
33
33
  expect: () => import_expect.expect,
34
34
  mergeExpects: () => import_expect2.mergeExpects,
35
35
  mergeTests: () => import_testType2.mergeTests,
36
- performCache: () => import_performTask2.performCache,
37
36
  test: () => test
38
37
  });
39
38
  module.exports = __toCommonJS(index_exports);
@@ -44,12 +43,10 @@ var import_utils = require("playwright-core/lib/utils");
44
43
  var import_globals = require("./common/globals");
45
44
  var import_testType = require("./common/testType");
46
45
  var import_browserBackend = require("./mcp/test/browserBackend");
47
- var import_performTask = require("./agents/performTask");
48
46
  var import_expect = require("./matchers/expect");
49
47
  var import_configLoader = require("./common/configLoader");
50
48
  var import_testType2 = require("./common/testType");
51
49
  var import_expect2 = require("./matchers/expect");
52
- var import_performTask2 = require("./agents/performTask");
53
50
  const _baseTest = import_testType.rootTestType.test;
54
51
  (0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]);
55
52
  if (process["__pw_initiator__"]) {
@@ -114,6 +111,7 @@ const playwrightFixtures = {
114
111
  await browser.close({ reason: "Test ended." });
115
112
  }, { scope: "worker", timeout: 0 }],
116
113
  acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
114
+ agent: [({ contextOptions }, use) => use(contextOptions.agent), { option: true, box: true }],
117
115
  bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
118
116
  colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
119
117
  deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
@@ -143,6 +141,7 @@ const playwrightFixtures = {
143
141
  contextOptions: [{}, { option: true, box: true }],
144
142
  _combinedContextOptions: [async ({
145
143
  acceptDownloads,
144
+ agent,
146
145
  bypassCSP,
147
146
  clientCertificates,
148
147
  colorScheme,
@@ -169,6 +168,8 @@ const playwrightFixtures = {
169
168
  const options = {};
170
169
  if (acceptDownloads !== void 0)
171
170
  options.acceptDownloads = acceptDownloads;
171
+ if (agent !== void 0)
172
+ options.agent = agent;
172
173
  if (bypassCSP !== void 0)
173
174
  options.bypassCSP = bypassCSP;
174
175
  if (colorScheme !== void 0)
@@ -418,11 +419,6 @@ const playwrightFixtures = {
418
419
  } else {
419
420
  await request.dispose();
420
421
  }
421
- },
422
- _perform: async ({ context }, use, testInfo) => {
423
- await use(async (task, options) => {
424
- await (0, import_performTask.performTask)(testInfo, context, task, options ?? {});
425
- });
426
422
  }
427
423
  };
428
424
  function normalizeVideoMode(video) {
@@ -686,6 +682,5 @@ const test = _baseTest.extend(playwrightFixtures);
686
682
  expect,
687
683
  mergeExpects,
688
684
  mergeTests,
689
- performCache,
690
685
  test
691
686
  });
@@ -70,10 +70,6 @@ class TeleReporterReceiver {
70
70
  this._onAttach(params.testId, params.resultId, params.attachments);
71
71
  return;
72
72
  }
73
- if (method === "onTestError") {
74
- this._onTestError(params.testId, params.resultId, params.error);
75
- return;
76
- }
77
73
  if (method === "onStepEnd") {
78
74
  this._onStepEnd(params.testId, params.resultId, params.step);
79
75
  return;
@@ -127,10 +123,8 @@ class TeleReporterReceiver {
127
123
  const result = test.results.find((r) => r._id === payload.id);
128
124
  result.duration = payload.duration;
129
125
  result.status = payload.status;
130
- if (!!payload.errors) {
131
- result.errors = payload.errors;
132
- result.error = result.errors[0];
133
- }
126
+ result.errors = payload.errors;
127
+ result.error = result.errors?.[0];
134
128
  if (!!payload.attachments)
135
129
  result.attachments = this._parseAttachments(payload.attachments);
136
130
  if (payload.annotations) {
@@ -173,12 +167,6 @@ class TeleReporterReceiver {
173
167
  body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0
174
168
  })));
175
169
  }
176
- _onTestError(testId, resultId, error) {
177
- const test = this._tests.get(testId);
178
- const result = test.results.find((r) => r._id === resultId);
179
- result.errors.push(error);
180
- result.error = result.errors[0];
181
- }
182
170
  _onError(error) {
183
171
  this._reporter.onError?.(error);
184
172
  }
@@ -66,11 +66,8 @@ class InternalReporter {
66
66
  onStdErr(chunk, test, result) {
67
67
  this._reporter.onStdErr?.(chunk, test, result);
68
68
  }
69
- onTestError(test, result, error) {
70
- addLocationAndSnippetToError(this._config, error, test.location.file);
71
- this._reporter.onTestError?.(test, result, error);
72
- }
73
69
  onTestEnd(test, result) {
70
+ this._addSnippetToTestErrors(test, result);
74
71
  this._reporter.onTestEnd?.(test, result);
75
72
  }
76
73
  async onEnd(result) {
@@ -87,20 +84,27 @@ class InternalReporter {
87
84
  await this._reporter.onExit?.();
88
85
  }
89
86
  onError(error) {
90
- addLocationAndSnippetToError(this._config, error, void 0);
87
+ addLocationAndSnippetToError(this._config, error);
91
88
  this._reporter.onError?.(error);
92
89
  }
93
90
  onStepBegin(test, result, step) {
94
91
  this._reporter.onStepBegin?.(test, result, step);
95
92
  }
96
93
  onStepEnd(test, result, step) {
97
- if (step.error)
98
- addLocationAndSnippetToError(this._config, step.error, test.location.file);
94
+ this._addSnippetToStepError(test, step);
99
95
  this._reporter.onStepEnd?.(test, result, step);
100
96
  }
101
97
  printsToStdio() {
102
98
  return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
103
99
  }
100
+ _addSnippetToTestErrors(test, result) {
101
+ for (const error of result.errors)
102
+ addLocationAndSnippetToError(this._config, error, test.location.file);
103
+ }
104
+ _addSnippetToStepError(test, step) {
105
+ if (step.error)
106
+ addLocationAndSnippetToError(this._config, step.error, test.location.file);
107
+ }
104
108
  }
105
109
  function addLocationAndSnippetToError(config, error, file) {
106
110
  if (error.stack && !error.location)
@@ -342,7 +342,6 @@ class IdsPatcher {
342
342
  case "onProject":
343
343
  this._onProject(params.project);
344
344
  return;
345
- case "onTestError":
346
345
  case "onAttach":
347
346
  case "onTestBegin":
348
347
  case "onStepBegin":
@@ -424,7 +423,7 @@ class PathSeparatorPatcher {
424
423
  test.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
425
424
  const testResult = jsonEvent.params.result;
426
425
  testResult.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
427
- testResult.errors?.forEach((error) => this._updateErrorLocations(error));
426
+ testResult.errors.forEach((error) => this._updateErrorLocations(error));
428
427
  (testResult.attachments ?? []).forEach((attachment) => {
429
428
  if (attachment.path)
430
429
  attachment.path = this._updatePath(attachment.path);
@@ -442,10 +441,6 @@ class PathSeparatorPatcher {
442
441
  step.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
443
442
  return;
444
443
  }
445
- if (jsonEvent.method === "onTestError") {
446
- this._updateErrorLocations(jsonEvent.params.error);
447
- return;
448
- }
449
444
  if (jsonEvent.method === "onAttach") {
450
445
  const attach = jsonEvent.params;
451
446
  attach.attachments.forEach((attachment) => {
@@ -48,10 +48,6 @@ class Multiplexer {
48
48
  for (const reporter of this._reporters)
49
49
  wrap(() => reporter.onStdErr?.(chunk, test, result));
50
50
  }
51
- onTestError(test, result, error) {
52
- for (const reporter of this._reporters)
53
- wrap(() => reporter.onTestError?.(test, result, error));
54
- }
55
51
  onTestEnd(test, result) {
56
52
  for (const reporter of this._reporters)
57
53
  wrap(() => reporter.onTestEnd?.(test, result));
@@ -66,16 +66,6 @@ class TeleReporterEmitter {
66
66
  }
67
67
  });
68
68
  }
69
- onTestError(test, result, error) {
70
- this._messageSink({
71
- method: "onTestError",
72
- params: {
73
- testId: test.id,
74
- resultId: result[this._idSymbol],
75
- error
76
- }
77
- });
78
- }
79
69
  onTestEnd(test, result) {
80
70
  const testEnd = {
81
71
  testId: test.id,
@@ -235,6 +225,7 @@ class TeleReporterEmitter {
235
225
  id: result[this._idSymbol],
236
226
  duration: result.duration,
237
227
  status: result.status,
228
+ errors: result.errors,
238
229
  annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : void 0
239
230
  };
240
231
  }
@@ -243,6 +243,7 @@ class JobDispatcher {
243
243
  _onTestEnd(params) {
244
244
  if (this._failureTracker.hasReachedMaxFailures()) {
245
245
  params.status = "interrupted";
246
+ params.errors = [];
246
247
  }
247
248
  const data = this._dataByTestId.get(params.testId);
248
249
  if (!data) {
@@ -252,6 +253,8 @@ class JobDispatcher {
252
253
  this._remainingByTestId.delete(params.testId);
253
254
  const { result, test } = data;
254
255
  result.duration = params.duration;
256
+ result.errors = params.errors;
257
+ result.error = result.errors[0];
255
258
  result.status = params.status;
256
259
  result.annotations = params.annotations;
257
260
  test.annotations = [...params.annotations];
@@ -338,20 +341,6 @@ class JobDispatcher {
338
341
  this._reporter.onStdErr?.("Internal error: step id not found: " + params.stepId);
339
342
  }
340
343
  }
341
- _onTestErrors(params) {
342
- if (this._failureTracker.hasReachedMaxFailures()) {
343
- return;
344
- }
345
- const data = this._dataByTestId.get(params.testId);
346
- if (!data)
347
- return;
348
- const { test, result } = data;
349
- for (const error of params.errors) {
350
- result.errors.push(error);
351
- result.error = result.errors[0];
352
- this._reporter.onTestError?.(test, result, error);
353
- }
354
- }
355
344
  _failTestWithErrors(test, errors) {
356
345
  const runData = this._dataByTestId.get(test.id);
357
346
  let result;
@@ -361,11 +350,8 @@ class JobDispatcher {
361
350
  result = test._appendTestResult();
362
351
  this._reporter.onTestBegin?.(test, result);
363
352
  }
364
- for (const error of errors) {
365
- result.errors.push(error);
366
- result.error = result.errors[0];
367
- this._reporter.onTestError?.(test, result, error);
368
- }
353
+ result.errors = [...errors];
354
+ result.error = result.errors[0];
369
355
  result.status = errors.length ? "failed" : "skipped";
370
356
  this._reportTestEnd(test, result);
371
357
  this._failedTests.add(test);
@@ -466,32 +452,29 @@ class JobDispatcher {
466
452
  import_utils.eventsHelper.addEventListener(worker, "stepBegin", this._onStepBegin.bind(this)),
467
453
  import_utils.eventsHelper.addEventListener(worker, "stepEnd", this._onStepEnd.bind(this)),
468
454
  import_utils.eventsHelper.addEventListener(worker, "attach", this._onAttach.bind(this)),
469
- import_utils.eventsHelper.addEventListener(worker, "testErrors", this._onTestErrors.bind(this)),
470
455
  import_utils.eventsHelper.addEventListener(worker, "testPaused", this._onTestPaused.bind(this, worker)),
471
456
  import_utils.eventsHelper.addEventListener(worker, "done", this._onDone.bind(this)),
472
457
  import_utils.eventsHelper.addEventListener(worker, "exit", this.onExit.bind(this))
473
458
  ];
474
459
  }
475
460
  _onTestPaused(worker, params) {
476
- const data = this._dataByTestId.get(params.testId);
477
- if (!data)
478
- return;
479
- const { test, result } = data;
480
461
  const sendMessage = async (message) => {
481
462
  try {
482
463
  if (this.jobResult.isDone())
483
464
  throw new Error("Test has already stopped");
484
465
  const response = await worker.sendCustomMessage({ testId: params.testId, request: message.request });
485
466
  if (response.error)
486
- (0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, response.error, test.location.file);
467
+ (0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, response.error);
487
468
  return response;
488
469
  } catch (e) {
489
470
  const error = (0, import_util.serializeError)(e);
490
- (0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error, test.location.file);
471
+ (0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error);
491
472
  return { response: void 0, error };
492
473
  }
493
474
  };
494
- this._failureTracker.onTestPaused?.({ errors: result.errors, sendMessage });
475
+ for (const error of params.errors)
476
+ (0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error);
477
+ this._failureTracker.onTestPaused?.({ ...params, sendMessage });
495
478
  }
496
479
  skipWholeJob() {
497
480
  const allTestsSkipped = this.job.tests.every((test) => test.expectedStatus === "skipped");
@@ -383,8 +383,7 @@ async function runAllTestsWithConfig(config) {
383
383
  (0, import_tasks.createLoadTask)("in-process", { filterOnly: true, failOnLoadErrors: true }),
384
384
  ...(0, import_tasks.createRunTestsTasks)(config)
385
385
  ];
386
- const testRun = new import_tasks.TestRun(config, reporter, { pauseAtEnd: config.configCLIOverrides.debug, pauseOnError: config.configCLIOverrides.debug });
387
- const status = await (0, import_tasks.runTasks)(testRun, tasks, config.config.globalTimeout);
386
+ const status = await (0, import_tasks.runTasks)(new import_tasks.TestRun(config, reporter), tasks, config.config.globalTimeout);
388
387
  await new Promise((resolve) => process.stdout.write("", () => resolve()));
389
388
  await new Promise((resolve) => process.stderr.write("", () => resolve()));
390
389
  return status;
@@ -42,9 +42,8 @@ var import_util = require("../util");
42
42
  var import_testTracing = require("./testTracing");
43
43
  var import_util2 = require("./util");
44
44
  var import_transform = require("../transform/transform");
45
- var import_babelHighlightUtils = require("../transform/babelHighlightUtils");
46
45
  class TestInfoImpl {
47
- constructor(configInternal, projectInternal, workerParams, test, retry, onStepBegin, onStepEnd, onAttach, onErrors, onTestPaused) {
46
+ constructor(configInternal, projectInternal, workerParams, test, retry, onStepBegin, onStepEnd, onAttach, onTestPaused) {
48
47
  this._snapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
49
48
  this._ariaSnapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
50
49
  this._interruptedPromise = new import_utils.ManualPromise();
@@ -60,12 +59,10 @@ class TestInfoImpl {
60
59
  this.status = "passed";
61
60
  this.snapshotSuffix = "";
62
61
  this.errors = [];
63
- this._reportedErrorCount = 0;
64
62
  this.testId = test?.id ?? "";
65
63
  this._onStepBegin = onStepBegin;
66
64
  this._onStepEnd = onStepEnd;
67
65
  this._onAttach = onAttach;
68
- this._onErrors = onErrors;
69
66
  this._onTestPaused = onTestPaused;
70
67
  this._startTime = (0, import_utils.monotonicTime)();
71
68
  this._startWallTime = Date.now();
@@ -341,32 +338,11 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
341
338
  async _didFinishTestFunction() {
342
339
  const shouldPause = this._workerParams.pauseAtEnd && !this._isFailure() || this._workerParams.pauseOnError && this._isFailure();
343
340
  if (shouldPause) {
344
- const location = (this._isFailure() ? this._errorLocation() : await this._testEndLocation()) ?? { file: this.file, line: this.line, column: this.column };
345
- this._emitErrors();
346
- this._onTestPaused({ testId: this.testId });
347
- await this._runAsStep({ title: this._isFailure() ? "Paused on Error" : "Paused at End", category: "test.step", location }, async () => {
348
- await this._interruptedPromise;
349
- });
341
+ this._onTestPaused({ testId: this.testId, errors: this._isFailure() ? this.errors : [] });
342
+ await this._interruptedPromise;
350
343
  }
351
344
  await this._onDidFinishTestFunctionCallback?.();
352
345
  }
353
- _emitErrors() {
354
- const errors = this.errors.slice(this._reportedErrorCount);
355
- this._reportedErrorCount = Math.max(this._reportedErrorCount, this.errors.length);
356
- if (errors.length)
357
- this._onErrors({ testId: this.testId, errors });
358
- }
359
- _errorLocation() {
360
- if (this.error?.stack)
361
- return (0, import_util.filteredStackTrace)(this.error.stack.split("\n"))[0];
362
- }
363
- async _testEndLocation() {
364
- try {
365
- const source = await import_fs.default.promises.readFile(this.file, "utf-8");
366
- return (0, import_babelHighlightUtils.findTestEndLocation)(source, { file: this.file, line: this.line, column: this.column });
367
- } catch {
368
- }
369
- }
370
346
  // ------------ TestInfo methods ------------
371
347
  async attach(name, options = {}) {
372
348
  const step = this._addStep({
@@ -99,7 +99,6 @@ class WorkerMain extends import_process.ProcessRunner {
99
99
  }, () => {
100
100
  }, () => {
101
101
  }, () => {
102
- }, () => {
103
102
  });
104
103
  const runnable = { type: "teardown" };
105
104
  await fakeTestInfo._runWithTimeout(runnable, () => this._loadIfNeeded()).catch(() => {
@@ -244,7 +243,6 @@ class WorkerMain extends import_process.ProcessRunner {
244
243
  (stepBeginPayload) => this.dispatchEvent("stepBegin", stepBeginPayload),
245
244
  (stepEndPayload) => this.dispatchEvent("stepEnd", stepEndPayload),
246
245
  (attachment) => this.dispatchEvent("attach", attachment),
247
- (errors) => this.dispatchEvent("testErrors", errors),
248
246
  (testPausedPayload) => this.dispatchEvent("testPaused", testPausedPayload)
249
247
  );
250
248
  const processAnnotation = (annotation) => {
@@ -285,7 +283,6 @@ class WorkerMain extends import_process.ProcessRunner {
285
283
  });
286
284
  if (isSkipped && nextTest && !hasAfterAllToRunBeforeNextTest) {
287
285
  testInfo.status = "skipped";
288
- testInfo._emitErrors();
289
286
  this.dispatchEvent("testEnd", buildTestEndPayload(testInfo));
290
287
  return;
291
288
  }
@@ -399,7 +396,6 @@ class WorkerMain extends import_process.ProcessRunner {
399
396
  testInfo.duration = testInfo._timeoutManager.defaultSlot().elapsed + afterHooksSlot.elapsed | 0;
400
397
  this._currentTest = null;
401
398
  (0, import_globals.setCurrentTestInfo)(null);
402
- testInfo._emitErrors();
403
399
  this.dispatchEvent("testEnd", buildTestEndPayload(testInfo));
404
400
  const preserveOutput = this._config.config.preserveOutput === "always" || this._config.config.preserveOutput === "failures-only" && testInfo._isFailure();
405
401
  if (!preserveOutput)
@@ -502,6 +498,7 @@ function buildTestEndPayload(testInfo) {
502
498
  testId: testInfo.testId,
503
499
  duration: testInfo.duration,
504
500
  status: testInfo.status,
501
+ errors: testInfo.errors,
505
502
  hasNonRetriableError: testInfo._hasNonRetriableError,
506
503
  expectedStatus: testInfo.expectedStatus,
507
504
  annotations: testInfo.annotations,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.58.0-alpha-2025-12-05",
3
+ "version": "1.58.0-alpha-2025-12-07",
4
4
  "description": "A high-level API to automate web browsers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "license": "Apache-2.0",
66
66
  "dependencies": {
67
- "playwright-core": "1.58.0-alpha-2025-12-05"
67
+ "playwright-core": "1.58.0-alpha-2025-12-07"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"
package/types/test.d.ts CHANGED
@@ -6644,6 +6644,7 @@ export type Fixtures<T extends {} = {}, W extends {} = {}, PT extends {} = {}, P
6644
6644
  [K in Exclude<keyof T, keyof PW | keyof PT>]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean | 'self' }];
6645
6645
  };
6646
6646
 
6647
+ type Agent = Exclude<BrowserContextOptions['agent'], undefined> | undefined;
6647
6648
  type BrowserName = 'chromium' | 'firefox' | 'webkit';
6648
6649
  type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
6649
6650
  type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
@@ -6974,6 +6975,11 @@ export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';
6974
6975
  *
6975
6976
  */
6976
6977
  export interface PlaywrightTestOptions {
6978
+ /**
6979
+ * Agent settings for [page.perform(task[, options])](https://playwright.dev/docs/api/class-page#page-perform) and
6980
+ * [page.extract(query, schema[, options])](https://playwright.dev/docs/api/class-page#page-extract).
6981
+ */
6982
+ agent: Agent;
6977
6983
  /**
6978
6984
  * Whether to automatically download all the attachments. Defaults to `true` where all the downloads are accepted.
6979
6985
  *
@@ -1,163 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var performTask_exports = {};
30
- __export(performTask_exports, {
31
- performCache: () => performCache,
32
- performTask: () => performTask
33
- });
34
- module.exports = __toCommonJS(performTask_exports);
35
- var import_fs = __toESM(require("fs"));
36
- var import_path = __toESM(require("path"));
37
- var import_utilsBundle = require("playwright-core/lib/utilsBundle");
38
- var import_mcpBundle = require("playwright-core/lib/mcpBundle");
39
- var import_browserContextFactory = require("../mcp/browser/browserContextFactory");
40
- var import_browserServerBackend = require("../mcp/browser/browserServerBackend");
41
- var import_config = require("../mcp/browser/config");
42
- var import_server = require("../mcp/sdk/server");
43
- const resultSchema = import_mcpBundle.z.object({
44
- code: import_mcpBundle.z.string().optional().describe(`
45
- Generated code to perform the task using Playwright API.
46
- Check out the <code> blocks and combine them. Should be presented in the following form:
47
-
48
- perform(async ({ page }) => {
49
- // generated code here.
50
- });
51
- `),
52
- error: import_mcpBundle.z.string().optional().describe("The error that occurred if execution failed.").optional()
53
- });
54
- async function performTask(testInfo, context, userTask, options) {
55
- const cacheStatus = await performTaskFromCache(testInfo, context, userTask);
56
- if (cacheStatus === "success")
57
- return;
58
- const backend = new import_browserServerBackend.BrowserServerBackend(import_config.defaultConfig, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
59
- const client = await (0, import_server.wrapInClient)(backend, { name: "Internal", version: "0.0.0" });
60
- const callTool = async (params) => {
61
- return await client.callTool(params);
62
- };
63
- const loop = new import_mcpBundle.Loop(options.provider ?? "github", {
64
- model: options.model ?? "claude-sonnet-4.5",
65
- reasoning: options.reasoning,
66
- temperature: options.temperature,
67
- maxTokens: options.maxTokens,
68
- summarize: true,
69
- debug: import_utilsBundle.debug,
70
- callTool,
71
- tools: await backend.listTools()
72
- });
73
- try {
74
- const result = await loop.run(userTask, { resultSchema: (0, import_mcpBundle.zodToJsonSchema)(resultSchema) });
75
- if (result.code)
76
- await updatePerformFile(testInfo, userTask, result.code, options);
77
- } finally {
78
- await client.close();
79
- }
80
- }
81
- async function updatePerformFile(testInfo, userTask, taskCode, options) {
82
- const relativeFile = import_path.default.relative(testInfo.project.testDir, testInfo.file);
83
- const promptCacheFile = testInfo.file.replace(".spec.ts", ".cache.ts");
84
- const testTitle = testInfo.title;
85
- const loop = new import_mcpBundle.Loop(options.provider ?? "github", {
86
- model: options.model ?? "claude-sonnet-4.5",
87
- reasoning: options.reasoning,
88
- temperature: options.temperature,
89
- maxTokens: options.maxTokens,
90
- summarize: true,
91
- debug: import_utilsBundle.debug,
92
- callTool: async () => ({ content: [] }),
93
- tools: []
94
- });
95
- const resultSchema2 = import_mcpBundle.z.object({
96
- code: import_mcpBundle.z.string().optional().describe(`
97
- Generated code with all the perofrm routines combined or updated into the following format:
98
-
99
- import { performCache } from '@playwright/test';
100
-
101
- performCache({
102
- file: 'tests/page/perform-task.spec.ts',
103
- test: 'perform task',
104
- task: 'Click the learn more button',
105
- code: async ({ page }) => {
106
- await page.getByRole('link', { name: 'Learn more' }).click();
107
- },
108
- });
109
- `)
110
- });
111
- const existingCode = await import_fs.default.promises.readFile(promptCacheFile, "utf8").catch(() => "");
112
- const task = `
113
- - Create or update a perform file to include performCache block for the given task and code.
114
- - Dedupe items with the same file, test, and task.
115
- - Should produce code in the following format
116
-
117
- import { performCache } from '@playwright/test';
118
-
119
- performCache({
120
- file: '<file>',
121
- test: '<test>',
122
- task: '<task>',
123
- code: async ({ page }) => {
124
- <code>
125
- },
126
- });
127
-
128
- performCache({
129
- ...
130
-
131
- ## Params for the new or updated performCache block
132
- <file-content>${existingCode}</file-content>
133
- <file>${relativeFile}</file>
134
- <test>${testTitle}</test>
135
- <task>${userTask}</task>
136
- <code>${taskCode}</code>
137
- `;
138
- const result = await loop.run(task, { resultSchema: (0, import_mcpBundle.zodToJsonSchema)(resultSchema2) });
139
- if (result.code)
140
- await import_fs.default.promises.writeFile(promptCacheFile, result.code);
141
- }
142
- const performCacheMap = /* @__PURE__ */ new Map();
143
- function performCache(entry) {
144
- performCacheMap.set(JSON.stringify({ ...entry, code: void 0 }), entry);
145
- }
146
- async function performTaskFromCache(testInfo, context, userTask) {
147
- const relativeFile = import_path.default.relative(testInfo.project.testDir, testInfo.file);
148
- const key = JSON.stringify({ file: relativeFile, test: testInfo.title, task: userTask });
149
- const entry = performCacheMap.get(key);
150
- if (!entry)
151
- return "cache-miss";
152
- try {
153
- await entry.code({ page: context.pages()[0] });
154
- return "success";
155
- } catch (error) {
156
- return error;
157
- }
158
- }
159
- // Annotate the CommonJS export names for ESM import in node:
160
- 0 && (module.exports = {
161
- performCache,
162
- performTask
163
- });
@@ -1,63 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var babelHighlightUtils_exports = {};
30
- __export(babelHighlightUtils_exports, {
31
- findTestEndLocation: () => findTestEndLocation
32
- });
33
- module.exports = __toCommonJS(babelHighlightUtils_exports);
34
- var import_path = __toESM(require("path"));
35
- var import_babelBundle = require("./babelBundle");
36
- function containsLocation(range, location) {
37
- if (location.line < range.start.line || location.line > range.end.line)
38
- return false;
39
- if (location.line === range.start.line && location.column < range.start.column)
40
- return false;
41
- if (location.line === range.end.line && location.column > range.end.column)
42
- return false;
43
- return true;
44
- }
45
- function findTestEndLocation(text, testStartLocation) {
46
- const ast = (0, import_babelBundle.babelParse)(text, import_path.default.basename(testStartLocation.file), false);
47
- let result;
48
- (0, import_babelBundle.traverse)(ast, {
49
- enter(path2) {
50
- if (import_babelBundle.types.isCallExpression(path2.node) && path2.node.loc && containsLocation(path2.node.loc, testStartLocation)) {
51
- const callNode = path2.node;
52
- const funcNode = callNode.arguments[callNode.arguments.length - 1];
53
- if (callNode.arguments.length >= 2 && import_babelBundle.types.isFunction(funcNode) && funcNode.body.loc)
54
- result = { file: testStartLocation.file, ...funcNode.body.loc.end };
55
- }
56
- }
57
- });
58
- return result;
59
- }
60
- // Annotate the CommonJS export names for ESM import in node:
61
- 0 && (module.exports = {
62
- findTestEndLocation
63
- });