@pedropaulovc/playwright 1.59.0-next → 1.59.0-next.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.
- package/lib/common/config.js +2 -3
- package/lib/index.js +1 -1
- package/lib/isomorphic/teleReceiver.js +0 -1
- package/lib/mcp/browser/response.js +20 -21
- package/lib/mcp/browser/tab.js +4 -4
- package/lib/mcp/browser/tools/common.js +1 -3
- package/lib/mcp/browser/tools/console.js +1 -1
- package/lib/mcp/browser/tools/dialogs.js +0 -1
- package/lib/mcp/browser/tools/evaluate.js +6 -6
- package/lib/mcp/browser/tools/keyboard.js +8 -1
- package/lib/mcp/browser/tools/network.js +1 -1
- package/lib/mcp/browser/tools/pdf.js +2 -2
- package/lib/mcp/browser/tools/runCode.js +2 -4
- package/lib/mcp/browser/tools/screenshot.js +6 -9
- package/lib/mcp/browser/tools/snapshot.js +4 -2
- package/lib/mcp/browser/tools/utils.js +2 -2
- package/lib/mcp/program.js +1 -0
- package/lib/mcp/terminal/SKILL.md +158 -0
- package/lib/mcp/terminal/cli.js +2 -291
- package/lib/mcp/terminal/command.js +1 -1
- package/lib/mcp/terminal/commands.js +19 -19
- package/lib/mcp/terminal/help.json +10 -10
- package/lib/mcp/terminal/program.js +310 -0
- package/lib/program.js +1 -0
- package/lib/reporters/base.js +5 -1
- package/lib/transform/transform.js +2 -2
- package/package.json +4 -3
- package/types/test.d.ts +37 -1
- package/lib/mcp/browser/actions.d.js +0 -16
- package/lib/mcp/browser/tools/navigateAndWait.js +0 -66
- package/lib/mcp/browser/tools/snapshotViewport.js +0 -63
package/lib/common/config.js
CHANGED
|
@@ -95,8 +95,7 @@ class FullConfigInternal {
|
|
|
95
95
|
threshold: 3e5
|
|
96
96
|
/* 5 minutes */
|
|
97
97
|
}),
|
|
98
|
-
|
|
99
|
-
runAgents: takeFirst(configCLIOverrides.runAgents, "none"),
|
|
98
|
+
runAgents: takeFirst(configCLIOverrides.runAgents, userConfig.runAgents, "none"),
|
|
100
99
|
shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
|
|
101
100
|
tags: globalTags,
|
|
102
101
|
updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, "missing"),
|
|
@@ -162,7 +161,7 @@ class FullProjectInternal {
|
|
|
162
161
|
testDir,
|
|
163
162
|
snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
|
|
164
163
|
testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
|
|
165
|
-
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, "**/*.@(spec|test)
|
|
164
|
+
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, "**/*.@(spec|test).{md,?(c|m)[jt]s?(x)}"),
|
|
166
165
|
timeout: takeFirst(configCLIOverrides.debug ? 0 : void 0, configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
|
|
167
166
|
use: (0, import_util.mergeObjects)(config.use, projectConfig.use, configCLIOverrides.use),
|
|
168
167
|
dependencies: projectConfig.dependencies || [],
|
package/lib/index.js
CHANGED
|
@@ -138,7 +138,7 @@ const playwrightFixtures = {
|
|
|
138
138
|
}, { option: true, box: true }],
|
|
139
139
|
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
|
|
140
140
|
contextOptions: [{}, { option: true, box: true }],
|
|
141
|
-
agentOptions: [void 0, { option: true, box: true }],
|
|
141
|
+
agentOptions: [({}, use) => use(void 0), { option: true, box: true }],
|
|
142
142
|
_combinedContextOptions: [async ({
|
|
143
143
|
acceptDownloads,
|
|
144
144
|
bypassCSP,
|
|
@@ -60,20 +60,19 @@ class Response {
|
|
|
60
60
|
return new Response(++Response._ordinal, context, toolName, toolArgs);
|
|
61
61
|
}
|
|
62
62
|
addTextResult(result) {
|
|
63
|
-
this._results.push({
|
|
63
|
+
this._results.push({ title: "", data: result });
|
|
64
64
|
}
|
|
65
|
-
async addResult(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
async addResult(title, data, file) {
|
|
66
|
+
let filename;
|
|
67
|
+
if (!file.suggestedFilename) {
|
|
68
|
+
if (typeof data !== "string")
|
|
69
|
+
filename = (0, import_utils.dateAsFileName)(file.prefix, file.ext);
|
|
70
|
+
if (this._context.config.outputMode === "file")
|
|
71
|
+
filename = (0, import_utils.dateAsFileName)(file.prefix, file.ext);
|
|
72
|
+
} else {
|
|
73
|
+
filename = await this._context.outputFile(file.suggestedFilename, { origin: "llm", title });
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
if (result.suggestedFilename)
|
|
74
|
-
entry.filename = await this._context.outputFile(result.suggestedFilename, { origin: "llm", title: result.title ?? "Saved result" });
|
|
75
|
-
this._results.push(entry);
|
|
76
|
-
return { fileName: entry.filename };
|
|
75
|
+
this._results.push({ data, title, filename });
|
|
77
76
|
}
|
|
78
77
|
addError(error) {
|
|
79
78
|
this._errors.push(error);
|
|
@@ -109,12 +108,12 @@ class Response {
|
|
|
109
108
|
for (const result of this._results) {
|
|
110
109
|
if (result.filename) {
|
|
111
110
|
text.push(`- [${result.title}](${rootPath ? import_path.default.relative(rootPath, result.filename) : result.filename})`);
|
|
112
|
-
if (result.data)
|
|
111
|
+
if (typeof result.data === "string")
|
|
112
|
+
await import_fs.default.promises.writeFile(result.filename, this._redactText(result.data), "utf-8");
|
|
113
|
+
else
|
|
113
114
|
await import_fs.default.promises.writeFile(result.filename, result.data);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
} else if (result.text) {
|
|
117
|
-
text.push(result.text);
|
|
115
|
+
} else if (typeof result.data === "string" && result.data.trim()) {
|
|
116
|
+
text.push(result.data);
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
}
|
|
@@ -134,16 +133,16 @@ class Response {
|
|
|
134
133
|
}
|
|
135
134
|
if (tabSnapshot?.modalStates.length) {
|
|
136
135
|
const text = addSection("Modal state");
|
|
137
|
-
text.push(...(0, import_tab.renderModalStates)(tabSnapshot.modalStates));
|
|
136
|
+
text.push(...(0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates));
|
|
138
137
|
}
|
|
139
138
|
if (tabSnapshot && this._includeSnapshot === "full") {
|
|
140
139
|
let fileName;
|
|
141
140
|
if (this._includeSnapshotFileName)
|
|
142
141
|
fileName = await this._context.outputFile(this._includeSnapshotFileName, { origin: "llm", title: "Saved snapshot" });
|
|
143
142
|
else if (this._context.config.outputMode === "file")
|
|
144
|
-
fileName = await this._context.outputFile(
|
|
143
|
+
fileName = await this._context.outputFile((0, import_utils.dateAsFileName)("snapshot", "yml"), { origin: "code", title: "Saved snapshot" });
|
|
145
144
|
if (fileName) {
|
|
146
|
-
await import_fs.default.promises.writeFile(fileName, tabSnapshot.ariaSnapshot);
|
|
145
|
+
await import_fs.default.promises.writeFile(fileName, tabSnapshot.ariaSnapshot, "utf-8");
|
|
147
146
|
const text = addSection("Snapshot");
|
|
148
147
|
text.push(`- File: ${rootPath ? import_path.default.relative(rootPath, fileName) : fileName}`);
|
|
149
148
|
} else {
|
|
@@ -211,7 +210,7 @@ function renderTabMarkdown(tab) {
|
|
|
211
210
|
}
|
|
212
211
|
function renderTabsMarkdown(tabs) {
|
|
213
212
|
if (!tabs.length)
|
|
214
|
-
return [
|
|
213
|
+
return ["No open tabs. Navigate to a URL to create one."];
|
|
215
214
|
const lines = [];
|
|
216
215
|
for (let i = 0; i < tabs.length; i++) {
|
|
217
216
|
const tab = tabs[i];
|
package/lib/mcp/browser/tab.js
CHANGED
|
@@ -57,7 +57,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
57
57
|
type: "fileChooser",
|
|
58
58
|
description: "File chooser",
|
|
59
59
|
fileChooser: chooser,
|
|
60
|
-
clearedBy: import_files.uploadFile.schema.name
|
|
60
|
+
clearedBy: { tool: import_files.uploadFile.schema.name, skill: "upload" }
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
page.on("dialog", (dialog) => this._dialogShown(dialog));
|
|
@@ -112,7 +112,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
112
112
|
type: "dialog",
|
|
113
113
|
description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
|
|
114
114
|
dialog,
|
|
115
|
-
clearedBy: import_dialogs.handleDialog.schema.name
|
|
115
|
+
clearedBy: { tool: import_dialogs.handleDialog.schema.name, skill: "dialog-accept or dialog-dismiss" }
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
async _downloadStarted(download) {
|
|
@@ -299,12 +299,12 @@ function pageErrorToConsoleMessage(errorOrValue) {
|
|
|
299
299
|
toString: () => String(errorOrValue)
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
|
-
function renderModalStates(modalStates) {
|
|
302
|
+
function renderModalStates(config, modalStates) {
|
|
303
303
|
const result = [];
|
|
304
304
|
if (modalStates.length === 0)
|
|
305
305
|
result.push("- There is no modal state present");
|
|
306
306
|
for (const state of modalStates)
|
|
307
|
-
result.push(`- [${state.description}]: can be handled by
|
|
307
|
+
result.push(`- [${state.description}]: can be handled by ${config.skillMode ? state.clearedBy.skill : state.clearedBy.tool}`);
|
|
308
308
|
return result;
|
|
309
309
|
}
|
|
310
310
|
const consoleMessageLevels = ["error", "warning", "info", "debug"];
|
|
@@ -54,9 +54,7 @@ const resize = (0, import_tool.defineTabTool)({
|
|
|
54
54
|
},
|
|
55
55
|
handle: async (tab, params, response) => {
|
|
56
56
|
response.addCode(`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`);
|
|
57
|
-
await tab.
|
|
58
|
-
await tab.page.setViewportSize({ width: params.width, height: params.height });
|
|
59
|
-
});
|
|
57
|
+
await tab.page.setViewportSize({ width: params.width, height: params.height });
|
|
60
58
|
}
|
|
61
59
|
});
|
|
62
60
|
var common_default = [
|
|
@@ -38,7 +38,7 @@ const console = (0, import_tool.defineTabTool)({
|
|
|
38
38
|
handle: async (tab, params, response) => {
|
|
39
39
|
const messages = await tab.consoleMessages(params.level);
|
|
40
40
|
const text = messages.map((message) => message.toString()).join("\n");
|
|
41
|
-
await response.addResult(
|
|
41
|
+
await response.addResult("Console", text, { prefix: "console", ext: "log", suggestedFilename: params.filename });
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
const consoleClear = (0, import_tool.defineTabTool)({
|
|
@@ -37,7 +37,6 @@ const handleDialog = (0, import_tool.defineTabTool)({
|
|
|
37
37
|
type: "action"
|
|
38
38
|
},
|
|
39
39
|
handle: async (tab, params, response) => {
|
|
40
|
-
response.setIncludeSnapshot();
|
|
41
40
|
const dialogState = tab.modalStates().find((state) => state.type === "dialog");
|
|
42
41
|
if (!dialogState)
|
|
43
42
|
throw new Error("No dialog visible");
|
|
@@ -27,8 +27,7 @@ var import_tool = require("./tool");
|
|
|
27
27
|
const evaluateSchema = import_mcpBundle.z.object({
|
|
28
28
|
function: import_mcpBundle.z.string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
29
29
|
element: import_mcpBundle.z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
30
|
-
ref: import_mcpBundle.z.string().optional().describe("Exact target element reference from the page snapshot")
|
|
31
|
-
filename: import_mcpBundle.z.string().optional().describe("Filename to save the result to. If not provided, result is returned as JSON string.")
|
|
30
|
+
ref: import_mcpBundle.z.string().optional().describe("Exact target element reference from the page snapshot")
|
|
32
31
|
});
|
|
33
32
|
const evaluate = (0, import_tool.defineTabTool)({
|
|
34
33
|
capability: "core",
|
|
@@ -40,10 +39,11 @@ const evaluate = (0, import_tool.defineTabTool)({
|
|
|
40
39
|
type: "action"
|
|
41
40
|
},
|
|
42
41
|
handle: async (tab, params, response) => {
|
|
43
|
-
response.setIncludeSnapshot();
|
|
44
42
|
let locator;
|
|
45
|
-
if (params.
|
|
46
|
-
|
|
43
|
+
if (!params.function.includes("=>"))
|
|
44
|
+
params.function = `() => (${params.function})`;
|
|
45
|
+
if (params.ref) {
|
|
46
|
+
locator = await tab.refLocator({ ref: params.ref, element: params.element || "element" });
|
|
47
47
|
response.addCode(`await page.${locator.resolved}.evaluate(${(0, import_utils.escapeWithQuotes)(params.function)});`);
|
|
48
48
|
} else {
|
|
49
49
|
response.addCode(`await page.evaluate(${(0, import_utils.escapeWithQuotes)(params.function)});`);
|
|
@@ -52,7 +52,7 @@ const evaluate = (0, import_tool.defineTabTool)({
|
|
|
52
52
|
const receiver = locator?.locator ?? tab.page;
|
|
53
53
|
const result = await receiver._evaluateFunction(params.function);
|
|
54
54
|
const text = JSON.stringify(result, null, 2) || "undefined";
|
|
55
|
-
|
|
55
|
+
response.addTextResult(text);
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -38,7 +38,14 @@ const press = (0, import_tool.defineTabTool)({
|
|
|
38
38
|
handle: async (tab, params, response) => {
|
|
39
39
|
response.addCode(`// Press ${params.key}`);
|
|
40
40
|
response.addCode(`await page.keyboard.press('${params.key}');`);
|
|
41
|
-
|
|
41
|
+
if (params.key === "Enter") {
|
|
42
|
+
response.setIncludeSnapshot();
|
|
43
|
+
await tab.waitForCompletion(async () => {
|
|
44
|
+
await tab.page.keyboard.press("Enter");
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
await tab.page.keyboard.press(params.key);
|
|
48
|
+
}
|
|
42
49
|
}
|
|
43
50
|
});
|
|
44
51
|
const pressSequentially = (0, import_tool.defineTabTool)({
|
|
@@ -43,7 +43,7 @@ const requests = (0, import_tool.defineTabTool)({
|
|
|
43
43
|
if (rendered)
|
|
44
44
|
text.push(rendered);
|
|
45
45
|
}
|
|
46
|
-
await response.addResult(
|
|
46
|
+
await response.addResult("Network", text.join("\n"), { prefix: "network", ext: "log", suggestedFilename: params.filename });
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
49
|
const networkClear = (0, import_tool.defineTabTool)({
|
|
@@ -39,8 +39,8 @@ const pdf = (0, import_tool.defineTabTool)({
|
|
|
39
39
|
},
|
|
40
40
|
handle: async (tab, params, response) => {
|
|
41
41
|
const data = await tab.page.pdf();
|
|
42
|
-
const suggestedFilename = params.filename ?? (0, import_utils2.dateAsFileName)("pdf");
|
|
43
|
-
await response.addResult(
|
|
42
|
+
const suggestedFilename = params.filename ?? (0, import_utils2.dateAsFileName)("page", "pdf");
|
|
43
|
+
await response.addResult("Page as pdf", data, { prefix: "page", ext: "pdf", suggestedFilename });
|
|
44
44
|
response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: suggestedFilename })});`);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
@@ -36,8 +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(`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
|
-
filename: import_mcpBundle.z.string().optional().describe("Filename to save the result to. If not provided, result is returned as JSON string.")
|
|
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(); }\``)
|
|
41
40
|
});
|
|
42
41
|
const runCode = (0, import_tool.defineTabTool)({
|
|
43
42
|
capability: "core",
|
|
@@ -49,7 +48,6 @@ const runCode = (0, import_tool.defineTabTool)({
|
|
|
49
48
|
type: "action"
|
|
50
49
|
},
|
|
51
50
|
handle: async (tab, params, response) => {
|
|
52
|
-
response.setIncludeSnapshot();
|
|
53
51
|
response.addCode(`await (${params.code})(page);`);
|
|
54
52
|
const __end__ = new import_utils.ManualPromise();
|
|
55
53
|
const context = {
|
|
@@ -69,7 +67,7 @@ const runCode = (0, import_tool.defineTabTool)({
|
|
|
69
67
|
await import_vm.default.runInContext(snippet, context);
|
|
70
68
|
const result = await __end__;
|
|
71
69
|
if (typeof result === "string")
|
|
72
|
-
|
|
70
|
+
response.addTextResult(result);
|
|
73
71
|
});
|
|
74
72
|
}
|
|
75
73
|
});
|
|
@@ -45,8 +45,6 @@ const screenshot = (0, import_tool.defineTabTool)({
|
|
|
45
45
|
type: "readOnly"
|
|
46
46
|
},
|
|
47
47
|
handle: async (tab, params, response) => {
|
|
48
|
-
if (!!params.element !== !!params.ref)
|
|
49
|
-
throw new Error("Both element and ref must be provided or neither.");
|
|
50
48
|
if (params.fullPage && params.ref)
|
|
51
49
|
throw new Error("fullPage cannot be used with element screenshots.");
|
|
52
50
|
const fileType = params.type || "png";
|
|
@@ -56,17 +54,16 @@ const screenshot = (0, import_tool.defineTabTool)({
|
|
|
56
54
|
scale: "css",
|
|
57
55
|
...params.fullPage !== void 0 && { fullPage: params.fullPage }
|
|
58
56
|
};
|
|
59
|
-
const
|
|
60
|
-
const screenshotTarget = isElementScreenshot ? params.element : params.fullPage ? "full page" : "viewport";
|
|
57
|
+
const screenshotTarget = params.ref ? params.element || "element" : params.fullPage ? "full page" : "viewport";
|
|
61
58
|
const ref = params.ref ? await tab.refLocator({ element: params.element || "", ref: params.ref }) : null;
|
|
62
59
|
const data = ref ? await ref.locator.screenshot(options) : await tab.page.screenshot(options);
|
|
63
|
-
const
|
|
64
|
-
response.addCode(`// Screenshot ${screenshotTarget} and save it as ${
|
|
60
|
+
const suggestedFilename = params.filename || (0, import_utils3.dateAsFileName)(ref ? "element" : "page", fileType);
|
|
61
|
+
response.addCode(`// Screenshot ${screenshotTarget} and save it as ${suggestedFilename}`);
|
|
65
62
|
if (ref)
|
|
66
|
-
response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)({ ...options, path:
|
|
63
|
+
response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)({ ...options, path: suggestedFilename })});`);
|
|
67
64
|
else
|
|
68
|
-
response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path:
|
|
69
|
-
await response.addResult(
|
|
65
|
+
response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path: suggestedFilename })});`);
|
|
66
|
+
await response.addResult(`Screenshot of ${screenshotTarget}`, data, { prefix: ref ? "element" : "page", ext: fileType, suggestedFilename });
|
|
70
67
|
response.addImage({
|
|
71
68
|
contentType: fileType === "png" ? "image/png" : "image/jpeg",
|
|
72
69
|
data: scaleImageToFitMessage(data, fileType)
|
|
@@ -170,8 +170,9 @@ const check = (0, import_tool.defineTabTool)({
|
|
|
170
170
|
type: "input"
|
|
171
171
|
},
|
|
172
172
|
handle: async (tab, params, response) => {
|
|
173
|
-
const { resolved } = await tab.refLocator(params);
|
|
173
|
+
const { locator, resolved } = await tab.refLocator(params);
|
|
174
174
|
response.addCode(`await page.${resolved}.check();`);
|
|
175
|
+
await locator.check();
|
|
175
176
|
}
|
|
176
177
|
});
|
|
177
178
|
const uncheck = (0, import_tool.defineTabTool)({
|
|
@@ -185,8 +186,9 @@ const uncheck = (0, import_tool.defineTabTool)({
|
|
|
185
186
|
type: "input"
|
|
186
187
|
},
|
|
187
188
|
handle: async (tab, params, response) => {
|
|
188
|
-
const { resolved } = await tab.refLocator(params);
|
|
189
|
+
const { locator, resolved } = await tab.refLocator(params);
|
|
189
190
|
response.addCode(`await page.${resolved}.uncheck();`);
|
|
191
|
+
await locator.uncheck();
|
|
190
192
|
}
|
|
191
193
|
});
|
|
192
194
|
var snapshot_default = [
|
|
@@ -62,9 +62,9 @@ async function waitForCompletion(tab, callback) {
|
|
|
62
62
|
async function callOnPageNoTrace(page, callback) {
|
|
63
63
|
return await page._wrapApiCall(() => callback(page), { internal: true });
|
|
64
64
|
}
|
|
65
|
-
function dateAsFileName(extension) {
|
|
65
|
+
function dateAsFileName(prefix, extension) {
|
|
66
66
|
const date = /* @__PURE__ */ new Date();
|
|
67
|
-
return
|
|
67
|
+
return `${prefix}-${date.toISOString().replace(/[:.]/g, "-")}.${extension}`;
|
|
68
68
|
}
|
|
69
69
|
function eventWaiter(page, event, timeout) {
|
|
70
70
|
const disposables = [];
|
package/lib/mcp/program.js
CHANGED
|
@@ -73,6 +73,7 @@ Please run the command below. It will install a local copy of ffmpeg and will no
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
if (options.daemon) {
|
|
76
|
+
config.skillMode = true;
|
|
76
77
|
config.outputDir = import_path.default.join(process.cwd(), ".playwright-cli");
|
|
77
78
|
config.outputMode = "file";
|
|
78
79
|
config.codegen = "none";
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playwright-cli
|
|
3
|
+
description: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.
|
|
4
|
+
allowed-tools: Bash(playwright-cli:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Browser Automation with playwright-cli
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
playwright-cli open https://playwright.dev
|
|
13
|
+
playwright-cli click e15
|
|
14
|
+
playwright-cli type "page.click"
|
|
15
|
+
playwright-cli press Enter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Core workflow
|
|
19
|
+
|
|
20
|
+
1. Navigate: `playwright-cli open https://example.com`
|
|
21
|
+
2. Interact using refs from the snapshot
|
|
22
|
+
3. Re-snapshot after significant changes
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
### Core
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
playwright-cli open https://example.com/
|
|
30
|
+
playwright-cli close
|
|
31
|
+
playwright-cli type "search query"
|
|
32
|
+
playwright-cli click e3
|
|
33
|
+
playwright-cli dblclick e7
|
|
34
|
+
playwright-cli fill e5 "user@example.com"
|
|
35
|
+
playwright-cli drag e2 e8
|
|
36
|
+
playwright-cli hover e4
|
|
37
|
+
playwright-cli select e9 "option-value"
|
|
38
|
+
playwright-cli upload ./document.pdf
|
|
39
|
+
playwright-cli check e12
|
|
40
|
+
playwright-cli uncheck e12
|
|
41
|
+
playwright-cli snapshot
|
|
42
|
+
playwright-cli eval "document.title"
|
|
43
|
+
playwright-cli eval "el => el.textContent" e5
|
|
44
|
+
playwright-cli dialog-accept
|
|
45
|
+
playwright-cli dialog-accept "confirmation text"
|
|
46
|
+
playwright-cli dialog-dismiss
|
|
47
|
+
playwright-cli resize 1920 1080
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Navigation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
playwright-cli go-back
|
|
54
|
+
playwright-cli go-forward
|
|
55
|
+
playwright-cli reload
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Keyboard
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
playwright-cli press Enter
|
|
62
|
+
playwright-cli press ArrowDown
|
|
63
|
+
playwright-cli keydown Shift
|
|
64
|
+
playwright-cli keyup Shift
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Mouse
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
playwright-cli mousemove 150 300
|
|
71
|
+
playwright-cli mousedown
|
|
72
|
+
playwright-cli mousedown right
|
|
73
|
+
playwright-cli mouseup
|
|
74
|
+
playwright-cli mouseup right
|
|
75
|
+
playwright-cli mousewheel 0 100
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Save as
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
playwright-cli screenshot
|
|
82
|
+
playwright-cli screenshot e5
|
|
83
|
+
playwright-cli pdf
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Tabs
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
playwright-cli tab-list
|
|
90
|
+
playwright-cli tab-new
|
|
91
|
+
playwright-cli tab-new https://example.com/page
|
|
92
|
+
playwright-cli tab-close
|
|
93
|
+
playwright-cli tab-close 2
|
|
94
|
+
playwright-cli tab-select 0
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### DevTools
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
playwright-cli console
|
|
101
|
+
playwright-cli console warning
|
|
102
|
+
playwright-cli network
|
|
103
|
+
playwright-cli run-code "await page.waitForTimeout(1000)"
|
|
104
|
+
playwright-cli tracing-start
|
|
105
|
+
playwright-cli tracing-stop
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Sessions
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
playwright-cli --session=mysession open example.com
|
|
112
|
+
playwright-cli --session=mysession click e6
|
|
113
|
+
playwright-cli session-list
|
|
114
|
+
playwright-cli session-stop mysession
|
|
115
|
+
playwright-cli session-stop-all
|
|
116
|
+
playwright-cli session-delete
|
|
117
|
+
playwright-cli session-delete mysession
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Example: Form submission
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
playwright-cli open https://example.com/form
|
|
124
|
+
playwright-cli snapshot
|
|
125
|
+
|
|
126
|
+
playwright-cli fill e1 "user@example.com"
|
|
127
|
+
playwright-cli fill e2 "password123"
|
|
128
|
+
playwright-cli click e3
|
|
129
|
+
playwright-cli snapshot
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Example: Multi-tab workflow
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
playwright-cli open https://example.com
|
|
136
|
+
playwright-cli tab-new https://example.com/other
|
|
137
|
+
playwright-cli tab-list
|
|
138
|
+
playwright-cli tab-select 0
|
|
139
|
+
playwright-cli snapshot
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Example: Debugging with DevTools
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
playwright-cli open https://example.com
|
|
146
|
+
playwright-cli click e4
|
|
147
|
+
playwright-cli fill e7 "test"
|
|
148
|
+
playwright-cli console
|
|
149
|
+
playwright-cli network
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
playwright-cli open https://example.com
|
|
154
|
+
playwright-cli tracing-start
|
|
155
|
+
playwright-cli click e4
|
|
156
|
+
playwright-cli fill e7 "test"
|
|
157
|
+
playwright-cli tracing-stop
|
|
158
|
+
```
|