@cotestdev/mcp_playwright 0.0.50 → 0.0.52
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/lib/mcp/browser/browserContextFactory.js +11 -4
- package/lib/mcp/browser/browserServerBackend.js +2 -4
- package/lib/mcp/browser/config.js +71 -47
- package/lib/mcp/browser/context.js +65 -4
- package/lib/mcp/browser/logFile.js +96 -0
- package/lib/mcp/browser/response.js +107 -104
- package/lib/mcp/browser/sessionLog.js +1 -1
- package/lib/mcp/browser/tab.js +73 -18
- package/lib/mcp/browser/tools/config.js +41 -0
- package/lib/mcp/browser/tools/console.js +6 -2
- package/lib/mcp/browser/tools/cookies.js +152 -0
- package/lib/mcp/browser/tools/install.js +1 -0
- package/lib/mcp/browser/tools/network.js +25 -11
- package/lib/mcp/browser/tools/pdf.js +3 -4
- package/lib/mcp/browser/tools/route.js +140 -0
- package/lib/mcp/browser/tools/runCode.js +0 -2
- package/lib/mcp/browser/tools/screenshot.js +6 -7
- package/lib/mcp/browser/tools/storage.js +3 -4
- package/lib/mcp/browser/tools/tracing.js +10 -9
- package/lib/mcp/browser/tools/utils.js +0 -6
- package/lib/mcp/browser/tools/video.js +31 -13
- package/lib/mcp/browser/tools/webstorage.js +223 -0
- package/lib/mcp/browser/tools.js +11 -3
- package/lib/mcp/extension/cdpRelay.js +7 -7
- package/lib/mcp/extension/extensionContextFactory.js +4 -2
- package/lib/mcp/program.js +19 -12
- package/lib/mcp/terminal/cli.js +23 -2
- package/lib/mcp/terminal/command.js +34 -30
- package/lib/mcp/terminal/commands.js +310 -38
- package/lib/mcp/terminal/daemon.js +23 -38
- package/lib/mcp/terminal/helpGenerator.js +8 -6
- package/lib/mcp/terminal/program.js +482 -199
- package/lib/mcp/terminal/socketConnection.js +17 -2
- package/package.json +2 -2
|
@@ -32,43 +32,66 @@ __export(response_exports, {
|
|
|
32
32
|
parseResponse: () => parseResponse,
|
|
33
33
|
renderTabMarkdown: () => renderTabMarkdown,
|
|
34
34
|
renderTabsMarkdown: () => renderTabsMarkdown,
|
|
35
|
-
requestDebug: () => requestDebug
|
|
36
|
-
serializeResponse: () => serializeResponse
|
|
35
|
+
requestDebug: () => requestDebug
|
|
37
36
|
});
|
|
38
37
|
module.exports = __toCommonJS(response_exports);
|
|
39
38
|
var import_fs = __toESM(require("fs"));
|
|
40
39
|
var import_path = __toESM(require("path"));
|
|
41
40
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
42
41
|
var import_tab = require("./tab");
|
|
43
|
-
var import_utils = require("./tools/utils");
|
|
44
42
|
var import_screenshot = require("./tools/screenshot");
|
|
45
43
|
const requestDebug = (0, import_utilsBundle.debug)("pw:mcp:request");
|
|
46
44
|
class Response {
|
|
47
|
-
constructor(
|
|
45
|
+
constructor(context, toolName, toolArgs, relativeTo) {
|
|
48
46
|
this._results = [];
|
|
49
47
|
this._errors = [];
|
|
50
48
|
this._code = [];
|
|
51
49
|
this._includeSnapshot = "none";
|
|
50
|
+
this._imageResults = [];
|
|
52
51
|
this._context = context;
|
|
53
52
|
this.toolName = toolName;
|
|
54
53
|
this.toolArgs = toolArgs;
|
|
54
|
+
this._clientWorkspace = relativeTo ?? context.firstRootPath();
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
this.
|
|
56
|
+
_computRelativeTo(fileName) {
|
|
57
|
+
if (this._clientWorkspace)
|
|
58
|
+
return import_path.default.relative(this._clientWorkspace, fileName);
|
|
59
|
+
return fileName;
|
|
58
60
|
}
|
|
59
|
-
|
|
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 };
|
|
61
70
|
}
|
|
62
71
|
addTextResult(text) {
|
|
63
|
-
this._results.push(
|
|
72
|
+
this._results.push(text);
|
|
64
73
|
}
|
|
65
|
-
addResult(title, data, file) {
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
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 });
|
|
72
95
|
}
|
|
73
96
|
addError(error) {
|
|
74
97
|
this._errors.push(error);
|
|
@@ -83,61 +106,90 @@ class Response {
|
|
|
83
106
|
this._includeSnapshot = "full";
|
|
84
107
|
this._includeSnapshotFileName = includeSnapshotFileName;
|
|
85
108
|
}
|
|
86
|
-
async
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
sections.push(section);
|
|
92
|
-
return section.content;
|
|
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;
|
|
93
114
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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("```");
|
|
101
126
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
}
|
|
106
138
|
}
|
|
107
|
-
|
|
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;
|
|
108
158
|
const tabHeaders = await Promise.all(this._context.tabs().map((tab) => tab.headerSnapshot()));
|
|
109
159
|
if (this._includeSnapshot !== "none" || tabHeaders.some((header) => header.changed)) {
|
|
110
|
-
if (tabHeaders.length !== 1)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
const content = addSection("Page");
|
|
115
|
-
content.push({ text: renderTabMarkdown(tabHeaders[0]).join("\n"), title: "Page" });
|
|
116
|
-
}
|
|
117
|
-
if (tabSnapshot?.modalStates.length) {
|
|
118
|
-
const content = addSection("Modal state");
|
|
119
|
-
content.push({ text: (0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates).join("\n"), title: "Modal state" });
|
|
160
|
+
if (tabHeaders.length !== 1)
|
|
161
|
+
addSection("Open tabs", renderTabsMarkdown(tabHeaders));
|
|
162
|
+
addSection("Page", renderTabMarkdown(tabHeaders[0]));
|
|
120
163
|
}
|
|
164
|
+
if (tabSnapshot?.modalStates.length)
|
|
165
|
+
addSection("Modal state", (0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates));
|
|
121
166
|
if (tabSnapshot && this._includeSnapshot !== "none") {
|
|
122
|
-
const content = addSection("Snapshot", "yaml");
|
|
123
167
|
const snapshot = this._includeSnapshot === "full" ? tabSnapshot.ariaSnapshot : tabSnapshot.ariaSnapshotDiff ?? tabSnapshot.ariaSnapshot;
|
|
124
|
-
|
|
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
|
+
}
|
|
125
175
|
}
|
|
176
|
+
const text = [];
|
|
177
|
+
if (tabSnapshot?.consoleLink)
|
|
178
|
+
text.push(`- New console entries: ${tabSnapshot.consoleLink}`);
|
|
126
179
|
if (tabSnapshot?.events.filter((event) => event.type !== "request").length) {
|
|
127
|
-
const content = addSection("Events");
|
|
128
|
-
const text = [];
|
|
129
180
|
for (const event of tabSnapshot.events) {
|
|
130
|
-
if (event.type === "console") {
|
|
181
|
+
if (event.type === "console" && this._context.config.outputMode !== "file") {
|
|
131
182
|
if ((0, import_tab.shouldIncludeMessage)(this._context.config.console.level, event.message.type))
|
|
132
183
|
text.push(`- ${trimMiddle(event.message.toString(), 100)}`);
|
|
133
184
|
} else if (event.type === "download-start") {
|
|
134
185
|
text.push(`- Downloading file ${event.download.download.suggestedFilename()} ...`);
|
|
135
186
|
} else if (event.type === "download-finish") {
|
|
136
|
-
text.push(`- Downloaded file ${event.download.download.suggestedFilename()} to "${
|
|
187
|
+
text.push(`- Downloaded file ${event.download.download.suggestedFilename()} to "${this._computRelativeTo(event.download.outputFile)}"`);
|
|
137
188
|
}
|
|
138
189
|
}
|
|
139
|
-
content.push({ text: text.join("\n"), title: "events" });
|
|
140
190
|
}
|
|
191
|
+
if (text.length)
|
|
192
|
+
addSection("Events", text);
|
|
141
193
|
return sections;
|
|
142
194
|
}
|
|
143
195
|
}
|
|
@@ -145,6 +197,8 @@ function renderTabMarkdown(tab) {
|
|
|
145
197
|
const lines = [`- Page URL: ${tab.url}`];
|
|
146
198
|
if (tab.title)
|
|
147
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`);
|
|
148
202
|
return lines;
|
|
149
203
|
}
|
|
150
204
|
function renderTabsMarkdown(tabs) {
|
|
@@ -176,56 +230,6 @@ function parseSections(text) {
|
|
|
176
230
|
}
|
|
177
231
|
return sections;
|
|
178
232
|
}
|
|
179
|
-
async function serializeResponse(context, sections, relativeTo) {
|
|
180
|
-
const redactText = (text2) => {
|
|
181
|
-
for (const [secretName, secretValue] of Object.entries(context.config.secrets ?? {}))
|
|
182
|
-
text2 = text2.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
183
|
-
return text2;
|
|
184
|
-
};
|
|
185
|
-
const text = [];
|
|
186
|
-
for (const section of sections) {
|
|
187
|
-
text.push(`### ${section.title}`);
|
|
188
|
-
const codeframe = [];
|
|
189
|
-
for (const result of section.content) {
|
|
190
|
-
if (result.file && (result.file.suggestedFilename || context.config.outputMode === "file" || result.data)) {
|
|
191
|
-
const generatedFileName = await context.outputFile((0, import_utils.dateAsFileName)(result.file.prefix, result.file.ext), { origin: "code", title: section.title });
|
|
192
|
-
const fileName = result.file.suggestedFilename ? await context.outputFile(result.file.suggestedFilename, { origin: "llm", title: section.title }) : generatedFileName;
|
|
193
|
-
text.push(`- [${result.title}](${relativeTo ? import_path.default.relative(relativeTo, fileName) : fileName})`);
|
|
194
|
-
if (result.data)
|
|
195
|
-
await import_fs.default.promises.writeFile(fileName, result.data, "utf-8");
|
|
196
|
-
else
|
|
197
|
-
await import_fs.default.promises.writeFile(fileName, result.text);
|
|
198
|
-
} else {
|
|
199
|
-
if (result.text !== void 0)
|
|
200
|
-
codeframe.push(result.text);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (codeframe?.length) {
|
|
204
|
-
if (section.codeframe)
|
|
205
|
-
text.push(`\`\`\`${section.codeframe}
|
|
206
|
-
${codeframe.join("\n")}
|
|
207
|
-
\`\`\``);
|
|
208
|
-
else
|
|
209
|
-
text.push(codeframe.join("\n"));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const content = [
|
|
213
|
-
{
|
|
214
|
-
type: "text",
|
|
215
|
-
text: redactText(text.join("\n"))
|
|
216
|
-
}
|
|
217
|
-
];
|
|
218
|
-
if (context.config.imageResponses !== "omit") {
|
|
219
|
-
for (const result of sections.flatMap((section) => section.content).filter((result2) => result2.file?.contentType)) {
|
|
220
|
-
const scaledData = (0, import_screenshot.scaleImageToFitMessage)(result.data, result.file.contentType === "image/png" ? "png" : "jpeg");
|
|
221
|
-
content.push({ type: "image", data: scaledData.toString("base64"), mimeType: result.file.contentType });
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return {
|
|
225
|
-
content,
|
|
226
|
-
...sections.some((section) => section.isError) ? { isError: true } : {}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
233
|
function parseResponse(response) {
|
|
230
234
|
if (response.content?.[0].type !== "text")
|
|
231
235
|
return void 0;
|
|
@@ -262,6 +266,5 @@ function parseResponse(response) {
|
|
|
262
266
|
parseResponse,
|
|
263
267
|
renderTabMarkdown,
|
|
264
268
|
renderTabsMarkdown,
|
|
265
|
-
requestDebug
|
|
266
|
-
serializeResponse
|
|
269
|
+
requestDebug
|
|
267
270
|
});
|
|
@@ -42,7 +42,7 @@ class SessionLog {
|
|
|
42
42
|
this._file = import_path.default.join(this._folder, "session.md");
|
|
43
43
|
}
|
|
44
44
|
static async create(config, clientInfo) {
|
|
45
|
-
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code"
|
|
45
|
+
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code" });
|
|
46
46
|
await import_fs.default.promises.mkdir(sessionFolder, { recursive: true });
|
|
47
47
|
console.error(`Session: ${sessionFolder}`);
|
|
48
48
|
return new SessionLog(sessionFolder);
|
package/lib/mcp/browser/tab.js
CHANGED
|
@@ -19,7 +19,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var tab_exports = {};
|
|
20
20
|
__export(tab_exports, {
|
|
21
21
|
Tab: () => Tab,
|
|
22
|
-
TabEvents: () => TabEvents,
|
|
23
22
|
renderModalStates: () => renderModalStates,
|
|
24
23
|
shouldIncludeMessage: () => shouldIncludeMessage
|
|
25
24
|
});
|
|
@@ -28,6 +27,7 @@ var import_events = require("events");
|
|
|
28
27
|
var import_utils = require("playwright-core/lib/utils");
|
|
29
28
|
var import_utils2 = require("./tools/utils");
|
|
30
29
|
var import_log = require("../log");
|
|
30
|
+
var import_logFile = require("./logFile");
|
|
31
31
|
var import_dialogs = require("./tools/dialogs");
|
|
32
32
|
var import_files = require("./tools/files");
|
|
33
33
|
var import_transform = require("../../transform/transform");
|
|
@@ -37,13 +37,12 @@ const TabEvents = {
|
|
|
37
37
|
class Tab extends import_events.EventEmitter {
|
|
38
38
|
constructor(context, page, onPageClose) {
|
|
39
39
|
super();
|
|
40
|
-
this._lastHeader = { title: "about:blank", url: "about:blank", current: false };
|
|
40
|
+
this._lastHeader = { title: "about:blank", url: "about:blank", current: false, console: { total: 0, warnings: 0, errors: 0 } };
|
|
41
41
|
this._consoleMessages = [];
|
|
42
42
|
this._downloads = [];
|
|
43
|
-
this._requests =
|
|
43
|
+
this._requests = [];
|
|
44
44
|
this._modalStates = [];
|
|
45
45
|
this._needsFullSnapshot = false;
|
|
46
|
-
this._eventEntries = [];
|
|
47
46
|
this._recentEventEntries = [];
|
|
48
47
|
this.context = context;
|
|
49
48
|
this.page = page;
|
|
@@ -51,6 +50,8 @@ class Tab extends import_events.EventEmitter {
|
|
|
51
50
|
page.on("console", (event) => this._handleConsoleMessage(messageToConsoleMessage(event)));
|
|
52
51
|
page.on("pageerror", (error) => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
|
|
53
52
|
page.on("request", (request) => this._handleRequest(request));
|
|
53
|
+
page.on("response", (response) => this._handleResponse(response));
|
|
54
|
+
page.on("requestfailed", (request) => this._handleRequestFailed(request));
|
|
54
55
|
page.on("close", () => this._onClose());
|
|
55
56
|
page.on("filechooser", (chooser) => {
|
|
56
57
|
this.setModalState({
|
|
@@ -67,6 +68,8 @@ class Tab extends import_events.EventEmitter {
|
|
|
67
68
|
page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation);
|
|
68
69
|
page.setDefaultTimeout(this.context.config.timeouts.action);
|
|
69
70
|
page[tabSymbol] = this;
|
|
71
|
+
const wallTime = Date.now();
|
|
72
|
+
this._consoleLog = new import_logFile.LogFile(this.context, wallTime, "console", "Console");
|
|
70
73
|
this._initializedPromise = this._initialize();
|
|
71
74
|
}
|
|
72
75
|
static forPage(page) {
|
|
@@ -86,8 +89,8 @@ class Tab extends import_events.EventEmitter {
|
|
|
86
89
|
for (const message of await Tab.collectConsoleMessages(this.page))
|
|
87
90
|
this._handleConsoleMessage(message);
|
|
88
91
|
const requests = await this.page.requests().catch(() => []);
|
|
89
|
-
for (const request of requests)
|
|
90
|
-
this._requests.
|
|
92
|
+
for (const request of requests.filter((r) => r.existingResponse() || r.failure()))
|
|
93
|
+
this._requests.push(request);
|
|
91
94
|
for (const initPage of this.context.config.browser.initPage || []) {
|
|
92
95
|
try {
|
|
93
96
|
const { default: func } = await (0, import_transform.requireOrImport)(initPage);
|
|
@@ -116,10 +119,11 @@ class Tab extends import_events.EventEmitter {
|
|
|
116
119
|
});
|
|
117
120
|
}
|
|
118
121
|
async _downloadStarted(download) {
|
|
122
|
+
const outputFile = await this.context.outputFile({ suggestedFilename: sanitizeForFilePath(download.suggestedFilename()), prefix: "download", ext: "bin" }, { origin: "code" });
|
|
119
123
|
const entry = {
|
|
120
124
|
download,
|
|
121
125
|
finished: false,
|
|
122
|
-
outputFile
|
|
126
|
+
outputFile
|
|
123
127
|
};
|
|
124
128
|
this._downloads.push(entry);
|
|
125
129
|
this._addLogEntry({ type: "download-start", wallTime: Date.now(), download: entry });
|
|
@@ -130,20 +134,40 @@ class Tab extends import_events.EventEmitter {
|
|
|
130
134
|
_clearCollectedArtifacts() {
|
|
131
135
|
this._consoleMessages.length = 0;
|
|
132
136
|
this._downloads.length = 0;
|
|
133
|
-
this._requests.
|
|
134
|
-
this._eventEntries.length = 0;
|
|
137
|
+
this._requests.length = 0;
|
|
135
138
|
this._recentEventEntries.length = 0;
|
|
139
|
+
this._resetLogs();
|
|
140
|
+
}
|
|
141
|
+
_resetLogs() {
|
|
142
|
+
const wallTime = Date.now();
|
|
143
|
+
this._consoleLog.stop();
|
|
144
|
+
this._consoleLog = new import_logFile.LogFile(this.context, wallTime, "console", "Console");
|
|
136
145
|
}
|
|
137
146
|
_handleRequest(request) {
|
|
138
|
-
this._requests.
|
|
139
|
-
|
|
147
|
+
this._requests.push(request);
|
|
148
|
+
const wallTime = request.timing().startTime || Date.now();
|
|
149
|
+
this._addLogEntry({ type: "request", wallTime, request });
|
|
150
|
+
}
|
|
151
|
+
_handleResponse(response) {
|
|
152
|
+
const timing = response.request().timing();
|
|
153
|
+
const wallTime = timing.responseStart + timing.startTime;
|
|
154
|
+
this._addLogEntry({ type: "request", wallTime, request: response.request() });
|
|
155
|
+
}
|
|
156
|
+
_handleRequestFailed(request) {
|
|
157
|
+
this._requests.push(request);
|
|
158
|
+
const timing = request.timing();
|
|
159
|
+
const wallTime = timing.responseEnd + timing.startTime;
|
|
160
|
+
this._addLogEntry({ type: "request", wallTime, request });
|
|
140
161
|
}
|
|
141
162
|
_handleConsoleMessage(message) {
|
|
142
163
|
this._consoleMessages.push(message);
|
|
143
|
-
|
|
164
|
+
const wallTime = message.timestamp;
|
|
165
|
+
this._addLogEntry({ type: "console", wallTime, message });
|
|
166
|
+
const level = consoleLevelForMessageType(message.type);
|
|
167
|
+
if (level === "error" || level === "warning")
|
|
168
|
+
this._consoleLog.appendLine(wallTime, () => message.toString());
|
|
144
169
|
}
|
|
145
170
|
_addLogEntry(entry) {
|
|
146
|
-
this._eventEntries.push(entry);
|
|
147
171
|
this._recentEventEntries.push(entry);
|
|
148
172
|
}
|
|
149
173
|
_onClose() {
|
|
@@ -155,8 +179,14 @@ class Tab extends import_events.EventEmitter {
|
|
|
155
179
|
await this._raceAgainstModalStates(async () => {
|
|
156
180
|
title = await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.title());
|
|
157
181
|
});
|
|
158
|
-
|
|
159
|
-
|
|
182
|
+
const newHeader = {
|
|
183
|
+
title: title ?? "",
|
|
184
|
+
url: this.page.url(),
|
|
185
|
+
current: this.isCurrentTab(),
|
|
186
|
+
console: await this.consoleMessageCount()
|
|
187
|
+
};
|
|
188
|
+
if (!tabHeaderEquals(this._lastHeader, newHeader)) {
|
|
189
|
+
this._lastHeader = newHeader;
|
|
160
190
|
return { ...this._lastHeader, changed: true };
|
|
161
191
|
}
|
|
162
192
|
return { ...this._lastHeader, changed: false };
|
|
@@ -188,6 +218,18 @@ class Tab extends import_events.EventEmitter {
|
|
|
188
218
|
}
|
|
189
219
|
await this.waitForLoadState("load", { timeout: 5e3 });
|
|
190
220
|
}
|
|
221
|
+
async consoleMessageCount() {
|
|
222
|
+
await this._initializedPromise;
|
|
223
|
+
let errors = 0;
|
|
224
|
+
let warnings = 0;
|
|
225
|
+
for (const message of this._consoleMessages) {
|
|
226
|
+
if (message.type === "error")
|
|
227
|
+
errors++;
|
|
228
|
+
else if (message.type === "warning")
|
|
229
|
+
warnings++;
|
|
230
|
+
}
|
|
231
|
+
return { total: this._consoleMessages.length, errors, warnings };
|
|
232
|
+
}
|
|
191
233
|
async consoleMessages(level) {
|
|
192
234
|
await this._initializedPromise;
|
|
193
235
|
return this._consoleMessages.filter((message) => shouldIncludeMessage(level, message.type));
|
|
@@ -202,9 +244,9 @@ class Tab extends import_events.EventEmitter {
|
|
|
202
244
|
}
|
|
203
245
|
async clearRequests() {
|
|
204
246
|
await this._initializedPromise;
|
|
205
|
-
this._requests.
|
|
247
|
+
this._requests.length = 0;
|
|
206
248
|
}
|
|
207
|
-
async captureSnapshot() {
|
|
249
|
+
async captureSnapshot(relativeTo) {
|
|
208
250
|
await this._initializedPromise;
|
|
209
251
|
let tabSnapshot;
|
|
210
252
|
const modalStates = await this._raceAgainstModalStates(async () => {
|
|
@@ -217,6 +259,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
217
259
|
};
|
|
218
260
|
});
|
|
219
261
|
if (tabSnapshot) {
|
|
262
|
+
tabSnapshot.consoleLink = await this._consoleLog.take(relativeTo);
|
|
220
263
|
tabSnapshot.events = this._recentEventEntries;
|
|
221
264
|
this._recentEventEntries = [];
|
|
222
265
|
}
|
|
@@ -281,6 +324,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
281
324
|
function messageToConsoleMessage(message) {
|
|
282
325
|
return {
|
|
283
326
|
type: message.type(),
|
|
327
|
+
timestamp: message.timestamp(),
|
|
284
328
|
text: message.text(),
|
|
285
329
|
toString: () => `[${message.type().toUpperCase()}] ${message.text()} @ ${message.location().url}:${message.location().lineNumber}`
|
|
286
330
|
};
|
|
@@ -289,12 +333,14 @@ function pageErrorToConsoleMessage(errorOrValue) {
|
|
|
289
333
|
if (errorOrValue instanceof Error) {
|
|
290
334
|
return {
|
|
291
335
|
type: "error",
|
|
336
|
+
timestamp: Date.now(),
|
|
292
337
|
text: errorOrValue.message,
|
|
293
338
|
toString: () => errorOrValue.stack || errorOrValue.message
|
|
294
339
|
};
|
|
295
340
|
}
|
|
296
341
|
return {
|
|
297
342
|
type: "error",
|
|
343
|
+
timestamp: Date.now(),
|
|
298
344
|
text: String(errorOrValue),
|
|
299
345
|
toString: () => String(errorOrValue)
|
|
300
346
|
};
|
|
@@ -342,10 +388,19 @@ function consoleLevelForMessageType(type) {
|
|
|
342
388
|
}
|
|
343
389
|
}
|
|
344
390
|
const tabSymbol = Symbol("tabSymbol");
|
|
391
|
+
function sanitizeForFilePath(s) {
|
|
392
|
+
const sanitize = (s2) => s2.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
|
|
393
|
+
const separator = s.lastIndexOf(".");
|
|
394
|
+
if (separator === -1)
|
|
395
|
+
return sanitize(s);
|
|
396
|
+
return sanitize(s.substring(0, separator)) + "." + sanitize(s.substring(separator + 1));
|
|
397
|
+
}
|
|
398
|
+
function tabHeaderEquals(a, b) {
|
|
399
|
+
return a.title === b.title && a.url === b.url && a.current === b.current && a.console.errors === b.console.errors && a.console.warnings === b.console.warnings && a.console.total === b.console.total;
|
|
400
|
+
}
|
|
345
401
|
// Annotate the CommonJS export names for ESM import in node:
|
|
346
402
|
0 && (module.exports = {
|
|
347
403
|
Tab,
|
|
348
|
-
TabEvents,
|
|
349
404
|
renderModalStates,
|
|
350
405
|
shouldIncludeMessage
|
|
351
406
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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 config_exports = {};
|
|
20
|
+
__export(config_exports, {
|
|
21
|
+
default: () => config_default
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(config_exports);
|
|
24
|
+
var import_mcpBundle = require("../../../mcpBundle");
|
|
25
|
+
var import_tool = require("./tool");
|
|
26
|
+
const configShow = (0, import_tool.defineTool)({
|
|
27
|
+
capability: "config",
|
|
28
|
+
schema: {
|
|
29
|
+
name: "browser_get_config",
|
|
30
|
+
title: "Get config",
|
|
31
|
+
description: "Get the final resolved config after merging CLI options, environment variables and config file.",
|
|
32
|
+
inputSchema: import_mcpBundle.z.object({}),
|
|
33
|
+
type: "readOnly"
|
|
34
|
+
},
|
|
35
|
+
handle: async (context, params, response) => {
|
|
36
|
+
response.addTextResult(JSON.stringify(context.config, null, 2));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
var config_default = [
|
|
40
|
+
configShow
|
|
41
|
+
];
|
|
@@ -36,9 +36,13 @@ const console = (0, import_tool.defineTabTool)({
|
|
|
36
36
|
type: "readOnly"
|
|
37
37
|
},
|
|
38
38
|
handle: async (tab, params, response) => {
|
|
39
|
+
const count = await tab.consoleMessageCount();
|
|
40
|
+
const header = [`Total messages: ${count.total} (Errors: ${count.errors}, Warnings: ${count.warnings})`];
|
|
39
41
|
const messages = await tab.consoleMessages(params.level);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
if (messages.length !== count.total)
|
|
43
|
+
header.push(`Returning ${messages.length} messages for level "${params.level}"`);
|
|
44
|
+
const text = [...header, "", ...messages.map((message) => message.toString())].join("\n");
|
|
45
|
+
await response.addResult("Console", text, { prefix: "console", ext: "log", suggestedFilename: params.filename });
|
|
42
46
|
}
|
|
43
47
|
});
|
|
44
48
|
const consoleClear = (0, import_tool.defineTabTool)({
|