playwriter 0.0.63 → 0.0.80
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 +41 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +131 -54
- 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 +408 -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 +295 -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 +281 -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 +594 -255
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/readability.js +1 -1
- 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 +44 -10
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +187 -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 +54 -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 +42 -4
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +88 -13
- package/dist/screen-recording.js.map +1 -1
- 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 +303 -116
- package/src/aria-snapshot.unit.test.ts +199 -141
- package/src/aria-snapshots/github-raw.txt +1 -1
- package/src/aria-snapshots/hackernews-interactive.txt +240 -240
- package/src/aria-snapshots/hackernews-raw.txt +270 -270
- 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 +949 -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 +372 -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 +368 -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 +107 -0
- package/src/recording-relay.ts +20 -12
- package/src/relay-client.ts +84 -17
- package/src/relay-core.test.ts +761 -583
- package/src/relay-navigation.test.ts +517 -484
- 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.ts +175 -31
- package/src/skill.md +619 -271
- package/src/snapshot-tools.test.ts +580 -528
- package/src/snapshots/shadcn-ui-accessibility-full.md +181 -183
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +119 -121
- 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
package/src/screen-recording.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Screen recording utility for playwriter using chrome.tabCapture.
|
|
3
3
|
* Recording happens in the extension context, so it survives page navigation.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* This module communicates with the relay server which forwards commands to the extension.
|
|
6
|
-
*
|
|
6
|
+
* sessionId (pw-tab-* format) is used to identify which tab to record.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import os from 'node:os'
|
|
10
10
|
import path from 'node:path'
|
|
11
|
-
import type { Page } from '@xmorse/playwright-core'
|
|
11
|
+
import type { BrowserContext, Page } from '@xmorse/playwright-core'
|
|
12
12
|
import type {
|
|
13
13
|
StartRecordingResult,
|
|
14
14
|
StopRecordingResult,
|
|
@@ -16,19 +16,22 @@ import type {
|
|
|
16
16
|
CancelRecordingResult,
|
|
17
17
|
} from './protocol.js'
|
|
18
18
|
import { EXTENSION_IDS } from './utils.js'
|
|
19
|
+
import { RecordingGhostCursorController } from './recording-ghost-cursor.js'
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Generate a shell command to quit and restart Chrome with flags that allow automatic tab capture.
|
|
22
23
|
* This enables screen recording without user interaction (clicking extension icon).
|
|
23
|
-
*
|
|
24
|
+
*
|
|
24
25
|
* Required flags:
|
|
25
26
|
* - --allowlisted-extension-id=<id> - grants the extension special privileges (one per extension)
|
|
26
27
|
* - --auto-accept-this-tab-capture - auto-accepts tab capture permission requests
|
|
27
28
|
*/
|
|
28
29
|
export function getChromeRestartCommand(): string {
|
|
29
30
|
const platform = os.platform()
|
|
30
|
-
const
|
|
31
|
-
|
|
31
|
+
const extensionFlags = EXTENSION_IDS.map((id) => `--allowlisted-extension-id=${id}`).join(' ')
|
|
32
|
+
// --profile-directory=Default skips the profile picker on startup, preventing Chrome from hanging
|
|
33
|
+
const flags = `${extensionFlags} --auto-accept-this-tab-capture --profile-directory=Default`
|
|
34
|
+
|
|
32
35
|
if (platform === 'darwin') {
|
|
33
36
|
return `osascript -e 'quit app "Google Chrome"' && sleep 1 && open -a "Google Chrome" --args ${flags}`
|
|
34
37
|
}
|
|
@@ -43,15 +46,17 @@ export function getChromeRestartCommand(): string {
|
|
|
43
46
|
* Check if an error is related to missing activeTab permission for recording.
|
|
44
47
|
*/
|
|
45
48
|
function isActiveTabPermissionError(error: string): boolean {
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
return (
|
|
50
|
+
error.includes('Extension has not been invoked') ||
|
|
51
|
+
error.includes('activeTab') ||
|
|
52
|
+
error.includes('enable recording')
|
|
53
|
+
)
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export interface StartRecordingOptions {
|
|
52
57
|
/** Target page to record */
|
|
53
58
|
page: Page
|
|
54
|
-
/**
|
|
59
|
+
/** CDP tab session ID (pw-tab-* format) to identify which tab to record */
|
|
55
60
|
sessionId?: string
|
|
56
61
|
/** Frame rate (default: 30) */
|
|
57
62
|
frameRate?: number
|
|
@@ -70,7 +75,7 @@ export interface StartRecordingOptions {
|
|
|
70
75
|
export interface StopRecordingOptions {
|
|
71
76
|
/** Target page that is being recorded */
|
|
72
77
|
page: Page
|
|
73
|
-
/**
|
|
78
|
+
/** CDP tab session ID (pw-tab-* format) to identify which tab to stop recording */
|
|
74
79
|
sessionId?: string
|
|
75
80
|
/** Relay server port (default: 19988) */
|
|
76
81
|
relayPort?: number
|
|
@@ -82,6 +87,127 @@ export interface RecordingState {
|
|
|
82
87
|
tabId?: number
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
export interface ExecutionTimestamp {
|
|
91
|
+
start: number
|
|
92
|
+
end: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface RecordingTargetOptions {
|
|
96
|
+
page?: Page
|
|
97
|
+
sessionId?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface CreateRecordingApiOptions {
|
|
101
|
+
context: BrowserContext
|
|
102
|
+
defaultPage: Page
|
|
103
|
+
relayPort: number
|
|
104
|
+
ghostCursorController: RecordingGhostCursorController
|
|
105
|
+
onStart: () => void
|
|
106
|
+
onFinish: () => void
|
|
107
|
+
getExecutionTimestamps: () => ExecutionTimestamp[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface StartRecordingWithDefaultsOptions extends Omit<StartRecordingOptions, 'relayPort'> {}
|
|
111
|
+
interface StopRecordingWithDefaultsOptions extends Omit<StopRecordingOptions, 'relayPort'> {}
|
|
112
|
+
interface IsRecordingWithDefaultsOptions {
|
|
113
|
+
page?: Page
|
|
114
|
+
sessionId?: string
|
|
115
|
+
}
|
|
116
|
+
interface CancelRecordingWithDefaultsOptions {
|
|
117
|
+
page?: Page
|
|
118
|
+
sessionId?: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveRecordingTargetPage(options: {
|
|
122
|
+
context: BrowserContext
|
|
123
|
+
defaultPage: Page
|
|
124
|
+
ghostCursorController: RecordingGhostCursorController
|
|
125
|
+
target?: RecordingTargetOptions
|
|
126
|
+
}): Page {
|
|
127
|
+
return options.ghostCursorController.resolveRecordingTargetPage({
|
|
128
|
+
context: options.context,
|
|
129
|
+
defaultPage: options.defaultPage,
|
|
130
|
+
target: options.target,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function withRecordingDefaults<T extends { page?: Page; sessionId?: string }, R>(options: {
|
|
135
|
+
relayPort: number
|
|
136
|
+
defaultPage: Page
|
|
137
|
+
fn: (opts: T & { relayPort: number; sessionId?: string }) => Promise<R>
|
|
138
|
+
}): (input?: T) => Promise<R> {
|
|
139
|
+
const { relayPort, defaultPage, fn } = options
|
|
140
|
+
return async (input: T = {} as T) => {
|
|
141
|
+
const targetPage = input.page || defaultPage
|
|
142
|
+
const sessionId = input.sessionId || targetPage.sessionId() || undefined
|
|
143
|
+
return fn({ page: targetPage, sessionId, relayPort, ...input })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createRecordingApi(options: CreateRecordingApiOptions): {
|
|
148
|
+
start: (opts?: StartRecordingWithDefaultsOptions) => Promise<RecordingState>
|
|
149
|
+
stop: (opts?: StopRecordingWithDefaultsOptions) => Promise<{ path: string; duration: number; size: number; executionTimestamps: ExecutionTimestamp[] }>
|
|
150
|
+
isRecording: (opts?: IsRecordingWithDefaultsOptions) => Promise<RecordingState>
|
|
151
|
+
cancel: (opts?: CancelRecordingWithDefaultsOptions) => Promise<void>
|
|
152
|
+
} {
|
|
153
|
+
const { context, defaultPage, relayPort, ghostCursorController, onStart, onFinish, getExecutionTimestamps } = options
|
|
154
|
+
|
|
155
|
+
const startWithDefaults = withRecordingDefaults<StartRecordingWithDefaultsOptions, RecordingState>({
|
|
156
|
+
relayPort,
|
|
157
|
+
defaultPage,
|
|
158
|
+
fn: startRecording,
|
|
159
|
+
})
|
|
160
|
+
const stopWithDefaults = withRecordingDefaults<StopRecordingWithDefaultsOptions, { path: string; duration: number; size: number }>({
|
|
161
|
+
relayPort,
|
|
162
|
+
defaultPage,
|
|
163
|
+
fn: stopRecording,
|
|
164
|
+
})
|
|
165
|
+
const isRecordingWithDefaults = async (opts: IsRecordingWithDefaultsOptions = {}): Promise<RecordingState> => {
|
|
166
|
+
const targetPage = opts.page || defaultPage
|
|
167
|
+
const sessionId = opts.sessionId || targetPage.sessionId() || undefined
|
|
168
|
+
return isRecording({ page: targetPage, sessionId, relayPort })
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const cancelWithDefaults = async (opts: CancelRecordingWithDefaultsOptions = {}): Promise<void> => {
|
|
172
|
+
const targetPage = opts.page || defaultPage
|
|
173
|
+
const sessionId = opts.sessionId || targetPage.sessionId() || undefined
|
|
174
|
+
await cancelRecording({ page: targetPage, sessionId, relayPort })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const start = async (opts?: StartRecordingWithDefaultsOptions): Promise<RecordingState> => {
|
|
178
|
+
const targetPage = resolveRecordingTargetPage({ context, defaultPage, ghostCursorController, target: opts })
|
|
179
|
+
const result = await startWithDefaults(opts)
|
|
180
|
+
onStart()
|
|
181
|
+
await ghostCursorController.enableForRecording({ page: targetPage })
|
|
182
|
+
return result
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const stop = async (
|
|
186
|
+
opts?: StopRecordingWithDefaultsOptions,
|
|
187
|
+
): Promise<{ path: string; duration: number; size: number; executionTimestamps: ExecutionTimestamp[] }> => {
|
|
188
|
+
const targetPage = resolveRecordingTargetPage({ context, defaultPage, ghostCursorController, target: opts })
|
|
189
|
+
const result = await stopWithDefaults(opts)
|
|
190
|
+
const executionTimestamps = [...getExecutionTimestamps()]
|
|
191
|
+
onFinish()
|
|
192
|
+
await ghostCursorController.disableForRecording({ page: targetPage })
|
|
193
|
+
return { ...result, executionTimestamps }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const cancel = async (opts?: CancelRecordingWithDefaultsOptions): Promise<void> => {
|
|
197
|
+
const targetPage = resolveRecordingTargetPage({ context, defaultPage, ghostCursorController, target: opts })
|
|
198
|
+
await cancelWithDefaults(opts)
|
|
199
|
+
onFinish()
|
|
200
|
+
await ghostCursorController.disableForRecording({ page: targetPage })
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
start,
|
|
205
|
+
stop,
|
|
206
|
+
isRecording: isRecordingWithDefaults,
|
|
207
|
+
cancel,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
85
211
|
/**
|
|
86
212
|
* Start recording the page.
|
|
87
213
|
* The recording is handled by the extension, so it survives page navigation.
|
|
@@ -96,34 +222,41 @@ export async function startRecording(options: StartRecordingOptions): Promise<Re
|
|
|
96
222
|
outputPath,
|
|
97
223
|
relayPort = 19988,
|
|
98
224
|
} = options
|
|
99
|
-
|
|
225
|
+
|
|
100
226
|
// Resolve relative paths to absolute using the caller's cwd.
|
|
101
227
|
// The relay server may have a different cwd, so we must resolve here.
|
|
102
228
|
const absoluteOutputPath = path.resolve(outputPath)
|
|
103
|
-
|
|
229
|
+
|
|
104
230
|
const response = await fetch(`http://127.0.0.1:${relayPort}/recording/start`, {
|
|
105
231
|
method: 'POST',
|
|
106
232
|
headers: { 'Content-Type': 'application/json' },
|
|
107
|
-
body: JSON.stringify({
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
sessionId,
|
|
235
|
+
frameRate,
|
|
236
|
+
videoBitsPerSecond,
|
|
237
|
+
audioBitsPerSecond,
|
|
238
|
+
audio,
|
|
239
|
+
outputPath: absoluteOutputPath,
|
|
240
|
+
}),
|
|
108
241
|
})
|
|
109
242
|
|
|
110
|
-
const result = await response.json() as StartRecordingResult
|
|
243
|
+
const result = (await response.json()) as StartRecordingResult
|
|
111
244
|
|
|
112
245
|
if (!result.success) {
|
|
113
246
|
const errorMsg = result.error || 'Unknown error'
|
|
114
|
-
|
|
247
|
+
|
|
115
248
|
// If the error is about missing activeTab permission, provide helpful guidance
|
|
116
249
|
if (isActiveTabPermissionError(errorMsg)) {
|
|
117
250
|
const restartCmd = getChromeRestartCommand()
|
|
118
251
|
throw new Error(
|
|
119
252
|
`Failed to start recording: ${errorMsg}\n\n` +
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
253
|
+
`For automated recording, Chrome must be restarted with special flags.\n` +
|
|
254
|
+
`WARNING: This will close all Chrome windows. Save your work first!\n\n` +
|
|
255
|
+
` ${restartCmd}\n\n` +
|
|
256
|
+
`Or click the Playwriter extension icon on the tab once to grant permission.`,
|
|
124
257
|
)
|
|
125
258
|
}
|
|
126
|
-
|
|
259
|
+
|
|
127
260
|
throw new Error(`Failed to start recording: ${errorMsg}`)
|
|
128
261
|
}
|
|
129
262
|
|
|
@@ -138,7 +271,9 @@ export async function startRecording(options: StartRecordingOptions): Promise<Re
|
|
|
138
271
|
* Stop recording and save to file.
|
|
139
272
|
* Returns the path to the saved video file.
|
|
140
273
|
*/
|
|
141
|
-
export async function stopRecording(
|
|
274
|
+
export async function stopRecording(
|
|
275
|
+
options: StopRecordingOptions,
|
|
276
|
+
): Promise<{ path: string; duration: number; size: number }> {
|
|
142
277
|
const { sessionId, relayPort = 19988 } = options
|
|
143
278
|
|
|
144
279
|
const response = await fetch(`http://127.0.0.1:${relayPort}/recording/stop`, {
|
|
@@ -147,7 +282,7 @@ export async function stopRecording(options: StopRecordingOptions): Promise<{ pa
|
|
|
147
282
|
body: JSON.stringify({ sessionId }),
|
|
148
283
|
})
|
|
149
284
|
|
|
150
|
-
const result = await response.json() as StopRecordingResult
|
|
285
|
+
const result = (await response.json()) as StopRecordingResult
|
|
151
286
|
|
|
152
287
|
if (!result.success) {
|
|
153
288
|
throw new Error(`Failed to stop recording: ${result.error}`)
|
|
@@ -159,14 +294,19 @@ export async function stopRecording(options: StopRecordingOptions): Promise<{ pa
|
|
|
159
294
|
/**
|
|
160
295
|
* Check if recording is currently active.
|
|
161
296
|
*/
|
|
162
|
-
export async function isRecording(options: {
|
|
297
|
+
export async function isRecording(options: {
|
|
298
|
+
page: Page
|
|
299
|
+
sessionId?: string
|
|
300
|
+
relayPort?: number
|
|
301
|
+
}): Promise<RecordingState> {
|
|
163
302
|
const { sessionId, relayPort = 19988 } = options
|
|
164
303
|
|
|
165
|
-
const url =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
304
|
+
const url = new URL(`http://127.0.0.1:${relayPort}/recording/status`)
|
|
305
|
+
if (sessionId) {
|
|
306
|
+
url.searchParams.set('sessionId', sessionId)
|
|
307
|
+
}
|
|
308
|
+
const response = await fetch(url.toString())
|
|
309
|
+
const result = (await response.json()) as IsRecordingResult
|
|
170
310
|
|
|
171
311
|
return { isRecording: result.isRecording, startedAt: result.startedAt, tabId: result.tabId }
|
|
172
312
|
}
|
|
@@ -174,7 +314,11 @@ export async function isRecording(options: { page: Page; sessionId?: string; rel
|
|
|
174
314
|
/**
|
|
175
315
|
* Cancel recording without saving.
|
|
176
316
|
*/
|
|
177
|
-
export async function cancelRecording(options: {
|
|
317
|
+
export async function cancelRecording(options: {
|
|
318
|
+
page: Page
|
|
319
|
+
sessionId?: string
|
|
320
|
+
relayPort?: number
|
|
321
|
+
}): Promise<void> {
|
|
178
322
|
const { sessionId, relayPort = 19988 } = options
|
|
179
323
|
|
|
180
324
|
const response = await fetch(`http://127.0.0.1:${relayPort}/recording/cancel`, {
|
|
@@ -183,7 +327,7 @@ export async function cancelRecording(options: { page: Page; sessionId?: string;
|
|
|
183
327
|
body: JSON.stringify({ sessionId }),
|
|
184
328
|
})
|
|
185
329
|
|
|
186
|
-
const result = await response.json() as CancelRecordingResult
|
|
330
|
+
const result = (await response.json()) as CancelRecordingResult
|
|
187
331
|
|
|
188
332
|
if (!result.success) {
|
|
189
333
|
throw new Error(`Failed to cancel recording: ${result.error}`)
|