@playwright/mcp 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,7 +137,10 @@ Playwright MCP server supports following arguments. They can be provided in the
137
137
  --ignore-https-errors ignore https errors
138
138
  --isolated keep the browser profile in memory, do not save
139
139
  it to disk.
140
- --no-image-responses do not send image responses to the client.
140
+ --image-responses <mode> whether to send image responses to the client.
141
+ Can be "allow", "omit", or "auto". Defaults to
142
+ "auto", which sends images if the client can
143
+ display them.
141
144
  --no-sandbox disable the sandbox for all process types that
142
145
  are normally sandboxed.
143
146
  --output-dir <path> path to the directory for output files.
@@ -146,6 +149,8 @@ Playwright MCP server supports following arguments. They can be provided in the
146
149
  example ".com,chromium.org,.domain.com"
147
150
  --proxy-server <proxy> specify proxy server, for example
148
151
  "http://myproxy:3128" or "socks5://myproxy:8080"
152
+ --save-trace Whether to save the Playwright Trace of the
153
+ session into the output directory.
149
154
  --storage-state <path> path to the storage state file for isolated
150
155
  sessions.
151
156
  --user-agent <ua string> specify user agent string
package/config.d.ts CHANGED
@@ -94,6 +94,11 @@ export type Config = {
94
94
  */
95
95
  vision?: boolean;
96
96
 
97
+ /**
98
+ * Whether to save the Playwright trace of the session into the output directory.
99
+ */
100
+ saveTrace?: boolean;
101
+
97
102
  /**
98
103
  * The directory to save output files.
99
104
  */
@@ -112,7 +117,7 @@ export type Config = {
112
117
  };
113
118
 
114
119
  /**
115
- * Do not send image responses to the client.
120
+ * Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.
116
121
  */
117
- noImageResponses?: boolean;
122
+ imageResponses?: 'allow' | 'omit' | 'auto';
118
123
  };
package/index.js CHANGED
@@ -15,5 +15,5 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import { createConnection } from './lib/index';
18
+ import { createConnection } from './lib/index.js';
19
19
  export { createConnection };
package/lib/config.js CHANGED
@@ -35,11 +35,21 @@ const defaultConfig = {
35
35
  allowedOrigins: undefined,
36
36
  blockedOrigins: undefined,
37
37
  },
38
+ outputDir: path.join(os.tmpdir(), 'playwright-mcp-output', sanitizeForFilePath(new Date().toISOString())),
38
39
  };
39
- export async function resolveConfig(cliOptions) {
40
- const config = await loadConfig(cliOptions.config);
40
+ export async function resolveConfig(config) {
41
+ return mergeConfig(defaultConfig, config);
42
+ }
43
+ export async function resolveCLIConfig(cliOptions) {
44
+ const configInFile = await loadConfig(cliOptions.config);
41
45
  const cliOverrides = await configFromCLIOptions(cliOptions);
42
- return mergeConfig(defaultConfig, mergeConfig(config, cliOverrides));
46
+ const result = mergeConfig(mergeConfig(defaultConfig, configInFile), cliOverrides);
47
+ // Derive artifact output directory from config.outputDir
48
+ if (result.saveTrace)
49
+ result.browser.launchOptions.tracesDir = path.join(result.outputDir, 'traces');
50
+ if (result.browser.browserName === 'chromium')
51
+ result.browser.launchOptions.cdpPort = await findFreePort();
52
+ return result;
43
53
  }
44
54
  export async function configFromCLIOptions(cliOptions) {
45
55
  let browserName;
@@ -63,9 +73,6 @@ export async function configFromCLIOptions(cliOptions) {
63
73
  case 'webkit':
64
74
  browserName = 'webkit';
65
75
  break;
66
- default:
67
- browserName = 'chromium';
68
- channel = 'chrome';
69
76
  }
70
77
  // Launch options
71
78
  const launchOptions = {
@@ -73,13 +80,9 @@ export async function configFromCLIOptions(cliOptions) {
73
80
  executablePath: cliOptions.executablePath,
74
81
  headless: cliOptions.headless,
75
82
  };
76
- if (browserName === 'chromium') {
77
- launchOptions.cdpPort = await findFreePort();
78
- if (!cliOptions.sandbox) {
79
- // --no-sandbox was passed, disable the sandbox
80
- launchOptions.chromiumSandbox = false;
81
- }
82
- }
83
+ // --no-sandbox was passed, disable the sandbox
84
+ if (!cliOptions.sandbox)
85
+ launchOptions.chromiumSandbox = false;
83
86
  if (cliOptions.proxyServer) {
84
87
  launchOptions.proxy = {
85
88
  server: cliOptions.proxyServer
@@ -127,12 +130,10 @@ export async function configFromCLIOptions(cliOptions) {
127
130
  allowedOrigins: cliOptions.allowedOrigins,
128
131
  blockedOrigins: cliOptions.blockedOrigins,
129
132
  },
133
+ saveTrace: cliOptions.saveTrace,
130
134
  outputDir: cliOptions.outputDir,
135
+ imageResponses: cliOptions.imageResponses,
131
136
  };
132
- if (!cliOptions.imageResponses) {
133
- // --no-image-responses was passed, disable image responses
134
- result.noImageResponses = true;
135
- }
136
137
  return result;
137
138
  }
138
139
  async function findFreePort() {
@@ -156,18 +157,17 @@ async function loadConfig(configFile) {
156
157
  }
157
158
  }
158
159
  export async function outputFile(config, name) {
159
- const result = config.outputDir ?? os.tmpdir();
160
- await fs.promises.mkdir(result, { recursive: true });
160
+ await fs.promises.mkdir(config.outputDir, { recursive: true });
161
161
  const fileName = sanitizeForFilePath(name);
162
- return path.join(result, fileName);
162
+ return path.join(config.outputDir, fileName);
163
163
  }
164
164
  function pickDefined(obj) {
165
165
  return Object.fromEntries(Object.entries(obj ?? {}).filter(([_, v]) => v !== undefined));
166
166
  }
167
167
  function mergeConfig(base, overrides) {
168
168
  const browser = {
169
- ...pickDefined(base.browser),
170
- ...pickDefined(overrides.browser),
169
+ browserName: overrides.browser?.browserName ?? base.browser?.browserName ?? 'chromium',
170
+ isolated: overrides.browser?.isolated ?? base.browser?.isolated ?? false,
171
171
  launchOptions: {
172
172
  ...pickDefined(base.browser?.launchOptions),
173
173
  ...pickDefined(overrides.browser?.launchOptions),
@@ -177,6 +177,9 @@ function mergeConfig(base, overrides) {
177
177
  ...pickDefined(base.browser?.contextOptions),
178
178
  ...pickDefined(overrides.browser?.contextOptions),
179
179
  },
180
+ userDataDir: overrides.browser?.userDataDir ?? base.browser?.userDataDir,
181
+ cdpEndpoint: overrides.browser?.cdpEndpoint ?? base.browser?.cdpEndpoint,
182
+ remoteEndpoint: overrides.browser?.remoteEndpoint ?? base.browser?.remoteEndpoint,
180
183
  };
181
184
  if (browser.browserName !== 'chromium' && browser.launchOptions)
182
185
  delete browser.launchOptions.channel;
@@ -187,6 +190,6 @@ function mergeConfig(base, overrides) {
187
190
  network: {
188
191
  ...pickDefined(base.network),
189
192
  ...pickDefined(overrides.network),
190
- },
193
+ }
191
194
  };
192
195
  }
package/lib/connection.js CHANGED
@@ -77,8 +77,7 @@ export class Connection {
77
77
  await new Promise(resolve => {
78
78
  this.server.oninitialized = () => resolve();
79
79
  });
80
- if (this.server.getClientVersion()?.name.includes('cursor'))
81
- this.context.config.noImageResponses = true;
80
+ this.context.clientVersion = this.server.getClientVersion();
82
81
  }
83
82
  async close() {
84
83
  await this.server.close();
package/lib/context.js CHANGED
@@ -18,7 +18,7 @@ import url from 'node:url';
18
18
  import os from 'node:os';
19
19
  import path from 'node:path';
20
20
  import * as playwright from 'playwright';
21
- import { waitForCompletion } from './tools/utils.js';
21
+ import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js';
22
22
  import { ManualPromise } from './manualPromise.js';
23
23
  import { Tab } from './tab.js';
24
24
  import { outputFile } from './config.js';
@@ -31,10 +31,18 @@ export class Context {
31
31
  _modalStates = [];
32
32
  _pendingAction;
33
33
  _downloads = [];
34
+ clientVersion;
34
35
  constructor(tools, config) {
35
36
  this.tools = tools;
36
37
  this.config = config;
37
38
  }
39
+ clientSupportsImages() {
40
+ if (this.config.imageResponses === 'allow')
41
+ return true;
42
+ if (this.config.imageResponses === 'omit')
43
+ return false;
44
+ return !this.clientVersion?.name.includes('cursor');
45
+ }
38
46
  modalStates() {
39
47
  return this._modalStates;
40
48
  }
@@ -84,7 +92,7 @@ export class Context {
84
92
  const lines = ['### Open tabs'];
85
93
  for (let i = 0; i < this._tabs.length; i++) {
86
94
  const tab = this._tabs[i];
87
- const title = await tab.page.title();
95
+ const title = await tab.title();
88
96
  const url = tab.page.url();
89
97
  const current = tab === this._currentTab ? ' (current)' : '';
90
98
  lines.push(`- ${i + 1}:${current} [${title}] (${url})`);
@@ -116,7 +124,7 @@ export class Context {
116
124
  let actionResult;
117
125
  try {
118
126
  if (waitForNetwork)
119
- actionResult = await waitForCompletion(this, tab.page, async () => racingAction?.()) ?? undefined;
127
+ actionResult = await waitForCompletion(this, tab, async () => racingAction?.()) ?? undefined;
120
128
  else
121
129
  actionResult = await racingAction?.() ?? undefined;
122
130
  }
@@ -153,7 +161,7 @@ ${code.join('\n')}
153
161
  result.push(await this.listTabsMarkdown(), '');
154
162
  if (this.tabs().length > 1)
155
163
  result.push('### Current tab');
156
- result.push(`- Page URL: ${tab.page.url()}`, `- Page Title: ${await tab.page.title()}`);
164
+ result.push(`- Page URL: ${tab.page.url()}`, `- Page Title: ${await tab.title()}`);
157
165
  if (captureSnapshot && tab.hasSnapshot())
158
166
  result.push(tab.snapshotOrDie().text());
159
167
  const content = actionResult?.content ?? [];
@@ -168,10 +176,13 @@ ${code.join('\n')}
168
176
  };
169
177
  }
170
178
  async waitForTimeout(time) {
171
- if (this._currentTab && !this._javaScriptBlocked())
172
- await this._currentTab.page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
173
- else
179
+ if (!this._currentTab || this._javaScriptBlocked()) {
174
180
  await new Promise(f => setTimeout(f, time));
181
+ return;
182
+ }
183
+ await callOnPageNoTrace(this._currentTab.page, page => {
184
+ return page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
185
+ });
175
186
  }
176
187
  async _raceAgainstModalDialogs(action) {
177
188
  this._pendingAction = {
@@ -233,6 +244,8 @@ ${code.join('\n')}
233
244
  const promise = this._browserContextPromise;
234
245
  this._browserContextPromise = undefined;
235
246
  await promise.then(async ({ browserContext, browser }) => {
247
+ if (this.config.saveTrace)
248
+ await browserContext.tracing.stop();
236
249
  await browserContext.close().then(async () => {
237
250
  await browser?.close();
238
251
  }).catch(() => { });
@@ -264,6 +277,14 @@ ${code.join('\n')}
264
277
  for (const page of browserContext.pages())
265
278
  this._onPageCreated(page);
266
279
  browserContext.on('page', page => this._onPageCreated(page));
280
+ if (this.config.saveTrace) {
281
+ await browserContext.tracing.start({
282
+ name: 'trace',
283
+ screenshots: false,
284
+ snapshots: true,
285
+ sources: false,
286
+ });
287
+ }
267
288
  return { browser, browserContext };
268
289
  }
269
290
  async _createBrowserContext() {
@@ -291,8 +312,8 @@ async function createIsolatedContext(browserConfig) {
291
312
  try {
292
313
  const browserName = browserConfig?.browserName ?? 'chromium';
293
314
  const browserType = playwright[browserName];
294
- const browser = await browserType.launch(browserConfig?.launchOptions);
295
- const browserContext = await browser.newContext(browserConfig?.contextOptions);
315
+ const browser = await browserType.launch(browserConfig.launchOptions);
316
+ const browserContext = await browser.newContext(browserConfig.contextOptions);
296
317
  return { browser, browserContext };
297
318
  }
298
319
  catch (error) {
@@ -303,10 +324,10 @@ async function createIsolatedContext(browserConfig) {
303
324
  }
304
325
  async function launchPersistentContext(browserConfig) {
305
326
  try {
306
- const browserName = browserConfig?.browserName ?? 'chromium';
307
- const userDataDir = browserConfig?.userDataDir ?? await createUserDataDir({ ...browserConfig, browserName });
327
+ const browserName = browserConfig.browserName ?? 'chromium';
328
+ const userDataDir = browserConfig.userDataDir ?? await createUserDataDir({ ...browserConfig, browserName });
308
329
  const browserType = playwright[browserName];
309
- const browserContext = await browserType.launchPersistentContext(userDataDir, { ...browserConfig?.launchOptions, ...browserConfig?.contextOptions });
330
+ const browserContext = await browserType.launchPersistentContext(userDataDir, { ...browserConfig.launchOptions, ...browserConfig.contextOptions });
310
331
  return { browserContext };
311
332
  }
312
333
  catch (error) {
@@ -325,12 +346,9 @@ async function createUserDataDir(browserConfig) {
325
346
  cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
326
347
  else
327
348
  throw new Error('Unsupported platform: ' + process.platform);
328
- const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${browserConfig?.launchOptions?.channel ?? browserConfig?.browserName}-profile`);
349
+ const result = path.join(cacheDirectory, 'ms-playwright', `mcp-${browserConfig.launchOptions?.channel ?? browserConfig?.browserName}-profile`);
329
350
  await fs.promises.mkdir(result, { recursive: true });
330
351
  return result;
331
352
  }
332
- export async function generateLocator(locator) {
333
- return locator._generateLocatorString();
334
- }
335
353
  const __filename = url.fileURLToPath(import.meta.url);
336
354
  export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', 'package.json'), 'utf8'));
package/lib/index.js CHANGED
@@ -14,6 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { createConnection as createConnectionImpl } from './connection.js';
17
- export async function createConnection(config = {}) {
17
+ import { resolveConfig } from './config.js';
18
+ export async function createConnection(userConfig = {}) {
19
+ const config = await resolveConfig(userConfig);
18
20
  return createConnectionImpl(config);
19
21
  }
@@ -13,6 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { callOnPageNoTrace } from './tools/utils.js';
16
17
  export class PageSnapshot {
17
18
  _page;
18
19
  _text;
@@ -28,15 +29,15 @@ export class PageSnapshot {
28
29
  return this._text;
29
30
  }
30
31
  async _build() {
31
- const yamlDocument = await this._page._snapshotForAI();
32
+ const snapshot = await callOnPageNoTrace(this._page, page => page._snapshotForAI());
32
33
  this._text = [
33
34
  `- Page Snapshot`,
34
35
  '```yaml',
35
- yamlDocument.toString({ indentSeq: false }).trim(),
36
+ snapshot,
36
37
  '```',
37
38
  ].join('\n');
38
39
  }
39
- refLocator(ref) {
40
- return this._page.locator(`aria-ref=${ref}`);
40
+ refLocator(params) {
41
+ return this._page.locator(`aria-ref=${params.ref}`).describe(params.element);
41
42
  }
42
43
  }
package/lib/program.js CHANGED
@@ -15,7 +15,9 @@
15
15
  */
16
16
  import { program } from 'commander';
17
17
  import { startHttpTransport, startStdioTransport } from './transport.js';
18
- import { resolveConfig } from './config.js';
18
+ import { resolveCLIConfig } from './config.js';
19
+ // @ts-ignore
20
+ import { startTraceViewerServer } from 'playwright-core/lib/server';
19
21
  import { packageJSON } from './context.js';
20
22
  program
21
23
  .version('Version ' + packageJSON.version)
@@ -33,25 +35,33 @@ program
33
35
  .option('--host <host>', 'host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.')
34
36
  .option('--ignore-https-errors', 'ignore https errors')
35
37
  .option('--isolated', 'keep the browser profile in memory, do not save it to disk.')
36
- .option('--no-image-responses', 'do not send image responses to the client.')
38
+ .option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.')
37
39
  .option('--no-sandbox', 'disable the sandbox for all process types that are normally sandboxed.')
38
40
  .option('--output-dir <path>', 'path to the directory for output files.')
39
41
  .option('--port <port>', 'port to listen on for SSE transport.')
40
42
  .option('--proxy-bypass <bypass>', 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"')
41
43
  .option('--proxy-server <proxy>', 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"')
44
+ .option('--save-trace', 'Whether to save the Playwright Trace of the session into the output directory.')
42
45
  .option('--storage-state <path>', 'path to the storage state file for isolated sessions.')
43
46
  .option('--user-agent <ua string>', 'specify user agent string')
44
47
  .option('--user-data-dir <path>', 'path to the user data directory. If not specified, a temporary directory will be created.')
45
48
  .option('--viewport-size <size>', 'specify browser viewport size in pixels, for example "1280, 720"')
46
49
  .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
47
50
  .action(async (options) => {
48
- const config = await resolveConfig(options);
51
+ const config = await resolveCLIConfig(options);
49
52
  const connectionList = [];
50
53
  setupExitWatchdog(connectionList);
51
54
  if (options.port)
52
55
  startHttpTransport(config, +options.port, options.host, connectionList);
53
56
  else
54
57
  await startStdioTransport(config, connectionList);
58
+ if (config.saveTrace) {
59
+ const server = await startTraceViewerServer();
60
+ const urlPrefix = server.urlPrefix('human-readable');
61
+ const url = urlPrefix + '/trace/index.html?trace=' + config.browser.launchOptions.tracesDir + '/trace.json';
62
+ // eslint-disable-next-line no-console
63
+ console.error('\nTrace viewer listening on ' + url);
64
+ }
55
65
  });
56
66
  function setupExitWatchdog(connectionList) {
57
67
  const handleExit = async () => {
package/lib/tab.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { PageSnapshot } from './pageSnapshot.js';
17
+ import { callOnPageNoTrace } from './tools/utils.js';
17
18
  export class Tab {
18
19
  context;
19
20
  page;
@@ -51,9 +52,15 @@ export class Tab {
51
52
  this._clearCollectedArtifacts();
52
53
  this._onPageClose(this);
53
54
  }
55
+ async title() {
56
+ return await callOnPageNoTrace(this.page, page => page.title());
57
+ }
58
+ async waitForLoadState(state, options) {
59
+ await callOnPageNoTrace(this.page, page => page.waitForLoadState(state, options).catch(() => { }));
60
+ }
54
61
  async navigate(url) {
55
62
  this._clearCollectedArtifacts();
56
- const downloadEvent = this.page.waitForEvent('download').catch(() => { });
63
+ const downloadEvent = callOnPageNoTrace(this.page, page => page.waitForEvent('download').catch(() => { }));
57
64
  try {
58
65
  await this.page.goto(url, { waitUntil: 'domcontentloaded' });
59
66
  }
@@ -72,7 +79,7 @@ export class Tab {
72
79
  throw e;
73
80
  }
74
81
  // Cap load event to 5 seconds, the page is operational at this point.
75
- await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
82
+ await this.waitForLoadState('load', { timeout: 5000 });
76
83
  }
77
84
  hasSnapshot() {
78
85
  return !!this._snapshot;
@@ -48,12 +48,12 @@ const screenshot = defineTool({
48
48
  const code = [
49
49
  `// Screenshot ${isElementScreenshot ? params.element : 'viewport'} and save it as ${fileName}`,
50
50
  ];
51
- const locator = params.ref ? snapshot.refLocator(params.ref) : null;
51
+ const locator = params.ref ? snapshot.refLocator({ element: params.element || '', ref: params.ref }) : null;
52
52
  if (locator)
53
53
  code.push(`await page.${await generateLocator(locator)}.screenshot(${javascript.formatObject(options)});`);
54
54
  else
55
55
  code.push(`await page.screenshot(${javascript.formatObject(options)});`);
56
- const includeBase64 = !context.config.noImageResponses;
56
+ const includeBase64 = context.clientSupportsImages();
57
57
  const action = async () => {
58
58
  const screenshot = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
59
59
  return {
@@ -50,7 +50,7 @@ const click = defineTool({
50
50
  },
51
51
  handle: async (context, params) => {
52
52
  const tab = context.currentTabOrDie();
53
- const locator = tab.snapshotOrDie().refLocator(params.ref);
53
+ const locator = tab.snapshotOrDie().refLocator(params);
54
54
  const code = [
55
55
  `// Click ${params.element}`,
56
56
  `await page.${await generateLocator(locator)}.click();`
@@ -79,8 +79,8 @@ const drag = defineTool({
79
79
  },
80
80
  handle: async (context, params) => {
81
81
  const snapshot = context.currentTabOrDie().snapshotOrDie();
82
- const startLocator = snapshot.refLocator(params.startRef);
83
- const endLocator = snapshot.refLocator(params.endRef);
82
+ const startLocator = snapshot.refLocator({ ref: params.startRef, element: params.startElement });
83
+ const endLocator = snapshot.refLocator({ ref: params.endRef, element: params.endElement });
84
84
  const code = [
85
85
  `// Drag ${params.startElement} to ${params.endElement}`,
86
86
  `await page.${await generateLocator(startLocator)}.dragTo(page.${await generateLocator(endLocator)});`
@@ -104,7 +104,7 @@ const hover = defineTool({
104
104
  },
105
105
  handle: async (context, params) => {
106
106
  const snapshot = context.currentTabOrDie().snapshotOrDie();
107
- const locator = snapshot.refLocator(params.ref);
107
+ const locator = snapshot.refLocator(params);
108
108
  const code = [
109
109
  `// Hover over ${params.element}`,
110
110
  `await page.${await generateLocator(locator)}.hover();`
@@ -133,7 +133,7 @@ const type = defineTool({
133
133
  },
134
134
  handle: async (context, params) => {
135
135
  const snapshot = context.currentTabOrDie().snapshotOrDie();
136
- const locator = snapshot.refLocator(params.ref);
136
+ const locator = snapshot.refLocator(params);
137
137
  const code = [];
138
138
  const steps = [];
139
139
  if (params.slowly) {
@@ -173,7 +173,7 @@ const selectOption = defineTool({
173
173
  },
174
174
  handle: async (context, params) => {
175
175
  const snapshot = context.currentTabOrDie().snapshotOrDie();
176
- const locator = snapshot.refLocator(params.ref);
176
+ const locator = snapshot.refLocator(params);
177
177
  const code = [
178
178
  `// Select options [${params.values.join(', ')}] in ${params.element}`,
179
179
  `await page.${await generateLocator(locator)}.selectOption(${javascript.formatObject(params.values)});`
@@ -13,7 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- export async function waitForCompletion(context, page, callback) {
16
+ export async function waitForCompletion(context, tab, callback) {
17
17
  const requests = new Set();
18
18
  let frameNavigated = false;
19
19
  let waitCallback = () => { };
@@ -30,22 +30,20 @@ export async function waitForCompletion(context, page, callback) {
30
30
  frameNavigated = true;
31
31
  dispose();
32
32
  clearTimeout(timeout);
33
- void frame.waitForLoadState('load').then(() => {
34
- waitCallback();
35
- });
33
+ void tab.waitForLoadState('load').then(waitCallback);
36
34
  };
37
35
  const onTimeout = () => {
38
36
  dispose();
39
37
  waitCallback();
40
38
  };
41
- page.on('request', requestListener);
42
- page.on('requestfinished', requestFinishedListener);
43
- page.on('framenavigated', frameNavigateListener);
39
+ tab.page.on('request', requestListener);
40
+ tab.page.on('requestfinished', requestFinishedListener);
41
+ tab.page.on('framenavigated', frameNavigateListener);
44
42
  const timeout = setTimeout(onTimeout, 10000);
45
43
  const dispose = () => {
46
- page.off('request', requestListener);
47
- page.off('requestfinished', requestFinishedListener);
48
- page.off('framenavigated', frameNavigateListener);
44
+ tab.page.off('request', requestListener);
45
+ tab.page.off('requestfinished', requestFinishedListener);
46
+ tab.page.off('framenavigated', frameNavigateListener);
49
47
  clearTimeout(timeout);
50
48
  };
51
49
  try {
@@ -70,3 +68,6 @@ export function sanitizeForFilePath(s) {
70
68
  export async function generateLocator(locator) {
71
69
  return locator._generateLocatorString();
72
70
  }
71
+ export async function callOnPageNoTrace(page, callback) {
72
+ return await page._wrapApiCall(() => callback(page), { internal: true });
73
+ }
package/lib/transport.js CHANGED
@@ -127,6 +127,6 @@ export function startHttpTransport(config, port, hostname, connectionList) {
127
127
  'If your client supports streamable HTTP, you can use the /mcp endpoint instead.',
128
128
  ].join('\n');
129
129
  // eslint-disable-next-line no-console
130
- console.log(message);
130
+ console.error(message);
131
131
  });
132
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwright/mcp",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Playwright Tools for MCP",
5
5
  "type": "module",
6
6
  "repository": {
@@ -37,13 +37,13 @@
37
37
  "dependencies": {
38
38
  "@modelcontextprotocol/sdk": "^1.11.0",
39
39
  "commander": "^13.1.0",
40
- "playwright": "1.53.0-alpha-1746832516000",
40
+ "playwright": "1.53.0-alpha-2025-05-27",
41
41
  "zod-to-json-schema": "^3.24.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/eslintrc": "^3.2.0",
45
45
  "@eslint/js": "^9.19.0",
46
- "@playwright/test": "1.53.0-alpha-1746832516000",
46
+ "@playwright/test": "1.53.0-alpha-2025-05-27",
47
47
  "@stylistic/eslint-plugin": "^3.0.1",
48
48
  "@types/node": "^22.13.10",
49
49
  "@typescript-eslint/eslint-plugin": "^8.26.1",