playwriter 0.0.63 → 0.0.89
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/a11y-client.js +18 -8
- package/dist/aria-snapshot.d.ts +41 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +134 -55
- package/dist/aria-snapshot.js.map +1 -1
- package/dist/aria-snapshot.test.js +5 -2
- package/dist/aria-snapshot.test.js.map +1 -1
- package/dist/aria-snapshot.unit.test.js +83 -41
- package/dist/aria-snapshot.unit.test.js.map +1 -1
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
- package/dist/bippy.js +1 -1
- package/dist/cdp-log.d.ts +1 -1
- package/dist/cdp-log.d.ts.map +1 -1
- package/dist/cdp-log.js +1 -1
- package/dist/cdp-log.js.map +1 -1
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +492 -298
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js.map +1 -1
- package/dist/cdp-types.d.ts.map +1 -1
- package/dist/cdp-types.js +7 -7
- package/dist/cdp-types.js.map +1 -1
- package/dist/clean-html.d.ts.map +1 -1
- package/dist/clean-html.js +4 -5
- package/dist/clean-html.js.map +1 -1
- package/dist/cli.js +45 -27
- package/dist/cli.js.map +1 -1
- package/dist/create-logger.d.ts.map +1 -1
- package/dist/create-logger.js +3 -1
- package/dist/create-logger.js.map +1 -1
- package/dist/debugger-examples-types.d.ts.map +1 -1
- package/dist/debugger.d.ts.map +1 -1
- package/dist/debugger.js +1 -3
- package/dist/debugger.js.map +1 -1
- package/dist/diff-utils.d.ts.map +1 -1
- package/dist/diff-utils.js +1 -4
- package/dist/diff-utils.js.map +1 -1
- package/dist/editor-api.md +12 -2
- package/dist/editor-examples.d.ts +1 -1
- package/dist/editor-examples.d.ts.map +1 -1
- package/dist/editor-examples.js +1 -1
- package/dist/editor-examples.js.map +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/executor.d.ts +26 -3
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +297 -64
- package/dist/executor.js.map +1 -1
- package/dist/executor.unit.test.js +38 -1
- package/dist/executor.unit.test.js.map +1 -1
- package/dist/extension-connection.test.js +139 -36
- package/dist/extension-connection.test.js.map +1 -1
- package/dist/ffmpeg.d.ts +148 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +523 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/ghost-browser.d.ts.map +1 -1
- package/dist/ghost-browser.js.map +1 -1
- package/dist/ghost-cursor-client.js +287 -0
- package/dist/ghost-cursor.d.ts +27 -0
- package/dist/ghost-cursor.d.ts.map +1 -0
- package/dist/ghost-cursor.js +63 -0
- package/dist/ghost-cursor.js.map +1 -0
- package/dist/htmlrewrite.d.ts.map +1 -1
- package/dist/htmlrewrite.js +17 -55
- package/dist/htmlrewrite.js.map +1 -1
- package/dist/htmlrewrite.test.js.map +1 -1
- package/dist/kill-port.d.ts.map +1 -1
- package/dist/kill-port.js +1 -3
- package/dist/kill-port.js.map +1 -1
- package/dist/locator-selector.test.d.ts +2 -0
- package/dist/locator-selector.test.d.ts.map +1 -0
- package/dist/locator-selector.test.js +96 -0
- package/dist/locator-selector.test.js.map +1 -0
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -3
- package/dist/mcp.js.map +1 -1
- package/dist/on-mouse-action.test.d.ts +2 -0
- package/dist/on-mouse-action.test.d.ts.map +1 -0
- package/dist/on-mouse-action.test.js +155 -0
- package/dist/on-mouse-action.test.js.map +1 -0
- package/dist/page-markdown.js +4 -4
- package/dist/page-markdown.js.map +1 -1
- package/dist/prompt.md +450 -377
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/readability.js +16 -2
- package/dist/recording-ghost-cursor.d.ts +41 -0
- package/dist/recording-ghost-cursor.d.ts.map +1 -0
- package/dist/recording-ghost-cursor.js +79 -0
- package/dist/recording-ghost-cursor.js.map +1 -0
- package/dist/recording-relay.d.ts.map +1 -1
- package/dist/recording-relay.js +8 -8
- package/dist/recording-relay.js.map +1 -1
- package/dist/relay-client.d.ts +17 -4
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +45 -11
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +515 -26
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-navigation.test.d.ts.map +1 -1
- package/dist/relay-navigation.test.js +169 -31
- package/dist/relay-navigation.test.js.map +1 -1
- package/dist/relay-session.test.d.ts.map +1 -1
- package/dist/relay-session.test.js +113 -65
- package/dist/relay-session.test.js.map +1 -1
- package/dist/relay-state.d.ts +158 -0
- package/dist/relay-state.d.ts.map +1 -0
- package/dist/relay-state.js +306 -0
- package/dist/relay-state.js.map +1 -0
- package/dist/relay-state.test.d.ts +2 -0
- package/dist/relay-state.test.d.ts.map +1 -0
- package/dist/relay-state.test.js +472 -0
- package/dist/relay-state.test.js.map +1 -0
- package/dist/scoped-fs.d.ts.map +1 -1
- package/dist/scoped-fs.js.map +1 -1
- package/dist/screen-recording.d.ts +66 -4
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +150 -13
- package/dist/screen-recording.js.map +1 -1
- package/dist/screen-recording.test.d.ts +2 -0
- package/dist/screen-recording.test.d.ts.map +1 -0
- package/dist/screen-recording.test.js +102 -0
- package/dist/screen-recording.test.js.map +1 -0
- package/dist/selector-generator.js +1 -1
- package/dist/snapshot-tools.test.js +71 -28
- package/dist/snapshot-tools.test.js.map +1 -1
- package/dist/start-relay-server.d.ts +1 -1
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +1 -1
- package/dist/start-relay-server.js.map +1 -1
- package/dist/styles-api.md +8 -1
- package/dist/styles-examples.d.ts +1 -1
- package/dist/styles-examples.d.ts.map +1 -1
- package/dist/styles-examples.js +1 -1
- package/dist/styles-examples.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +1 -3
- package/dist/styles.js.map +1 -1
- package/dist/test-declarations.d.ts.map +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +7 -5
- package/dist/test-utils.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/wait-for-page-load.d.ts.map +1 -1
- package/dist/wait-for-page-load.js +1 -1
- package/dist/wait-for-page-load.js.map +1 -1
- package/package.json +4 -3
- package/src/a11y-client.ts +5 -4
- package/src/aria-snapshot.test.ts +5 -2
- package/src/aria-snapshot.ts +306 -117
- package/src/aria-snapshot.unit.test.ts +199 -141
- package/src/aria-snapshots/github-interactive.txt +2 -0
- package/src/aria-snapshots/github-raw.txt +5 -1
- package/src/aria-snapshots/hackernews-interactive.txt +238 -241
- package/src/aria-snapshots/hackernews-raw.txt +265 -269
- package/src/assets/aria-labels-example.png +0 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
- package/src/cdp-log.ts +4 -1
- package/src/cdp-relay.ts +1059 -737
- package/src/cdp-session.ts +12 -3
- package/src/cdp-types.ts +51 -51
- package/src/clean-html.ts +4 -5
- package/src/cli.ts +82 -55
- package/src/create-logger.ts +5 -3
- package/src/debugger-examples-types.ts +4 -1
- package/src/debugger.ts +1 -5
- package/src/diff-utils.ts +2 -5
- package/src/editor-examples.ts +11 -1
- package/src/editor.ts +10 -2
- package/src/executor.ts +374 -73
- package/src/executor.unit.test.ts +48 -1
- package/src/extension-connection.test.ts +612 -488
- package/src/ffmpeg.ts +769 -0
- package/src/ghost-browser.ts +4 -6
- package/src/ghost-cursor-client.ts +369 -0
- package/src/ghost-cursor.ts +110 -0
- package/src/htmlrewrite.test.ts +6 -2
- package/src/htmlrewrite.ts +348 -386
- package/src/kill-port.ts +1 -3
- package/src/locator-selector.test.ts +115 -0
- package/src/mcp-client.ts +1 -1
- package/src/mcp.ts +21 -15
- package/src/on-mouse-action.test.ts +196 -0
- package/src/page-markdown.ts +7 -7
- package/src/protocol.ts +73 -57
- package/src/recording-ghost-cursor.ts +113 -0
- package/src/recording-relay.ts +20 -12
- package/src/relay-client.ts +85 -18
- package/src/relay-core.test.ts +1117 -578
- package/src/relay-navigation.test.ts +648 -483
- package/src/relay-session.test.ts +984 -929
- package/src/relay-state.test.ts +570 -0
- package/src/relay-state.ts +497 -0
- package/src/resource.md +21 -49
- package/src/scoped-fs.ts +9 -3
- package/src/screen-recording.test.ts +111 -0
- package/src/screen-recording.ts +256 -31
- package/src/skill.md +476 -396
- package/src/snapshot-tools.test.ts +580 -528
- package/src/snapshots/shadcn-ui-accessibility-full.md +8 -8
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +8 -8
- package/src/start-relay-server.ts +14 -11
- package/src/styles-examples.ts +8 -1
- package/src/styles.ts +20 -21
- package/src/test-declarations.ts +6 -6
- package/src/test-utils.ts +104 -91
- package/src/utils.ts +2 -1
- package/src/wait-for-page-load.ts +6 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encapsulates ghost cursor lifecycle for recording sessions.
|
|
3
|
+
* Keeps onMouseAction chaining/restoration isolated from executor logic.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BrowserContext, Page } from '@xmorse/playwright-core'
|
|
7
|
+
import {
|
|
8
|
+
applyGhostCursorMouseAction,
|
|
9
|
+
disableGhostCursor,
|
|
10
|
+
enableGhostCursor,
|
|
11
|
+
type GhostCursorClientOptions,
|
|
12
|
+
} from './ghost-cursor.js'
|
|
13
|
+
|
|
14
|
+
interface RecordingGhostCursorLogger {
|
|
15
|
+
error: (...args: unknown[]) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface RecordingTargetOptions {
|
|
19
|
+
page?: Page
|
|
20
|
+
sessionId?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class RecordingGhostCursorController {
|
|
24
|
+
private readonly previousMouseActionByPage = new WeakMap<Page, Page['onMouseAction']>()
|
|
25
|
+
private readonly cursorApplyQueueByPage = new WeakMap<Page, Promise<void>>()
|
|
26
|
+
private readonly logger: RecordingGhostCursorLogger
|
|
27
|
+
|
|
28
|
+
constructor(options: { logger: RecordingGhostCursorLogger }) {
|
|
29
|
+
this.logger = options.logger
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
resolveRecordingTargetPage(options: {
|
|
33
|
+
context: BrowserContext
|
|
34
|
+
defaultPage: Page
|
|
35
|
+
target?: RecordingTargetOptions
|
|
36
|
+
}): Page {
|
|
37
|
+
const { context, defaultPage, target } = options
|
|
38
|
+
|
|
39
|
+
if (target?.page) {
|
|
40
|
+
return target.page
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (target?.sessionId) {
|
|
44
|
+
const pageForSession = context.pages().find((candidatePage) => {
|
|
45
|
+
return candidatePage.sessionId() === target.sessionId
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (pageForSession) {
|
|
49
|
+
return pageForSession
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return defaultPage
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async enableForRecording(options: { page: Page }): Promise<void> {
|
|
57
|
+
const { page } = options
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await enableGhostCursor({ page })
|
|
61
|
+
|
|
62
|
+
if (!this.previousMouseActionByPage.has(page)) {
|
|
63
|
+
this.previousMouseActionByPage.set(page, page.onMouseAction)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const previousMouseAction = this.previousMouseActionByPage.get(page)
|
|
67
|
+
page.onMouseAction = async (event) => {
|
|
68
|
+
const pendingCursorApply = this.cursorApplyQueueByPage.get(page) || Promise.resolve()
|
|
69
|
+
const nextCursorApply = pendingCursorApply
|
|
70
|
+
.then(async () => {
|
|
71
|
+
await applyGhostCursorMouseAction({ page, event })
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
this.logger.error('[playwriter] Failed to apply ghost cursor action', error)
|
|
75
|
+
})
|
|
76
|
+
this.cursorApplyQueueByPage.set(page, nextCursorApply)
|
|
77
|
+
|
|
78
|
+
if (!previousMouseAction) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await previousMouseAction(event)
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
page.onMouseAction = this.previousMouseActionByPage.get(page) ?? null
|
|
86
|
+
this.previousMouseActionByPage.delete(page)
|
|
87
|
+
this.logger.error('[playwriter] Failed to enable ghost cursor', error)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async disableForRecording(options: { page: Page }): Promise<void> {
|
|
92
|
+
const { page } = options
|
|
93
|
+
page.onMouseAction = this.previousMouseActionByPage.get(page) ?? null
|
|
94
|
+
this.previousMouseActionByPage.delete(page)
|
|
95
|
+
this.cursorApplyQueueByPage.delete(page)
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await disableGhostCursor({ page })
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.logger.error('[playwriter] Failed to disable ghost cursor', error)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async show(options: { page: Page; cursorOptions?: GhostCursorClientOptions }): Promise<void> {
|
|
105
|
+
const { page, cursorOptions } = options
|
|
106
|
+
await enableGhostCursor({ page, cursorOptions })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async hide(options: { page: Page }): Promise<void> {
|
|
110
|
+
const { page } = options
|
|
111
|
+
await disableGhostCursor({ page })
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/recording-relay.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
// Recording state - tracks active recordings and their accumulated chunks
|
|
23
23
|
export interface ActiveRecording {
|
|
24
24
|
tabId: number
|
|
25
|
-
sessionId?: string
|
|
25
|
+
sessionId?: string // The sessionId used to start this recording, for lookup when stopping
|
|
26
26
|
outputPath: string
|
|
27
27
|
chunks: Buffer[]
|
|
28
28
|
startedAt: number
|
|
@@ -40,7 +40,7 @@ export class RecordingRelay {
|
|
|
40
40
|
constructor(
|
|
41
41
|
sendToExtension: (params: { method: string; params?: unknown; timeout?: number }) => Promise<unknown>,
|
|
42
42
|
isExtensionConnected: () => boolean,
|
|
43
|
-
logger?: { log(...args: unknown[]): void; error(...args: unknown[]): void }
|
|
43
|
+
logger?: { log(...args: unknown[]): void; error(...args: unknown[]): void },
|
|
44
44
|
) {
|
|
45
45
|
this.sendToExtension = sendToExtension
|
|
46
46
|
this.isExtensionConnected = isExtensionConnected
|
|
@@ -58,7 +58,11 @@ export class RecordingRelay {
|
|
|
58
58
|
const recording = this.activeRecordings.get(tabId)
|
|
59
59
|
if (recording) {
|
|
60
60
|
recording.chunks.push(buffer)
|
|
61
|
-
this.logger?.log(
|
|
61
|
+
this.logger?.log(
|
|
62
|
+
pc.blue(
|
|
63
|
+
`Received recording chunk for tab ${tabId}: ${buffer.length} bytes (total chunks: ${recording.chunks.length})`,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
62
66
|
} else {
|
|
63
67
|
this.logger?.log(pc.yellow(`Received recording chunk for unknown tab ${tabId}, ignoring`))
|
|
64
68
|
}
|
|
@@ -140,11 +144,11 @@ export class RecordingRelay {
|
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
try {
|
|
143
|
-
const result = await this.sendToExtension({
|
|
147
|
+
const result = (await this.sendToExtension({
|
|
144
148
|
method: 'startRecording',
|
|
145
149
|
params: recordingParams,
|
|
146
150
|
timeout: 10000,
|
|
147
|
-
}) as StartRecordingResult
|
|
151
|
+
})) as StartRecordingResult
|
|
148
152
|
|
|
149
153
|
if (!result) {
|
|
150
154
|
return { success: false, error: 'Extension returned empty result' }
|
|
@@ -158,7 +162,11 @@ export class RecordingRelay {
|
|
|
158
162
|
chunks: [],
|
|
159
163
|
startedAt: result.startedAt,
|
|
160
164
|
})
|
|
161
|
-
this.logger?.log(
|
|
165
|
+
this.logger?.log(
|
|
166
|
+
pc.green(
|
|
167
|
+
`Recording started for tab ${result.tabId} (sessionId: ${recordingParams.sessionId || 'none'}), output: ${outputPath}`,
|
|
168
|
+
),
|
|
169
|
+
)
|
|
162
170
|
}
|
|
163
171
|
|
|
164
172
|
return result
|
|
@@ -211,11 +219,11 @@ export class RecordingRelay {
|
|
|
211
219
|
})
|
|
212
220
|
|
|
213
221
|
try {
|
|
214
|
-
const result = await this.sendToExtension({
|
|
222
|
+
const result = (await this.sendToExtension({
|
|
215
223
|
method: 'stopRecording',
|
|
216
224
|
params,
|
|
217
225
|
timeout: 10000,
|
|
218
|
-
}) as StopRecordingResult
|
|
226
|
+
})) as StopRecordingResult
|
|
219
227
|
|
|
220
228
|
if (!result.success) {
|
|
221
229
|
recording.resolveStop = undefined
|
|
@@ -237,11 +245,11 @@ export class RecordingRelay {
|
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
try {
|
|
240
|
-
return await this.sendToExtension({
|
|
248
|
+
return (await this.sendToExtension({
|
|
241
249
|
method: 'isRecording',
|
|
242
250
|
params,
|
|
243
251
|
timeout: 5000,
|
|
244
|
-
}) as IsRecordingResult
|
|
252
|
+
})) as IsRecordingResult
|
|
245
253
|
} catch {
|
|
246
254
|
return { isRecording: false }
|
|
247
255
|
}
|
|
@@ -253,11 +261,11 @@ export class RecordingRelay {
|
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
try {
|
|
256
|
-
return await this.sendToExtension({
|
|
264
|
+
return (await this.sendToExtension({
|
|
257
265
|
method: 'cancelRecording',
|
|
258
266
|
params,
|
|
259
267
|
timeout: 5000,
|
|
260
|
-
}) as CancelRecordingResult
|
|
268
|
+
})) as CancelRecordingResult
|
|
261
269
|
} catch (error: unknown) {
|
|
262
270
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
263
271
|
this.logger?.error('Cancel recording error:', error)
|
package/src/relay-client.ts
CHANGED
|
@@ -15,10 +15,19 @@ const __dirname = path.dirname(__filename)
|
|
|
15
15
|
|
|
16
16
|
export const RELAY_PORT = Number(process.env.PLAYWRITER_PORT) || 19988
|
|
17
17
|
|
|
18
|
+
export type ExtensionStatus = {
|
|
19
|
+
extensionId: string
|
|
20
|
+
stableKey?: string
|
|
21
|
+
browser: string | null
|
|
22
|
+
profile: { email: string; id: string } | null
|
|
23
|
+
activeTargets: number
|
|
24
|
+
playwriterVersion: string | null
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
export async function getRelayServerVersion(port: number = RELAY_PORT): Promise<string | null> {
|
|
19
28
|
try {
|
|
20
29
|
const response = await fetch(`http://127.0.0.1:${port}/version`, {
|
|
21
|
-
signal: AbortSignal.timeout(
|
|
30
|
+
signal: AbortSignal.timeout(2000),
|
|
22
31
|
})
|
|
23
32
|
if (!response.ok) {
|
|
24
33
|
return null
|
|
@@ -30,7 +39,9 @@ export async function getRelayServerVersion(port: number = RELAY_PORT): Promise<
|
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
export async function getExtensionStatus(
|
|
42
|
+
export async function getExtensionStatus(
|
|
43
|
+
port: number = RELAY_PORT,
|
|
44
|
+
): Promise<{ connected: boolean; activeTargets: number; playwriterVersion: string | null } | null> {
|
|
34
45
|
try {
|
|
35
46
|
const response = await fetch(`http://127.0.0.1:${port}/extension/status`, {
|
|
36
47
|
signal: AbortSignal.timeout(500),
|
|
@@ -38,37 +49,87 @@ export async function getExtensionStatus(port: number = RELAY_PORT): Promise<{ c
|
|
|
38
49
|
if (!response.ok) {
|
|
39
50
|
return null
|
|
40
51
|
}
|
|
41
|
-
return await response.json() as { connected: boolean; activeTargets: number; playwriterVersion: string | null }
|
|
52
|
+
return (await response.json()) as { connected: boolean; activeTargets: number; playwriterVersion: string | null }
|
|
42
53
|
} catch {
|
|
43
54
|
return null
|
|
44
55
|
}
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
export async function getExtensionsStatus(port: number = RELAY_PORT): Promise<ExtensionStatus[]> {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`http://127.0.0.1:${port}/extensions/status`, {
|
|
61
|
+
signal: AbortSignal.timeout(2000),
|
|
62
|
+
})
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const fallback = await fetch(`http://127.0.0.1:${port}/extension/status`, {
|
|
65
|
+
signal: AbortSignal.timeout(2000),
|
|
66
|
+
})
|
|
67
|
+
if (!fallback.ok) {
|
|
68
|
+
return []
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const fallbackData = (await fallback.json()) as {
|
|
72
|
+
connected: boolean
|
|
73
|
+
activeTargets: number
|
|
74
|
+
browser: string | null
|
|
75
|
+
profile: { email: string; id: string } | null
|
|
76
|
+
playwriterVersion?: string | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!fallbackData?.connected) {
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
extensionId: 'default',
|
|
86
|
+
stableKey: undefined,
|
|
87
|
+
browser: fallbackData.browser,
|
|
88
|
+
profile: fallbackData.profile,
|
|
89
|
+
activeTargets: fallbackData.activeTargets,
|
|
90
|
+
playwriterVersion: fallbackData.playwriterVersion || null,
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const data = (await response.json()) as {
|
|
96
|
+
extensions: ExtensionStatus[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return data.extensions || []
|
|
100
|
+
} catch {
|
|
101
|
+
return []
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
47
105
|
/**
|
|
48
|
-
* Wait for
|
|
49
|
-
* Returns
|
|
106
|
+
* Wait for at least one extension to appear in extensions status.
|
|
107
|
+
* Returns connected extension entries, or [] on timeout.
|
|
50
108
|
*/
|
|
51
|
-
export async function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
109
|
+
export async function waitForConnectedExtensions(
|
|
110
|
+
options: {
|
|
111
|
+
port?: number
|
|
112
|
+
timeoutMs?: number
|
|
113
|
+
pollIntervalMs?: number
|
|
114
|
+
logger?: { log: (...args: any[]) => void }
|
|
115
|
+
} = {},
|
|
116
|
+
): Promise<ExtensionStatus[]> {
|
|
117
|
+
const { port = RELAY_PORT, timeoutMs = 5000, pollIntervalMs = 200, logger } = options
|
|
57
118
|
const startTime = Date.now()
|
|
58
119
|
|
|
59
120
|
logger?.log(pc.dim('Waiting for extension to connect...'))
|
|
60
121
|
|
|
61
122
|
while (Date.now() - startTime < timeoutMs) {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
123
|
+
const extensions = await getExtensionsStatus(port)
|
|
124
|
+
if (extensions.length > 0) {
|
|
64
125
|
logger?.log(pc.green('Extension connected'))
|
|
65
|
-
return
|
|
126
|
+
return extensions
|
|
66
127
|
}
|
|
67
|
-
await sleep(
|
|
128
|
+
await sleep(pollIntervalMs)
|
|
68
129
|
}
|
|
69
130
|
|
|
70
131
|
logger?.log(pc.yellow('Extension did not connect within timeout'))
|
|
71
|
-
return
|
|
132
|
+
return []
|
|
72
133
|
}
|
|
73
134
|
|
|
74
135
|
async function killRelayServer(options: { port: number; waitForFreeMs?: number }): Promise<void> {
|
|
@@ -155,7 +216,9 @@ export async function ensureRelayServer(options: EnsureRelayServerOptions = {}):
|
|
|
155
216
|
|
|
156
217
|
if (serverVersion !== null) {
|
|
157
218
|
if (restartOnVersionMismatch) {
|
|
158
|
-
logger?.log(
|
|
219
|
+
logger?.log(
|
|
220
|
+
pc.yellow(`CDP relay server version mismatch (server: ${serverVersion}, client: ${VERSION}), restarting...`),
|
|
221
|
+
)
|
|
159
222
|
await killRelayServer({ port: RELAY_PORT })
|
|
160
223
|
} else {
|
|
161
224
|
// Server is running but different version, just use it
|
|
@@ -164,7 +227,11 @@ export async function ensureRelayServer(options: EnsureRelayServerOptions = {}):
|
|
|
164
227
|
} else {
|
|
165
228
|
const listeningPids = await getListeningPidsForPort({ port: RELAY_PORT }).catch(() => [])
|
|
166
229
|
if (listeningPids.length > 0) {
|
|
167
|
-
logger?.log(
|
|
230
|
+
logger?.log(
|
|
231
|
+
pc.yellow(
|
|
232
|
+
`Port ${RELAY_PORT} is already in use (pid(s): ${listeningPids.join(', ')}). Attempting to stop the existing process...`,
|
|
233
|
+
),
|
|
234
|
+
)
|
|
168
235
|
await killRelayServer({ port: RELAY_PORT })
|
|
169
236
|
}
|
|
170
237
|
|