playwriter 0.0.33 → 0.0.37
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/dist/aria-snapshot.d.ts +68 -0
- package/dist/aria-snapshot.d.ts.map +1 -0
- package/dist/aria-snapshot.js +359 -0
- package/dist/aria-snapshot.js.map +1 -0
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +95 -5
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cdp-session.d.ts +24 -3
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js +23 -0
- package/dist/cdp-session.js.map +1 -1
- package/dist/debugger-api.md +4 -3
- package/dist/debugger.d.ts +4 -3
- package/dist/debugger.d.ts.map +1 -1
- package/dist/debugger.js +3 -1
- package/dist/debugger.js.map +1 -1
- package/dist/editor-api.md +2 -2
- package/dist/editor.d.ts +2 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1 -0
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +151 -14
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.js +340 -5
- package/dist/mcp.test.js.map +1 -1
- package/dist/protocol.d.ts +12 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/react-source.d.ts +3 -3
- package/dist/react-source.d.ts.map +1 -1
- package/dist/react-source.js +3 -1
- package/dist/react-source.js.map +1 -1
- package/dist/scoped-fs.d.ts +94 -0
- package/dist/scoped-fs.d.ts.map +1 -0
- package/dist/scoped-fs.js +356 -0
- package/dist/scoped-fs.js.map +1 -0
- package/dist/styles-api.md +3 -3
- package/dist/styles.d.ts +3 -3
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +3 -1
- package/dist/styles.js.map +1 -1
- package/package.json +13 -13
- package/src/aria-snapshot.ts +446 -0
- package/src/assets/aria-labels-github-snapshot.txt +605 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-google-snapshot.txt +110 -0
- package/src/assets/aria-labels-google.png +0 -0
- package/src/assets/aria-labels-hacker-news-snapshot.txt +1023 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/cdp-relay.ts +103 -5
- package/src/cdp-session.ts +50 -3
- package/src/debugger.ts +6 -4
- package/src/editor.ts +4 -3
- package/src/index.ts +8 -0
- package/src/mcp.test.ts +424 -5
- package/src/mcp.ts +242 -66
- package/src/prompt.md +209 -167
- package/src/protocol.ts +14 -1
- package/src/react-source.ts +5 -3
- package/src/scoped-fs.ts +411 -0
- package/src/styles.ts +5 -3
package/src/mcp.ts
CHANGED
|
@@ -15,11 +15,15 @@ import { createPatch } from 'diff'
|
|
|
15
15
|
import { getCdpUrl, LOG_FILE_PATH, VERSION, sleep } from './utils.js'
|
|
16
16
|
import { killPortProcess } from 'kill-port-process'
|
|
17
17
|
import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js'
|
|
18
|
-
import { getCDPSessionForPage, CDPSession } from './cdp-session.js'
|
|
18
|
+
import { getCDPSessionForPage, CDPSession, ICDPSession } from './cdp-session.js'
|
|
19
19
|
import { Debugger } from './debugger.js'
|
|
20
20
|
import { Editor } from './editor.js'
|
|
21
21
|
import { getStylesForLocator, formatStylesAsText, type StylesResult } from './styles.js'
|
|
22
22
|
import { getReactSource, type ReactSourceLocation } from './react-source.js'
|
|
23
|
+
import { ScopedFS } from './scoped-fs.js'
|
|
24
|
+
import { showAriaRefLabels, hideAriaRefLabels } from './aria-snapshot.js'
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
26
|
+
const __dirname = path.dirname(__filename)
|
|
23
27
|
|
|
24
28
|
class CodeExecutionTimeoutError extends Error {
|
|
25
29
|
constructor(timeout: number) {
|
|
@@ -77,11 +81,13 @@ interface VMContext {
|
|
|
77
81
|
clearAllLogs: () => void
|
|
78
82
|
waitForPageLoad: (options: WaitForPageLoadOptions) => Promise<WaitForPageLoadResult>
|
|
79
83
|
getCDPSession: (options: { page: Page }) => Promise<CDPSession>
|
|
80
|
-
createDebugger: (options: { cdp:
|
|
81
|
-
createEditor: (options: { cdp:
|
|
84
|
+
createDebugger: (options: { cdp: ICDPSession }) => Debugger
|
|
85
|
+
createEditor: (options: { cdp: ICDPSession }) => Editor
|
|
82
86
|
getStylesForLocator: (options: { locator: any }) => Promise<StylesResult>
|
|
83
87
|
formatStylesAsText: (styles: StylesResult) => string
|
|
84
88
|
getReactSource: (options: { locator: any }) => Promise<ReactSourceLocation | null>
|
|
89
|
+
showAriaRefLabels: (options: { page: Page; interactiveOnly?: boolean }) => Promise<{ snapshot: string; labelCount: number }>
|
|
90
|
+
hideAriaRefLabels: (options: { page: Page }) => Promise<void>
|
|
85
91
|
require: NodeRequire
|
|
86
92
|
import: (specifier: string) => Promise<any>
|
|
87
93
|
}
|
|
@@ -112,6 +118,102 @@ const cdpSessionCache: WeakMap<Page, CDPSession> = new WeakMap()
|
|
|
112
118
|
const RELAY_PORT = Number(process.env.PLAYWRITER_PORT) || 19988
|
|
113
119
|
const NO_TABS_ERROR = `No browser tabs are connected. Please install and enable the Playwriter extension on at least one tab: https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe`
|
|
114
120
|
|
|
121
|
+
// Create a scoped fs instance that allows access to cwd, /tmp, and os.tmpdir()
|
|
122
|
+
const scopedFs = new ScopedFS()
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Allowlist of Node.js built-in modules that are safe to use in the sandbox.
|
|
126
|
+
* Dangerous modules like child_process, cluster, worker_threads, vm, net are blocked.
|
|
127
|
+
*/
|
|
128
|
+
const ALLOWED_MODULES = new Set([
|
|
129
|
+
// Safe utility modules
|
|
130
|
+
'path',
|
|
131
|
+
'node:path',
|
|
132
|
+
'url',
|
|
133
|
+
'node:url',
|
|
134
|
+
'querystring',
|
|
135
|
+
'node:querystring',
|
|
136
|
+
'punycode',
|
|
137
|
+
'node:punycode',
|
|
138
|
+
|
|
139
|
+
// Crypto and encoding
|
|
140
|
+
'crypto',
|
|
141
|
+
'node:crypto',
|
|
142
|
+
'buffer',
|
|
143
|
+
'node:buffer',
|
|
144
|
+
'string_decoder',
|
|
145
|
+
'node:string_decoder',
|
|
146
|
+
|
|
147
|
+
// Utilities
|
|
148
|
+
'util',
|
|
149
|
+
'node:util',
|
|
150
|
+
'assert',
|
|
151
|
+
'node:assert',
|
|
152
|
+
'events',
|
|
153
|
+
'node:events',
|
|
154
|
+
'timers',
|
|
155
|
+
'node:timers',
|
|
156
|
+
|
|
157
|
+
// Streams and compression
|
|
158
|
+
'stream',
|
|
159
|
+
'node:stream',
|
|
160
|
+
'zlib',
|
|
161
|
+
'node:zlib',
|
|
162
|
+
|
|
163
|
+
// HTTP (fetch is already available, these are consistent)
|
|
164
|
+
'http',
|
|
165
|
+
'node:http',
|
|
166
|
+
'https',
|
|
167
|
+
'node:https',
|
|
168
|
+
'http2',
|
|
169
|
+
'node:http2',
|
|
170
|
+
|
|
171
|
+
// System info (read-only, useful for debugging)
|
|
172
|
+
'os',
|
|
173
|
+
'node:os',
|
|
174
|
+
|
|
175
|
+
// fs is allowed but returns sandboxed version
|
|
176
|
+
'fs',
|
|
177
|
+
'node:fs',
|
|
178
|
+
])
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create a sandboxed require function that:
|
|
182
|
+
* 1. Returns scoped fs for 'fs' and 'node:fs'
|
|
183
|
+
* 2. Only allows modules in the ALLOWED_MODULES allowlist
|
|
184
|
+
* 3. Blocks all other modules (child_process, net, vm, third-party packages, etc.)
|
|
185
|
+
*/
|
|
186
|
+
function createSandboxedRequire(originalRequire: NodeRequire): NodeRequire {
|
|
187
|
+
const sandboxedRequire = ((id: string) => {
|
|
188
|
+
// Check allowlist first
|
|
189
|
+
if (!ALLOWED_MODULES.has(id)) {
|
|
190
|
+
const error = new Error(
|
|
191
|
+
`Module "${id}" is not allowed in the sandbox. ` +
|
|
192
|
+
`Only safe Node.js built-ins are permitted: ${[...ALLOWED_MODULES].filter((m) => !m.startsWith('node:')).join(', ')}`,
|
|
193
|
+
)
|
|
194
|
+
error.name = 'ModuleNotAllowedError'
|
|
195
|
+
throw error
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Return sandboxed fs
|
|
199
|
+
if (id === 'fs' || id === 'node:fs') {
|
|
200
|
+
return scopedFs
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return originalRequire(id)
|
|
204
|
+
}) as NodeRequire
|
|
205
|
+
|
|
206
|
+
// Copy over require properties
|
|
207
|
+
sandboxedRequire.resolve = originalRequire.resolve
|
|
208
|
+
sandboxedRequire.cache = originalRequire.cache
|
|
209
|
+
sandboxedRequire.extensions = originalRequire.extensions
|
|
210
|
+
sandboxedRequire.main = originalRequire.main
|
|
211
|
+
|
|
212
|
+
return sandboxedRequire
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const sandboxedRequire = createSandboxedRequire(require)
|
|
216
|
+
|
|
115
217
|
interface RemoteConfig {
|
|
116
218
|
host: string
|
|
117
219
|
port: number
|
|
@@ -189,6 +291,15 @@ async function sendLogToRelayServer(level: string, ...args: any[]) {
|
|
|
189
291
|
}
|
|
190
292
|
}
|
|
191
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Log to both console.error (for early startup) and relay server log file.
|
|
296
|
+
* Fire-and-forget to avoid blocking.
|
|
297
|
+
*/
|
|
298
|
+
function mcpLog(...args: any[]) {
|
|
299
|
+
console.error(...args)
|
|
300
|
+
sendLogToRelayServer('log', ...args)
|
|
301
|
+
}
|
|
302
|
+
|
|
192
303
|
async function getServerVersion(port: number): Promise<string | null> {
|
|
193
304
|
try {
|
|
194
305
|
const response = await fetch(`http://127.0.0.1:${port}/version`, {
|
|
@@ -211,6 +322,27 @@ async function killRelayServer(port: number): Promise<void> {
|
|
|
211
322
|
} catch {}
|
|
212
323
|
}
|
|
213
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Compare two semver versions. Returns:
|
|
327
|
+
* - negative if v1 < v2
|
|
328
|
+
* - 0 if v1 === v2
|
|
329
|
+
* - positive if v1 > v2
|
|
330
|
+
*/
|
|
331
|
+
function compareVersions(v1: string, v2: string): number {
|
|
332
|
+
const parts1 = v1.split('.').map(Number)
|
|
333
|
+
const parts2 = v2.split('.').map(Number)
|
|
334
|
+
const len = Math.max(parts1.length, parts2.length)
|
|
335
|
+
|
|
336
|
+
for (let i = 0; i < len; i++) {
|
|
337
|
+
const p1 = parts1[i] || 0
|
|
338
|
+
const p2 = parts2[i] || 0
|
|
339
|
+
if (p1 !== p2) {
|
|
340
|
+
return p1 - p2
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return 0
|
|
344
|
+
}
|
|
345
|
+
|
|
214
346
|
async function ensureRelayServer(): Promise<void> {
|
|
215
347
|
const serverVersion = await getServerVersion(RELAY_PORT)
|
|
216
348
|
|
|
@@ -218,18 +350,30 @@ async function ensureRelayServer(): Promise<void> {
|
|
|
218
350
|
return
|
|
219
351
|
}
|
|
220
352
|
|
|
353
|
+
// Don't restart if server version is higher than MCP version.
|
|
354
|
+
// This prevents older MCPs from killing a newer server.
|
|
355
|
+
if (serverVersion !== null && compareVersions(serverVersion, VERSION) > 0) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
221
359
|
if (serverVersion !== null) {
|
|
222
|
-
|
|
360
|
+
mcpLog(`CDP relay server version mismatch (server: ${serverVersion}, mcp: ${VERSION}), restarting...`)
|
|
223
361
|
await killRelayServer(RELAY_PORT)
|
|
224
362
|
} else {
|
|
225
|
-
|
|
363
|
+
mcpLog('CDP relay server not running, starting it...')
|
|
226
364
|
}
|
|
227
365
|
|
|
228
|
-
const
|
|
366
|
+
const dev = process.env.PLAYWRITER_NODE_ENV === 'development'
|
|
367
|
+
const scriptPath = dev
|
|
368
|
+
? path.resolve(__dirname, '../src/start-relay-server.ts')
|
|
369
|
+
: require.resolve('../dist/start-relay-server.js')
|
|
229
370
|
|
|
230
|
-
const serverProcess = spawn(process.execPath, [scriptPath], {
|
|
371
|
+
const serverProcess = spawn(dev ? 'tsx' : process.execPath, [scriptPath], {
|
|
231
372
|
detached: true,
|
|
232
373
|
stdio: 'ignore',
|
|
374
|
+
env: {
|
|
375
|
+
...process.env,
|
|
376
|
+
},
|
|
233
377
|
})
|
|
234
378
|
|
|
235
379
|
serverProcess.unref()
|
|
@@ -238,7 +382,7 @@ async function ensureRelayServer(): Promise<void> {
|
|
|
238
382
|
await sleep(500)
|
|
239
383
|
const newVersion = await getServerVersion(RELAY_PORT)
|
|
240
384
|
if (newVersion === VERSION) {
|
|
241
|
-
|
|
385
|
+
mcpLog('CDP relay server started successfully, waiting for extension to connect...')
|
|
242
386
|
await sleep(1000)
|
|
243
387
|
return
|
|
244
388
|
}
|
|
@@ -260,6 +404,12 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> {
|
|
|
260
404
|
const cdpEndpoint = getCdpUrl(remote || { port: RELAY_PORT })
|
|
261
405
|
const browser = await chromium.connectOverCDP(cdpEndpoint)
|
|
262
406
|
|
|
407
|
+
// Clear connection state when browser disconnects (e.g., extension reconnects, relay server restarts)
|
|
408
|
+
browser.on('disconnected', () => {
|
|
409
|
+
mcpLog('Browser disconnected, clearing connection state')
|
|
410
|
+
clearConnectionState()
|
|
411
|
+
})
|
|
412
|
+
|
|
263
413
|
const contexts = browser.contexts()
|
|
264
414
|
const context = contexts.length > 0 ? contexts[0] : await browser.newContext()
|
|
265
415
|
|
|
@@ -343,7 +493,7 @@ function setupPageConsoleListener(page: Page) {
|
|
|
343
493
|
pageLogs.shift()
|
|
344
494
|
}
|
|
345
495
|
} catch (e) {
|
|
346
|
-
|
|
496
|
+
mcpLog('[MCP] Failed to get console message text:', e)
|
|
347
497
|
return
|
|
348
498
|
}
|
|
349
499
|
})
|
|
@@ -375,7 +525,7 @@ async function resetConnection(): Promise<{ browser: Browser; page: Page; contex
|
|
|
375
525
|
try {
|
|
376
526
|
await state.browser.close()
|
|
377
527
|
} catch (e) {
|
|
378
|
-
|
|
528
|
+
mcpLog('Error closing browser:', e)
|
|
379
529
|
}
|
|
380
530
|
}
|
|
381
531
|
|
|
@@ -393,6 +543,12 @@ async function resetConnection(): Promise<{ browser: Browser; page: Page; contex
|
|
|
393
543
|
const cdpEndpoint = getCdpUrl(remote || { port: RELAY_PORT })
|
|
394
544
|
const browser = await chromium.connectOverCDP(cdpEndpoint)
|
|
395
545
|
|
|
546
|
+
// Clear connection state when browser disconnects (e.g., extension reconnects, relay server restarts)
|
|
547
|
+
browser.on('disconnected', () => {
|
|
548
|
+
mcpLog('Browser disconnected, clearing connection state')
|
|
549
|
+
clearConnectionState()
|
|
550
|
+
})
|
|
551
|
+
|
|
396
552
|
const contexts = browser.contexts()
|
|
397
553
|
const context = contexts.length > 0 ? contexts[0] : await browser.newContext()
|
|
398
554
|
|
|
@@ -431,53 +587,68 @@ const promptContent =
|
|
|
431
587
|
fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src', 'prompt.md'), 'utf-8') +
|
|
432
588
|
`\n\nfor debugging internal playwriter errors, check playwriter relay server logs at: ${LOG_FILE_PATH}`
|
|
433
589
|
|
|
434
|
-
server.resource(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
return {
|
|
456
|
-
contents: [
|
|
457
|
-
{
|
|
458
|
-
uri: 'https://playwriter.dev/resources/editor-api.md',
|
|
459
|
-
text: content,
|
|
460
|
-
mimeType: 'text/plain',
|
|
461
|
-
},
|
|
462
|
-
],
|
|
463
|
-
}
|
|
464
|
-
})
|
|
590
|
+
server.resource(
|
|
591
|
+
'debugger-api',
|
|
592
|
+
'https://playwriter.dev/resources/debugger-api.md',
|
|
593
|
+
{ mimeType: 'text/plain' },
|
|
594
|
+
async () => {
|
|
595
|
+
const packageJsonPath = require.resolve('playwriter/package.json')
|
|
596
|
+
const packageDir = path.dirname(packageJsonPath)
|
|
597
|
+
const content = fs.readFileSync(path.join(packageDir, 'dist', 'debugger-api.md'), 'utf-8')
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
contents: [
|
|
601
|
+
{
|
|
602
|
+
uri: 'https://playwriter.dev/resources/debugger-api.md',
|
|
603
|
+
text: content,
|
|
604
|
+
mimeType: 'text/plain',
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
)
|
|
465
610
|
|
|
466
|
-
server.resource(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
611
|
+
server.resource(
|
|
612
|
+
'editor-api',
|
|
613
|
+
'https://playwriter.dev/resources/editor-api.md',
|
|
614
|
+
{ mimeType: 'text/plain' },
|
|
615
|
+
async () => {
|
|
616
|
+
const packageJsonPath = require.resolve('playwriter/package.json')
|
|
617
|
+
const packageDir = path.dirname(packageJsonPath)
|
|
618
|
+
const content = fs.readFileSync(path.join(packageDir, 'dist', 'editor-api.md'), 'utf-8')
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
contents: [
|
|
622
|
+
{
|
|
623
|
+
uri: 'https://playwriter.dev/resources/editor-api.md',
|
|
624
|
+
text: content,
|
|
625
|
+
mimeType: 'text/plain',
|
|
626
|
+
},
|
|
627
|
+
],
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
)
|
|
470
631
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
632
|
+
server.resource(
|
|
633
|
+
'styles-api',
|
|
634
|
+
'https://playwriter.dev/resources/styles-api.md',
|
|
635
|
+
{ mimeType: 'text/plain' },
|
|
636
|
+
async () => {
|
|
637
|
+
const packageJsonPath = require.resolve('playwriter/package.json')
|
|
638
|
+
const packageDir = path.dirname(packageJsonPath)
|
|
639
|
+
const content = fs.readFileSync(path.join(packageDir, 'dist', 'styles-api.md'), 'utf-8')
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
contents: [
|
|
643
|
+
{
|
|
644
|
+
uri: 'https://playwriter.dev/resources/styles-api.md',
|
|
645
|
+
text: content,
|
|
646
|
+
mimeType: 'text/plain',
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
)
|
|
481
652
|
|
|
482
653
|
server.tool(
|
|
483
654
|
'execute',
|
|
@@ -520,7 +691,7 @@ server.tool(
|
|
|
520
691
|
const page = await getCurrentPage(timeout)
|
|
521
692
|
const context = state.context || page.context()
|
|
522
693
|
|
|
523
|
-
|
|
694
|
+
mcpLog('Executing code:', code)
|
|
524
695
|
|
|
525
696
|
const customConsole = {
|
|
526
697
|
log: (...args: any[]) => {
|
|
@@ -675,11 +846,11 @@ server.tool(
|
|
|
675
846
|
return session
|
|
676
847
|
}
|
|
677
848
|
|
|
678
|
-
const createDebugger = (options: { cdp:
|
|
849
|
+
const createDebugger = (options: { cdp: ICDPSession }) => {
|
|
679
850
|
return new Debugger(options)
|
|
680
851
|
}
|
|
681
852
|
|
|
682
|
-
const createEditor = (options: { cdp:
|
|
853
|
+
const createEditor = (options: { cdp: ICDPSession }) => {
|
|
683
854
|
return new Editor(options)
|
|
684
855
|
}
|
|
685
856
|
|
|
@@ -709,6 +880,8 @@ server.tool(
|
|
|
709
880
|
getStylesForLocator: getStylesForLocatorFn,
|
|
710
881
|
formatStylesAsText,
|
|
711
882
|
getReactSource: getReactSourceFn,
|
|
883
|
+
showAriaRefLabels,
|
|
884
|
+
hideAriaRefLabels,
|
|
712
885
|
resetPlaywright: async () => {
|
|
713
886
|
const { page: newPage, context: newContext } = await resetConnection()
|
|
714
887
|
|
|
@@ -728,8 +901,10 @@ server.tool(
|
|
|
728
901
|
getStylesForLocator: getStylesForLocatorFn,
|
|
729
902
|
formatStylesAsText,
|
|
730
903
|
getReactSource: getReactSourceFn,
|
|
904
|
+
showAriaRefLabels,
|
|
905
|
+
hideAriaRefLabels,
|
|
731
906
|
resetPlaywright: vmContextObj.resetPlaywright,
|
|
732
|
-
require,
|
|
907
|
+
require: sandboxedRequire,
|
|
733
908
|
// TODO --experimental-vm-modules is needed to make import work in vm
|
|
734
909
|
import: vmContextObj.import,
|
|
735
910
|
...usefulGlobals,
|
|
@@ -738,7 +913,7 @@ server.tool(
|
|
|
738
913
|
Object.assign(vmContextObj, resetObj)
|
|
739
914
|
return { page: newPage, context: newContext }
|
|
740
915
|
},
|
|
741
|
-
require,
|
|
916
|
+
require: sandboxedRequire,
|
|
742
917
|
import: (specifier: string) => import(specifier),
|
|
743
918
|
...usefulGlobals,
|
|
744
919
|
}
|
|
@@ -786,15 +961,16 @@ server.tool(
|
|
|
786
961
|
}
|
|
787
962
|
} catch (error: any) {
|
|
788
963
|
const errorStack = error.stack || error.message
|
|
789
|
-
console.error('Error in execute tool:', errorStack)
|
|
790
|
-
|
|
791
|
-
const logsText = formatConsoleLogs(consoleLogs, 'Console output (before error)')
|
|
792
|
-
|
|
793
964
|
const isTimeoutError = error instanceof CodeExecutionTimeoutError || error.name === 'TimeoutError'
|
|
965
|
+
|
|
966
|
+
// Always log to stderr, but only send non-timeout errors to relay server
|
|
967
|
+
console.error('Error in execute tool:', errorStack)
|
|
794
968
|
if (!isTimeoutError) {
|
|
795
|
-
sendLogToRelayServer('error', '
|
|
969
|
+
sendLogToRelayServer('error', 'Error in execute tool:', errorStack)
|
|
796
970
|
}
|
|
797
971
|
|
|
972
|
+
const logsText = formatConsoleLogs(consoleLogs, 'Console output (before error)')
|
|
973
|
+
|
|
798
974
|
const resetHint = isTimeoutError
|
|
799
975
|
? ''
|
|
800
976
|
: '\n\n[HINT: If this is an internal Playwright error, page/browser closed, or connection issue, call the `reset` tool to reconnect. Do NOT reset for other non-connection non-internal errors.]'
|
|
@@ -881,7 +1057,7 @@ export async function startMcp(options: { host?: string; token?: string } = {})
|
|
|
881
1057
|
if (!remote) {
|
|
882
1058
|
await ensureRelayServer()
|
|
883
1059
|
} else {
|
|
884
|
-
|
|
1060
|
+
mcpLog(`Using remote CDP relay server: ${remote.host}:${remote.port}`)
|
|
885
1061
|
await checkRemoteServer(remote)
|
|
886
1062
|
}
|
|
887
1063
|
const transport = new StdioServerTransport()
|