playwright 1.58.0-alpha-2025-12-10 → 1.58.0-alpha-2025-12-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.
@@ -33,7 +33,7 @@ class Agent {
33
33
  const { clients, tools, callTool } = await this._initClients();
34
34
  const prompt = this.spec.description;
35
35
  try {
36
- return await this.loop.run(`${prompt}
36
+ const { result } = await this.loop.run(`${prompt}
37
37
 
38
38
  Task:
39
39
  ${task}
@@ -45,6 +45,7 @@ ${JSON.stringify(params, null, 2)}`, {
45
45
  callTool,
46
46
  resultSchema: this.resultSchema
47
47
  });
48
+ return result;
48
49
  } finally {
49
50
  await this._disconnectFromServers(clients);
50
51
  }
@@ -17,6 +17,7 @@ tools:
17
17
  - playwright-test/browser_navigate_back
18
18
  - playwright-test/browser_network_requests
19
19
  - playwright-test/browser_press_key
20
+ - playwright-test/browser_run_code
20
21
  - playwright-test/browser_select_option
21
22
  - playwright-test/browser_snapshot
22
23
  - playwright-test/browser_take_screenshot
package/lib/index.js CHANGED
@@ -212,37 +212,26 @@ const playwrightFixtures = {
212
212
  options.baseURL = baseURL;
213
213
  if (serviceWorkers !== void 0)
214
214
  options.serviceWorkers = serviceWorkers;
215
- const workerFile = agent?.cacheFile && agent.cacheMode !== "ignore" ? await testInfo._cloneStorage(agent.cacheFile) : void 0;
216
- if (agent && workerFile) {
217
- options.agent = {
218
- ...agent,
219
- cacheFile: workerFile
220
- };
221
- }
222
215
  await use({
223
216
  ...contextOptions,
224
217
  ...options
225
218
  });
226
- if (workerFile)
227
- await testInfo._upstreamStorage(workerFile);
228
219
  }, { box: true }],
229
- _setupContextOptions: [async ({ playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
220
+ _setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
230
221
  if (testIdAttribute)
231
222
  playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
232
223
  testInfo.snapshotSuffix = process.platform;
233
224
  if ((0, import_utils.debugMode)() === "inspector")
234
225
  testInfo._setDebugMode();
235
- playwright._defaultContextOptions = _combinedContextOptions;
236
226
  playwright._defaultContextTimeout = actionTimeout || 0;
237
227
  playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
238
228
  await use();
239
- playwright._defaultContextOptions = void 0;
240
229
  playwright._defaultContextTimeout = void 0;
241
230
  playwright._defaultContextNavigationTimeout = void 0;
242
231
  }, { auto: "all-hooks-included", title: "context configuration", box: true }],
243
- _setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
232
+ _setupArtifacts: [async ({ playwright, screenshot, _combinedContextOptions, agent }, use, testInfo) => {
244
233
  testInfo.setTimeout(testInfo.project.timeout);
245
- const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
234
+ const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, agent);
246
235
  await artifactsRecorder.willStartTest(testInfo);
247
236
  const tracingGroupSteps = [];
248
237
  const csiListener = {
@@ -288,20 +277,33 @@ const playwrightFixtures = {
288
277
  if (!keepTestTimeout)
289
278
  (0, import_globals.currentTestInfo)()?._setDebugMode();
290
279
  },
280
+ runBeforeCreateBrowserContext: async (options) => {
281
+ for (const [key, value] of Object.entries(_combinedContextOptions)) {
282
+ if (!(key in options))
283
+ options[key] = value;
284
+ }
285
+ await artifactsRecorder.willCreateBrowserContext(options);
286
+ },
287
+ runBeforeCreateRequestContext: async (options) => {
288
+ for (const [key, value] of Object.entries(_combinedContextOptions)) {
289
+ if (!(key in options))
290
+ options[key] = value;
291
+ }
292
+ },
291
293
  runAfterCreateBrowserContext: async (context) => {
292
- await artifactsRecorder?.didCreateBrowserContext(context);
294
+ await artifactsRecorder.didCreateBrowserContext(context);
293
295
  const testInfo2 = (0, import_globals.currentTestInfo)();
294
296
  if (testInfo2)
295
297
  attachConnectedHeaderIfNeeded(testInfo2, context.browser());
296
298
  },
297
299
  runAfterCreateRequestContext: async (context) => {
298
- await artifactsRecorder?.didCreateRequestContext(context);
300
+ await artifactsRecorder.didCreateRequestContext(context);
299
301
  },
300
302
  runBeforeCloseBrowserContext: async (context) => {
301
- await artifactsRecorder?.willCloseBrowserContext(context);
303
+ await artifactsRecorder.willCloseBrowserContext(context);
302
304
  },
303
305
  runBeforeCloseRequestContext: async (context) => {
304
- await artifactsRecorder?.willCloseRequestContext(context);
306
+ await artifactsRecorder.willCloseRequestContext(context);
305
307
  }
306
308
  };
307
309
  const clientInstrumentation = playwright._instrumentation;
@@ -560,9 +562,10 @@ class SnapshotRecorder {
560
562
  }
561
563
  }
562
564
  class ArtifactsRecorder {
563
- constructor(playwright, artifactsDir, screenshot) {
565
+ constructor(playwright, artifactsDir, screenshot, agent) {
564
566
  this._playwright = playwright;
565
567
  this._artifactsDir = artifactsDir;
568
+ this._agent = agent;
566
569
  const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
567
570
  this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
568
571
  this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
@@ -582,10 +585,14 @@ class ArtifactsRecorder {
582
585
  async didCreateBrowserContext(context) {
583
586
  await this._startTraceChunkOnContextCreation(context, context.tracing);
584
587
  }
588
+ async willCreateBrowserContext(options) {
589
+ await this._cloneAgentCache(options);
590
+ }
585
591
  async willCloseBrowserContext(context) {
586
592
  await this._stopTracing(context, context.tracing);
587
593
  await this._screenshotRecorder.captureTemporary(context);
588
594
  await this._takePageSnapshot(context);
595
+ await this._upstreamAgentCache(context);
589
596
  }
590
597
  async _takePageSnapshot(context) {
591
598
  if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
@@ -604,6 +611,21 @@ class ArtifactsRecorder {
604
611
  } catch {
605
612
  }
606
613
  }
614
+ async _cloneAgentCache(options) {
615
+ if (!this._agent || this._agent.cacheMode === "ignore")
616
+ return;
617
+ if (!this._agent.cacheFile && !this._agent.cachePathTemplate)
618
+ return;
619
+ const cacheFile = this._agent.cacheFile ?? this._testInfo._applyPathTemplate(this._agent.cachePathTemplate, "cache", ".json");
620
+ const workerFile = await this._testInfo._cloneStorage(cacheFile);
621
+ if (this._agent && workerFile)
622
+ options.agent = { ...this._agent, cacheFile: workerFile };
623
+ }
624
+ async _upstreamAgentCache(context) {
625
+ const agent = context._options.agent;
626
+ if (this._testInfo.status === "passed" && agent?.cacheFile)
627
+ await this._testInfo._upstreamStorage(agent.cacheFile);
628
+ }
607
629
  async didCreateRequestContext(context) {
608
630
  await this._startTraceChunkOnContextCreation(context, context._tracing);
609
631
  }
@@ -36,7 +36,7 @@ var import_utils = require("playwright-core/lib/utils");
36
36
  var import_mcpBundle = require("playwright-core/lib/mcpBundle");
37
37
  var import_tool = require("./tool");
38
38
  const codeSchema = import_mcpBundle.z.object({
39
- code: import_mcpBundle.z.string().describe(`Playwright code snippet to run. The snippet should access the \`page\` object to interact with the page. Can make multiple statements. For example: \`await page.getByRole('button', { name: 'Submit' }).click();\``)
39
+ code: import_mcpBundle.z.string().describe(`A JavaScript function containing Playwright code to execute. It will be invoked with a single argument, page, which you can use for any page interaction. For example: \`async (page) => { await page.getByRole('button', { name: 'Submit' }).click(); return await page.title(); }\``)
40
40
  });
41
41
  const runCode = (0, import_tool.defineTabTool)({
42
42
  capability: "core",
@@ -49,7 +49,7 @@ const runCode = (0, import_tool.defineTabTool)({
49
49
  },
50
50
  handle: async (tab, params, response) => {
51
51
  response.setIncludeSnapshot();
52
- response.addCode(params.code);
52
+ response.addCode(`await (${params.code})(page);`);
53
53
  const __end__ = new import_utils.ManualPromise();
54
54
  const context = {
55
55
  page: tab.page,
@@ -59,14 +59,16 @@ const runCode = (0, import_tool.defineTabTool)({
59
59
  await tab.waitForCompletion(async () => {
60
60
  const snippet = `(async () => {
61
61
  try {
62
- ${params.code};
63
- __end__.resolve();
62
+ const result = await (${params.code})(page);
63
+ __end__.resolve(JSON.stringify(result));
64
64
  } catch (e) {
65
65
  __end__.reject(e);
66
66
  }
67
67
  })()`;
68
- import_vm.default.runInContext(snippet, context);
69
- await __end__;
68
+ await import_vm.default.runInContext(snippet, context);
69
+ const result = await __end__;
70
+ if (typeof result === "string")
71
+ response.addResult(result);
70
72
  });
71
73
  }
72
74
  });
@@ -445,10 +445,6 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
445
445
  if (index > 1)
446
446
  relativeOutputPath = (0, import_util.addSuffixToFilePath)(relativeOutputPath, `-${index - 1}`);
447
447
  }
448
- const absoluteSnapshotPath = this._applyPathTemplate(kind, subPath, ext);
449
- return { absoluteSnapshotPath, relativeOutputPath };
450
- }
451
- _applyPathTemplate(kind, relativePath, ext) {
452
448
  const legacyTemplate = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
453
449
  let template;
454
450
  if (kind === "screenshot") {
@@ -459,12 +455,15 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
459
455
  } else {
460
456
  template = this._projectInternal.snapshotPathTemplate || legacyTemplate;
461
457
  }
462
- const dir = import_path.default.dirname(relativePath);
463
- const name = import_path.default.basename(relativePath, ext);
458
+ const nameArgument = import_path.default.join(import_path.default.dirname(subPath), import_path.default.basename(subPath, ext));
459
+ const absoluteSnapshotPath = this._applyPathTemplate(template, nameArgument, ext);
460
+ return { absoluteSnapshotPath, relativeOutputPath };
461
+ }
462
+ _applyPathTemplate(template, nameArgument, ext) {
464
463
  const relativeTestFilePath = import_path.default.relative(this.project.testDir, this._requireFile);
465
464
  const parsedRelativeTestFilePath = import_path.default.parse(relativeTestFilePath);
466
465
  const projectNamePathSegment = (0, import_utils.sanitizeForFilePath)(this.project.name);
467
- const snapshotPath = template.replace(/\{(.)?testDir\}/g, "$1" + this.project.testDir).replace(/\{(.)?snapshotDir\}/g, "$1" + this.project.snapshotDir).replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? "$1" + this.snapshotSuffix : "").replace(/\{(.)?testFileDir\}/g, "$1" + parsedRelativeTestFilePath.dir).replace(/\{(.)?platform\}/g, "$1" + process.platform).replace(/\{(.)?projectName\}/g, projectNamePathSegment ? "$1" + projectNamePathSegment : "").replace(/\{(.)?testName\}/g, "$1" + this._fsSanitizedTestName()).replace(/\{(.)?testFileName\}/g, "$1" + parsedRelativeTestFilePath.base).replace(/\{(.)?testFilePath\}/g, "$1" + relativeTestFilePath).replace(/\{(.)?arg\}/g, "$1" + import_path.default.join(dir, name)).replace(/\{(.)?ext\}/g, ext ? "$1" + ext : "");
466
+ const snapshotPath = template.replace(/\{(.)?testDir\}/g, "$1" + this.project.testDir).replace(/\{(.)?snapshotDir\}/g, "$1" + this.project.snapshotDir).replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? "$1" + this.snapshotSuffix : "").replace(/\{(.)?testFileDir\}/g, "$1" + parsedRelativeTestFilePath.dir).replace(/\{(.)?platform\}/g, "$1" + process.platform).replace(/\{(.)?projectName\}/g, projectNamePathSegment ? "$1" + projectNamePathSegment : "").replace(/\{(.)?testName\}/g, "$1" + this._fsSanitizedTestName()).replace(/\{(.)?testFileName\}/g, "$1" + parsedRelativeTestFilePath.base).replace(/\{(.)?testFilePath\}/g, "$1" + relativeTestFilePath).replace(/\{(.)?arg\}/g, "$1" + nameArgument).replace(/\{(.)?ext\}/g, ext ? "$1" + ext : "");
468
467
  return import_path.default.normalize(import_path.default.resolve(this._configInternal.configDir, snapshotPath));
469
468
  }
470
469
  snapshotPath(...args) {
@@ -482,7 +481,8 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
482
481
  setTimeout(timeout) {
483
482
  this._timeoutManager.setTimeout(timeout);
484
483
  }
485
- async _cloneStorage(storageFile) {
484
+ async _cloneStorage(cacheFileTemplate) {
485
+ const storageFile = this._applyPathTemplate(cacheFileTemplate, "cache", ".json");
486
486
  return await this._callbacks.onCloneStorage?.({ storageFile });
487
487
  }
488
488
  async _upstreamStorage(workerFile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.58.0-alpha-2025-12-10",
3
+ "version": "1.58.0-alpha-2025-12-11",
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-10"
67
+ "playwright-core": "1.58.0-alpha-2025-12-11"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"
package/types/test.d.ts CHANGED
@@ -6644,7 +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
+ type Agent = Exclude<BrowserContextOptions['agent'], undefined> & { cachePathTemplate?: string } | undefined;
6648
6648
  type BrowserName = 'chromium' | 'firefox' | 'webkit';
6649
6649
  type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
6650
6650
  type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;