Update npm packages (73 packages including @jqhtml 2.3.36)

Update npm registry domain from privatenpm.hanson.xyz to npm.internal.hanson.xyz

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-02-20 11:31:28 +00:00
parent d01a6179aa
commit b5eb27a827
1690 changed files with 47348 additions and 16848 deletions

View File

@@ -1,16 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var actions_d_exports = {};
module.exports = __toCommonJS(actions_d_exports);

View File

@@ -29,7 +29,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
var browserContextFactory_exports = {};
__export(browserContextFactory_exports, {
SharedContextFactory: () => SharedContextFactory,
contextFactory: () => contextFactory
contextFactory: () => contextFactory,
identityBrowserContextFactory: () => identityBrowserContextFactory
});
module.exports = __toCommonJS(browserContextFactory_exports);
var import_crypto = __toESM(require("crypto"));
@@ -53,16 +54,27 @@ function contextFactory(config) {
return new IsolatedContextFactory(config);
return new PersistentContextFactory(config);
}
function identityBrowserContextFactory(browserContext) {
return {
createContext: async (clientInfo, abortSignal, options) => {
return {
browserContext,
close: async () => {
}
};
}
};
}
class BaseContextFactory {
constructor(name, config) {
this._logName = name;
this.config = config;
}
async _obtainBrowser(clientInfo) {
async _obtainBrowser(clientInfo, options) {
if (this._browserPromise)
return this._browserPromise;
(0, import_log.testDebug)(`obtain browser (${this._logName})`);
this._browserPromise = this._doObtainBrowser(clientInfo);
this._browserPromise = this._doObtainBrowser(clientInfo, options);
void this._browserPromise.then((browser) => {
browser.on("disconnected", () => {
this._browserPromise = void 0;
@@ -72,28 +84,27 @@ class BaseContextFactory {
});
return this._browserPromise;
}
async _doObtainBrowser(clientInfo) {
async _doObtainBrowser(clientInfo, options) {
throw new Error("Not implemented");
}
async createContext(clientInfo) {
async createContext(clientInfo, _, options) {
(0, import_log.testDebug)(`create browser context (${this._logName})`);
const browser = await this._obtainBrowser(clientInfo);
const browserContext = await this._doCreateContext(browser);
const browser = await this._obtainBrowser(clientInfo, options);
const browserContext = await this._doCreateContext(browser, clientInfo);
await addInitScript(browserContext, this.config.browser.initScript);
return {
browserContext,
close: (afterClose) => this._closeBrowserContext(browserContext, browser, afterClose)
close: () => this._closeBrowserContext(browserContext, browser)
};
}
async _doCreateContext(browser) {
async _doCreateContext(browser, clientInfo) {
throw new Error("Not implemented");
}
async _closeBrowserContext(browserContext, browser, afterClose) {
async _closeBrowserContext(browserContext, browser) {
(0, import_log.testDebug)(`close browser context (${this._logName})`);
if (browser.contexts().length === 1)
this._browserPromise = void 0;
await browserContext.close().catch(import_log.logUnhandledError);
await afterClose();
if (browser.contexts().length === 0) {
(0, import_log.testDebug)(`close browser (${this._logName})`);
await browser.close().catch(import_log.logUnhandledError);
@@ -104,7 +115,7 @@ class IsolatedContextFactory extends BaseContextFactory {
constructor(config) {
super("isolated", config);
}
async _doObtainBrowser(clientInfo) {
async _doObtainBrowser(clientInfo, options) {
await injectCdpPort(this.config.browser);
const browserType = playwright[this.config.browser.browserName];
const tracesDir = await computeTracesDir(this.config, clientInfo);
@@ -114,15 +125,16 @@ class IsolatedContextFactory extends BaseContextFactory {
tracesDir,
...this.config.browser.launchOptions,
handleSIGINT: false,
handleSIGTERM: false
handleSIGTERM: false,
...options.forceHeadless !== void 0 ? { headless: options.forceHeadless === "headless" } : {}
}).catch((error) => {
if (error.message.includes("Executable doesn't exist"))
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
throw error;
});
}
async _doCreateContext(browser) {
return browser.newContext(this.config.browser.contextOptions);
async _doCreateContext(browser, clientInfo) {
return browser.newContext(await browserContextOptionsFromConfig(this.config, clientInfo));
}
}
class CdpContextFactory extends BaseContextFactory {
@@ -130,7 +142,10 @@ class CdpContextFactory extends BaseContextFactory {
super("cdp", config);
}
async _doObtainBrowser() {
return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint, { headers: this.config.browser.cdpHeaders });
return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint, {
headers: this.config.browser.cdpHeaders,
timeout: this.config.browser.cdpTimeout
});
}
async _doCreateContext(browser) {
return this.config.browser.isolated ? await browser.newContext() : browser.contexts()[0];
@@ -158,7 +173,7 @@ class PersistentContextFactory {
this._userDataDirs = /* @__PURE__ */ new Set();
this.config = config;
}
async createContext(clientInfo) {
async createContext(clientInfo, abortSignal, options) {
await injectCdpPort(this.config.browser);
(0, import_log.testDebug)("create browser context (persistent)");
const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo);
@@ -172,23 +187,29 @@ class PersistentContextFactory {
const launchOptions = {
tracesDir,
...this.config.browser.launchOptions,
...this.config.browser.contextOptions,
...await browserContextOptionsFromConfig(this.config, clientInfo),
handleSIGINT: false,
handleSIGTERM: false,
ignoreDefaultArgs: [
"--disable-extensions"
],
assistantMode: true
assistantMode: true,
...options.forceHeadless !== void 0 ? { headless: options.forceHeadless === "headless" } : {}
};
try {
const browserContext = await browserType.launchPersistentContext(userDataDir, launchOptions);
await addInitScript(browserContext, this.config.browser.initScript);
const close = (afterClose) => this._closeBrowserContext(browserContext, userDataDir, afterClose);
const close = () => this._closeBrowserContext(browserContext, userDataDir);
return { browserContext, close };
} catch (error) {
if (error.message.includes("Executable doesn't exist"))
throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
if (error.message.includes("ProcessSingleton") || error.message.includes("Invalid URL")) {
if (error.message.includes("cannot open shared object file: No such file or directory")) {
const browserName = launchOptions.channel ?? this.config.browser.browserName;
throw new Error(`Missing system dependencies required to run browser ${browserName}. Install them with: sudo npx playwright install-deps ${browserName}`);
}
if (error.message.includes("ProcessSingleton") || // On Windows the process exits silently with code 21 when the profile is in use.
error.message.includes("exitCode=21")) {
await new Promise((resolve) => setTimeout(resolve, 1e3));
continue;
}
@@ -197,13 +218,14 @@ class PersistentContextFactory {
}
throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
}
async _closeBrowserContext(browserContext, userDataDir, afterClose) {
async _closeBrowserContext(browserContext, userDataDir) {
(0, import_log.testDebug)("close browser context (persistent)");
(0, import_log.testDebug)("release user data dir", userDataDir);
await browserContext.close().catch(() => {
});
await afterClose();
this._userDataDirs.delete(userDataDir);
if (process.env.PWMCP_PROFILES_DIR_FOR_TEST && userDataDir.startsWith(process.env.PWMCP_PROFILES_DIR_FOR_TEST))
await import_fs.default.promises.rm(userDataDir, { recursive: true }).catch(import_log.logUnhandledError);
(0, import_log.testDebug)("close browser context complete (persistent)");
}
async _createUserDataDir(clientInfo) {
@@ -257,10 +279,10 @@ class SharedContextFactory {
constructor(baseFactory) {
this._baseFactory = baseFactory;
}
async createContext(clientInfo, abortSignal, toolName) {
async createContext(clientInfo, abortSignal, options) {
if (!this._contextPromise) {
(0, import_log.testDebug)("create shared browser context");
this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, toolName);
this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, options);
}
const { browserContext } = await this._contextPromise;
(0, import_log.testDebug)(`shared context client connected`);
@@ -280,17 +302,28 @@ class SharedContextFactory {
if (!contextPromise)
return;
const { close } = await contextPromise;
await close(async () => {
});
await close();
}
}
async function computeTracesDir(config, clientInfo) {
if (!config.saveTrace && !config.capabilities?.includes("tracing"))
return;
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", reason: "Collecting trace" });
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", title: "Collecting trace" });
}
async function browserContextOptionsFromConfig(config, clientInfo) {
const result = { ...config.browser.contextOptions };
if (config.saveVideo) {
const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code", title: "Saving video" });
result.recordVideo = {
dir,
size: config.saveVideo
};
}
return result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SharedContextFactory,
contextFactory
contextFactory,
identityBrowserContextFactory
});

View File

@@ -47,24 +47,32 @@ class BrowserServerBackend {
}
async callTool(name, rawArguments) {
const tool = this._tools.find((tool2) => tool2.schema.name === name);
if (!tool)
throw new Error(`Tool "${name}" not found`);
if (!tool) {
return {
content: [{ type: "text", text: `### Error
Tool "${name}" not found` }],
isError: true
};
}
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
const context = this._context;
const response = new import_response.Response(context, name, parsedArguments);
response.logBegin();
const response = import_response.Response.create(context, name, parsedArguments);
context.setRunningTool(name);
let responseObject;
try {
await tool.handle(context, parsedArguments, response);
await response.finish();
this._sessionLog?.logResponse(response);
responseObject = await response.build();
this._sessionLog?.logResponse(name, parsedArguments, responseObject);
} catch (error) {
response.addError(String(error));
return {
content: [{ type: "text", text: `### Error
${String(error)}` }],
isError: true
};
} finally {
context.setRunningTool(void 0);
}
response.logEnd();
return response.serialize();
return responseObject;
}
serverClosed() {
void this._context?.dispose().catch(import_log.logUnhandledError);

View File

@@ -1,66 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var codegen_exports = {};
__export(codegen_exports, {
escapeWithQuotes: () => escapeWithQuotes,
formatObject: () => formatObject,
quote: () => quote
});
module.exports = __toCommonJS(codegen_exports);
function escapeWithQuotes(text, char = "'") {
const stringified = JSON.stringify(text);
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
if (char === "'")
return char + escapedText.replace(/[']/g, "\\'") + char;
if (char === '"')
return char + escapedText.replace(/["]/g, '\\"') + char;
if (char === "`")
return char + escapedText.replace(/[`]/g, "\\`") + char;
throw new Error("Invalid escape char");
}
function quote(text) {
return escapeWithQuotes(text, "'");
}
function formatObject(value, indent = " ", mode = "multiline") {
if (typeof value === "string")
return quote(value);
if (Array.isArray(value))
return `[${value.map((o) => formatObject(o)).join(", ")}]`;
if (typeof value === "object") {
const keys = Object.keys(value).filter((key) => value[key] !== void 0).sort();
if (!keys.length)
return "{}";
const tokens = [];
for (const key of keys)
tokens.push(`${key}: ${formatObject(value[key])}`);
if (mode === "multiline")
return `{
${tokens.join(`,
${indent}`)}
}`;
return `{ ${tokens.join(", ")} }`;
}
return String(value);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
escapeWithQuotes,
formatObject,
quote
});

View File

@@ -32,13 +32,15 @@ __export(config_exports, {
configFromCLIOptions: () => configFromCLIOptions,
defaultConfig: () => defaultConfig,
dotenvFileLoader: () => dotenvFileLoader,
enumParser: () => enumParser,
headerParser: () => headerParser,
numberParser: () => numberParser,
outputDir: () => outputDir,
outputFile: () => outputFile,
resolutionParser: () => resolutionParser,
resolveCLIConfig: () => resolveCLIConfig,
resolveConfig: () => resolveConfig
resolveConfig: () => resolveConfig,
semicolonSeparatedList: () => semicolonSeparatedList
});
module.exports = __toCommonJS(config_exports);
var import_fs = __toESM(require("fs"));
@@ -60,8 +62,19 @@ const defaultConfig = {
viewport: null
}
},
console: {
level: "info"
},
network: {
allowedOrigins: void 0,
blockedOrigins: void 0
},
server: {},
saveTrace: false,
snapshot: {
mode: "incremental",
output: "stdout"
},
timeouts: {
action: 5e3,
navigation: 6e4
@@ -88,6 +101,12 @@ async function validateConfig(config) {
throw new Error(`Init script file does not exist: ${script}`);
}
}
if (config.browser.initPage) {
for (const page of config.browser.initPage) {
if (!await (0, import_util.fileExistsAsync)(page))
throw new Error(`Init page file does not exist: ${page}`);
}
}
if (config.sharedBrowserContext && config.saveVideo)
throw new Error("saveVideo is not supported when sharedBrowserContext is true");
}
@@ -143,13 +162,6 @@ function configFromCLIOptions(cliOptions) {
contextOptions.serviceWorkers = "block";
if (cliOptions.grantPermissions)
contextOptions.permissions = cliOptions.grantPermissions;
if (cliOptions.saveVideo) {
contextOptions.recordVideo = {
// Videos are moved to output directory on saveAs.
dir: tmpDir(),
size: cliOptions.saveVideo
};
}
const result = {
browser: {
browserName,
@@ -168,11 +180,22 @@ function configFromCLIOptions(cliOptions) {
allowedHosts: cliOptions.allowedHosts
},
capabilities: cliOptions.caps,
console: {
level: cliOptions.consoleLevel
},
network: {
allowedOrigins: cliOptions.allowedOrigins,
blockedOrigins: cliOptions.blockedOrigins
},
allowUnrestrictedFileAccess: cliOptions.allowUnrestrictedFileAccess,
codegen: cliOptions.codegen,
saveSession: cliOptions.saveSession,
saveTrace: cliOptions.saveTrace,
saveVideo: cliOptions.saveVideo,
secrets: cliOptions.secrets,
sharedBrowserContext: cliOptions.sharedBrowserContext,
snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : void 0,
outputMode: cliOptions.outputMode,
outputDir: cliOptions.outputDir,
imageResponses: cliOptions.imageResponses,
testIdAttribute: cliOptions.testIdAttribute,
@@ -186,12 +209,17 @@ function configFromCLIOptions(cliOptions) {
function configFromEnv() {
const options = {};
options.allowedHosts = commaSeparatedList(process.env.PLAYWRIGHT_MCP_ALLOWED_HOSTNAMES);
options.allowedOrigins = semicolonSeparatedList(process.env.PLAYWRIGHT_MCP_ALLOWED_ORIGINS);
options.allowUnrestrictedFileAccess = envToBoolean(process.env.PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS);
options.blockedOrigins = semicolonSeparatedList(process.env.PLAYWRIGHT_MCP_BLOCKED_ORIGINS);
options.blockServiceWorkers = envToBoolean(process.env.PLAYWRIGHT_MCP_BLOCK_SERVICE_WORKERS);
options.browser = envToString(process.env.PLAYWRIGHT_MCP_BROWSER);
options.caps = commaSeparatedList(process.env.PLAYWRIGHT_MCP_CAPS);
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
@@ -205,8 +233,8 @@ function configFromEnv() {
if (initScript)
options.initScript = [initScript];
options.isolated = envToBoolean(process.env.PLAYWRIGHT_MCP_ISOLATED);
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES === "omit")
options.imageResponses = "omit";
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
options.imageResponses = enumParser("--image-responses", ["allow", "omit"], process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX);
options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR);
options.port = numberParser(process.env.PLAYWRIGHT_MCP_PORT);
@@ -242,7 +270,8 @@ function outputDir(config, clientInfo) {
}
async function outputFile(config, clientInfo, fileName, options) {
const file = await resolveFile(config, clientInfo, fileName, options);
(0, import_utilsBundle.debug)("pw:mcp:file")(options.reason, file);
await import_fs.default.promises.mkdir(import_path.default.dirname(file), { recursive: true });
(0, import_utilsBundle.debug)("pw:mcp:file")(options.title, file);
return file;
}
async function resolveFile(config, clientInfo, fileName, options) {
@@ -285,16 +314,33 @@ function mergeConfig(base, overrides) {
...pickDefined(base),
...pickDefined(overrides),
browser,
console: {
...pickDefined(base.console),
...pickDefined(overrides.console)
},
network: {
...pickDefined(base.network),
...pickDefined(overrides.network)
},
server: {
...pickDefined(base.server),
...pickDefined(overrides.server)
},
snapshot: {
...pickDefined(base.snapshot),
...pickDefined(overrides.snapshot)
},
timeouts: {
...pickDefined(base.timeouts),
...pickDefined(overrides.timeouts)
}
};
}
function semicolonSeparatedList(value) {
if (!value)
return void 0;
return value.split(";").map((v) => v.trim());
}
function commaSeparatedList(value) {
if (!value)
return void 0;
@@ -335,6 +381,11 @@ function headerParser(arg, previous) {
result[name] = value;
return result;
}
function enumParser(name, options, value) {
if (!options.includes(value))
throw new Error(`Invalid ${name}: ${value}. Valid values are: ${options.join(", ")}`);
return value;
}
function envToBoolean(value) {
if (value === "true" || value === "1")
return true;
@@ -358,11 +409,13 @@ function sanitizeForFilePath(s) {
configFromCLIOptions,
defaultConfig,
dotenvFileLoader,
enumParser,
headerParser,
numberParser,
outputDir,
outputFile,
resolutionParser,
resolveCLIConfig,
resolveConfig
resolveConfig,
semicolonSeparatedList
});

View File

@@ -28,19 +28,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var context_exports = {};
__export(context_exports, {
Context: () => Context,
InputRecorder: () => InputRecorder
Context: () => Context
});
module.exports = __toCommonJS(context_exports);
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
var import_utils = require("playwright-core/lib/utils");
var import_playwright_core = require("playwright-core");
var import_url = require("url");
var import_os = __toESM(require("os"));
var import_log = require("../log");
var import_tab = require("./tab");
var import_config = require("./config");
var codegen = __toESM(require("./codegen"));
var import_utils = require("./tools/utils");
const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
class Context {
constructor(options) {
@@ -68,14 +66,13 @@ class Context {
}
currentTabOrDie() {
if (!this._currentTab)
throw new Error('No open pages available. Use the "browser_navigate" tool to navigate to a page first.');
throw new Error("No open pages available.");
return this._currentTab;
}
async newTab() {
const { browserContext } = await this._ensureBrowserContext();
const { browserContext } = await this._ensureBrowserContext({});
const page = await browserContext.newPage();
this._currentTab = this._tabs.find((t) => t.page === page);
await this._currentTab.initializedPromise;
return this._currentTab;
}
async selectTab(index) {
@@ -86,8 +83,8 @@ class Context {
this._currentTab = tab;
return tab;
}
async ensureTab() {
const { browserContext } = await this._ensureBrowserContext();
async ensureTab(options = {}) {
const { browserContext } = await this._ensureBrowserContext(options);
if (!this._currentTab)
await browserContext.newPage();
return this._currentTab;
@@ -137,31 +134,11 @@ class Context {
testDebug("close context");
const promise = this._browserContextPromise;
this._browserContextPromise = void 0;
this._browserContextOption = void 0;
await promise.then(async ({ browserContext, close }) => {
if (this.config.saveTrace)
await browserContext.tracing.stop();
const videos = this.config.saveVideo ? browserContext.pages().map((page) => page.video()).filter((video) => !!video) : [];
await close(async () => {
for (const video of videos) {
const name = await this.outputFile((0, import_utils.dateAsFileName)("webm"), { origin: "code", reason: "Saving video" });
await import_fs.default.promises.mkdir(import_path.default.dirname(name), { recursive: true });
const p = await video.path();
if (import_fs.default.existsSync(p)) {
try {
await import_fs.default.promises.rename(p, name);
} catch (e) {
if (e.code !== "EXDEV")
(0, import_log.logUnhandledError)(e);
try {
await import_fs.default.promises.copyFile(p, name);
await import_fs.default.promises.unlink(p);
} catch (e2) {
(0, import_log.logUnhandledError)(e2);
}
}
}
}
});
await close();
});
}
async dispose() {
@@ -169,28 +146,45 @@ class Context {
await this.closeBrowserContext();
Context._allContexts.delete(this);
}
async ensureBrowserContext() {
const { browserContext } = await this._ensureBrowserContext();
async _setupRequestInterception(context) {
if (this.config.network?.allowedOrigins?.length) {
await context.route("**", (route) => route.abort("blockedbyclient"));
for (const origin of this.config.network.allowedOrigins)
await context.route(originOrHostGlob(origin), (route) => route.continue());
}
if (this.config.network?.blockedOrigins?.length) {
for (const origin of this.config.network.blockedOrigins)
await context.route(originOrHostGlob(origin), (route) => route.abort("blockedbyclient"));
}
}
async ensureBrowserContext(options = {}) {
const { browserContext } = await this._ensureBrowserContext(options);
return browserContext;
}
_ensureBrowserContext() {
if (!this._browserContextPromise) {
this._browserContextPromise = this._setupBrowserContext();
this._browserContextPromise.catch(() => {
this._browserContextPromise = void 0;
});
}
_ensureBrowserContext(options) {
if (this._browserContextPromise && (options.forceHeadless === void 0 || this._browserContextOption?.forceHeadless === options.forceHeadless))
return this._browserContextPromise;
const closePrework = this._browserContextPromise ? this.closeBrowserContext() : Promise.resolve();
this._browserContextPromise = closePrework.then(() => this._setupBrowserContext(options));
this._browserContextPromise.catch(() => {
this._browserContextPromise = void 0;
this._browserContextOption = void 0;
});
this._browserContextOption = options;
return this._browserContextPromise;
}
async _setupBrowserContext() {
async _setupBrowserContext(options) {
if (this._closeBrowserContextPromise)
throw new Error("Another browser context is being closed.");
if (this.config.testIdAttribute)
import_playwright_core.selectors.setTestIdAttribute(this.config.testIdAttribute);
const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, this._runningToolName);
const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, { toolName: this._runningToolName, ...options });
const { browserContext } = result;
if (this.sessionLog)
await InputRecorder.create(this, browserContext);
if (!this.config.allowUnrestrictedFileAccess) {
browserContext._setAllowedProtocols(["http:", "https:", "about:", "data:"]);
browserContext._setAllowedDirectories(allRootPaths(this._clientInfo));
}
await this._setupRequestInterception(browserContext);
for (const page of browserContext.pages())
this._onPageCreated(page);
browserContext.on("page", (page) => this._onPageCreated(page));
@@ -206,62 +200,45 @@ class Context {
}
lookupSecret(secretName) {
if (!this.config.secrets?.[secretName])
return { value: secretName, code: codegen.quote(secretName) };
return { value: secretName, code: (0, import_utils.escapeWithQuotes)(secretName, "'") };
return {
value: this.config.secrets[secretName],
code: `process.env['${secretName}']`
};
}
firstRootPath() {
return allRootPaths(this._clientInfo)[0];
}
}
class InputRecorder {
constructor(context, browserContext) {
this._context = context;
this._browserContext = browserContext;
function allRootPaths(clientInfo) {
const paths = [];
for (const root of clientInfo.roots) {
const url = new URL(root.uri);
let rootPath;
try {
rootPath = (0, import_url.fileURLToPath)(url);
} catch (e) {
if (e.code === "ERR_INVALID_FILE_URL_PATH" && import_os.default.platform() === "win32")
rootPath = decodeURIComponent(url.pathname);
}
if (!rootPath)
continue;
paths.push(rootPath);
}
static async create(context, browserContext) {
const recorder = new InputRecorder(context, browserContext);
await recorder._initialize();
return recorder;
}
async _initialize() {
const sessionLog = this._context.sessionLog;
await this._browserContext._enableRecorder({
mode: "recording",
recorderMode: "api"
}, {
actionAdded: (page, data, code) => {
if (this._context.isRunningTool())
return;
const tab = import_tab.Tab.forPage(page);
if (tab)
sessionLog.logUserAction(data.action, tab, code, false);
},
actionUpdated: (page, data, code) => {
if (this._context.isRunningTool())
return;
const tab = import_tab.Tab.forPage(page);
if (tab)
sessionLog.logUserAction(data.action, tab, code, true);
},
signalAdded: (page, data) => {
if (this._context.isRunningTool())
return;
if (data.signal.name !== "navigation")
return;
const tab = import_tab.Tab.forPage(page);
const navigateAction = {
name: "navigate",
url: data.signal.url,
signals: []
};
if (tab)
sessionLog.logUserAction(navigateAction, tab, `await page.goto('${data.signal.url}');`, false);
}
});
if (paths.length === 0)
paths.push(process.cwd());
return paths;
}
function originOrHostGlob(originOrHost) {
try {
const url = new URL(originOrHost);
if (url.origin !== "null")
return `${url.origin}/**`;
} catch {
}
return `*://${originOrHost}/**`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Context,
InputRecorder
Context
});

View File

@@ -1,7 +1,9 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -15,179 +17,213 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var response_exports = {};
__export(response_exports, {
Response: () => Response,
parseResponse: () => parseResponse,
renderTabMarkdown: () => renderTabMarkdown,
renderTabsMarkdown: () => renderTabsMarkdown,
requestDebug: () => requestDebug
});
module.exports = __toCommonJS(response_exports);
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
var import_tab = require("./tab");
var import_utils = require("./tools/utils");
const requestDebug = (0, import_utilsBundle.debug)("pw:mcp:request");
class Response {
constructor(context, toolName, toolArgs) {
this._result = [];
constructor(ordinal, context, toolName, toolArgs) {
this._results = [];
this._errors = [];
this._code = [];
this._images = [];
this._includeSnapshot = "none";
this._includeTabs = false;
this._ordinal = ordinal;
this._context = context;
this.toolName = toolName;
this.toolArgs = toolArgs;
}
addResult(result) {
this._result.push(result);
static {
this._ordinal = 0;
}
static create(context, toolName, toolArgs) {
return new Response(++Response._ordinal, context, toolName, toolArgs);
}
addTextResult(result) {
this._results.push({ text: result });
}
async addResult(result) {
if (result.data && !result.suggestedFilename)
result.suggestedFilename = (0, import_utils.dateAsFileName)(result.ext ?? "bin");
if (this._context.config.outputMode === "file") {
if (!result.suggestedFilename)
result.suggestedFilename = (0, import_utils.dateAsFileName)(result.ext ?? (result.text ? "txt" : "bin"));
}
const entry = { text: result.text, data: result.data, title: result.title };
if (result.suggestedFilename)
entry.filename = await this._context.outputFile(result.suggestedFilename, { origin: "llm", title: result.title ?? "Saved result" });
this._results.push(entry);
return { fileName: entry.filename };
}
addError(error) {
this._result.push(error);
this._isError = true;
}
isError() {
return this._isError;
}
result() {
return this._result.join("\n");
this._errors.push(error);
}
addCode(code) {
this._code.push(code);
}
code() {
return this._code.join("\n");
}
addImage(image) {
this._images.push(image);
}
images() {
return this._images;
setIncludeSnapshot() {
this._includeSnapshot = this._context.config.snapshot.mode;
}
setIncludeSnapshot(full) {
this._includeSnapshot = full ?? "incremental";
setIncludeFullSnapshot(includeSnapshotFileName) {
this._includeSnapshot = "full";
this._includeSnapshotFileName = includeSnapshotFileName;
}
setIncludeTabs() {
this._includeTabs = true;
}
async finish() {
if (this._includeSnapshot !== "none" && this._context.currentTab())
this._tabSnapshot = await this._context.currentTabOrDie().captureSnapshot();
for (const tab of this._context.tabs())
await tab.updateTitle();
}
tabSnapshot() {
return this._tabSnapshot;
}
logBegin() {
if (requestDebug.enabled)
requestDebug(this.toolName, this.toolArgs);
}
logEnd() {
if (requestDebug.enabled)
requestDebug(this.serialize({ omitSnapshot: true, omitBlobs: true }));
}
serialize(options = {}) {
const response = [];
if (this._result.length) {
response.push("### Result");
response.push(this._result.join("\n"));
response.push("");
async build() {
const rootPath = this._context.firstRootPath();
const sections = [];
const addSection = (title) => {
const section = { title, content: [] };
sections.push(section);
return section.content;
};
if (this._errors.length) {
const text = addSection("Error");
text.push("### Error");
text.push(this._errors.join("\n"));
}
if (this._code.length) {
response.push(`### Ran Playwright code
\`\`\`js
${this._code.join("\n")}
\`\`\``);
response.push("");
if (this._results.length) {
const text = addSection("Result");
for (const result of this._results) {
if (result.filename) {
text.push(`- [${result.title}](${rootPath ? import_path.default.relative(rootPath, result.filename) : result.filename})`);
if (result.data)
await import_fs.default.promises.writeFile(result.filename, result.data);
else if (result.text)
await import_fs.default.promises.writeFile(result.filename, this._redactText(result.text));
} else if (result.text) {
text.push(result.text);
}
}
}
if (this._includeSnapshot !== "none" || this._includeTabs)
response.push(...renderTabsMarkdown(this._context.tabs(), this._includeTabs));
if (this._tabSnapshot?.modalStates.length) {
response.push(...(0, import_tab.renderModalStates)(this._context, this._tabSnapshot.modalStates));
response.push("");
} else if (this._tabSnapshot) {
const includeSnapshot = options.omitSnapshot ? "none" : this._includeSnapshot;
response.push(renderTabSnapshot(this._tabSnapshot, includeSnapshot));
response.push("");
if (this._context.config.codegen !== "none" && this._code.length) {
const text = addSection("Ran Playwright code");
text.push(...this._code);
}
const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot() : void 0;
const tabHeaders = await Promise.all(this._context.tabs().map((tab) => tab.headerSnapshot()));
if (tabHeaders.some((header) => header.changed)) {
if (tabHeaders.length !== 1) {
const text2 = addSection("Open tabs");
text2.push(...renderTabsMarkdown(tabHeaders));
}
const text = addSection("Page");
text.push(...renderTabMarkdown(tabHeaders[0]));
}
if (tabSnapshot?.modalStates.length) {
const text = addSection("Modal state");
text.push(...(0, import_tab.renderModalStates)(tabSnapshot.modalStates));
}
if (tabSnapshot && this._includeSnapshot === "full") {
let fileName;
if (this._includeSnapshotFileName)
fileName = await this._context.outputFile(this._includeSnapshotFileName, { origin: "llm", title: "Saved snapshot" });
else if (this._context.config.outputMode === "file")
fileName = await this._context.outputFile(`snapshot-${this._ordinal}.yml`, { origin: "code", title: "Saved snapshot" });
if (fileName) {
await import_fs.default.promises.writeFile(fileName, tabSnapshot.ariaSnapshot);
const text = addSection("Snapshot");
text.push(`- File: ${rootPath ? import_path.default.relative(rootPath, fileName) : fileName}`);
} else {
const text = addSection("Snapshot");
text.push("```yaml");
text.push(tabSnapshot.ariaSnapshot);
text.push("```");
}
}
if (tabSnapshot && this._includeSnapshot === "incremental") {
const text = addSection("Snapshot");
text.push("```yaml");
if (tabSnapshot.ariaSnapshotDiff !== void 0)
text.push(tabSnapshot.ariaSnapshotDiff);
else
text.push(tabSnapshot.ariaSnapshot);
text.push("```");
}
if (tabSnapshot?.events.filter((event) => event.type !== "request").length) {
const text = addSection("Events");
for (const event of tabSnapshot.events) {
if (event.type === "console") {
if ((0, import_tab.shouldIncludeMessage)(this._context.config.console.level, event.message.type))
text.push(`- ${trimMiddle(event.message.toString(), 100)}`);
} else if (event.type === "download-start") {
text.push(`- Downloading file ${event.download.download.suggestedFilename()} ...`);
} else if (event.type === "download-finish") {
text.push(`- Downloaded file ${event.download.download.suggestedFilename()} to "${rootPath ? import_path.default.relative(rootPath, event.download.outputFile) : event.download.outputFile}"`);
}
}
}
const allText = sections.flatMap((section) => {
const content2 = [];
content2.push(`### ${section.title}`);
content2.push(...section.content);
content2.push("");
return content2;
}).join("\n");
const content = [
{ type: "text", text: response.join("\n") }
{
type: "text",
text: this._redactText(allText)
}
];
if (this._context.config.imageResponses !== "omit") {
for (const image of this._images)
content.push({ type: "image", data: options.omitBlobs ? "<blob>" : image.data.toString("base64"), mimeType: image.contentType });
content.push({ type: "image", data: image.data.toString("base64"), mimeType: image.contentType });
}
this._redactSecrets(content);
return { content, isError: this._isError };
return {
content,
...this._errors.length > 0 ? { isError: true } : {}
};
}
_redactSecrets(content) {
if (!this._context.config.secrets)
return;
for (const item of content) {
if (item.type !== "text")
continue;
for (const [secretName, secretValue] of Object.entries(this._context.config.secrets))
item.text = item.text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
}
_redactText(text) {
for (const [secretName, secretValue] of Object.entries(this._context.config.secrets ?? {}))
text = text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
return text;
}
}
function renderTabSnapshot(tabSnapshot, includeSnapshot) {
const lines = [];
if (tabSnapshot.consoleMessages.length) {
lines.push(`### New console messages`);
for (const message of tabSnapshot.consoleMessages)
lines.push(`- ${trim(message.toString(), 100)}`);
lines.push("");
}
if (tabSnapshot.downloads.length) {
lines.push(`### Downloads`);
for (const entry of tabSnapshot.downloads) {
if (entry.finished)
lines.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`);
else
lines.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
}
lines.push("");
}
if (includeSnapshot === "incremental" && tabSnapshot.ariaSnapshotDiff === "") {
return lines.join("\n");
}
lines.push(`### Page state`);
lines.push(`- Page URL: ${tabSnapshot.url}`);
lines.push(`- Page Title: ${tabSnapshot.title}`);
if (includeSnapshot !== "none") {
lines.push(`- Page Snapshot:`);
lines.push("```yaml");
if (includeSnapshot === "incremental" && tabSnapshot.ariaSnapshotDiff !== void 0)
lines.push(tabSnapshot.ariaSnapshotDiff);
else
lines.push(tabSnapshot.ariaSnapshot);
lines.push("```");
}
return lines.join("\n");
}
function renderTabsMarkdown(tabs, force = false) {
if (tabs.length === 1 && !force)
return [];
if (!tabs.length) {
return [
"### Open tabs",
'No open tabs. Use the "browser_navigate" tool to navigate to a page first.',
""
];
}
const lines = ["### Open tabs"];
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
const current = tab.isCurrentTab() ? " (current)" : "";
lines.push(`- ${i}:${current} [${tab.lastTitle()}] (${tab.page.url()})`);
}
lines.push("");
function renderTabMarkdown(tab) {
const lines = [`- Page URL: ${tab.url}`];
if (tab.title)
lines.push(`- Page Title: ${tab.title}`);
return lines;
}
function trim(text, maxLength) {
function renderTabsMarkdown(tabs) {
if (!tabs.length)
return ['No open tabs. Use the "browser_navigate" tool to navigate to a page first.'];
const lines = [];
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
const current = tab.current ? " (current)" : "";
lines.push(`- ${i}:${current} [${tab.title}](${tab.url})`);
}
return lines;
}
function trimMiddle(text, maxLength) {
if (text.length <= maxLength)
return text;
return text.slice(0, maxLength) + "...";
return text.slice(0, Math.floor(maxLength / 2)) + "..." + text.slice(-3 - Math.floor(maxLength / 2));
}
function parseSections(text) {
const sections = /* @__PURE__ */ new Map();
@@ -207,31 +243,36 @@ function parseResponse(response) {
return void 0;
const text = response.content[0].text;
const sections = parseSections(text);
const error = sections.get("Error");
const result = sections.get("Result");
const code = sections.get("Ran Playwright code");
const tabs = sections.get("Open tabs");
const pageState = sections.get("Page state");
const consoleMessages = sections.get("New console messages");
const page = sections.get("Page");
const snapshot = sections.get("Snapshot");
const events = sections.get("Events");
const modalState = sections.get("Modal state");
const downloads = sections.get("Downloads");
const codeNoFrame = code?.replace(/^```js\n/, "").replace(/\n```$/, "");
const isError = response.isError;
const attachments = response.content.slice(1);
const attachments = response.content.length > 1 ? response.content.slice(1) : void 0;
return {
result,
error,
code: codeNoFrame,
tabs,
pageState,
consoleMessages,
page,
snapshot,
events,
modalState,
downloads,
isError,
attachments
attachments,
text
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Response,
parseResponse,
renderTabMarkdown,
renderTabsMarkdown,
requestDebug
});

View File

@@ -33,124 +33,39 @@ __export(sessionLog_exports, {
module.exports = __toCommonJS(sessionLog_exports);
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
var import_log = require("../log");
var import_config = require("./config");
var import_response = require("./response");
class SessionLog {
constructor(sessionFolder) {
this._ordinal = 0;
this._pendingEntries = [];
this._sessionFileQueue = Promise.resolve();
this._folder = sessionFolder;
this._file = import_path.default.join(this._folder, "session.md");
}
static async create(config, clientInfo) {
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code", reason: "Saving session" });
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code", title: "Saving session" });
await import_fs.default.promises.mkdir(sessionFolder, { recursive: true });
console.error(`Session: ${sessionFolder}`);
return new SessionLog(sessionFolder);
}
logResponse(response) {
const entry = {
timestamp: performance.now(),
toolCall: {
toolName: response.toolName,
toolArgs: response.toolArgs,
result: response.result(),
isError: response.isError()
},
code: response.code(),
tabSnapshot: response.tabSnapshot()
};
this._appendEntry(entry);
}
logUserAction(action, tab, code, isUpdate) {
code = code.trim();
if (isUpdate) {
const lastEntry = this._pendingEntries[this._pendingEntries.length - 1];
if (lastEntry?.userAction?.name === action.name) {
lastEntry.userAction = action;
lastEntry.code = code;
return;
}
}
if (action.name === "navigate") {
const lastEntry = this._pendingEntries[this._pendingEntries.length - 1];
if (lastEntry?.tabSnapshot?.url === action.url)
return;
}
const entry = {
timestamp: performance.now(),
userAction: action,
code,
tabSnapshot: {
url: tab.page.url(),
title: "",
ariaSnapshot: action.ariaSnapshot || "",
modalStates: [],
consoleMessages: [],
downloads: []
}
};
this._appendEntry(entry);
}
_appendEntry(entry) {
this._pendingEntries.push(entry);
if (this._flushEntriesTimeout)
clearTimeout(this._flushEntriesTimeout);
this._flushEntriesTimeout = setTimeout(() => this._flushEntries(), 1e3);
}
async _flushEntries() {
clearTimeout(this._flushEntriesTimeout);
const entries = this._pendingEntries;
this._pendingEntries = [];
logResponse(toolName, toolArgs, responseObject) {
const parsed = (0, import_response.parseResponse)(responseObject);
if (parsed)
delete parsed.text;
const lines = [""];
for (const entry of entries) {
const ordinal = (++this._ordinal).toString().padStart(3, "0");
if (entry.toolCall) {
lines.push(
`### Tool call: ${entry.toolCall.toolName}`,
`- Args`,
"```json",
JSON.stringify(entry.toolCall.toolArgs, null, 2),
"```"
);
if (entry.toolCall.result) {
lines.push(
entry.toolCall.isError ? `- Error` : `- Result`,
"```",
entry.toolCall.result,
"```"
);
}
}
if (entry.userAction) {
const actionData = { ...entry.userAction };
delete actionData.ariaSnapshot;
delete actionData.selector;
delete actionData.signals;
lines.push(
`### User action: ${entry.userAction.name}`,
`- Args`,
"```json",
JSON.stringify(actionData, null, 2),
"```"
);
}
if (entry.code) {
lines.push(
`- Code`,
"```js",
entry.code,
"```"
);
}
if (entry.tabSnapshot) {
const fileName = `${ordinal}.snapshot.yml`;
import_fs.default.promises.writeFile(import_path.default.join(this._folder, fileName), entry.tabSnapshot.ariaSnapshot).catch(import_log.logUnhandledError);
lines.push(`- Snapshot: ${fileName}`);
}
lines.push("", "");
lines.push(
`### Tool call: ${toolName}`,
`- Args`,
"```json",
JSON.stringify(toolArgs, null, 2),
"```"
);
if (parsed) {
lines.push(`- Result`);
lines.push("```json");
lines.push(JSON.stringify(parsed, null, 2));
lines.push("```");
}
lines.push("");
this._sessionFileQueue = this._sessionFileQueue.then(() => import_fs.default.promises.appendFile(this._file, lines.join("\n")));
}
}

View File

@@ -20,7 +20,8 @@ var tab_exports = {};
__export(tab_exports, {
Tab: () => Tab,
TabEvents: () => TabEvents,
renderModalStates: () => renderModalStates
renderModalStates: () => renderModalStates,
shouldIncludeMessage: () => shouldIncludeMessage
});
module.exports = __toCommonJS(tab_exports);
var import_events = require("events");
@@ -36,19 +37,20 @@ const TabEvents = {
class Tab extends import_events.EventEmitter {
constructor(context, page, onPageClose) {
super();
this._lastTitle = "about:blank";
this._lastHeader = { title: "about:blank", url: "about:blank", current: false };
this._consoleMessages = [];
this._recentConsoleMessages = [];
this._downloads = [];
this._requests = /* @__PURE__ */ new Set();
this._modalStates = [];
this._downloads = [];
this._needsFullSnapshot = false;
this._eventEntries = [];
this._recentEventEntries = [];
this.context = context;
this.page = page;
this._onPageClose = onPageClose;
page.on("console", (event) => this._handleConsoleMessage(messageToConsoleMessage(event)));
page.on("pageerror", (error) => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
page.on("request", (request) => this._requests.add(request));
page.on("request", (request) => this._handleRequest(request));
page.on("close", () => this._onClose());
page.on("filechooser", (chooser) => {
this.setModalState({
@@ -65,7 +67,7 @@ class Tab extends import_events.EventEmitter {
page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation);
page.setDefaultTimeout(this.context.config.timeouts.action);
page[tabSymbol] = this;
this.initializedPromise = this._initialize();
this._initializedPromise = this._initialize();
}
static forPage(page) {
return page[tabSymbol];
@@ -105,9 +107,6 @@ class Tab extends import_events.EventEmitter {
clearModalState(modalState) {
this._modalStates = this._modalStates.filter((state) => state !== modalState);
}
modalStatesMarkdown() {
return renderModalStates(this.context, this.modalStates());
}
_dialogShown(dialog) {
this.setModalState({
type: "dialog",
@@ -120,53 +119,68 @@ class Tab extends import_events.EventEmitter {
const entry = {
download,
finished: false,
outputFile: await this.context.outputFile(download.suggestedFilename(), { origin: "web", reason: "Saving download" })
outputFile: await this.context.outputFile(download.suggestedFilename(), { origin: "web", title: "Saving download" })
};
this._downloads.push(entry);
this._addLogEntry({ type: "download-start", wallTime: Date.now(), download: entry });
await download.saveAs(entry.outputFile);
entry.finished = true;
this._addLogEntry({ type: "download-finish", wallTime: Date.now(), download: entry });
}
_clearCollectedArtifacts() {
this._consoleMessages.length = 0;
this._recentConsoleMessages.length = 0;
this._downloads.length = 0;
this._requests.clear();
this._eventEntries.length = 0;
this._recentEventEntries.length = 0;
}
_handleRequest(request) {
this._requests.add(request);
this._addLogEntry({ type: "request", wallTime: Date.now(), request });
}
_handleConsoleMessage(message) {
this._consoleMessages.push(message);
this._recentConsoleMessages.push(message);
this._addLogEntry({ type: "console", wallTime: Date.now(), message });
}
_addLogEntry(entry) {
this._eventEntries.push(entry);
this._recentEventEntries.push(entry);
}
_onClose() {
this._clearCollectedArtifacts();
this._onPageClose(this);
}
async updateTitle() {
async headerSnapshot() {
let title;
await this._raceAgainstModalStates(async () => {
this._lastTitle = await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.title());
title = await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.title());
});
}
lastTitle() {
return this._lastTitle;
if (this._lastHeader.title !== title || this._lastHeader.url !== this.page.url() || this._lastHeader.current !== this.isCurrentTab()) {
this._lastHeader = { title: title ?? "", url: this.page.url(), current: this.isCurrentTab() };
return { ...this._lastHeader, changed: true };
}
return { ...this._lastHeader, changed: false };
}
isCurrentTab() {
return this === this.context.currentTab();
}
async waitForLoadState(state, options) {
await this._initializedPromise;
await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.waitForLoadState(state, options).catch(import_log.logUnhandledError));
}
async navigate(url) {
await this._initializedPromise;
this._clearCollectedArtifacts();
const downloadEvent = (0, import_utils2.callOnPageNoTrace)(this.page, (page) => page.waitForEvent("download").catch(import_log.logUnhandledError));
const { promise: downloadEvent, abort: abortDownloadEvent } = (0, import_utils2.eventWaiter)(this.page, "download", 3e3);
try {
await this.page.goto(url, { waitUntil: "domcontentloaded" });
abortDownloadEvent();
} catch (_e) {
const e = _e;
const mightBeDownload = e.message.includes("net::ERR_ABORTED") || e.message.includes("Download is starting");
if (!mightBeDownload)
throw e;
const download = await Promise.race([
downloadEvent,
new Promise((resolve) => setTimeout(resolve, 3e3))
]);
const download = await downloadEvent;
if (!download)
throw e;
await new Promise((resolve) => setTimeout(resolve, 500));
@@ -174,40 +188,36 @@ class Tab extends import_events.EventEmitter {
}
await this.waitForLoadState("load", { timeout: 5e3 });
}
async consoleMessages(type) {
await this.initializedPromise;
return this._consoleMessages.filter((message) => type ? message.type === type : true);
async consoleMessages(level) {
await this._initializedPromise;
return this._consoleMessages.filter((message) => shouldIncludeMessage(level, message.type));
}
async requests() {
await this.initializedPromise;
await this._initializedPromise;
return this._requests;
}
async captureSnapshot() {
await this._initializedPromise;
let tabSnapshot;
const modalStates = await this._raceAgainstModalStates(async () => {
const snapshot = await this.page._snapshotForAI({ track: "response" });
tabSnapshot = {
url: this.page.url(),
title: await this.page.title(),
ariaSnapshot: snapshot.full,
ariaSnapshotDiff: this._needsFullSnapshot ? void 0 : snapshot.incremental,
modalStates: [],
consoleMessages: [],
downloads: this._downloads
events: []
};
});
if (tabSnapshot) {
tabSnapshot.consoleMessages = this._recentConsoleMessages;
this._recentConsoleMessages = [];
tabSnapshot.events = this._recentEventEntries;
this._recentEventEntries = [];
}
this._needsFullSnapshot = !tabSnapshot;
return tabSnapshot ?? {
url: this.page.url(),
title: "",
ariaSnapshot: "",
ariaSnapshotDiff: "",
modalStates,
consoleMessages: [],
downloads: []
events: []
};
}
_javaScriptBlocked() {
@@ -228,15 +238,20 @@ class Tab extends import_events.EventEmitter {
]);
}
async waitForCompletion(callback) {
await this._initializedPromise;
await this._raceAgainstModalStates(() => (0, import_utils2.waitForCompletion)(this, callback));
}
async refLocator(params) {
await this._initializedPromise;
return (await this.refLocators([params]))[0];
}
async refLocators(params) {
await this._initializedPromise;
return Promise.all(params.map(async (param) => {
try {
const locator = this.page.locator(`aria-ref=${param.ref}`).describe(param.element);
let locator = this.page.locator(`aria-ref=${param.ref}`);
if (param.element)
locator = locator.describe(param.element);
const { resolvedSelector } = await locator._resolveSelector();
return { locator, resolved: (0, import_utils.asLocator)("javascript", resolvedSelector) };
} catch (e) {
@@ -250,7 +265,8 @@ class Tab extends import_events.EventEmitter {
return;
}
await (0, import_utils2.callOnPageNoTrace)(this.page, (page) => {
return page.evaluate(() => new Promise((f) => setTimeout(f, 1e3)));
return page.evaluate(() => new Promise((f) => setTimeout(f, 1e3))).catch(() => {
});
});
}
}
@@ -275,18 +291,53 @@ function pageErrorToConsoleMessage(errorOrValue) {
toString: () => String(errorOrValue)
};
}
function renderModalStates(context, modalStates) {
const result = ["### Modal state"];
function renderModalStates(modalStates) {
const result = [];
if (modalStates.length === 0)
result.push("- There is no modal state present");
for (const state of modalStates)
result.push(`- [${state.description}]: can be handled by the "${state.clearedBy}" tool`);
return result;
}
const consoleMessageLevels = ["error", "warning", "info", "debug"];
function shouldIncludeMessage(thresholdLevel, type) {
const messageLevel = consoleLevelForMessageType(type);
return consoleMessageLevels.indexOf(messageLevel) <= consoleMessageLevels.indexOf(thresholdLevel);
}
function consoleLevelForMessageType(type) {
switch (type) {
case "assert":
case "error":
return "error";
case "warning":
return "warning";
case "count":
case "dir":
case "dirxml":
case "info":
case "log":
case "table":
case "time":
case "timeEnd":
return "info";
case "clear":
case "debug":
case "endGroup":
case "profile":
case "profileEnd":
case "startGroup":
case "startGroupCollapsed":
case "trace":
return "debug";
default:
return "info";
}
}
const tabSymbol = Symbol("tabSymbol");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Tab,
TabEvents,
renderModalStates
renderModalStates,
shouldIncludeMessage
});

View File

@@ -43,6 +43,7 @@ var import_keyboard = __toESM(require("./tools/keyboard"));
var import_mouse = __toESM(require("./tools/mouse"));
var import_navigate = __toESM(require("./tools/navigate"));
var import_network = __toESM(require("./tools/network"));
var import_open = __toESM(require("./tools/open"));
var import_pdf = __toESM(require("./tools/pdf"));
var import_runCode = __toESM(require("./tools/runCode"));
var import_snapshot = __toESM(require("./tools/snapshot"));
@@ -60,9 +61,10 @@ const browserTools = [
...import_form.default,
...import_install.default,
...import_keyboard.default,
...import_mouse.default,
...import_navigate.default,
...import_network.default,
...import_mouse.default,
...import_open.default,
...import_pdf.default,
...import_runCode.default,
...import_screenshot.default,

View File

@@ -21,20 +21,22 @@ __export(common_exports, {
default: () => common_default
});
module.exports = __toCommonJS(common_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
var import_response = require("../response");
const close = (0, import_tool.defineTool)({
capability: "core",
schema: {
name: "browser_close",
title: "Close browser",
description: "Close the page",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({}),
type: "action"
},
handle: async (context, params, response) => {
await context.closeBrowserContext();
response.setIncludeTabs();
const result = (0, import_response.renderTabsMarkdown)([]);
response.addTextResult(result.join("\n"));
response.addCode(`await page.close()`);
}
});
@@ -44,9 +46,9 @@ const resize = (0, import_tool.defineTabTool)({
name: "browser_resize",
title: "Resize browser window",
description: "Resize the browser window",
inputSchema: import_bundle.z.object({
width: import_bundle.z.number().describe("Width of the browser window"),
height: import_bundle.z.number().describe("Height of the browser window")
inputSchema: import_mcpBundle.z.object({
width: import_mcpBundle.z.number().describe("Width of the browser window"),
height: import_mcpBundle.z.number().describe("Height of the browser window")
}),
type: "action"
},

View File

@@ -21,7 +21,7 @@ __export(console_exports, {
default: () => console_default
});
module.exports = __toCommonJS(console_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const console = (0, import_tool.defineTabTool)({
capability: "core",
@@ -29,14 +29,16 @@ const console = (0, import_tool.defineTabTool)({
name: "browser_console_messages",
title: "Get console messages",
description: "Returns all console messages",
inputSchema: import_bundle.z.object({
onlyErrors: import_bundle.z.boolean().optional().describe("Only return error messages")
inputSchema: import_mcpBundle.z.object({
level: import_mcpBundle.z.enum(["error", "warning", "info", "debug"]).default("info").describe('Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info".'),
filename: import_mcpBundle.z.string().optional().describe("Filename to save the console messages to. If not provided, messages are returned as text.")
}),
type: "readOnly"
},
handle: async (tab, params, response) => {
const messages = await tab.consoleMessages(params.onlyErrors ? "error" : void 0);
messages.map((message) => response.addResult(message.toString()));
const messages = await tab.consoleMessages(params.level);
const text = messages.map((message) => message.toString()).join("\n");
await response.addResult({ text, suggestedFilename: params.filename });
}
});
var console_default = [

View File

@@ -22,7 +22,7 @@ __export(dialogs_exports, {
handleDialog: () => handleDialog
});
module.exports = __toCommonJS(dialogs_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const handleDialog = (0, import_tool.defineTabTool)({
capability: "core",
@@ -30,9 +30,9 @@ const handleDialog = (0, import_tool.defineTabTool)({
name: "browser_handle_dialog",
title: "Handle a dialog",
description: "Handle a dialog",
inputSchema: import_bundle.z.object({
accept: import_bundle.z.boolean().describe("Whether to accept the dialog."),
promptText: import_bundle.z.string().optional().describe("The text of the prompt in case of a prompt dialog.")
inputSchema: import_mcpBundle.z.object({
accept: import_mcpBundle.z.boolean().describe("Whether to accept the dialog."),
promptText: import_mcpBundle.z.string().optional().describe("The text of the prompt in case of a prompt dialog.")
}),
type: "action"
},

View File

@@ -1,9 +1,7 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -17,27 +15,20 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var evaluate_exports = {};
__export(evaluate_exports, {
default: () => evaluate_default
});
module.exports = __toCommonJS(evaluate_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_utils = require("playwright-core/lib/utils");
var import_tool = require("./tool");
var javascript = __toESM(require("../codegen"));
const evaluateSchema = import_bundle.z.object({
function: import_bundle.z.string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
element: import_bundle.z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
ref: import_bundle.z.string().optional().describe("Exact target element reference from the page snapshot")
const evaluateSchema = import_mcpBundle.z.object({
function: import_mcpBundle.z.string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
element: import_mcpBundle.z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
ref: import_mcpBundle.z.string().optional().describe("Exact target element reference from the page snapshot"),
filename: import_mcpBundle.z.string().optional().describe("Filename to save the result to. If not provided, result is returned as JSON string.")
});
const evaluate = (0, import_tool.defineTabTool)({
capability: "core",
@@ -53,14 +44,15 @@ const evaluate = (0, import_tool.defineTabTool)({
let locator;
if (params.ref && params.element) {
locator = await tab.refLocator({ ref: params.ref, element: params.element });
response.addCode(`await page.${locator.resolved}.evaluate(${javascript.quote(params.function)});`);
response.addCode(`await page.${locator.resolved}.evaluate(${(0, import_utils.escapeWithQuotes)(params.function)});`);
} else {
response.addCode(`await page.evaluate(${javascript.quote(params.function)});`);
response.addCode(`await page.evaluate(${(0, import_utils.escapeWithQuotes)(params.function)});`);
}
await tab.waitForCompletion(async () => {
const receiver = locator?.locator ?? tab.page;
const result = await receiver._evaluateFunction(params.function);
response.addResult(JSON.stringify(result, null, 2) || "undefined");
const text = JSON.stringify(result, null, 2) || "undefined";
await response.addResult({ text, suggestedFilename: params.filename });
});
}
});

View File

@@ -22,7 +22,7 @@ __export(files_exports, {
uploadFile: () => uploadFile
});
module.exports = __toCommonJS(files_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const uploadFile = (0, import_tool.defineTabTool)({
capability: "core",
@@ -30,8 +30,8 @@ const uploadFile = (0, import_tool.defineTabTool)({
name: "browser_file_upload",
title: "Upload files",
description: "Upload one or multiple files",
inputSchema: import_bundle.z.object({
paths: import_bundle.z.array(import_bundle.z.string()).optional().describe("The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled.")
inputSchema: import_mcpBundle.z.object({
paths: import_mcpBundle.z.array(import_mcpBundle.z.string()).optional().describe("The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled.")
}),
type: "action"
},

View File

@@ -1,9 +1,7 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -17,35 +15,27 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var form_exports = {};
__export(form_exports, {
default: () => form_default
});
module.exports = __toCommonJS(form_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_utils = require("playwright-core/lib/utils");
var import_tool = require("./tool");
var codegen = __toESM(require("../codegen"));
const fillForm = (0, import_tool.defineTabTool)({
capability: "core",
schema: {
name: "browser_fill_form",
title: "Fill form",
description: "Fill multiple form fields",
inputSchema: import_bundle.z.object({
fields: import_bundle.z.array(import_bundle.z.object({
name: import_bundle.z.string().describe("Human-readable field name"),
type: import_bundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the field"),
ref: import_bundle.z.string().describe("Exact target field reference from the page snapshot"),
value: import_bundle.z.string().describe("Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.")
inputSchema: import_mcpBundle.z.object({
fields: import_mcpBundle.z.array(import_mcpBundle.z.object({
name: import_mcpBundle.z.string().describe("Human-readable field name"),
type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the field"),
ref: import_mcpBundle.z.string().describe("Exact target field reference from the page snapshot"),
value: import_mcpBundle.z.string().describe("Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.")
})).describe("Fields to fill in")
}),
type: "input"
@@ -63,7 +53,7 @@ const fillForm = (0, import_tool.defineTabTool)({
response.addCode(`${locatorSource}.setChecked(${field.value});`);
} else if (field.type === "combobox") {
await locator.selectOption({ label: field.value });
response.addCode(`${locatorSource}.selectOption(${codegen.quote(field.value)});`);
response.addCode(`${locatorSource}.selectOption(${(0, import_utils.escapeWithQuotes)(field.value)});`);
}
}
}

View File

@@ -33,15 +33,16 @@ __export(install_exports, {
module.exports = __toCommonJS(install_exports);
var import_child_process = require("child_process");
var import_path = __toESM(require("path"));
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
var import_response = require("../response");
const install = (0, import_tool.defineTool)({
capability: "core-install",
schema: {
name: "browser_install",
title: "Install the browser specified in the config",
description: "Install the browser specified in the config. Call this if you get an error about the browser not being installed.",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({}),
type: "action"
},
handle: async (context, params, response) => {
@@ -61,7 +62,9 @@ const install = (0, import_tool.defineTool)({
reject(new Error(`Failed to install browser: ${output.join("")}`));
});
});
response.setIncludeTabs();
const tabHeaders = await Promise.all(context.tabs().map((tab) => tab.headerSnapshot()));
const result = (0, import_response.renderTabsMarkdown)(tabHeaders);
response.addTextResult(result.join("\n"));
}
});
var install_default = [

View File

@@ -21,7 +21,7 @@ __export(keyboard_exports, {
default: () => keyboard_default
});
module.exports = __toCommonJS(keyboard_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
var import_snapshot = require("./snapshot");
const pressKey = (0, import_tool.defineTabTool)({
@@ -30,24 +30,46 @@ const pressKey = (0, import_tool.defineTabTool)({
name: "browser_press_key",
title: "Press a key",
description: "Press a key on the keyboard",
inputSchema: import_bundle.z.object({
key: import_bundle.z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`")
inputSchema: import_mcpBundle.z.object({
key: import_mcpBundle.z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`")
}),
type: "input"
},
handle: async (tab, params, response) => {
response.setIncludeSnapshot();
response.addCode(`// Press ${params.key}`);
response.addCode(`await page.keyboard.press('${params.key}');`);
await tab.waitForCompletion(async () => {
await tab.page.keyboard.press(params.key);
});
await tab.page.keyboard.press(params.key);
}
});
const pressSequentially = (0, import_tool.defineTabTool)({
capability: "internal",
schema: {
name: "browser_press_sequentially",
title: "Press sequentially",
description: "Press text sequentially on the keyboard",
inputSchema: import_mcpBundle.z.object({
text: import_mcpBundle.z.string().describe("Text to press sequentially"),
submit: import_mcpBundle.z.boolean().optional().describe("Whether to submit entered text (press Enter after)")
}),
type: "input"
},
handle: async (tab, params, response) => {
response.addCode(`// Press ${params.text}`);
response.addCode(`await page.keyboard.type('${params.text}');`);
await tab.page.keyboard.type(params.text);
if (params.submit) {
response.addCode(`await page.keyboard.press('Enter');`);
response.setIncludeSnapshot();
await tab.waitForCompletion(async () => {
await tab.page.keyboard.press("Enter");
});
}
}
});
const typeSchema = import_snapshot.elementSchema.extend({
text: import_bundle.z.string().describe("Text to type into the element"),
submit: import_bundle.z.boolean().optional().describe("Whether to submit entered text (press Enter after)"),
slowly: import_bundle.z.boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
text: import_mcpBundle.z.string().describe("Text to type into the element"),
submit: import_mcpBundle.z.boolean().optional().describe("Whether to submit entered text (press Enter after)"),
slowly: import_mcpBundle.z.boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
});
const type = (0, import_tool.defineTabTool)({
capability: "core",
@@ -80,5 +102,6 @@ const type = (0, import_tool.defineTabTool)({
});
var keyboard_default = [
pressKey,
type
type,
pressSequentially
];

View File

@@ -21,10 +21,10 @@ __export(mouse_exports, {
default: () => mouse_default
});
module.exports = __toCommonJS(mouse_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const elementSchema = import_bundle.z.object({
element: import_bundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element")
const elementSchema = import_mcpBundle.z.object({
element: import_mcpBundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element")
});
const mouseMove = (0, import_tool.defineTabTool)({
capability: "vision",
@@ -33,8 +33,8 @@ const mouseMove = (0, import_tool.defineTabTool)({
title: "Move mouse",
description: "Move mouse to a given position",
inputSchema: elementSchema.extend({
x: import_bundle.z.number().describe("X coordinate"),
y: import_bundle.z.number().describe("Y coordinate")
x: import_mcpBundle.z.number().describe("X coordinate"),
y: import_mcpBundle.z.number().describe("Y coordinate")
}),
type: "input"
},
@@ -53,8 +53,8 @@ const mouseClick = (0, import_tool.defineTabTool)({
title: "Click",
description: "Click left mouse button at a given position",
inputSchema: elementSchema.extend({
x: import_bundle.z.number().describe("X coordinate"),
y: import_bundle.z.number().describe("Y coordinate")
x: import_mcpBundle.z.number().describe("X coordinate"),
y: import_mcpBundle.z.number().describe("Y coordinate")
}),
type: "input"
},
@@ -78,10 +78,10 @@ const mouseDrag = (0, import_tool.defineTabTool)({
title: "Drag mouse",
description: "Drag left mouse button to a given position",
inputSchema: elementSchema.extend({
startX: import_bundle.z.number().describe("Start X coordinate"),
startY: import_bundle.z.number().describe("Start Y coordinate"),
endX: import_bundle.z.number().describe("End X coordinate"),
endY: import_bundle.z.number().describe("End Y coordinate")
startX: import_mcpBundle.z.number().describe("Start X coordinate"),
startY: import_mcpBundle.z.number().describe("Start Y coordinate"),
endX: import_mcpBundle.z.number().describe("End X coordinate"),
endY: import_mcpBundle.z.number().describe("End Y coordinate")
}),
type: "input"
},

View File

@@ -21,7 +21,7 @@ __export(navigate_exports, {
default: () => navigate_default
});
module.exports = __toCommonJS(navigate_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const navigate = (0, import_tool.defineTool)({
capability: "core",
@@ -29,14 +29,23 @@ const navigate = (0, import_tool.defineTool)({
name: "browser_navigate",
title: "Navigate to a URL",
description: "Navigate to a URL",
inputSchema: import_bundle.z.object({
url: import_bundle.z.string().describe("The URL to navigate to")
inputSchema: import_mcpBundle.z.object({
url: import_mcpBundle.z.string().describe("The URL to navigate to")
}),
type: "action"
},
handle: async (context, params, response) => {
const tab = await context.ensureTab();
await tab.navigate(params.url);
let url = params.url;
try {
new URL(url);
} catch (e) {
if (url.startsWith("localhost"))
url = "http://" + url;
else
url = "https://" + url;
}
await tab.navigate(url);
response.setIncludeSnapshot();
response.addCode(`await page.goto('${params.url}');`);
}
@@ -47,7 +56,7 @@ const goBack = (0, import_tool.defineTabTool)({
name: "browser_navigate_back",
title: "Go back",
description: "Go back to the previous page",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({}),
type: "action"
},
handle: async (tab, params, response) => {

View File

@@ -21,7 +21,7 @@ __export(network_exports, {
default: () => network_default
});
module.exports = __toCommonJS(network_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const requests = (0, import_tool.defineTabTool)({
capability: "core",
@@ -29,24 +29,33 @@ const requests = (0, import_tool.defineTabTool)({
name: "browser_network_requests",
title: "List network requests",
description: "Returns all network requests since loading the page",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({
includeStatic: import_mcpBundle.z.boolean().default(false).describe("Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false."),
filename: import_mcpBundle.z.string().optional().describe("Filename to save the network requests to. If not provided, requests are returned as text.")
}),
type: "readOnly"
},
handle: async (tab, params, response) => {
const requests2 = await tab.requests();
for (const request of requests2)
response.addResult(await renderRequest(request));
const text = [];
for (const request of requests2) {
const rendered = await renderRequest(request, params.includeStatic);
if (rendered)
text.push(rendered);
}
await response.addResult({ text: text.join("\n"), suggestedFilename: params.filename });
}
});
async function renderRequest(request) {
async function renderRequest(request, includeStatic) {
const response = request._hasResponse ? await request.response() : void 0;
const isStaticRequest = ["document", "stylesheet", "image", "media", "font", "script", "manifest"].includes(request.resourceType());
const isSuccessfulRequest = !response || response.status() < 400;
if (isStaticRequest && isSuccessfulRequest && !includeStatic)
return void 0;
const result = [];
result.push(`[${request.method().toUpperCase()}] ${request.url()}`);
const hasResponse = request._hasResponse;
if (hasResponse) {
const response = await request.response();
if (response)
result.push(`=> [${response.status()}] ${response.statusText()}`);
}
if (response)
result.push(`=> [${response.status()}] ${response.statusText()}`);
return result.join(" ");
}
var network_default = [

57
node_modules/playwright/lib/mcp/browser/tools/open.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var open_exports = {};
__export(open_exports, {
default: () => open_default
});
module.exports = __toCommonJS(open_exports);
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const open = (0, import_tool.defineTool)({
capability: "internal",
schema: {
name: "browser_open",
title: "Open URL",
description: "Open a URL in the browser",
inputSchema: import_mcpBundle.z.object({
url: import_mcpBundle.z.string().describe("The URL to open"),
headed: import_mcpBundle.z.boolean().optional().describe("Run browser in headed mode")
}),
type: "action"
},
handle: async (context, params, response) => {
const forceHeadless = params.headed ? "headed" : "headless";
const tab = await context.ensureTab({ forceHeadless });
let url = params.url;
try {
new URL(url);
} catch (e) {
if (url.startsWith("localhost"))
url = "http://" + url;
else
url = "https://" + url;
}
await tab.navigate(url);
response.setIncludeSnapshot();
response.addCode(`await page.goto('${params.url}');`);
}
});
var open_default = [
open
];

View File

@@ -1,9 +1,7 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -17,26 +15,18 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var pdf_exports = {};
__export(pdf_exports, {
default: () => pdf_default
});
module.exports = __toCommonJS(pdf_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_utils = require("playwright-core/lib/utils");
var import_tool = require("./tool");
var javascript = __toESM(require("../codegen"));
var import_utils = require("./utils");
const pdfSchema = import_bundle.z.object({
filename: import_bundle.z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. Prefer relative file names to stay within the output directory.")
var import_utils2 = require("./utils");
const pdfSchema = import_mcpBundle.z.object({
filename: import_mcpBundle.z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. Prefer relative file names to stay within the output directory.")
});
const pdf = (0, import_tool.defineTabTool)({
capability: "pdf",
@@ -48,10 +38,10 @@ const pdf = (0, import_tool.defineTabTool)({
type: "readOnly"
},
handle: async (tab, params, response) => {
const fileName = await tab.context.outputFile(params.filename ?? (0, import_utils.dateAsFileName)("pdf"), { origin: "llm", reason: "Saving PDF" });
response.addCode(`await page.pdf(${javascript.formatObject({ path: fileName })});`);
response.addResult(`Saved page as ${fileName}`);
await tab.page.pdf({ path: fileName });
const data = await tab.page.pdf();
const suggestedFilename = params.filename ?? (0, import_utils2.dateAsFileName)("pdf");
await response.addResult({ data, title: "Page as pdf", suggestedFilename });
response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: suggestedFilename })});`);
}
});
var pdf_default = [

View File

@@ -33,10 +33,11 @@ __export(runCode_exports, {
module.exports = __toCommonJS(runCode_exports);
var import_vm = __toESM(require("vm"));
var import_utils = require("playwright-core/lib/utils");
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const codeSchema = import_bundle.z.object({
code: import_bundle.z.string().describe(`Playwright code snippet to run. The snippet should access the \`page\` object to interact with the page. Can make multiple statements. For example: \`await page.getByRole('button', { name: 'Submit' }).click();\``)
const codeSchema = import_mcpBundle.z.object({
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(); }\``),
filename: import_mcpBundle.z.string().optional().describe("Filename to save the result to. If not provided, result is returned as JSON string.")
});
const runCode = (0, import_tool.defineTabTool)({
capability: "core",
@@ -49,7 +50,7 @@ const runCode = (0, import_tool.defineTabTool)({
},
handle: async (tab, params, response) => {
response.setIncludeSnapshot();
response.addCode(params.code);
response.addCode(`await (${params.code})(page);`);
const __end__ = new import_utils.ManualPromise();
const context = {
page: tab.page,
@@ -59,14 +60,16 @@ const runCode = (0, import_tool.defineTabTool)({
await tab.waitForCompletion(async () => {
const snippet = `(async () => {
try {
${params.code};
__end__.resolve();
const result = await (${params.code})(page);
__end__.resolve(JSON.stringify(result));
} catch (e) {
__end__.reject(e);
}
})()`;
import_vm.default.runInContext(snippet, context);
await __end__;
await import_vm.default.runInContext(snippet, context);
const result = await __end__;
if (typeof result === "string")
await response.addResult({ text: result, suggestedFilename: params.filename });
});
}
});

View File

@@ -1,9 +1,7 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var screenshot_exports = {};
__export(screenshot_exports, {
@@ -32,19 +22,18 @@ __export(screenshot_exports, {
scaleImageToFitMessage: () => scaleImageToFitMessage
});
module.exports = __toCommonJS(screenshot_exports);
var import_fs = __toESM(require("fs"));
var import_utils = require("playwright-core/lib/utils");
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
var import_bundle = require("../../sdk/bundle");
var import_utils2 = require("playwright-core/lib/utils");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
var javascript = __toESM(require("../codegen"));
var import_utils2 = require("./utils");
const screenshotSchema = import_bundle.z.object({
type: import_bundle.z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
filename: import_bundle.z.string().optional().describe("File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory."),
element: import_bundle.z.string().optional().describe("Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too."),
ref: import_bundle.z.string().optional().describe("Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too."),
fullPage: import_bundle.z.boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
var import_utils3 = require("./utils");
const screenshotSchema = import_mcpBundle.z.object({
type: import_mcpBundle.z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
filename: import_mcpBundle.z.string().optional().describe("File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory."),
element: import_mcpBundle.z.string().optional().describe("Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too."),
ref: import_mcpBundle.z.string().optional().describe("Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too."),
fullPage: import_mcpBundle.z.boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
});
const screenshot = (0, import_tool.defineTabTool)({
capability: "core",
@@ -61,7 +50,6 @@ const screenshot = (0, import_tool.defineTabTool)({
if (params.fullPage && params.ref)
throw new Error("fullPage cannot be used with element screenshots.");
const fileType = params.type || "png";
const fileName = await tab.context.outputFile(params.filename || (0, import_utils2.dateAsFileName)(fileType), { origin: "llm", reason: "Saving screenshot" });
const options = {
type: fileType,
quality: fileType === "png" ? void 0 : 90,
@@ -70,19 +58,18 @@ const screenshot = (0, import_tool.defineTabTool)({
};
const isElementScreenshot = params.element && params.ref;
const screenshotTarget = isElementScreenshot ? params.element : params.fullPage ? "full page" : "viewport";
response.addCode(`// Screenshot ${screenshotTarget} and save it as ${fileName}`);
const ref = params.ref ? await tab.refLocator({ element: params.element || "", ref: params.ref }) : null;
const data = ref ? await ref.locator.screenshot(options) : await tab.page.screenshot(options);
const fileName = params.filename || (0, import_utils3.dateAsFileName)(fileType);
response.addCode(`// Screenshot ${screenshotTarget} and save it as ${fileName}`);
if (ref)
response.addCode(`await page.${ref.resolved}.screenshot(${javascript.formatObject(options)});`);
response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)({ ...options, path: fileName })});`);
else
response.addCode(`await page.screenshot(${javascript.formatObject(options)});`);
const buffer = ref ? await ref.locator.screenshot(options) : await tab.page.screenshot(options);
await (0, import_utils.mkdirIfNeeded)(fileName);
await import_fs.default.promises.writeFile(fileName, buffer);
response.addResult(`Took the ${screenshotTarget} screenshot and saved it as ${fileName}`);
response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path: fileName })});`);
await response.addResult({ data, title: `Screenshot of ${screenshotTarget}`, suggestedFilename: fileName });
response.addImage({
contentType: fileType === "png" ? "image/png" : "image/jpeg",
data: scaleImageToFitMessage(buffer, fileType)
data: scaleImageToFitMessage(data, fileType)
});
}
});

View File

@@ -1,9 +1,7 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var snapshot_exports = {};
__export(snapshot_exports, {
@@ -32,31 +22,33 @@ __export(snapshot_exports, {
elementSchema: () => elementSchema
});
module.exports = __toCommonJS(snapshot_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_utils = require("playwright-core/lib/utils");
var import_tool = require("./tool");
var javascript = __toESM(require("../codegen"));
const snapshot = (0, import_tool.defineTool)({
capability: "core",
schema: {
name: "browser_snapshot",
title: "Page snapshot",
description: "Capture accessibility snapshot of the current page, this is better than screenshot",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({
filename: import_mcpBundle.z.string().optional().describe("Save snapshot to markdown file instead of returning it in the response.")
}),
type: "readOnly"
},
handle: async (context, params, response) => {
await context.ensureTab();
response.setIncludeSnapshot("full");
response.setIncludeFullSnapshot(params.filename);
}
});
const elementSchema = import_bundle.z.object({
element: import_bundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
ref: import_bundle.z.string().describe("Exact target element reference from the page snapshot")
const elementSchema = import_mcpBundle.z.object({
element: import_mcpBundle.z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
});
const clickSchema = elementSchema.extend({
doubleClick: import_bundle.z.boolean().optional().describe("Whether to perform a double click instead of a single click"),
button: import_bundle.z.enum(["left", "right", "middle"]).optional().describe("Button to click, defaults to left"),
modifiers: import_bundle.z.array(import_bundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
doubleClick: import_mcpBundle.z.boolean().optional().describe("Whether to perform a double click instead of a single click"),
button: import_mcpBundle.z.enum(["left", "right", "middle"]).optional().describe("Button to click, defaults to left"),
modifiers: import_mcpBundle.z.array(import_mcpBundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
});
const click = (0, import_tool.defineTabTool)({
capability: "core",
@@ -74,7 +66,7 @@ const click = (0, import_tool.defineTabTool)({
button: params.button,
modifiers: params.modifiers
};
const formatted = javascript.formatObject(options, " ", "oneline");
const formatted = (0, import_utils.formatObject)(options, " ", "oneline");
const optionsAttr = formatted !== "{}" ? formatted : "";
if (params.doubleClick)
response.addCode(`await page.${resolved}.dblclick(${optionsAttr});`);
@@ -94,11 +86,11 @@ const drag = (0, import_tool.defineTabTool)({
name: "browser_drag",
title: "Drag mouse",
description: "Perform drag and drop between two elements",
inputSchema: import_bundle.z.object({
startElement: import_bundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
startRef: import_bundle.z.string().describe("Exact source element reference from the page snapshot"),
endElement: import_bundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
endRef: import_bundle.z.string().describe("Exact target element reference from the page snapshot")
inputSchema: import_mcpBundle.z.object({
startElement: import_mcpBundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
startRef: import_mcpBundle.z.string().describe("Exact source element reference from the page snapshot"),
endElement: import_mcpBundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
endRef: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
}),
type: "input"
},
@@ -133,7 +125,7 @@ const hover = (0, import_tool.defineTabTool)({
}
});
const selectOptionSchema = elementSchema.extend({
values: import_bundle.z.array(import_bundle.z.string()).describe("Array of values to select in the dropdown. This can be a single value or multiple values.")
values: import_mcpBundle.z.array(import_mcpBundle.z.string()).describe("Array of values to select in the dropdown. This can be a single value or multiple values.")
});
const selectOption = (0, import_tool.defineTabTool)({
capability: "core",
@@ -147,7 +139,7 @@ const selectOption = (0, import_tool.defineTabTool)({
handle: async (tab, params, response) => {
response.setIncludeSnapshot();
const { locator, resolved } = await tab.refLocator(params);
response.addCode(`await page.${resolved}.selectOption(${javascript.formatObject(params.values)});`);
response.addCode(`await page.${resolved}.selectOption(${(0, import_utils.formatObject)(params.values)});`);
await tab.waitForCompletion(async () => {
await locator.selectOption(params.values);
});
@@ -164,7 +156,7 @@ const pickLocator = (0, import_tool.defineTabTool)({
},
handle: async (tab, params, response) => {
const { resolved } = await tab.refLocator(params);
response.addResult(resolved);
response.addTextResult(resolved);
}
});
var snapshot_default = [

View File

@@ -21,17 +21,18 @@ __export(tabs_exports, {
default: () => tabs_default
});
module.exports = __toCommonJS(tabs_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
var import_response = require("../response");
const browserTabs = (0, import_tool.defineTool)({
capability: "core-tabs",
schema: {
name: "browser_tabs",
title: "Manage tabs",
description: "List, create, close, or select a browser tab.",
inputSchema: import_bundle.z.object({
action: import_bundle.z.enum(["list", "new", "close", "select"]).describe("Operation to perform"),
index: import_bundle.z.number().optional().describe("Tab index, used for close/select. If omitted for close, current tab is closed.")
inputSchema: import_mcpBundle.z.object({
action: import_mcpBundle.z.enum(["list", "new", "close", "select"]).describe("Operation to perform"),
index: import_mcpBundle.z.number().optional().describe("Tab index, used for close/select. If omitted for close, current tab is closed.")
}),
type: "action"
},
@@ -39,27 +40,26 @@ const browserTabs = (0, import_tool.defineTool)({
switch (params.action) {
case "list": {
await context.ensureTab();
response.setIncludeTabs();
return;
break;
}
case "new": {
await context.newTab();
response.setIncludeTabs();
return;
break;
}
case "close": {
await context.closeTab(params.index);
response.setIncludeSnapshot("full");
return;
break;
}
case "select": {
if (params.index === void 0)
throw new Error("Tab index is required");
await context.selectTab(params.index);
response.setIncludeSnapshot("full");
return;
break;
}
}
const tabHeaders = await Promise.all(context.tabs().map((tab) => tab.headerSnapshot()));
const result = (0, import_response.renderTabsMarkdown)(tabHeaders);
response.addTextResult(result.join("\n"));
}
});
var tabs_default = [

View File

@@ -32,11 +32,9 @@ function defineTabTool(tool) {
const tab = await context.ensureTab();
const modalStates = tab.modalStates().map((state) => state.type);
if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState))
response.addError(`Error: The tool "${tool.schema.name}" can only be used when there is related modal state present.
` + tab.modalStatesMarkdown().join("\n"));
response.addError(`Error: The tool "${tool.schema.name}" can only be used when there is related modal state present.`);
else if (!tool.clearsModalState && modalStates.length)
response.addError(`Error: Tool "${tool.schema.name}" does not handle the modal state.
` + tab.modalStatesMarkdown().join("\n"));
response.addError(`Error: Tool "${tool.schema.name}" does not handle the modal state.`);
else
return tool.handle(tab, params, response);
}

View File

@@ -21,7 +21,7 @@ __export(tracing_exports, {
default: () => tracing_default
});
module.exports = __toCommonJS(tracing_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const tracingStart = (0, import_tool.defineTool)({
capability: "tracing",
@@ -29,12 +29,12 @@ const tracingStart = (0, import_tool.defineTool)({
name: "browser_start_tracing",
title: "Start tracing",
description: "Start trace recording",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({}),
type: "readOnly"
},
handle: async (context, params, response) => {
const browserContext = await context.ensureBrowserContext();
const tracesDir = await context.outputFile(`traces`, { origin: "code", reason: "Collecting trace" });
const tracesDir = await context.outputFile(`traces`, { origin: "code", title: "Collecting trace" });
const name = "trace-" + Date.now();
await browserContext.tracing.start({
name,
@@ -45,7 +45,7 @@ const tracingStart = (0, import_tool.defineTool)({
const traceLegend = `- Action log: ${tracesDir}/${name}.trace
- Network log: ${tracesDir}/${name}.network
- Resources with content by sha1: ${tracesDir}/resources`;
response.addResult(`Tracing started, saving to ${tracesDir}.
response.addTextResult(`Tracing started, saving to ${tracesDir}.
${traceLegend}`);
browserContext.tracing[traceLegendSymbol] = traceLegend;
}
@@ -56,14 +56,14 @@ const tracingStop = (0, import_tool.defineTool)({
name: "browser_stop_tracing",
title: "Stop tracing",
description: "Stop trace recording",
inputSchema: import_bundle.z.object({}),
inputSchema: import_mcpBundle.z.object({}),
type: "readOnly"
},
handle: async (context, params, response) => {
const browserContext = await context.ensureBrowserContext();
await browserContext.tracing.stop();
const traceLegend = browserContext.tracing[traceLegendSymbol];
response.addResult(`Tracing stopped.
response.addTextResult(`Tracing stopped.
${traceLegend}`);
}
});

View File

@@ -20,59 +20,44 @@ var utils_exports = {};
__export(utils_exports, {
callOnPageNoTrace: () => callOnPageNoTrace,
dateAsFileName: () => dateAsFileName,
eventWaiter: () => eventWaiter,
waitForCompletion: () => waitForCompletion
});
module.exports = __toCommonJS(utils_exports);
async function waitForCompletion(tab, callback) {
const requests = /* @__PURE__ */ new Set();
let frameNavigated = false;
let waitCallback = () => {
};
const waitBarrier = new Promise((f) => {
waitCallback = f;
});
const responseListener = (request) => {
requests.delete(request);
if (!requests.size)
waitCallback();
};
const requestListener = (request) => {
requests.add(request);
void request.response().then(() => responseListener(request)).catch(() => {
});
};
const frameNavigateListener = (frame) => {
if (frame.parentFrame())
return;
frameNavigated = true;
dispose();
clearTimeout(timeout);
void tab.waitForLoadState("load").then(waitCallback);
};
const onTimeout = () => {
dispose();
waitCallback();
const requests = [];
const requestListener = (request) => requests.push(request);
const disposeListeners = () => {
tab.page.off("request", requestListener);
};
tab.page.on("request", requestListener);
tab.page.on("requestfailed", responseListener);
tab.page.on("framenavigated", frameNavigateListener);
const timeout = setTimeout(onTimeout, 1e4);
const dispose = () => {
tab.page.off("request", requestListener);
tab.page.off("requestfailed", responseListener);
tab.page.off("framenavigated", frameNavigateListener);
clearTimeout(timeout);
};
let result;
try {
const result = await callback();
if (!requests.size && !frameNavigated)
waitCallback();
await waitBarrier;
await tab.waitForTimeout(1e3);
return result;
result = await callback();
await tab.waitForTimeout(500);
} finally {
dispose();
disposeListeners();
}
const requestedNavigation = requests.some((request) => request.isNavigationRequest());
if (requestedNavigation) {
await tab.page.mainFrame().waitForLoadState("load", { timeout: 1e4 }).catch(() => {
});
return result;
}
const promises = [];
for (const request of requests) {
if (["document", "stylesheet", "script", "xhr", "fetch"].includes(request.resourceType()))
promises.push(request.response().then((r) => r?.finished()).catch(() => {
}));
else
promises.push(request.response().catch(() => {
}));
}
const timeout = new Promise((resolve) => setTimeout(resolve, 5e3));
await Promise.race([Promise.all(promises), timeout]);
if (requests.length)
await tab.waitForTimeout(500);
return result;
}
async function callOnPageNoTrace(page, callback) {
return await page._wrapApiCall(() => callback(page), { internal: true });
@@ -81,9 +66,29 @@ function dateAsFileName(extension) {
const date = /* @__PURE__ */ new Date();
return `page-${date.toISOString().replace(/[:.]/g, "-")}.${extension}`;
}
function eventWaiter(page, event, timeout) {
const disposables = [];
const eventPromise = new Promise((resolve, reject) => {
page.on(event, resolve);
disposables.push(() => page.off(event, resolve));
});
let abort;
const abortPromise = new Promise((resolve, reject) => {
abort = () => resolve(void 0);
});
const timeoutPromise = new Promise((f) => {
const timeoutId = setTimeout(() => f(void 0), timeout);
disposables.push(() => clearTimeout(timeoutId));
});
return {
promise: Promise.race([eventPromise, abortPromise, timeoutPromise]).finally(() => disposables.forEach((dispose) => dispose())),
abort
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
callOnPageNoTrace,
dateAsFileName,
eventWaiter,
waitForCompletion
});

View File

@@ -1,9 +1,7 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
@@ -17,32 +15,24 @@ var __copyProps = (to, from, except, desc) => {
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var verify_exports = {};
__export(verify_exports, {
default: () => verify_default
});
module.exports = __toCommonJS(verify_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_utils = require("playwright-core/lib/utils");
var import_tool = require("./tool");
var javascript = __toESM(require("../codegen"));
const verifyElement = (0, import_tool.defineTabTool)({
capability: "testing",
schema: {
name: "browser_verify_element_visible",
title: "Verify element visible",
description: "Verify element is visible on the page",
inputSchema: import_bundle.z.object({
role: import_bundle.z.string().describe('ROLE of the element. Can be found in the snapshot like this: `- {ROLE} "Accessible Name":`'),
accessibleName: import_bundle.z.string().describe('ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: `- role "{ACCESSIBLE_NAME}"`')
inputSchema: import_mcpBundle.z.object({
role: import_mcpBundle.z.string().describe('ROLE of the element. Can be found in the snapshot like this: `- {ROLE} "Accessible Name":`'),
accessibleName: import_mcpBundle.z.string().describe('ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: `- role "{ACCESSIBLE_NAME}"`')
}),
type: "assertion"
},
@@ -52,8 +42,8 @@ const verifyElement = (0, import_tool.defineTabTool)({
response.addError(`Element with role "${params.role}" and accessible name "${params.accessibleName}" not found`);
return;
}
response.addCode(`await expect(page.getByRole(${javascript.escapeWithQuotes(params.role)}, { name: ${javascript.escapeWithQuotes(params.accessibleName)} })).toBeVisible();`);
response.addResult("Done");
response.addCode(`await expect(page.getByRole(${(0, import_utils.escapeWithQuotes)(params.role)}, { name: ${(0, import_utils.escapeWithQuotes)(params.accessibleName)} })).toBeVisible();`);
response.addTextResult("Done");
}
});
const verifyText = (0, import_tool.defineTabTool)({
@@ -62,8 +52,8 @@ const verifyText = (0, import_tool.defineTabTool)({
name: "browser_verify_text_visible",
title: "Verify text visible",
description: `Verify text is visible on the page. Prefer ${verifyElement.schema.name} if possible.`,
inputSchema: import_bundle.z.object({
text: import_bundle.z.string().describe('TEXT to verify. Can be found in the snapshot like this: `- role "Accessible Name": {TEXT}` or like this: `- text: {TEXT}`')
inputSchema: import_mcpBundle.z.object({
text: import_mcpBundle.z.string().describe('TEXT to verify. Can be found in the snapshot like this: `- role "Accessible Name": {TEXT}` or like this: `- text: {TEXT}`')
}),
type: "assertion"
},
@@ -73,8 +63,8 @@ const verifyText = (0, import_tool.defineTabTool)({
response.addError("Text not found");
return;
}
response.addCode(`await expect(page.getByText(${javascript.escapeWithQuotes(params.text)})).toBeVisible();`);
response.addResult("Done");
response.addCode(`await expect(page.getByText(${(0, import_utils.escapeWithQuotes)(params.text)})).toBeVisible();`);
response.addTextResult("Done");
}
});
const verifyList = (0, import_tool.defineTabTool)({
@@ -83,10 +73,10 @@ const verifyList = (0, import_tool.defineTabTool)({
name: "browser_verify_list_visible",
title: "Verify list visible",
description: "Verify list is visible on the page",
inputSchema: import_bundle.z.object({
element: import_bundle.z.string().describe("Human-readable list description"),
ref: import_bundle.z.string().describe("Exact target element reference that points to the list"),
items: import_bundle.z.array(import_bundle.z.string()).describe("Items to verify")
inputSchema: import_mcpBundle.z.object({
element: import_mcpBundle.z.string().describe("Human-readable list description"),
ref: import_mcpBundle.z.string().describe("Exact target element reference that points to the list"),
items: import_mcpBundle.z.array(import_mcpBundle.z.string()).describe("Items to verify")
}),
type: "assertion"
},
@@ -103,10 +93,10 @@ const verifyList = (0, import_tool.defineTabTool)({
}
const ariaSnapshot = `\`
- list:
${itemTexts.map((t) => ` - listitem: ${javascript.escapeWithQuotes(t, '"')}`).join("\n")}
${itemTexts.map((t) => ` - listitem: ${(0, import_utils.escapeWithQuotes)(t, '"')}`).join("\n")}
\``;
response.addCode(`await expect(page.locator('body')).toMatchAriaSnapshot(${ariaSnapshot});`);
response.addResult("Done");
response.addTextResult("Done");
}
});
const verifyValue = (0, import_tool.defineTabTool)({
@@ -115,11 +105,11 @@ const verifyValue = (0, import_tool.defineTabTool)({
name: "browser_verify_value",
title: "Verify value",
description: "Verify element value",
inputSchema: import_bundle.z.object({
type: import_bundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the element"),
element: import_bundle.z.string().describe("Human-readable element description"),
ref: import_bundle.z.string().describe("Exact target element reference that points to the element"),
value: import_bundle.z.string().describe('Value to verify. For checkbox, use "true" or "false".')
inputSchema: import_mcpBundle.z.object({
type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the element"),
element: import_mcpBundle.z.string().describe("Human-readable element description"),
ref: import_mcpBundle.z.string().describe("Exact target element reference that points to the element"),
value: import_mcpBundle.z.string().describe('Value to verify. For checkbox, use "true" or "false".')
}),
type: "assertion"
},
@@ -132,7 +122,7 @@ const verifyValue = (0, import_tool.defineTabTool)({
response.addError(`Expected value "${params.value}", but got "${value}"`);
return;
}
response.addCode(`await expect(${locatorSource}).toHaveValue(${javascript.quote(params.value)});`);
response.addCode(`await expect(${locatorSource}).toHaveValue(${(0, import_utils.escapeWithQuotes)(params.value)});`);
} else if (params.type === "checkbox" || params.type === "radio") {
const value = await locator.isChecked();
if (value !== (params.value === "true")) {
@@ -142,7 +132,7 @@ const verifyValue = (0, import_tool.defineTabTool)({
const matcher = value ? "toBeChecked" : "not.toBeChecked";
response.addCode(`await expect(${locatorSource}).${matcher}();`);
}
response.addResult("Done");
response.addTextResult("Done");
}
});
var verify_default = [

View File

@@ -21,7 +21,7 @@ __export(wait_exports, {
default: () => wait_default
});
module.exports = __toCommonJS(wait_exports);
var import_bundle = require("../../sdk/bundle");
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
var import_tool = require("./tool");
const wait = (0, import_tool.defineTool)({
capability: "core",
@@ -29,10 +29,10 @@ const wait = (0, import_tool.defineTool)({
name: "browser_wait_for",
title: "Wait for",
description: "Wait for text to appear or disappear or a specified time to pass",
inputSchema: import_bundle.z.object({
time: import_bundle.z.number().optional().describe("The time to wait in seconds"),
text: import_bundle.z.string().optional().describe("The text to wait for"),
textGone: import_bundle.z.string().optional().describe("The text to wait for to disappear")
inputSchema: import_mcpBundle.z.object({
time: import_mcpBundle.z.number().optional().describe("The time to wait in seconds"),
text: import_mcpBundle.z.string().optional().describe("The text to wait for"),
textGone: import_mcpBundle.z.string().optional().describe("The text to wait for to disappear")
}),
type: "assertion"
},
@@ -54,7 +54,7 @@ const wait = (0, import_tool.defineTool)({
response.addCode(`await page.getByText(${JSON.stringify(params.text)}).first().waitFor({ state: 'visible' });`);
await locator.waitFor({ state: "visible" });
}
response.addResult(`Waited for ${params.text || params.textGone || params.time}`);
response.addTextResult(`Waited for ${params.text || params.textGone || params.time}`);
response.setIncludeSnapshot();
}
});