playwriter 0.0.15 → 0.0.20
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/cdp-session.d.ts +21 -0
- package/dist/cdp-session.d.ts.map +1 -0
- package/dist/cdp-session.js +114 -0
- package/dist/cdp-session.js.map +1 -0
- package/dist/cdp-types.d.ts +15 -0
- package/dist/cdp-types.d.ts.map +1 -1
- package/dist/cdp-types.js.map +1 -1
- package/dist/create-logger.d.ts +9 -0
- package/dist/create-logger.d.ts.map +1 -0
- package/dist/create-logger.js +43 -0
- package/dist/create-logger.js.map +1 -0
- package/dist/extension/cdp-relay.d.ts +7 -3
- package/dist/extension/cdp-relay.d.ts.map +1 -1
- package/dist/extension/cdp-relay.js +46 -13
- package/dist/extension/cdp-relay.js.map +1 -1
- package/dist/mcp.js +52 -27
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.d.ts.map +1 -1
- package/dist/mcp.test.js +625 -185
- package/dist/mcp.test.js.map +1 -1
- package/dist/prompt.md +36 -8
- package/dist/selector-generator.js +331 -0
- package/dist/start-relay-server.d.ts +1 -3
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +3 -16
- package/dist/start-relay-server.js.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +34 -0
- package/dist/utils.js.map +1 -1
- package/dist/wait-for-page-load.d.ts +16 -0
- package/dist/wait-for-page-load.d.ts.map +1 -0
- package/dist/wait-for-page-load.js +114 -0
- package/dist/wait-for-page-load.js.map +1 -0
- package/package.json +4 -2
- package/src/cdp-session.ts +142 -0
- package/src/cdp-types.ts +6 -0
- package/src/create-logger.ts +56 -0
- package/src/debugger.md +453 -0
- package/src/extension/cdp-relay.ts +57 -15
- package/src/mcp.test.ts +743 -191
- package/src/mcp.ts +63 -29
- package/src/prompt.md +36 -8
- package/src/snapshots/shadcn-ui-accessibility.md +94 -91
- package/src/start-relay-server.ts +3 -20
- package/src/utils.ts +43 -0
- package/src/wait-for-page-load.ts +162 -0
package/src/mcp.ts
CHANGED
|
@@ -4,13 +4,16 @@ import { z } from 'zod'
|
|
|
4
4
|
import { Page, Browser, BrowserContext, chromium } from 'playwright-core'
|
|
5
5
|
import fs from 'node:fs'
|
|
6
6
|
import path from 'node:path'
|
|
7
|
+
import os from 'node:os'
|
|
7
8
|
import { spawn } from 'node:child_process'
|
|
8
9
|
import { createRequire } from 'node:module'
|
|
9
10
|
import { fileURLToPath } from 'node:url'
|
|
10
11
|
import vm from 'node:vm'
|
|
11
12
|
import dedent from 'string-dedent'
|
|
12
13
|
import { createPatch } from 'diff'
|
|
13
|
-
import { getCdpUrl } from './utils.js'
|
|
14
|
+
import { getCdpUrl, LOG_FILE_PATH } from './utils.js'
|
|
15
|
+
import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js'
|
|
16
|
+
import { getCDPSessionForPage, CDPSession } from './cdp-session.js'
|
|
14
17
|
|
|
15
18
|
const require = createRequire(import.meta.url)
|
|
16
19
|
|
|
@@ -59,6 +62,8 @@ interface VMContext {
|
|
|
59
62
|
resetPlaywright: () => Promise<{ page: Page; context: BrowserContext }>
|
|
60
63
|
getLatestLogs: (options?: { page?: Page; count?: number; search?: string | RegExp }) => Promise<string[]>
|
|
61
64
|
clearAllLogs: () => void
|
|
65
|
+
waitForPageLoad: (options: WaitForPageLoadOptions) => Promise<WaitForPageLoadResult>
|
|
66
|
+
getCDPSession: (options: { page: Page }) => Promise<CDPSession>
|
|
62
67
|
require: NodeRequire
|
|
63
68
|
import: (specifier: string) => Promise<any>
|
|
64
69
|
}
|
|
@@ -83,9 +88,31 @@ const MAX_LOGS_PER_PAGE = 5000
|
|
|
83
88
|
// Store last accessibility snapshot per page for diff feature
|
|
84
89
|
const lastSnapshots: WeakMap<Page, string> = new WeakMap()
|
|
85
90
|
|
|
91
|
+
// Cache CDP sessions per page
|
|
92
|
+
const cdpSessionCache: WeakMap<Page, CDPSession> = new WeakMap()
|
|
93
|
+
|
|
86
94
|
const RELAY_PORT = 19988
|
|
87
95
|
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`
|
|
88
96
|
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async function setDeviceScaleFactorForMacOS(context: BrowserContext): Promise<void> {
|
|
100
|
+
if (os.platform() !== 'darwin') {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
const options = (context as any)._options
|
|
104
|
+
if (!options || options.deviceScaleFactor === 2) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
options.deviceScaleFactor = 2
|
|
108
|
+
for (const page of context.pages()) {
|
|
109
|
+
const delegate = (page as any)._delegate
|
|
110
|
+
if (delegate?.updateEmulatedViewportSize) {
|
|
111
|
+
await delegate.updateEmulatedViewportSize().catch(() => {})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
89
116
|
function isRegExp(value: any): value is RegExp {
|
|
90
117
|
return typeof value === 'object' && value !== null && typeof value.test === 'function' && typeof value.exec === 'function'
|
|
91
118
|
}
|
|
@@ -152,7 +179,7 @@ async function ensureRelayServer(): Promise<void> {
|
|
|
152
179
|
}
|
|
153
180
|
}
|
|
154
181
|
|
|
155
|
-
throw new Error(
|
|
182
|
+
throw new Error(`Failed to start CDP relay server after 5 seconds. Check logs at: ${LOG_FILE_PATH}`)
|
|
156
183
|
}
|
|
157
184
|
|
|
158
185
|
async function ensureConnection(): Promise<{ browser: Browser; page: Page }> {
|
|
@@ -182,6 +209,8 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> {
|
|
|
182
209
|
// Set up console listener for all existing pages
|
|
183
210
|
context.pages().forEach((p) => setupPageConsoleListener(p))
|
|
184
211
|
|
|
212
|
+
await setDeviceScaleFactorForMacOS(context)
|
|
213
|
+
|
|
185
214
|
state.browser = browser
|
|
186
215
|
state.page = page
|
|
187
216
|
state.context = context
|
|
@@ -195,22 +224,12 @@ async function getPageTargetId(page: Page): Promise<string> {
|
|
|
195
224
|
throw new Error('Page is null or undefined')
|
|
196
225
|
}
|
|
197
226
|
|
|
198
|
-
// Always use internal _guid for consistency and speed
|
|
199
227
|
const guid = (page as any)._guid
|
|
200
228
|
if (guid) {
|
|
201
229
|
return guid
|
|
202
230
|
}
|
|
203
231
|
|
|
204
|
-
|
|
205
|
-
// Fallback to CDP if _guid is not available
|
|
206
|
-
const client = await page.context().newCDPSession(page)
|
|
207
|
-
const { targetInfo } = await client.send('Target.getTargetInfo')
|
|
208
|
-
await client.detach()
|
|
209
|
-
|
|
210
|
-
return targetInfo.targetId
|
|
211
|
-
} catch (e) {
|
|
212
|
-
throw new Error(`Could not get page identifier: ${e}`)
|
|
213
|
-
}
|
|
232
|
+
throw new Error('Could not get page identifier: _guid not available')
|
|
214
233
|
}
|
|
215
234
|
|
|
216
235
|
function setupPageConsoleListener(page: Page) {
|
|
@@ -315,6 +334,8 @@ async function resetConnection(): Promise<{ browser: Browser; page: Page; contex
|
|
|
315
334
|
// Set up console listener for all existing pages
|
|
316
335
|
context.pages().forEach((p) => setupPageConsoleListener(p))
|
|
317
336
|
|
|
337
|
+
await setDeviceScaleFactorForMacOS(context)
|
|
338
|
+
|
|
318
339
|
state.browser = browser
|
|
319
340
|
state.page = page
|
|
320
341
|
state.context = context
|
|
@@ -329,7 +350,7 @@ const server = new McpServer({
|
|
|
329
350
|
version: '1.0.0',
|
|
330
351
|
})
|
|
331
352
|
|
|
332
|
-
const promptContent = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'prompt.md'), 'utf-8')
|
|
353
|
+
const promptContent = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'prompt.md'), 'utf-8') + `\n\nfor debugging errors, check relay server logs at: ${LOG_FILE_PATH}`
|
|
333
354
|
|
|
334
355
|
server.tool(
|
|
335
356
|
'execute',
|
|
@@ -464,20 +485,19 @@ server.tool(
|
|
|
464
485
|
throw new Error('getLocatorStringForElement: argument must be a Playwright Locator or ElementHandle')
|
|
465
486
|
}
|
|
466
487
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
const
|
|
480
|
-
const generator = createSelectorGenerator(WIN)
|
|
488
|
+
const elementPage = element.page ? element.page() : page
|
|
489
|
+
const hasGenerator = await elementPage.evaluate(() => !!(globalThis as any).__selectorGenerator)
|
|
490
|
+
|
|
491
|
+
if (!hasGenerator) {
|
|
492
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
493
|
+
const scriptPath = path.join(currentDir, '..', 'dist', 'selector-generator.js')
|
|
494
|
+
const scriptContent = fs.readFileSync(scriptPath, 'utf-8')
|
|
495
|
+
await elementPage.addScriptTag({ content: scriptContent })
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return await element.evaluate((el: any) => {
|
|
499
|
+
const { createSelectorGenerator, toLocator } = (globalThis as any).__selectorGenerator
|
|
500
|
+
const generator = createSelectorGenerator(globalThis)
|
|
481
501
|
const result = generator(el)
|
|
482
502
|
return toLocator(result.selector, 'javascript')
|
|
483
503
|
})
|
|
@@ -516,6 +536,17 @@ server.tool(
|
|
|
516
536
|
browserLogs.clear()
|
|
517
537
|
}
|
|
518
538
|
|
|
539
|
+
const getCDPSession = async (options: { page: Page }) => {
|
|
540
|
+
const cached = cdpSessionCache.get(options.page)
|
|
541
|
+
if (cached) {
|
|
542
|
+
return cached
|
|
543
|
+
}
|
|
544
|
+
const wsUrl = getCdpUrl({ port: RELAY_PORT })
|
|
545
|
+
const session = await getCDPSessionForPage({ page: options.page, wsUrl })
|
|
546
|
+
cdpSessionCache.set(options.page, session)
|
|
547
|
+
return session
|
|
548
|
+
}
|
|
549
|
+
|
|
519
550
|
let vmContextObj: VMContextWithGlobals = {
|
|
520
551
|
page,
|
|
521
552
|
context,
|
|
@@ -525,6 +556,8 @@ server.tool(
|
|
|
525
556
|
getLocatorStringForElement,
|
|
526
557
|
getLatestLogs,
|
|
527
558
|
clearAllLogs,
|
|
559
|
+
waitForPageLoad,
|
|
560
|
+
getCDPSession,
|
|
528
561
|
resetPlaywright: async () => {
|
|
529
562
|
const { page: newPage, context: newContext } = await resetConnection()
|
|
530
563
|
|
|
@@ -537,6 +570,8 @@ server.tool(
|
|
|
537
570
|
getLocatorStringForElement,
|
|
538
571
|
getLatestLogs,
|
|
539
572
|
clearAllLogs,
|
|
573
|
+
waitForPageLoad,
|
|
574
|
+
getCDPSession,
|
|
540
575
|
resetPlaywright: vmContextObj.resetPlaywright,
|
|
541
576
|
require,
|
|
542
577
|
// TODO --experimental-vm-modules is needed to make import work in vm
|
|
@@ -668,4 +703,3 @@ async function main() {
|
|
|
668
703
|
}
|
|
669
704
|
|
|
670
705
|
main().catch(console.error)
|
|
671
|
-
|
package/src/prompt.md
CHANGED
|
@@ -52,12 +52,13 @@ IMPORTANT! never call bringToFront unless specifically asked by the user. It is
|
|
|
52
52
|
- only call `page.close()` if the user asks you so or if you previously created this page yourself with `newPage`. do not close user created pages unless asked
|
|
53
53
|
- try to never sleep or run `page.waitForTimeout` unless you have to. there are better ways to wait for an element
|
|
54
54
|
- never close browser or context. NEVER call `browser.close()`
|
|
55
|
+
- NEVER use `page.context().newCDPSession()` or `browser.newCDPSession()` - these do not work through the playwriter relay. If you need to send raw CDP commands, use the `getCDPSession` utility function instead.
|
|
55
56
|
|
|
56
57
|
## always check the current page state after an action
|
|
57
58
|
|
|
58
59
|
after you click a button or submit a form you ALWAYS have to then check what is the current state of the page. you cannot assume what happened after doing an action. instead run the following code to know what happened after the action:
|
|
59
60
|
|
|
60
|
-
`console.log('url:', page.url()); console.log(await accessibilitySnapshot({ page }).then(x => x.slice(0,
|
|
61
|
+
`console.log('url:', page.url()); console.log(await accessibilitySnapshot({ page }).then(x => x.split('\n').slice(0, 30).join('\n')));`
|
|
61
62
|
|
|
62
63
|
if nothing happened you may need to wait before the action completes, using something like `page.waitForNavigation({timeout: 3000})` or `await page.waitForLoadState('networkidle', {timeout: 3000})`
|
|
63
64
|
|
|
@@ -81,8 +82,25 @@ you have access to some functions in addition to playwright methods:
|
|
|
81
82
|
- `page`: (optional) filter logs by a specific page instance. Only returns logs from that page
|
|
82
83
|
- `count`: (optional) limit number of logs to return. If not specified, returns all available logs
|
|
83
84
|
- `search`: (optional) string or regex to filter logs. Only returns logs that match
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
- `waitForPageLoad({ page, timeout, pollInterval, minWait })`: smart network-aware page load detection. Playwright's `networkidle` waits for ALL requests to finish, which often times out on sites with analytics/ads. This function ignores those and returns when meaningful content is loaded.
|
|
86
|
+
- `page`: the page object to wait on
|
|
87
|
+
- `timeout`: (optional) max wait time in ms (default: 30000)
|
|
88
|
+
- `pollInterval`: (optional) how often to check in ms (default: 100)
|
|
89
|
+
- `minWait`: (optional) minimum wait before checking in ms (default: 500)
|
|
90
|
+
- Returns: `{ success, readyState, pendingRequests, waitTimeMs, timedOut }`
|
|
91
|
+
- Filters out: ad networks (doubleclick, googlesyndication), analytics (google-analytics, mixpanel, segment), social (facebook.net, twitter), support widgets (intercom, zendesk), and slow fonts/images
|
|
92
|
+
- `getCDPSession({ page })`: creates a CDP session to send raw Chrome DevTools Protocol commands. Use this instead of `page.context().newCDPSession()` which does not work through the playwriter relay. Sessions are cached per page.
|
|
93
|
+
- `page`: the page object to create the session for
|
|
94
|
+
- Returns: `{ send(method, params?), on(event, callback), off(event, callback) }`
|
|
95
|
+
- Example: `const cdp = await getCDPSession({ page }); const metrics = await cdp.send('Page.getLayoutMetrics');`
|
|
96
|
+
- Example listening for events:
|
|
97
|
+
```js
|
|
98
|
+
const cdp = await getCDPSession({ page });
|
|
99
|
+
await cdp.send('Debugger.enable');
|
|
100
|
+
const pausedEvent = await new Promise((resolve) => { cdp.on('Debugger.paused', resolve); });
|
|
101
|
+
console.log('Paused at:', pausedEvent.callFrames[0].location);
|
|
102
|
+
await cdp.send('Debugger.resume');
|
|
103
|
+
```
|
|
86
104
|
|
|
87
105
|
example:
|
|
88
106
|
|
|
@@ -108,14 +126,24 @@ Then you can use `page.locator(`aria-ref=${ref}`)` to get an element with a spec
|
|
|
108
126
|
|
|
109
127
|
IMPORTANT: notice that we do not add any quotes in `aria-ref`! it MUST be called without quotes
|
|
110
128
|
|
|
111
|
-
## getting selector for
|
|
129
|
+
## getting a stable selector for an element (getLocatorStringForElement)
|
|
130
|
+
|
|
131
|
+
The `aria-ref` values from accessibility snapshots are ephemeral - they change on page reload and when components remount. Use `getLocatorStringForElement(element)` to get a stable Playwright locator string that you can reuse programmatically.
|
|
112
132
|
|
|
113
|
-
|
|
133
|
+
This is useful for:
|
|
134
|
+
- Getting a selector you can store and reuse across page reloads
|
|
135
|
+
- Finding similar elements in a list (modify the selector pattern)
|
|
136
|
+
- Debugging which selector Playwright would use for an element
|
|
114
137
|
|
|
115
138
|
```js
|
|
116
|
-
const loc = page.locator('aria-ref=
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
const loc = page.locator('aria-ref=e14');
|
|
140
|
+
const selector = await getLocatorStringForElement(loc);
|
|
141
|
+
console.log(selector);
|
|
142
|
+
// => "getByRole('button', { name: 'Save' })"
|
|
143
|
+
|
|
144
|
+
// use the selector programmatically with eval:
|
|
145
|
+
const stableLocator = page.getByRole('button', { name: 'Save' })
|
|
146
|
+
await stableLocator.click();
|
|
119
147
|
```
|
|
120
148
|
|
|
121
149
|
## finding specific elements with snapshot
|
|
@@ -18,124 +18,127 @@ Return value:
|
|
|
18
18
|
- /url: /charts/area
|
|
19
19
|
- link "Directory" [ref=e13] [cursor=pointer]:
|
|
20
20
|
- /url: /docs/directory
|
|
21
|
-
- link "
|
|
22
|
-
- /url: /
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- generic: ⌘
|
|
30
|
-
- generic: K
|
|
31
|
-
- link "100.5k" [ref=e22] [cursor=pointer]:
|
|
21
|
+
- link "Create" [ref=e14] [cursor=pointer]:
|
|
22
|
+
- /url: /create
|
|
23
|
+
- generic [ref=e15]:
|
|
24
|
+
- button "Search documentation... ⌘K" [ref=e17]:
|
|
25
|
+
- generic [ref=e18]: Search documentation...
|
|
26
|
+
- generic [ref=e19]:
|
|
27
|
+
- generic: ⌘K
|
|
28
|
+
- link "103k" [ref=e20] [cursor=pointer]:
|
|
32
29
|
- /url: https://github.com/shadcn-ui/ui
|
|
33
30
|
- img
|
|
34
|
-
- generic [ref=
|
|
35
|
-
- button "Toggle theme" [ref=
|
|
31
|
+
- generic [ref=e21]: 103k
|
|
32
|
+
- button "Toggle theme" [ref=e22]:
|
|
36
33
|
- img
|
|
37
|
-
- generic [ref=
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
- generic [ref=e23]: Toggle theme
|
|
35
|
+
- link "New Project" [ref=e24] [cursor=pointer]:
|
|
36
|
+
- /url: /create
|
|
37
|
+
- img
|
|
38
|
+
- text: New Project
|
|
39
|
+
- main [ref=e25]:
|
|
40
|
+
- generic [ref=e26]:
|
|
41
|
+
- generic [ref=e29]:
|
|
42
|
+
- link "New npx shadcn create" [ref=e30] [cursor=pointer]:
|
|
42
43
|
- /url: /docs/changelog
|
|
43
|
-
- generic "New" [ref=
|
|
44
|
-
- text:
|
|
44
|
+
- generic "New" [ref=e31]
|
|
45
|
+
- text: npx shadcn create
|
|
45
46
|
- img
|
|
46
|
-
- heading "The Foundation for your Design System" [level=1] [ref=
|
|
47
|
-
- paragraph [ref=
|
|
48
|
-
- generic [ref=
|
|
49
|
-
- link "
|
|
50
|
-
- /url: /
|
|
51
|
-
|
|
47
|
+
- heading "The Foundation for your Design System" [level=1] [ref=e32]
|
|
48
|
+
- paragraph [ref=e33]: A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code.
|
|
49
|
+
- generic [ref=e34]:
|
|
50
|
+
- link "New Project" [ref=e35] [cursor=pointer]:
|
|
51
|
+
- /url: /create
|
|
52
|
+
- img
|
|
53
|
+
- text: New Project
|
|
54
|
+
- link "View Components" [ref=e36] [cursor=pointer]:
|
|
52
55
|
- /url: /docs/components
|
|
53
|
-
- generic [ref=
|
|
54
|
-
- generic [ref=
|
|
55
|
-
- link "Examples" [ref=
|
|
56
|
+
- generic [ref=e38]:
|
|
57
|
+
- generic [ref=e43]:
|
|
58
|
+
- link "Examples" [ref=e44] [cursor=pointer]:
|
|
56
59
|
- /url: /
|
|
57
|
-
- link "Dashboard" [ref=
|
|
60
|
+
- link "Dashboard" [ref=e45] [cursor=pointer]:
|
|
58
61
|
- /url: /examples/dashboard
|
|
59
|
-
- link "Tasks" [ref=
|
|
62
|
+
- link "Tasks" [ref=e46] [cursor=pointer]:
|
|
60
63
|
- /url: /examples/tasks
|
|
61
|
-
- link "Playground" [ref=
|
|
64
|
+
- link "Playground" [ref=e47] [cursor=pointer]:
|
|
62
65
|
- /url: /examples/playground
|
|
63
|
-
- link "Authentication" [ref=
|
|
66
|
+
- link "Authentication" [ref=e48] [cursor=pointer]:
|
|
64
67
|
- /url: /examples/authentication
|
|
65
|
-
- generic [ref=
|
|
66
|
-
- generic [ref=
|
|
67
|
-
- combobox "Theme" [ref=
|
|
68
|
-
- generic [ref=
|
|
68
|
+
- generic [ref=e49]:
|
|
69
|
+
- generic [ref=e50]: Theme
|
|
70
|
+
- combobox "Theme" [ref=e51]:
|
|
71
|
+
- generic [ref=e52]: "Theme:"
|
|
69
72
|
- generic: Neutral
|
|
70
73
|
- img
|
|
71
|
-
- button "Copy Code" [ref=
|
|
74
|
+
- button "Copy Code" [ref=e53]:
|
|
72
75
|
- img
|
|
73
|
-
- generic [ref=
|
|
74
|
-
- generic [ref=
|
|
75
|
-
- generic [ref=
|
|
76
|
-
- group "Payment Method" [ref=
|
|
77
|
-
- generic [ref=
|
|
78
|
-
- paragraph [ref=
|
|
79
|
-
- generic [ref=
|
|
80
|
-
- group [ref=
|
|
81
|
-
- generic [ref=
|
|
82
|
-
- textbox "Name on Card" [ref=
|
|
76
|
+
- generic [ref=e54]: Copy Code
|
|
77
|
+
- generic [ref=e58]:
|
|
78
|
+
- generic [ref=e62]:
|
|
79
|
+
- group "Payment Method" [ref=e63]:
|
|
80
|
+
- generic [ref=e64]: Payment Method
|
|
81
|
+
- paragraph [ref=e65]: All transactions are secure and encrypted
|
|
82
|
+
- generic [ref=e66]:
|
|
83
|
+
- group [ref=e67]:
|
|
84
|
+
- generic [ref=e68]: Name on Card
|
|
85
|
+
- textbox "Name on Card" [ref=e69]:
|
|
83
86
|
- /placeholder: John Doe
|
|
84
|
-
- generic [ref=
|
|
85
|
-
- group [ref=
|
|
86
|
-
- generic [ref=
|
|
87
|
-
- textbox "Card Number" [ref=
|
|
87
|
+
- generic [ref=e70]:
|
|
88
|
+
- group [ref=e71]:
|
|
89
|
+
- generic [ref=e72]: Card Number
|
|
90
|
+
- textbox "Card Number" [ref=e73]:
|
|
88
91
|
- /placeholder: 1234 5678 9012 3456
|
|
89
|
-
- paragraph [ref=
|
|
90
|
-
- group [ref=
|
|
91
|
-
- generic [ref=
|
|
92
|
-
- textbox "CVV" [ref=
|
|
92
|
+
- paragraph [ref=e74]: Enter your 16-digit number.
|
|
93
|
+
- group [ref=e75]:
|
|
94
|
+
- generic [ref=e76]: CVV
|
|
95
|
+
- textbox "CVV" [ref=e77]:
|
|
93
96
|
- /placeholder: "123"
|
|
94
|
-
- generic [ref=
|
|
95
|
-
- group [ref=
|
|
96
|
-
- generic [ref=
|
|
97
|
-
- combobox "Month" [ref=
|
|
97
|
+
- generic [ref=e78]:
|
|
98
|
+
- group [ref=e79]:
|
|
99
|
+
- generic [ref=e80]: Month
|
|
100
|
+
- combobox "Month" [ref=e81]:
|
|
98
101
|
- generic: MM
|
|
99
102
|
- img
|
|
100
|
-
- combobox [ref=
|
|
101
|
-
- group [ref=
|
|
102
|
-
- generic [ref=
|
|
103
|
-
- combobox "Year" [ref=
|
|
103
|
+
- combobox [ref=e82]
|
|
104
|
+
- group [ref=e83]:
|
|
105
|
+
- generic [ref=e84]: Year
|
|
106
|
+
- combobox "Year" [ref=e85]:
|
|
104
107
|
- generic: YYYY
|
|
105
108
|
- img
|
|
106
|
-
- combobox [ref=
|
|
107
|
-
- group "Billing Address" [ref=
|
|
108
|
-
- generic [ref=
|
|
109
|
-
- paragraph [ref=
|
|
110
|
-
- group [ref=
|
|
111
|
-
- checkbox "Same as shipping address" [checked] [ref=
|
|
109
|
+
- combobox [ref=e86]
|
|
110
|
+
- group "Billing Address" [ref=e88]:
|
|
111
|
+
- generic [ref=e89]: Billing Address
|
|
112
|
+
- paragraph [ref=e90]: The billing address associated with your payment method
|
|
113
|
+
- group [ref=e92]:
|
|
114
|
+
- checkbox "Same as shipping address" [checked] [ref=e93]:
|
|
112
115
|
- generic:
|
|
113
116
|
- img
|
|
114
117
|
- checkbox [checked]
|
|
115
|
-
- generic [ref=
|
|
116
|
-
- group [ref=
|
|
117
|
-
- group [ref=
|
|
118
|
-
- generic [ref=
|
|
119
|
-
- textbox "Comments" [ref=
|
|
118
|
+
- generic [ref=e94]: Same as shipping address
|
|
119
|
+
- group [ref=e96]:
|
|
120
|
+
- group [ref=e98]:
|
|
121
|
+
- generic [ref=e99]: Comments
|
|
122
|
+
- textbox "Comments" [ref=e100]:
|
|
120
123
|
- /placeholder: Add any additional comments
|
|
121
|
-
- group [ref=
|
|
122
|
-
- button "Submit" [ref=
|
|
123
|
-
- button "Cancel" [ref=
|
|
124
|
-
- generic [ref=
|
|
125
|
-
- generic [ref=
|
|
126
|
-
- generic [ref=
|
|
127
|
-
- generic [ref=
|
|
128
|
-
- img "@shadcn" [ref=
|
|
129
|
-
- img "@maxleiter" [ref=
|
|
130
|
-
- img "@evilrabbit" [ref=
|
|
131
|
-
- generic [ref=
|
|
132
|
-
- generic [ref=
|
|
133
|
-
- button "Invite Members" [ref=
|
|
124
|
+
- group [ref=e101]:
|
|
125
|
+
- button "Submit" [ref=e102]
|
|
126
|
+
- button "Cancel" [ref=e103]
|
|
127
|
+
- generic [ref=e104]:
|
|
128
|
+
- generic [ref=e105]:
|
|
129
|
+
- generic [ref=e106]:
|
|
130
|
+
- generic [ref=e108]:
|
|
131
|
+
- img "@shadcn" [ref=e110]
|
|
132
|
+
- img "@maxleiter" [ref=e112]
|
|
133
|
+
- img "@evilrabbit" [ref=e114]
|
|
134
|
+
- generic [ref=e115]: No Team Members
|
|
135
|
+
- generic [ref=e116]: Invite your team to collaborate on this project.
|
|
136
|
+
- button "Invite Members" [ref=e118]:
|
|
134
137
|
- img
|
|
135
138
|
- text: Invite Members
|
|
136
|
-
- generic [ref=
|
|
137
|
-
- generic [ref=
|
|
139
|
+
- generic [ref=e119]:
|
|
140
|
+
- generic [ref=e120]:
|
|
138
141
|
- status "Loading"
|
|
139
|
-
|
|
142
|
+
|
|
140
143
|
|
|
141
144
|
[Truncated to 6000 characters. Better manage your logs or paginate them to read the full logs]
|
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
import { startPlayWriterCDPRelayServer } from './extension/cdp-relay.js'
|
|
2
|
-
import
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import util from 'node:util'
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const logFilePath = path.join(__dirname, '..', 'relay-server.log')
|
|
2
|
+
import { createFileLogger } from './create-logger.js'
|
|
9
3
|
|
|
10
4
|
process.title = 'playwriter-ws-server'
|
|
11
|
-
fs.writeFileSync(logFilePath, '')
|
|
12
|
-
|
|
13
|
-
const log = (...args: any[]) => {
|
|
14
|
-
const message = args.map(arg =>
|
|
15
|
-
typeof arg === 'string' ? arg : util.inspect(arg, { depth: null, colors: false })
|
|
16
|
-
).join(' ')
|
|
17
|
-
return fs.promises.appendFile(logFilePath, message + '\n')
|
|
18
|
-
}
|
|
19
5
|
|
|
20
|
-
const logger =
|
|
21
|
-
log,
|
|
22
|
-
error: log
|
|
23
|
-
}
|
|
6
|
+
const logger = createFileLogger()
|
|
24
7
|
|
|
25
8
|
process.on('uncaughtException', async (err) => {
|
|
26
9
|
await logger.error('Uncaught Exception:', err);
|
|
@@ -41,7 +24,7 @@ export async function startServer({ port = 19988 }: { port?: number } = {}) {
|
|
|
41
24
|
const server = await startPlayWriterCDPRelayServer({ port, logger })
|
|
42
25
|
|
|
43
26
|
console.log('CDP Relay Server running. Press Ctrl+C to stop.')
|
|
44
|
-
console.log('Logs are being written to:', logFilePath)
|
|
27
|
+
console.log('Logs are being written to:', logger.logFilePath)
|
|
45
28
|
|
|
46
29
|
process.on('SIGINT', () => {
|
|
47
30
|
console.log('\nShutting down...')
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { xdgData } from 'xdg-basedir'
|
|
4
|
+
|
|
1
5
|
export function getCdpUrl({ port = 19988, host = '127.0.0.1' }: { port?: number; host?: string } = {}) {
|
|
2
6
|
const id = `${Math.random().toString(36).substring(2, 15)}_${Date.now()}`
|
|
3
7
|
return `ws://${host}:${port}/cdp/${id}`
|
|
4
8
|
}
|
|
9
|
+
|
|
10
|
+
export function getDataDir(): string {
|
|
11
|
+
return path.join(xdgData!, 'playwriter')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ensureDataDir(): string {
|
|
15
|
+
const dataDir = getDataDir()
|
|
16
|
+
if (!fs.existsSync(dataDir)) {
|
|
17
|
+
fs.mkdirSync(dataDir, { recursive: true })
|
|
18
|
+
}
|
|
19
|
+
return dataDir
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getLogsDir(): string {
|
|
23
|
+
return path.join(getDataDir(), 'logs')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getLogFilePath(): string {
|
|
27
|
+
if (process.env.PLAYWRITER_LOG_PATH) {
|
|
28
|
+
return process.env.PLAYWRITER_LOG_PATH
|
|
29
|
+
}
|
|
30
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
31
|
+
return path.join(getLogsDir(), `relay-server-${timestamp}.log`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const LOG_FILE_PATH = getLogFilePath()
|
|
35
|
+
|
|
36
|
+
// export function getDidPromptReviewPath(): string {
|
|
37
|
+
// return path.join(getDataDir(), 'did-prompt-review')
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
// export function hasReviewedPrompt(): boolean {
|
|
41
|
+
// return fs.existsSync(getDidPromptReviewPath())
|
|
42
|
+
// }
|
|
43
|
+
|
|
44
|
+
// export function markPromptReviewed(): void {
|
|
45
|
+
// ensureDataDir()
|
|
46
|
+
// fs.writeFileSync(getDidPromptReviewPath(), new Date().toISOString())
|
|
47
|
+
// }
|