@treegress.com/treegress-browser-mcp 0.0.56-treegress.3

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 (67) hide show
  1. package/README.md +68 -0
  2. package/cli.js +25 -0
  3. package/config.d.ts +223 -0
  4. package/index.d.ts +23 -0
  5. package/index.js +19 -0
  6. package/mcp/browser/browserContextFactory.js +332 -0
  7. package/mcp/browser/browserServerBackend.js +105 -0
  8. package/mcp/browser/config.js +489 -0
  9. package/mcp/browser/configIni.js +194 -0
  10. package/mcp/browser/context.js +302 -0
  11. package/mcp/browser/domSnapshot.js +307 -0
  12. package/mcp/browser/logFile.js +96 -0
  13. package/mcp/browser/response.js +299 -0
  14. package/mcp/browser/sessionLog.js +75 -0
  15. package/mcp/browser/tab.js +1193 -0
  16. package/mcp/browser/tools/common.js +63 -0
  17. package/mcp/browser/tools/config.js +41 -0
  18. package/mcp/browser/tools/console.js +65 -0
  19. package/mcp/browser/tools/cookies.js +152 -0
  20. package/mcp/browser/tools/devtools.js +42 -0
  21. package/mcp/browser/tools/dialogs.js +59 -0
  22. package/mcp/browser/tools/evaluate.js +61 -0
  23. package/mcp/browser/tools/files.js +58 -0
  24. package/mcp/browser/tools/form.js +63 -0
  25. package/mcp/browser/tools/install.js +73 -0
  26. package/mcp/browser/tools/keyboard.js +151 -0
  27. package/mcp/browser/tools/mouse.js +159 -0
  28. package/mcp/browser/tools/navigate.js +105 -0
  29. package/mcp/browser/tools/network.js +92 -0
  30. package/mcp/browser/tools/pdf.js +48 -0
  31. package/mcp/browser/tools/route.js +140 -0
  32. package/mcp/browser/tools/runCode.js +76 -0
  33. package/mcp/browser/tools/screenshot.js +86 -0
  34. package/mcp/browser/tools/snapshot.js +207 -0
  35. package/mcp/browser/tools/storage.js +67 -0
  36. package/mcp/browser/tools/tabs.js +67 -0
  37. package/mcp/browser/tools/tool.js +47 -0
  38. package/mcp/browser/tools/tracing.js +75 -0
  39. package/mcp/browser/tools/utils.js +88 -0
  40. package/mcp/browser/tools/verify.js +143 -0
  41. package/mcp/browser/tools/video.js +89 -0
  42. package/mcp/browser/tools/wait.js +63 -0
  43. package/mcp/browser/tools/webstorage.js +223 -0
  44. package/mcp/browser/tools.js +96 -0
  45. package/mcp/browser/watchdog.js +44 -0
  46. package/mcp/config.d.js +16 -0
  47. package/mcp/extension/cdpRelay.js +354 -0
  48. package/mcp/extension/extensionContextFactory.js +77 -0
  49. package/mcp/extension/protocol.js +28 -0
  50. package/mcp/index.js +61 -0
  51. package/mcp/log.js +35 -0
  52. package/mcp/program.js +126 -0
  53. package/mcp/sdk/exports.js +28 -0
  54. package/mcp/sdk/http.js +172 -0
  55. package/mcp/sdk/inProcessTransport.js +71 -0
  56. package/mcp/sdk/server.js +223 -0
  57. package/mcp/sdk/tool.js +54 -0
  58. package/mcp/test/browserBackend.js +98 -0
  59. package/mcp/test/generatorTools.js +122 -0
  60. package/mcp/test/plannerTools.js +145 -0
  61. package/mcp/test/seed.js +82 -0
  62. package/mcp/test/streams.js +44 -0
  63. package/mcp/test/testBackend.js +99 -0
  64. package/mcp/test/testContext.js +285 -0
  65. package/mcp/test/testTool.js +30 -0
  66. package/mcp/test/testTools.js +108 -0
  67. package/package.json +46 -0
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+
3
+ var import_fs = require("fs");
4
+ var import_path = require("path");
5
+
6
+ const KNOWN_KIND_PRIORITY = [
7
+ "testId",
8
+ "role",
9
+ "text",
10
+ "label",
11
+ "placeholder",
12
+ "altText",
13
+ "title",
14
+ "id",
15
+ "name",
16
+ "dataAttr",
17
+ "css",
18
+ "xpath"
19
+ ];
20
+
21
+ function defaultDomSerializerPath(configuredPath) {
22
+ if (configuredPath)
23
+ return configuredPath;
24
+ return process.cwd() + "/domSerializer.js";
25
+ }
26
+
27
+ async function loadDomSerializerSource(configuredPath, fallbackToDefault = true) {
28
+ const candidates = [];
29
+ if (configuredPath)
30
+ candidates.push(configuredPath);
31
+ if (fallbackToDefault)
32
+ candidates.push(defaultDomSerializerPath());
33
+ for (const candidate of candidates) {
34
+ const resolvedPath = import_path.resolve(process.cwd(), candidate);
35
+ if (import_fs.existsSync(resolvedPath))
36
+ return { path: resolvedPath, source: await import_fs.promises.readFile(resolvedPath, "utf-8") };
37
+ }
38
+ return { path: void 0, source: void 0 };
39
+ }
40
+
41
+ function buildCandidatesFromRaw(rawCandidates) {
42
+ if (!rawCandidates || typeof rawCandidates !== "object")
43
+ return [];
44
+ const entries = [];
45
+ const seen = new Set();
46
+
47
+ const addCandidate = (kind, payload) => {
48
+ if (!kind || !payload)
49
+ return;
50
+ const key = JSON.stringify({ kind, ...payload });
51
+ if (seen.has(key))
52
+ return;
53
+ seen.add(key);
54
+ entries.push({ kind, payload });
55
+ };
56
+
57
+ const parseStrategy = (strategy, details) => {
58
+ if (!details)
59
+ return;
60
+ switch (strategy) {
61
+ case "getByTestId": {
62
+ if (details.testId)
63
+ addCandidate("testId", { testId: details.testId });
64
+ break;
65
+ }
66
+ case "getByRole": {
67
+ if (details.role)
68
+ addCandidate("role", { role: details.role, name: details.name, exact: details.exact !== false });
69
+ break;
70
+ }
71
+ case "getByText": {
72
+ if (details.text)
73
+ addCandidate("text", { text: details.text, exact: details.exact !== false });
74
+ break;
75
+ }
76
+ case "getByPlaceholder": {
77
+ if (details.placeholder)
78
+ addCandidate("placeholder", { value: details.placeholder });
79
+ break;
80
+ }
81
+ case "getByCss": {
82
+ if (details.css)
83
+ addCandidate("css", { value: details.css });
84
+ break;
85
+ }
86
+ case "getByXpath": {
87
+ if (details.xpath)
88
+ addCandidate("xpath", { value: details.xpath });
89
+ break;
90
+ }
91
+ case "filtered_getByRole":
92
+ case "chained_getByRole": {
93
+ if (details.target?.details?.role)
94
+ addCandidate("role", { role: details.target.details.role, name: details.target.details.name, exact: details.target.details.exact !== false });
95
+ else if (details.base?.details?.role)
96
+ addCandidate("role", { role: details.base.details.role, name: details.base.details.name, exact: details.base.details.exact !== false });
97
+ break;
98
+ }
99
+ case "filtered_getByText":
100
+ case "chained_getByText": {
101
+ if (details.target?.details?.text)
102
+ addCandidate("text", { text: details.target.details.text, exact: details.target.details.exact !== false });
103
+ else if (details.base?.details?.text)
104
+ addCandidate("text", { text: details.base.details.text, exact: details.base.details.exact !== false });
105
+ break;
106
+ }
107
+ case "label": {
108
+ if (details.label)
109
+ addCandidate("label", { value: details.label });
110
+ break;
111
+ }
112
+ default:
113
+ break;
114
+ }
115
+ };
116
+
117
+ if (Array.isArray(rawCandidates.priority)) {
118
+ for (const strategy of rawCandidates.priority)
119
+ parseStrategy(strategy, rawCandidates[strategy]);
120
+ }
121
+
122
+ if (rawCandidates.getByTestId && rawCandidates.getByTestId.testId)
123
+ parseStrategy("getByTestId", rawCandidates.getByTestId);
124
+ if (rawCandidates.getByRole && rawCandidates.getByRole.role)
125
+ parseStrategy("getByRole", rawCandidates.getByRole);
126
+ if (rawCandidates.getByText && rawCandidates.getByText.text)
127
+ parseStrategy("getByText", rawCandidates.getByText);
128
+ if (rawCandidates.getByPlaceholder && rawCandidates.getByPlaceholder.placeholder)
129
+ parseStrategy("getByPlaceholder", rawCandidates.getByPlaceholder);
130
+ if (rawCandidates.getByCss && rawCandidates.getByCss.css)
131
+ parseStrategy("getByCss", rawCandidates.getByCss);
132
+ if (rawCandidates.getByXpath && rawCandidates.getByXpath.xpath)
133
+ parseStrategy("getByXpath", rawCandidates.getByXpath);
134
+
135
+ const ordered = [];
136
+ const addByPriority = (kind) => {
137
+ for (const candidate of entries) {
138
+ if (candidate.kind === kind)
139
+ ordered.push(candidate);
140
+ }
141
+ };
142
+ for (const kind of KNOWN_KIND_PRIORITY) {
143
+ addByPriority(kind);
144
+ }
145
+ for (const candidate of entries) {
146
+ if (!KNOWN_KIND_PRIORITY.includes(candidate.kind))
147
+ ordered.push(candidate);
148
+ }
149
+ return ordered;
150
+ }
151
+
152
+ function collectDomNodes(node, result, onNode) {
153
+ if (!node || typeof node !== "object")
154
+ return;
155
+ onNode(node, result);
156
+ if (Array.isArray(node.children)) {
157
+ for (const child of node.children)
158
+ collectDomNodes(child, result, onNode);
159
+ }
160
+ }
161
+
162
+ function candidateFromNodeAttributes(node) {
163
+ const candidates = [];
164
+ if (!node || typeof node !== "object")
165
+ return candidates;
166
+
167
+ if (node.id)
168
+ candidates.push({ kind: "id", payload: { id: node.id } });
169
+
170
+ if (node.tag === "input" && node.type)
171
+ candidates.push({ kind: "dataAttr", payload: { attr: "type", value: node.type } });
172
+
173
+ if (node.name)
174
+ candidates.push({ kind: "name", payload: { name: node.name } });
175
+
176
+ return candidates;
177
+ }
178
+
179
+ function normalizeDomSerializerResult(raw) {
180
+ const result = {
181
+ domSnapshotText: "",
182
+ elements: []
183
+ };
184
+ if (!raw || typeof raw !== "object") {
185
+ return result;
186
+ }
187
+
188
+ const dom = raw.dom || raw.snapshot || {};
189
+ const locators = raw.locators || raw.locatorMap || {};
190
+ const explicitElements = Array.isArray(raw.elements) ? raw.elements : [];
191
+
192
+ const explicitById = new Map();
193
+ for (const element of explicitElements) {
194
+ if (!element?.id)
195
+ continue;
196
+ const id = String(element.id);
197
+ explicitById.set(id, element);
198
+ }
199
+
200
+ collectDomNodes(dom, null, (node) => {
201
+ if (!node || typeof node !== "object")
202
+ return;
203
+
204
+ const id = node.id != null ? String(node.id) : void 0;
205
+ if (!id)
206
+ return;
207
+
208
+ const explicit = explicitById.get(id);
209
+ const locatorInfo = explicit?.locator || explicit?.locators || locators[id];
210
+ const candidates = explicit?.candidates?.length ? explicit.candidates : buildCandidatesFromRaw(locatorInfo) || [];
211
+ const meta = {
212
+ tag: node.tag || explicit?.tag,
213
+ role: node.role || explicit?.role,
214
+ text: node.text || explicit?.text,
215
+ accessibleName: node.accessibleName || explicit?.accessibleName,
216
+ isFocused: !!node.isFocused,
217
+ onTop: !!node.onTop
218
+ };
219
+
220
+ const mergedCandidates = [...candidates, ...candidateFromNodeAttributes(node)];
221
+
222
+ result.elements.push({
223
+ id,
224
+ candidates: mergedCandidates,
225
+ framePath: node.framePath,
226
+ meta
227
+ });
228
+ });
229
+
230
+ if (explicitElements.length && result.elements.length === 0) {
231
+ for (const element of explicitElements) {
232
+ if (!element?.id)
233
+ continue;
234
+ result.elements.push({
235
+ id: String(element.id),
236
+ candidates: Array.isArray(element.candidates) ? element.candidates : [],
237
+ framePath: element.framePath,
238
+ meta: {
239
+ tag: element.tag,
240
+ role: element.role,
241
+ text: element.text || element.accessibleName,
242
+ accessibleName: element.accessibleName
243
+ }
244
+ });
245
+ }
246
+ }
247
+
248
+ if (!result.elements.length && Array.isArray(raw.elements)) {
249
+ for (const element of raw.elements) {
250
+ if (!element?.id)
251
+ continue;
252
+ result.elements.push({
253
+ id: String(element.id),
254
+ candidates: Array.isArray(element.candidates) ? element.candidates : [],
255
+ framePath: element.framePath,
256
+ meta: element.meta || {}
257
+ });
258
+ }
259
+ }
260
+
261
+ result.domSnapshotText = buildDomText(dom);
262
+ return result;
263
+ }
264
+
265
+ function escapeText(value) {
266
+ if (!value)
267
+ return "";
268
+ return String(value).replace(/[\r\n]/g, " ").trim();
269
+ }
270
+
271
+ function buildDomText(node, depth = 0, result = []) {
272
+ if (!node || typeof node !== "object")
273
+ return result.join("\n");
274
+
275
+ const isInteractive = node.isInteractive || node.onTop || node.role === "button" || node.role === "link" || node.role === "textbox" || node.role === "combobox" || node.tag === "input" || node.tag === "select";
276
+ const id = node.id != null ? `[ref=${node.id}]` : "";
277
+ if (isInteractive) {
278
+ const indent = " ".repeat(Math.max(0, depth));
279
+ const role = node.role || node.tag || "element";
280
+ const state = [];
281
+ if (node.isFocused)
282
+ state.push("active");
283
+ if (node.onTop)
284
+ state.push("interactive");
285
+ const descriptor = [
286
+ `${indent}- ${role}`,
287
+ escapeText(node.accessibleName || node.text) ? ` \"${escapeText(node.accessibleName || node.text)}\"` : "",
288
+ state.length ? ` [${state.join(", ")}]` : "",
289
+ id ? ` [${id}]` : ""
290
+ ].join("");
291
+ result.push(descriptor);
292
+ }
293
+ if (Array.isArray(node.children)) {
294
+ for (const child of node.children)
295
+ buildDomText(child, depth + 1, result);
296
+ }
297
+ return result.join("\n");
298
+ }
299
+
300
+ const normalizeDomSnapshotResult = normalizeDomSerializerResult;
301
+
302
+ module.exports = {
303
+ defaultDomSerializerPath,
304
+ loadDomSerializerSource,
305
+ normalizeDomSerializerResult,
306
+ normalizeDomSnapshotResult
307
+ };
@@ -0,0 +1,96 @@
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 logFile_exports = {};
30
+ __export(logFile_exports, {
31
+ LogFile: () => LogFile
32
+ });
33
+ module.exports = __toCommonJS(logFile_exports);
34
+ var import_fs = __toESM(require("fs"));
35
+ var import_path = __toESM(require("path"));
36
+ var import_log = require("../log");
37
+ class LogFile {
38
+ constructor(context, startTime, filePrefix, title) {
39
+ this._stopped = false;
40
+ this._line = 0;
41
+ this._entries = 0;
42
+ this._lastLine = 0;
43
+ this._lastEntries = 0;
44
+ this._writeChain = Promise.resolve();
45
+ this._context = context;
46
+ this._startTime = startTime;
47
+ this._filePrefix = filePrefix;
48
+ this._title = title;
49
+ }
50
+ appendLine(wallTime, text) {
51
+ this._writeChain = this._writeChain.then(() => this._write(wallTime, text)).catch(import_log.logUnhandledError);
52
+ }
53
+ stop() {
54
+ this._stopped = true;
55
+ }
56
+ async take(relativeTo) {
57
+ const logChunk = await this._take();
58
+ if (!logChunk)
59
+ return void 0;
60
+ const logFilePath = relativeTo ? import_path.default.relative(relativeTo, logChunk.file) : logChunk.file;
61
+ const lineRange = logChunk.fromLine === logChunk.toLine ? `#L${logChunk.fromLine}` : `#L${logChunk.fromLine}-L${logChunk.toLine}`;
62
+ return `${logFilePath}${lineRange}`;
63
+ }
64
+ async _take() {
65
+ await this._writeChain;
66
+ if (!this._file || this._entries === this._lastEntries)
67
+ return void 0;
68
+ const chunk = {
69
+ type: this._title.toLowerCase(),
70
+ file: this._file,
71
+ fromLine: this._lastLine + 1,
72
+ toLine: this._line,
73
+ entryCount: this._entries - this._lastEntries
74
+ };
75
+ this._lastLine = this._line;
76
+ this._lastEntries = this._entries;
77
+ return chunk;
78
+ }
79
+ async _write(wallTime, text) {
80
+ if (this._stopped)
81
+ return;
82
+ this._file ??= await this._context.outputFile({ prefix: this._filePrefix, ext: "log", date: new Date(this._startTime) }, { origin: "code" });
83
+ const relativeTime = Math.round(wallTime - this._startTime);
84
+ const renderedText = await text();
85
+ const logLine = `[${String(relativeTime).padStart(8, " ")}ms] ${renderedText}
86
+ `;
87
+ await import_fs.default.promises.appendFile(this._file, logLine);
88
+ const lineCount = logLine.split("\n").length - 1;
89
+ this._line += lineCount;
90
+ this._entries++;
91
+ }
92
+ }
93
+ // Annotate the CommonJS export names for ESM import in node:
94
+ 0 && (module.exports = {
95
+ LogFile
96
+ });
@@ -0,0 +1,299 @@
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 response_exports = {};
30
+ __export(response_exports, {
31
+ Response: () => Response,
32
+ parseResponse: () => parseResponse,
33
+ renderTabMarkdown: () => renderTabMarkdown,
34
+ renderTabsMarkdown: () => renderTabsMarkdown,
35
+ requestDebug: () => requestDebug
36
+ });
37
+ module.exports = __toCommonJS(response_exports);
38
+ var import_fs = __toESM(require("fs"));
39
+ var import_path = __toESM(require("path"));
40
+ var import_utilsBundle = require("playwright-core/lib/utilsBundle");
41
+ var import_tab = require("./tab");
42
+ var import_screenshot = require("./tools/screenshot");
43
+ const requestDebug = (0, import_utilsBundle.debug)("pw:mcp:request");
44
+ class Response {
45
+ constructor(context, toolName, toolArgs, relativeTo) {
46
+ this._results = [];
47
+ this._errors = [];
48
+ this._code = [];
49
+ this._includeSnapshot = "none";
50
+ this._imageResults = [];
51
+ this._context = context;
52
+ this.toolName = toolName;
53
+ this.toolArgs = toolArgs;
54
+ this._clientWorkspace = relativeTo ?? context.firstRootPath();
55
+ }
56
+ _computRelativeTo(fileName) {
57
+ if (this._clientWorkspace)
58
+ return import_path.default.relative(this._clientWorkspace, fileName);
59
+ return fileName;
60
+ }
61
+ async resolveClientFile(template, title) {
62
+ let fileName;
63
+ if (template.suggestedFilename)
64
+ fileName = await this._context.workspaceFile(template.suggestedFilename, this._clientWorkspace);
65
+ else
66
+ fileName = await this._context.outputFile(template, { origin: "llm" });
67
+ const relativeName = this._computRelativeTo(fileName);
68
+ const printableLink = `- [${title}](${relativeName})`;
69
+ return { fileName, relativeName, printableLink };
70
+ }
71
+ addTextResult(text) {
72
+ this._results.push(text);
73
+ }
74
+ async addResult(title, data, file) {
75
+ if (this._context.config.outputMode === "file" || file.suggestedFilename || typeof data !== "string") {
76
+ const resolvedFile = await this.resolveClientFile(file, title);
77
+ await this.addFileResult(resolvedFile, data);
78
+ } else {
79
+ this.addTextResult(data);
80
+ }
81
+ }
82
+ async addFileResult(resolvedFile, data) {
83
+ if (typeof data === "string")
84
+ await import_fs.default.promises.writeFile(resolvedFile.fileName, data, "utf-8");
85
+ else if (data)
86
+ await import_fs.default.promises.writeFile(resolvedFile.fileName, data);
87
+ this.addTextResult(resolvedFile.printableLink);
88
+ }
89
+ addFileLink(title, fileName) {
90
+ const relativeName = this._computRelativeTo(fileName);
91
+ this.addTextResult(`- [${title}](${relativeName})`);
92
+ }
93
+ async registerImageResult(data, imageType) {
94
+ this._imageResults.push({ data, imageType });
95
+ }
96
+ addError(error) {
97
+ this._errors.push(error);
98
+ }
99
+ addCode(code) {
100
+ this._code.push(code);
101
+ }
102
+ setIncludeSnapshot() {
103
+ this._includeSnapshot = this._context.config.snapshot.mode;
104
+ }
105
+ setIncludeFullSnapshot(includeSnapshotFileName) {
106
+ this._includeSnapshot = "full";
107
+ this._includeSnapshotFileName = sanitizeSnapshotFileName(includeSnapshotFileName);
108
+ }
109
+ async serialize() {
110
+ const redactText = (text2) => {
111
+ for (const [secretName, secretValue] of Object.entries(this._context.config.secrets ?? {}))
112
+ text2 = text2.replaceAll(secretValue, `<secret>${secretName}</secret>`);
113
+ return text2;
114
+ };
115
+ const sections = await this._build();
116
+ const text = [];
117
+ for (const section of sections) {
118
+ if (!section.content.length)
119
+ continue;
120
+ text.push(`### ${section.title}`);
121
+ if (section.codeframe)
122
+ text.push(`\`\`\`${section.codeframe}`);
123
+ text.push(...section.content);
124
+ if (section.codeframe)
125
+ text.push("```");
126
+ }
127
+ const content = [
128
+ {
129
+ type: "text",
130
+ text: redactText(text.join("\n"))
131
+ }
132
+ ];
133
+ if (this._context.config.imageResponses !== "omit") {
134
+ for (const imageResult of this._imageResults) {
135
+ const scaledData = (0, import_screenshot.scaleImageToFitMessage)(imageResult.data, imageResult.imageType);
136
+ content.push({ type: "image", data: scaledData.toString("base64"), mimeType: imageResult.imageType === "png" ? "image/png" : "image/jpeg" });
137
+ }
138
+ }
139
+ return {
140
+ content,
141
+ ...sections.some((section) => section.isError) ? { isError: true } : {}
142
+ };
143
+ }
144
+ async _build() {
145
+ const sections = [];
146
+ const addSection = (title, content, codeframe) => {
147
+ const section = { title, content, isError: title === "Error", codeframe };
148
+ sections.push(section);
149
+ return content;
150
+ };
151
+ if (this._errors.length)
152
+ addSection("Error", this._errors);
153
+ if (this._results.length)
154
+ addSection("Result", this._results);
155
+ if (this._context.config.codegen !== "none" && this._code.length)
156
+ addSection("Ran Playwright code", this._code, "js");
157
+ const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot(this._clientWorkspace) : void 0;
158
+ const tabHeaders = await Promise.all(this._context.tabs().map((tab) => tab.headerSnapshot()));
159
+ if (this._includeSnapshot !== "none" || tabHeaders.some((header) => header.changed)) {
160
+ if (tabHeaders.length !== 1)
161
+ addSection("Open tabs", renderTabsMarkdown(tabHeaders));
162
+ addSection("Page", renderTabMarkdown(tabHeaders[0]));
163
+ }
164
+ if (tabSnapshot?.modalStates.length)
165
+ addSection("Modal state", (0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates));
166
+ if (tabSnapshot && this._includeSnapshot !== "none") {
167
+ const snapshot = this._includeSnapshot === "full" ? tabSnapshot.ariaSnapshot : tabSnapshot.ariaSnapshotDiff ?? tabSnapshot.ariaSnapshot;
168
+ if (this._context.config.outputMode === "file" || this._includeSnapshotFileName) {
169
+ const resolvedFile = await this.resolveClientFile({ prefix: "page", ext: "yml", suggestedFilename: this._includeSnapshotFileName }, "Snapshot");
170
+ await import_fs.default.promises.writeFile(resolvedFile.fileName, snapshot, "utf-8");
171
+ addSection("Snapshot", [resolvedFile.printableLink]);
172
+ } else {
173
+ addSection("Snapshot", [snapshot], "yaml");
174
+ }
175
+ }
176
+ const text = [];
177
+ if (tabSnapshot?.consoleLink)
178
+ text.push(`- New console entries: ${tabSnapshot.consoleLink}`);
179
+ if (tabSnapshot?.events.filter((event) => event.type !== "request").length) {
180
+ for (const event of tabSnapshot.events) {
181
+ if (event.type === "console" && this._context.config.outputMode !== "file") {
182
+ if ((0, import_tab.shouldIncludeMessage)(this._context.config.console.level, event.message.type))
183
+ text.push(`- ${trimMiddle(event.message.toString(), 100)}`);
184
+ } else if (event.type === "download-start") {
185
+ text.push(`- Downloading file ${event.download.download.suggestedFilename()} ...`);
186
+ } else if (event.type === "download-finish") {
187
+ text.push(`- Downloaded file ${event.download.download.suggestedFilename()} to "${this._computRelativeTo(event.download.outputFile)}"`);
188
+ }
189
+ }
190
+ }
191
+ if (text.length)
192
+ addSection("Events", text);
193
+ return sections;
194
+ }
195
+ }
196
+ function renderTabMarkdown(tab) {
197
+ const lines = [`- Page URL: ${tab.url}`];
198
+ if (tab.title)
199
+ lines.push(`- Page Title: ${tab.title}`);
200
+ if (tab.console.errors || tab.console.warnings)
201
+ lines.push(`- Console: ${tab.console.errors} errors, ${tab.console.warnings} warnings`);
202
+ return lines;
203
+ }
204
+ function renderTabsMarkdown(tabs) {
205
+ if (!tabs.length)
206
+ return ["No open tabs. Navigate to a URL to create one."];
207
+ const lines = [];
208
+ for (let i = 0; i < tabs.length; i++) {
209
+ const tab = tabs[i];
210
+ const current = tab.current ? " (current)" : "";
211
+ lines.push(`- ${i}:${current} [${tab.title}](${tab.url})`);
212
+ }
213
+ return lines;
214
+ }
215
+ function trimMiddle(text, maxLength) {
216
+ if (text.length <= maxLength)
217
+ return text;
218
+ return text.slice(0, Math.floor(maxLength / 2)) + "..." + text.slice(-3 - Math.floor(maxLength / 2));
219
+ }
220
+ function parseSections(text) {
221
+ const sections = /* @__PURE__ */ new Map();
222
+ const sectionHeaders = text.split(/^### /m).slice(1);
223
+ for (const section of sectionHeaders) {
224
+ const firstNewlineIndex = section.indexOf("\n");
225
+ if (firstNewlineIndex === -1)
226
+ continue;
227
+ const sectionName = section.substring(0, firstNewlineIndex);
228
+ const sectionContent = section.substring(firstNewlineIndex + 1).trim();
229
+ sections.set(sectionName, sectionContent);
230
+ }
231
+ return sections;
232
+ }
233
+ function parseResponse(response) {
234
+ if (response.content?.[0].type !== "text")
235
+ return void 0;
236
+ const text = response.content[0].text;
237
+ const sections = parseSections(text);
238
+ const error = sections.get("Error");
239
+ const result = sections.get("Result");
240
+ const code = sections.get("Ran Playwright code");
241
+ const tabs = sections.get("Open tabs");
242
+ const page = sections.get("Page");
243
+ const snapshot = sections.get("Snapshot");
244
+ const events = sections.get("Events");
245
+ const modalState = sections.get("Modal state");
246
+ const codeNoFrame = code?.replace(/^```js\n/, "").replace(/\n```$/, "");
247
+ const isError = response.isError;
248
+ const attachments = response.content.length > 1 ? response.content.slice(1) : void 0;
249
+ return {
250
+ result,
251
+ error,
252
+ code: codeNoFrame,
253
+ tabs,
254
+ page,
255
+ snapshot,
256
+ events,
257
+ modalState,
258
+ isError,
259
+ attachments,
260
+ text
261
+ };
262
+ }
263
+ function sanitizeSnapshotFileName(fileName) {
264
+ if (typeof fileName !== "string")
265
+ return void 0;
266
+ let safeName = fileName.trim();
267
+ if (!safeName)
268
+ return void 0;
269
+ const maybeUrl = safeName.match(/^[a-z][a-z0-9+.-]*:\/\//i);
270
+ if (maybeUrl) {
271
+ try {
272
+ const parsedUrl = new URL(safeName);
273
+ const pathname = parsedUrl.pathname || "";
274
+ const segments = pathname.split("/").filter(Boolean);
275
+ safeName = segments[segments.length - 1] || "snapshot";
276
+ if (safeName.includes(".") && safeName.startsWith("."))
277
+ safeName = "snapshot" + safeName;
278
+ } catch (error) {
279
+ }
280
+ }
281
+ safeName = safeName.split(/[\\/]/).filter(Boolean).at(-1) || safeName;
282
+ safeName = safeName.replace(/[#?].*$/, "");
283
+ if (safeName.includes(":"))
284
+ safeName = safeName.split(":").at(-1);
285
+ safeName = safeName.replace(/[^a-zA-Z0-9._-]/g, "_");
286
+ safeName = safeName.replace(/_+/g, "_");
287
+ safeName = safeName.replace(/^_+|_+$/g, "");
288
+ if (!safeName)
289
+ return void 0;
290
+ return safeName.slice(0, 100);
291
+ }
292
+ // Annotate the CommonJS export names for ESM import in node:
293
+ 0 && (module.exports = {
294
+ Response,
295
+ parseResponse,
296
+ renderTabMarkdown,
297
+ renderTabsMarkdown,
298
+ requestDebug
299
+ });