@cotestdev/mcp_playwright 0.0.34 → 0.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/lib/common/testType.js +1 -1
  2. package/lib/mcp/browser/browserContextFactory.js +12 -12
  3. package/lib/mcp/browser/browserServerBackend.js +24 -12
  4. package/lib/mcp/browser/config.js +37 -7
  5. package/lib/mcp/browser/context.js +17 -63
  6. package/lib/mcp/browser/response.js +183 -251
  7. package/lib/mcp/browser/sessionLog.js +19 -104
  8. package/lib/mcp/browser/tab.js +49 -28
  9. package/lib/mcp/browser/tools/common.js +4 -4
  10. package/lib/mcp/browser/tools/console.js +20 -3
  11. package/lib/mcp/browser/tools/dialogs.js +0 -1
  12. package/lib/mcp/browser/tools/evaluate.js +6 -4
  13. package/lib/mcp/browser/tools/install.js +4 -1
  14. package/lib/mcp/browser/tools/keyboard.js +75 -8
  15. package/lib/mcp/browser/tools/mouse.js +59 -7
  16. package/lib/mcp/browser/tools/navigate.js +48 -5
  17. package/lib/mcp/browser/tools/network.js +21 -3
  18. package/lib/mcp/browser/tools/pdf.js +4 -3
  19. package/lib/mcp/browser/tools/runCode.js +6 -10
  20. package/lib/mcp/browser/tools/screenshot.js +8 -26
  21. package/lib/mcp/browser/tools/snapshot.js +38 -22
  22. package/lib/mcp/browser/tools/tabs.js +8 -8
  23. package/lib/mcp/browser/tools/tool.js +3 -6
  24. package/lib/mcp/browser/tools/tracing.js +3 -3
  25. package/lib/mcp/browser/tools/utils.js +2 -2
  26. package/lib/mcp/browser/tools/verify.js +4 -4
  27. package/lib/mcp/browser/tools/wait.js +1 -1
  28. package/lib/mcp/browser/tools.js +2 -2
  29. package/lib/mcp/extension/extensionContextFactory.js +2 -2
  30. package/lib/mcp/program.js +3 -2
  31. package/lib/mcp/terminal/cli.js +4 -216
  32. package/lib/mcp/terminal/command.js +56 -0
  33. package/lib/mcp/terminal/commands.js +528 -0
  34. package/lib/mcp/terminal/daemon.js +42 -25
  35. package/lib/mcp/terminal/helpGenerator.js +152 -0
  36. package/lib/mcp/terminal/program.js +434 -0
  37. package/lib/mcp/terminal/socketConnection.js +2 -4
  38. package/lib/mcpBundleImpl/index.js +44 -44
  39. package/lib/util.js +3 -6
  40. package/package.json +3 -2
@@ -23,7 +23,7 @@ __export(testType_exports, {
23
23
  rootTestType: () => rootTestType
24
24
  });
25
25
  module.exports = __toCommonJS(testType_exports);
26
- var import_playwright_core = require("playwright-core");
26
+ var import_playwright_core = require("../mcpBundleImpl");
27
27
  var import_utils = require("playwright-core/lib/utils");
28
28
  var import_globals = require("./globals");
29
29
  var import_test = require("./test");
@@ -56,7 +56,7 @@ function contextFactory(config) {
56
56
  }
57
57
  function identityBrowserContextFactory(browserContext) {
58
58
  return {
59
- createContext: async (clientInfo, abortSignal, toolName) => {
59
+ createContext: async (clientInfo, abortSignal, options) => {
60
60
  return {
61
61
  browserContext,
62
62
  close: async () => {
@@ -70,11 +70,11 @@ class BaseContextFactory {
70
70
  this._logName = name;
71
71
  this.config = config;
72
72
  }
73
- async _obtainBrowser(clientInfo) {
73
+ async _obtainBrowser(clientInfo, options) {
74
74
  if (this._browserPromise)
75
75
  return this._browserPromise;
76
76
  (0, import_log.testDebug)(`obtain browser (${this._logName})`);
77
- this._browserPromise = this._doObtainBrowser(clientInfo);
77
+ this._browserPromise = this._doObtainBrowser(clientInfo, options);
78
78
  void this._browserPromise.then((browser) => {
79
79
  browser.on("disconnected", () => {
80
80
  this._browserPromise = void 0;
@@ -84,12 +84,12 @@ class BaseContextFactory {
84
84
  });
85
85
  return this._browserPromise;
86
86
  }
87
- async _doObtainBrowser(clientInfo) {
87
+ async _doObtainBrowser(clientInfo, options) {
88
88
  throw new Error("Not implemented");
89
89
  }
90
- async createContext(clientInfo) {
90
+ async createContext(clientInfo, _, options) {
91
91
  (0, import_log.testDebug)(`create browser context (${this._logName})`);
92
- const browser = await this._obtainBrowser(clientInfo);
92
+ const browser = await this._obtainBrowser(clientInfo, options);
93
93
  const browserContext = await this._doCreateContext(browser, clientInfo);
94
94
  await addInitScript(browserContext, this.config.browser.initScript);
95
95
  return {
@@ -115,7 +115,7 @@ class IsolatedContextFactory extends BaseContextFactory {
115
115
  constructor(config) {
116
116
  super("isolated", config);
117
117
  }
118
- async _doObtainBrowser(clientInfo) {
118
+ async _doObtainBrowser(clientInfo, options) {
119
119
  await injectCdpPort(this.config.browser);
120
120
  const browserType = playwright[this.config.browser.browserName];
121
121
  const tracesDir = await computeTracesDir(this.config, clientInfo);
@@ -172,7 +172,7 @@ class PersistentContextFactory {
172
172
  this._userDataDirs = /* @__PURE__ */ new Set();
173
173
  this.config = config;
174
174
  }
175
- async createContext(clientInfo) {
175
+ async createContext(clientInfo, abortSignal, options) {
176
176
  await injectCdpPort(this.config.browser);
177
177
  (0, import_log.testDebug)("create browser context (persistent)");
178
178
  const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo);
@@ -277,10 +277,10 @@ class SharedContextFactory {
277
277
  constructor(baseFactory) {
278
278
  this._baseFactory = baseFactory;
279
279
  }
280
- async createContext(clientInfo, abortSignal, toolName) {
280
+ async createContext(clientInfo, abortSignal, options) {
281
281
  if (!this._contextPromise) {
282
282
  (0, import_log.testDebug)("create shared browser context");
283
- this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, toolName);
283
+ this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, options);
284
284
  }
285
285
  const { browserContext } = await this._contextPromise;
286
286
  (0, import_log.testDebug)(`shared context client connected`);
@@ -306,12 +306,12 @@ class SharedContextFactory {
306
306
  async function computeTracesDir(config, clientInfo) {
307
307
  if (!config.saveTrace && !config.capabilities?.includes("tracing"))
308
308
  return;
309
- return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", reason: "Collecting trace" });
309
+ return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", title: "Collecting trace" });
310
310
  }
311
311
  async function browserContextOptionsFromConfig(config, clientInfo) {
312
312
  const result = { ...config.browser.contextOptions };
313
313
  if (config.saveVideo) {
314
- const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code", reason: "Saving video" });
314
+ const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code", title: "Saving video" });
315
315
  result.recordVideo = {
316
316
  dir,
317
317
  size: config.saveVideo
@@ -28,10 +28,11 @@ var import_sessionLog = require("./sessionLog");
28
28
  var import_tools = require("./tools");
29
29
  var import_tool = require("../sdk/tool");
30
30
  class BrowserServerBackend {
31
- constructor(config, factory) {
31
+ constructor(config, factory, options = {}) {
32
32
  this._config = config;
33
33
  this._browserContextFactory = factory;
34
- this._tools = (0, import_tools.filteredTools)(config);
34
+ this._tools = options.allTools ? import_tools.browserTools : (0, import_tools.filteredTools)(config);
35
+ this._isStructuredOutput = options.structuredOutput ?? false;
35
36
  }
36
37
  async initialize(clientInfo) {
37
38
  this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, clientInfo) : void 0;
@@ -47,25 +48,36 @@ class BrowserServerBackend {
47
48
  }
48
49
  async callTool(name, rawArguments) {
49
50
  const tool = this._tools.find((tool2) => tool2.schema.name === name);
50
- if (!tool)
51
- throw new Error(`Tool "${name}" not found`);
51
+ if (!tool) {
52
+ return {
53
+ content: [{ type: "text", text: `### Error
54
+ Tool "${name}" not found` }],
55
+ isError: true
56
+ };
57
+ }
52
58
  const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
53
59
  const context = this._context;
54
- const response = new import_response.Response(context, name, parsedArguments);
55
- response.logBegin();
60
+ const response = import_response.Response.create(context, name, parsedArguments);
56
61
  context.setRunningTool(name);
62
+ let responseObject;
57
63
  try {
58
64
  await tool.handle(context, parsedArguments, response);
59
- await response.finish();
60
- this._sessionLog?.logResponse(response);
65
+ const sections = await response.build();
66
+ if (this._isStructuredOutput)
67
+ responseObject = await (0, import_response.serializeStructuredResponse)(sections);
68
+ else
69
+ responseObject = await (0, import_response.serializeResponse)(context, sections, context.firstRootPath());
70
+ this._sessionLog?.logResponse(name, parsedArguments, responseObject);
61
71
  } catch (error) {
62
- response.addError(String(error));
72
+ return {
73
+ content: [{ type: "text", text: `### Error
74
+ ${String(error)}` }],
75
+ isError: true
76
+ };
63
77
  } finally {
64
78
  context.setRunningTool(void 0);
65
79
  }
66
- response.logEnd();
67
- const _meta = rawArguments?._meta;
68
- return response.serialize({ _meta });
80
+ return responseObject;
69
81
  }
70
82
  serverClosed() {
71
83
  void this._context?.dispose().catch(import_log.logUnhandledError);
@@ -55,8 +55,7 @@ const defaultConfig = {
55
55
  browserName: "chromium",
56
56
  launchOptions: {
57
57
  channel: "chrome",
58
- headless: import_os.default.platform() === "linux" && !process.env.DISPLAY,
59
- chromiumSandbox: true
58
+ headless: import_os.default.platform() === "linux" && !process.env.DISPLAY
60
59
  },
61
60
  contextOptions: {
62
61
  viewport: null
@@ -72,13 +71,30 @@ const defaultConfig = {
72
71
  server: {},
73
72
  saveTrace: false,
74
73
  snapshot: {
75
- mode: "incremental"
74
+ mode: "incremental",
75
+ output: "stdout"
76
76
  },
77
77
  timeouts: {
78
78
  action: 5e3,
79
79
  navigation: 6e4
80
80
  }
81
81
  };
82
+ const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
83
+ browser: {
84
+ userDataDir: "<daemon-data-dir>",
85
+ launchOptions: {
86
+ headless: !cliOptions.daemonHeaded
87
+ },
88
+ contextOptions: {
89
+ viewport: cliOptions.daemonHeaded ? null : { width: 1280, height: 720 }
90
+ }
91
+ },
92
+ outputMode: "file",
93
+ codegen: "none",
94
+ snapshot: {
95
+ mode: "full"
96
+ }
97
+ });
82
98
  async function resolveConfig(config) {
83
99
  return mergeConfig(defaultConfig, config);
84
100
  }
@@ -86,10 +102,22 @@ async function resolveCLIConfig(cliOptions) {
86
102
  const configInFile = await loadConfig(cliOptions.config);
87
103
  const envOverrides = configFromEnv();
88
104
  const cliOverrides = configFromCLIOptions(cliOptions);
89
- let result = defaultConfig;
105
+ let result = cliOptions.daemon ? defaultDaemonConfig(cliOptions) : defaultConfig;
90
106
  result = mergeConfig(result, configInFile);
91
107
  result = mergeConfig(result, envOverrides);
92
108
  result = mergeConfig(result, cliOverrides);
109
+ if (cliOptions.daemon)
110
+ result.skillMode = true;
111
+ if (result.browser.userDataDir === "<daemon-data-dir>") {
112
+ const browserToken = result.browser.launchOptions?.channel ?? result.browser?.browserName;
113
+ result.browser.userDataDir = `${cliOptions.daemonDataDir}-${browserToken}`;
114
+ }
115
+ if (result.browser.browserName === "chromium" && result.browser.launchOptions.chromiumSandbox === void 0) {
116
+ if (process.platform === "linux")
117
+ result.browser.launchOptions.chromiumSandbox = result.browser.launchOptions.channel !== "chromium";
118
+ else
119
+ result.browser.launchOptions.chromiumSandbox = true;
120
+ }
93
121
  await validateConfig(result);
94
122
  return result;
95
123
  }
@@ -137,8 +165,8 @@ function configFromCLIOptions(cliOptions) {
137
165
  executablePath: cliOptions.executablePath,
138
166
  headless: cliOptions.headless
139
167
  };
140
- if (cliOptions.sandbox === false)
141
- launchOptions.chromiumSandbox = false;
168
+ if (cliOptions.sandbox !== void 0)
169
+ launchOptions.chromiumSandbox = cliOptions.sandbox;
142
170
  if (cliOptions.proxyServer) {
143
171
  launchOptions.proxy = {
144
172
  server: cliOptions.proxyServer
@@ -187,12 +215,14 @@ function configFromCLIOptions(cliOptions) {
187
215
  blockedOrigins: cliOptions.blockedOrigins
188
216
  },
189
217
  allowUnrestrictedFileAccess: cliOptions.allowUnrestrictedFileAccess,
218
+ codegen: cliOptions.codegen,
190
219
  saveSession: cliOptions.saveSession,
191
220
  saveTrace: cliOptions.saveTrace,
192
221
  saveVideo: cliOptions.saveVideo,
193
222
  secrets: cliOptions.secrets,
194
223
  sharedBrowserContext: cliOptions.sharedBrowserContext,
195
224
  snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : void 0,
225
+ outputMode: cliOptions.outputMode,
196
226
  outputDir: cliOptions.outputDir,
197
227
  imageResponses: cliOptions.imageResponses,
198
228
  testIdAttribute: cliOptions.testIdAttribute,
@@ -268,7 +298,7 @@ function outputDir(config, clientInfo) {
268
298
  async function outputFile(config, clientInfo, fileName, options) {
269
299
  const file = await resolveFile(config, clientInfo, fileName, options);
270
300
  await import_fs.default.promises.mkdir(import_path.default.dirname(file), { recursive: true });
271
- (0, import_utilsBundle.debug)("pw:mcp:file")(options.reason, file);
301
+ (0, import_utilsBundle.debug)("pw:mcp:file")(options.title, file);
272
302
  return file;
273
303
  }
274
304
  async function resolveFile(config, clientInfo, fileName, options) {
@@ -28,8 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var context_exports = {};
30
30
  __export(context_exports, {
31
- Context: () => Context,
32
- InputRecorder: () => InputRecorder
31
+ Context: () => Context
33
32
  });
34
33
  module.exports = __toCommonJS(context_exports);
35
34
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
@@ -67,7 +66,7 @@ class Context {
67
66
  }
68
67
  currentTabOrDie() {
69
68
  if (!this._currentTab)
70
- throw new Error('No open pages available. Use the "browser_navigate" tool to navigate to a page first.');
69
+ throw new Error("No open pages available.");
71
70
  return this._currentTab;
72
71
  }
73
72
  async newTab() {
@@ -162,12 +161,12 @@ class Context {
162
161
  return browserContext;
163
162
  }
164
163
  _ensureBrowserContext() {
165
- if (!this._browserContextPromise) {
166
- this._browserContextPromise = this._setupBrowserContext();
167
- this._browserContextPromise.catch(() => {
168
- this._browserContextPromise = void 0;
169
- });
170
- }
164
+ if (this._browserContextPromise)
165
+ return this._browserContextPromise;
166
+ this._browserContextPromise = this._setupBrowserContext();
167
+ this._browserContextPromise.catch(() => {
168
+ this._browserContextPromise = void 0;
169
+ });
171
170
  return this._browserContextPromise;
172
171
  }
173
172
  async _setupBrowserContext() {
@@ -175,15 +174,15 @@ class Context {
175
174
  throw new Error("Another browser context is being closed.");
176
175
  if (this.config.testIdAttribute)
177
176
  import_playwright_core.selectors.setTestIdAttribute(this.config.testIdAttribute);
178
- const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, this._runningToolName);
177
+ const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, { toolName: this._runningToolName });
179
178
  const { browserContext } = result;
180
179
  if (!this.config.allowUnrestrictedFileAccess) {
181
- browserContext._setAllowedProtocols(["http:", "https:", "about:", "data:"]);
182
- browserContext._setAllowedDirectories(allRootPaths(this._clientInfo));
180
+ if (typeof browserContext._setAllowedProtocols === "function")
181
+ browserContext._setAllowedProtocols(["http:", "https:", "about:", "data:"]);
182
+ if (typeof browserContext._setAllowedDirectories === "function")
183
+ browserContext._setAllowedDirectories(allRootPaths(this._clientInfo));
183
184
  }
184
185
  await this._setupRequestInterception(browserContext);
185
- if (this.sessionLog)
186
- await InputRecorder.create(this, browserContext);
187
186
  for (const page of browserContext.pages())
188
187
  this._onPageCreated(page);
189
188
  browserContext.on("page", (page) => this._onPageCreated(page));
@@ -205,6 +204,9 @@ class Context {
205
204
  code: `process.env['${secretName}']`
206
205
  };
207
206
  }
207
+ firstRootPath() {
208
+ return allRootPaths(this._clientInfo)[0];
209
+ }
208
210
  }
209
211
  function allRootPaths(clientInfo) {
210
212
  const paths = [];
@@ -234,55 +236,7 @@ function originOrHostGlob(originOrHost) {
234
236
  }
235
237
  return `*://${originOrHost}/**`;
236
238
  }
237
- class InputRecorder {
238
- constructor(context, browserContext) {
239
- this._context = context;
240
- this._browserContext = browserContext;
241
- }
242
- static async create(context, browserContext) {
243
- const recorder = new InputRecorder(context, browserContext);
244
- await recorder._initialize();
245
- return recorder;
246
- }
247
- async _initialize() {
248
- const sessionLog = this._context.sessionLog;
249
- await this._browserContext._enableRecorder({
250
- mode: "recording",
251
- recorderMode: "api"
252
- }, {
253
- actionAdded: (page, data, code) => {
254
- if (this._context.isRunningTool())
255
- return;
256
- const tab = import_tab.Tab.forPage(page);
257
- if (tab)
258
- sessionLog.logUserAction(data.action, tab, code, false);
259
- },
260
- actionUpdated: (page, data, code) => {
261
- if (this._context.isRunningTool())
262
- return;
263
- const tab = import_tab.Tab.forPage(page);
264
- if (tab)
265
- sessionLog.logUserAction(data.action, tab, code, true);
266
- },
267
- signalAdded: (page, data) => {
268
- if (this._context.isRunningTool())
269
- return;
270
- if (data.signal.name !== "navigation")
271
- return;
272
- const tab = import_tab.Tab.forPage(page);
273
- const navigateAction = {
274
- name: "navigate",
275
- url: data.signal.url,
276
- signals: []
277
- };
278
- if (tab)
279
- sessionLog.logUserAction(navigateAction, tab, `await page.goto('${data.signal.url}');`, false);
280
- }
281
- });
282
- }
283
- }
284
239
  // Annotate the CommonJS export names for ESM import in node:
285
240
  0 && (module.exports = {
286
- Context,
287
- InputRecorder
241
+ Context
288
242
  });