playwright 1.56.0-alpha-1758292576000 → 1.56.0-alpha-2025-09-21

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.
@@ -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
  }
@@ -102,7 +102,7 @@ class IsolatedContextFactory extends BaseContextFactory {
102
102
  async _doObtainBrowser(clientInfo) {
103
103
  await injectCdpPort(this.config.browser);
104
104
  const browserType = playwright[this.config.browser.browserName];
105
- const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo, `traces`);
105
+ const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo, `traces`, { origin: "code" });
106
106
  if (this.config.saveTrace)
107
107
  await startTraceServer(this.config, tracesDir);
108
108
  return browserType.launch({
@@ -157,7 +157,7 @@ class PersistentContextFactory {
157
157
  await injectCdpPort(this.config.browser);
158
158
  (0, import_log.testDebug)("create browser context (persistent)");
159
159
  const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo);
160
- const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo, `traces`);
160
+ const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo, `traces`, { origin: "code" });
161
161
  if (this.config.saveTrace)
162
162
  await startTraceServer(this.config, tracesDir);
163
163
  this._userDataDirs.add(userDataDir);
@@ -217,12 +217,19 @@ async function loadConfig(configFile) {
217
217
  throw new Error(`Failed to load config file: ${configFile}, ${error}`);
218
218
  }
219
219
  }
220
- async function outputFile(config, clientInfo, name) {
220
+ async function outputFile(config, clientInfo, fileName, options) {
221
221
  const rootPath = (0, import_server.firstRootPath)(clientInfo);
222
222
  const outputDir = config.outputDir ?? (rootPath ? import_path.default.join(rootPath, ".playwright-mcp") : void 0) ?? import_path.default.join(process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir(), "playwright-mcp-output", String(clientInfo.timestamp));
223
- await import_fs.default.promises.mkdir(outputDir, { recursive: true });
224
- const fileName = sanitizeForFilePath(name);
225
- return import_path.default.join(outputDir, fileName);
223
+ if (options.origin === "code")
224
+ return import_path.default.resolve(outputDir, fileName);
225
+ if (options.origin === "llm") {
226
+ fileName = fileName.split("\\").join("/");
227
+ const resolvedFile = import_path.default.resolve(outputDir, fileName);
228
+ if (!resolvedFile.startsWith(import_path.default.resolve(outputDir) + import_path.default.sep))
229
+ throw new Error(`Resolved file path for ${fileName} is outside of the output directory`);
230
+ return resolvedFile;
231
+ }
232
+ return import_path.default.join(outputDir, sanitizeForFilePath(fileName));
226
233
  }
227
234
  function pickDefined(obj) {
228
235
  return Object.fromEntries(
@@ -95,8 +95,8 @@ class Context {
95
95
  await tab.page.close();
96
96
  return url;
97
97
  }
98
- async outputFile(name) {
99
- return (0, import_config.outputFile)(this.config, this._clientInfo, name);
98
+ async outputFile(fileName, options) {
99
+ return (0, import_config.outputFile)(this.config, this._clientInfo, fileName, options);
100
100
  }
101
101
  _onPageCreated(page) {
102
102
  const tab = new import_tab.Tab(this, page, (tab2) => this._onPageClosed(tab2));
@@ -44,7 +44,7 @@ class SessionLog {
44
44
  this._file = import_path.default.join(this._folder, "session.md");
45
45
  }
46
46
  static async create(config, clientInfo) {
47
- const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`);
47
+ const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code" });
48
48
  await import_fs.default.promises.mkdir(sessionFolder, { recursive: true });
49
49
  console.error(`Session: ${sessionFolder}`);
50
50
  return new SessionLog(sessionFolder);
@@ -91,7 +91,7 @@ class Tab extends import_events.EventEmitter {
91
91
  const entry = {
92
92
  download,
93
93
  finished: false,
94
- outputFile: await this.context.outputFile(download.suggestedFilename())
94
+ outputFile: await this.context.outputFile(download.suggestedFilename(), { origin: "web" })
95
95
  };
96
96
  this._downloads.push(entry);
97
97
  await download.saveAs(entry.outputFile);
@@ -34,6 +34,7 @@ module.exports = __toCommonJS(pdf_exports);
34
34
  var import_bundle = require("../../sdk/bundle");
35
35
  var import_tool = require("./tool");
36
36
  var javascript = __toESM(require("../codegen"));
37
+ var import_utils = require("./utils");
37
38
  const pdfSchema = import_bundle.z.object({
38
39
  filename: import_bundle.z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.")
39
40
  });
@@ -47,7 +48,7 @@ const pdf = (0, import_tool.defineTabTool)({
47
48
  type: "readOnly"
48
49
  },
49
50
  handle: async (tab, params, response) => {
50
- const fileName = await tab.context.outputFile(params.filename ?? `page-${(/* @__PURE__ */ new Date()).toISOString()}.pdf`);
51
+ const fileName = await tab.context.outputFile(params.filename ?? `page-${(0, import_utils.dateAsFileName)()}.pdf`, { origin: "llm" });
51
52
  response.addCode(`await page.pdf(${javascript.formatObject({ path: fileName })});`);
52
53
  response.addResult(`Saved page as ${fileName}`);
53
54
  await tab.page.pdf({ path: fileName });
@@ -63,7 +63,7 @@ const screenshot = (0, import_tool.defineTabTool)({
63
63
  },
64
64
  handle: async (tab, params, response) => {
65
65
  const fileType = params.type || "png";
66
- const fileName = await tab.context.outputFile(params.filename ?? `page-${(/* @__PURE__ */ new Date()).toISOString()}.${fileType}`);
66
+ const fileName = await tab.context.outputFile(params.filename ?? `page-${(0, import_utils.dateAsFileName)()}.${fileType}`, { origin: "llm" });
67
67
  const options = {
68
68
  type: fileType,
69
69
  quality: fileType === "png" ? void 0 : 90,
@@ -34,7 +34,7 @@ const tracingStart = (0, import_tool.defineTool)({
34
34
  },
35
35
  handle: async (context, params, response) => {
36
36
  const browserContext = await context.ensureBrowserContext();
37
- const tracesDir = await context.outputFile(`traces`);
37
+ const tracesDir = await context.outputFile(`traces`, { origin: "code" });
38
38
  const name = "trace-" + Date.now();
39
39
  await browserContext.tracing.start({
40
40
  name,
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var utils_exports = {};
20
20
  __export(utils_exports, {
21
21
  callOnPageNoTrace: () => callOnPageNoTrace,
22
+ dateAsFileName: () => dateAsFileName,
22
23
  generateLocator: () => generateLocator,
23
24
  waitForCompletion: () => waitForCompletion
24
25
  });
@@ -82,9 +83,14 @@ async function generateLocator(locator) {
82
83
  async function callOnPageNoTrace(page, callback) {
83
84
  return await page._wrapApiCall(() => callback(page), { internal: true });
84
85
  }
86
+ function dateAsFileName() {
87
+ const date = /* @__PURE__ */ new Date();
88
+ return date.toISOString().replace(/[:.]/g, "-");
89
+ }
85
90
  // Annotate the CommonJS export names for ESM import in node:
86
91
  0 && (module.exports = {
87
92
  callOnPageNoTrace,
93
+ dateAsFileName,
88
94
  generateLocator,
89
95
  waitForCompletion
90
96
  });
@@ -78,7 +78,7 @@ const runTests = (0, import_testTool.defineTestTool)({
78
78
  const configDir = context.configLocation.configDir;
79
79
  const reporter = new import_list.default({ configDir, screen, includeTestId: true });
80
80
  const testRunner = await context.createTestRunner();
81
- const result = await testRunner.runTests(reporter, {
81
+ await testRunner.runTests(reporter, {
82
82
  locations: params.locations,
83
83
  projects: params.projects,
84
84
  disableConfigReporters: true
@@ -87,8 +87,7 @@ const runTests = (0, import_testTool.defineTestTool)({
87
87
  return {
88
88
  content: [
89
89
  { type: "text", text }
90
- ],
91
- isError: result.status !== "passed"
90
+ ]
92
91
  };
93
92
  }
94
93
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.56.0-alpha-1758292576000",
3
+ "version": "1.56.0-alpha-2025-09-21",
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.56.0-alpha-1758292576000"
67
+ "playwright-core": "1.56.0-alpha-2025-09-21"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"