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.
Files changed (216) hide show
  1. package/dist/aria-snapshot.d.ts +41 -3
  2. package/dist/aria-snapshot.d.ts.map +1 -1
  3. package/dist/aria-snapshot.js +131 -54
  4. package/dist/aria-snapshot.js.map +1 -1
  5. package/dist/aria-snapshot.test.js +5 -2
  6. package/dist/aria-snapshot.test.js.map +1 -1
  7. package/dist/aria-snapshot.unit.test.js +83 -41
  8. package/dist/aria-snapshot.unit.test.js.map +1 -1
  9. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
  10. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
  11. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
  12. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
  13. package/dist/bippy.js +1 -1
  14. package/dist/cdp-log.d.ts +1 -1
  15. package/dist/cdp-log.d.ts.map +1 -1
  16. package/dist/cdp-log.js +1 -1
  17. package/dist/cdp-log.js.map +1 -1
  18. package/dist/cdp-relay.d.ts.map +1 -1
  19. package/dist/cdp-relay.js +408 -298
  20. package/dist/cdp-relay.js.map +1 -1
  21. package/dist/cdp-session.d.ts.map +1 -1
  22. package/dist/cdp-session.js.map +1 -1
  23. package/dist/cdp-types.d.ts.map +1 -1
  24. package/dist/cdp-types.js +7 -7
  25. package/dist/cdp-types.js.map +1 -1
  26. package/dist/clean-html.d.ts.map +1 -1
  27. package/dist/clean-html.js +4 -5
  28. package/dist/clean-html.js.map +1 -1
  29. package/dist/cli.js +45 -27
  30. package/dist/cli.js.map +1 -1
  31. package/dist/create-logger.d.ts.map +1 -1
  32. package/dist/create-logger.js +3 -1
  33. package/dist/create-logger.js.map +1 -1
  34. package/dist/debugger-examples-types.d.ts.map +1 -1
  35. package/dist/debugger.d.ts.map +1 -1
  36. package/dist/debugger.js +1 -3
  37. package/dist/debugger.js.map +1 -1
  38. package/dist/diff-utils.d.ts.map +1 -1
  39. package/dist/diff-utils.js +1 -4
  40. package/dist/diff-utils.js.map +1 -1
  41. package/dist/editor-api.md +12 -2
  42. package/dist/editor-examples.d.ts +1 -1
  43. package/dist/editor-examples.d.ts.map +1 -1
  44. package/dist/editor-examples.js +1 -1
  45. package/dist/editor-examples.js.map +1 -1
  46. package/dist/editor.d.ts +1 -1
  47. package/dist/editor.d.ts.map +1 -1
  48. package/dist/editor.js +1 -1
  49. package/dist/editor.js.map +1 -1
  50. package/dist/executor.d.ts +26 -3
  51. package/dist/executor.d.ts.map +1 -1
  52. package/dist/executor.js +295 -64
  53. package/dist/executor.js.map +1 -1
  54. package/dist/executor.unit.test.js +38 -1
  55. package/dist/executor.unit.test.js.map +1 -1
  56. package/dist/extension-connection.test.js +139 -36
  57. package/dist/extension-connection.test.js.map +1 -1
  58. package/dist/ffmpeg.d.ts +148 -0
  59. package/dist/ffmpeg.d.ts.map +1 -0
  60. package/dist/ffmpeg.js +523 -0
  61. package/dist/ffmpeg.js.map +1 -0
  62. package/dist/ghost-browser.d.ts.map +1 -1
  63. package/dist/ghost-browser.js.map +1 -1
  64. package/dist/ghost-cursor-client.js +281 -0
  65. package/dist/ghost-cursor.d.ts +27 -0
  66. package/dist/ghost-cursor.d.ts.map +1 -0
  67. package/dist/ghost-cursor.js +63 -0
  68. package/dist/ghost-cursor.js.map +1 -0
  69. package/dist/htmlrewrite.d.ts.map +1 -1
  70. package/dist/htmlrewrite.js +17 -55
  71. package/dist/htmlrewrite.js.map +1 -1
  72. package/dist/htmlrewrite.test.js.map +1 -1
  73. package/dist/kill-port.d.ts.map +1 -1
  74. package/dist/kill-port.js +1 -3
  75. package/dist/kill-port.js.map +1 -1
  76. package/dist/locator-selector.test.d.ts +2 -0
  77. package/dist/locator-selector.test.d.ts.map +1 -0
  78. package/dist/locator-selector.test.js +96 -0
  79. package/dist/locator-selector.test.js.map +1 -0
  80. package/dist/mcp-client.js.map +1 -1
  81. package/dist/mcp.d.ts.map +1 -1
  82. package/dist/mcp.js +8 -3
  83. package/dist/mcp.js.map +1 -1
  84. package/dist/on-mouse-action.test.d.ts +2 -0
  85. package/dist/on-mouse-action.test.d.ts.map +1 -0
  86. package/dist/on-mouse-action.test.js +155 -0
  87. package/dist/on-mouse-action.test.js.map +1 -0
  88. package/dist/page-markdown.js +4 -4
  89. package/dist/page-markdown.js.map +1 -1
  90. package/dist/prompt.md +594 -255
  91. package/dist/protocol.d.ts +4 -0
  92. package/dist/protocol.d.ts.map +1 -1
  93. package/dist/readability.js +1 -1
  94. package/dist/recording-ghost-cursor.d.ts +41 -0
  95. package/dist/recording-ghost-cursor.d.ts.map +1 -0
  96. package/dist/recording-ghost-cursor.js +79 -0
  97. package/dist/recording-ghost-cursor.js.map +1 -0
  98. package/dist/recording-relay.d.ts.map +1 -1
  99. package/dist/recording-relay.js +8 -8
  100. package/dist/recording-relay.js.map +1 -1
  101. package/dist/relay-client.d.ts +17 -4
  102. package/dist/relay-client.d.ts.map +1 -1
  103. package/dist/relay-client.js +44 -10
  104. package/dist/relay-client.js.map +1 -1
  105. package/dist/relay-core.test.d.ts.map +1 -1
  106. package/dist/relay-core.test.js +187 -26
  107. package/dist/relay-core.test.js.map +1 -1
  108. package/dist/relay-navigation.test.d.ts.map +1 -1
  109. package/dist/relay-navigation.test.js +54 -31
  110. package/dist/relay-navigation.test.js.map +1 -1
  111. package/dist/relay-session.test.d.ts.map +1 -1
  112. package/dist/relay-session.test.js +113 -65
  113. package/dist/relay-session.test.js.map +1 -1
  114. package/dist/relay-state.d.ts +158 -0
  115. package/dist/relay-state.d.ts.map +1 -0
  116. package/dist/relay-state.js +306 -0
  117. package/dist/relay-state.js.map +1 -0
  118. package/dist/relay-state.test.d.ts +2 -0
  119. package/dist/relay-state.test.d.ts.map +1 -0
  120. package/dist/relay-state.test.js +472 -0
  121. package/dist/relay-state.test.js.map +1 -0
  122. package/dist/scoped-fs.d.ts.map +1 -1
  123. package/dist/scoped-fs.js.map +1 -1
  124. package/dist/screen-recording.d.ts +42 -4
  125. package/dist/screen-recording.d.ts.map +1 -1
  126. package/dist/screen-recording.js +88 -13
  127. package/dist/screen-recording.js.map +1 -1
  128. package/dist/selector-generator.js +1 -1
  129. package/dist/snapshot-tools.test.js +71 -28
  130. package/dist/snapshot-tools.test.js.map +1 -1
  131. package/dist/start-relay-server.d.ts +1 -1
  132. package/dist/start-relay-server.d.ts.map +1 -1
  133. package/dist/start-relay-server.js +1 -1
  134. package/dist/start-relay-server.js.map +1 -1
  135. package/dist/styles-api.md +8 -1
  136. package/dist/styles-examples.d.ts +1 -1
  137. package/dist/styles-examples.d.ts.map +1 -1
  138. package/dist/styles-examples.js +1 -1
  139. package/dist/styles-examples.js.map +1 -1
  140. package/dist/styles.d.ts.map +1 -1
  141. package/dist/styles.js +1 -3
  142. package/dist/styles.js.map +1 -1
  143. package/dist/test-declarations.d.ts.map +1 -1
  144. package/dist/test-utils.d.ts +1 -1
  145. package/dist/test-utils.d.ts.map +1 -1
  146. package/dist/test-utils.js +7 -5
  147. package/dist/test-utils.js.map +1 -1
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/utils.js.map +1 -1
  150. package/dist/wait-for-page-load.d.ts.map +1 -1
  151. package/dist/wait-for-page-load.js +1 -1
  152. package/dist/wait-for-page-load.js.map +1 -1
  153. package/package.json +4 -3
  154. package/src/a11y-client.ts +5 -4
  155. package/src/aria-snapshot.test.ts +5 -2
  156. package/src/aria-snapshot.ts +303 -116
  157. package/src/aria-snapshot.unit.test.ts +199 -141
  158. package/src/aria-snapshots/github-raw.txt +1 -1
  159. package/src/aria-snapshots/hackernews-interactive.txt +240 -240
  160. package/src/aria-snapshots/hackernews-raw.txt +270 -270
  161. package/src/assets/aria-labels-example.png +0 -0
  162. package/src/assets/aria-labels-github.png +0 -0
  163. package/src/assets/aria-labels-hacker-news.png +0 -0
  164. package/src/assets/aria-labels-old-reddit.png +0 -0
  165. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
  166. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
  167. package/src/cdp-log.ts +4 -1
  168. package/src/cdp-relay.ts +949 -737
  169. package/src/cdp-session.ts +12 -3
  170. package/src/cdp-types.ts +51 -51
  171. package/src/clean-html.ts +4 -5
  172. package/src/cli.ts +82 -55
  173. package/src/create-logger.ts +5 -3
  174. package/src/debugger-examples-types.ts +4 -1
  175. package/src/debugger.ts +1 -5
  176. package/src/diff-utils.ts +2 -5
  177. package/src/editor-examples.ts +11 -1
  178. package/src/editor.ts +10 -2
  179. package/src/executor.ts +372 -73
  180. package/src/executor.unit.test.ts +48 -1
  181. package/src/extension-connection.test.ts +612 -488
  182. package/src/ffmpeg.ts +769 -0
  183. package/src/ghost-browser.ts +4 -6
  184. package/src/ghost-cursor-client.ts +368 -0
  185. package/src/ghost-cursor.ts +110 -0
  186. package/src/htmlrewrite.test.ts +6 -2
  187. package/src/htmlrewrite.ts +348 -386
  188. package/src/kill-port.ts +1 -3
  189. package/src/locator-selector.test.ts +115 -0
  190. package/src/mcp-client.ts +1 -1
  191. package/src/mcp.ts +21 -15
  192. package/src/on-mouse-action.test.ts +196 -0
  193. package/src/page-markdown.ts +7 -7
  194. package/src/protocol.ts +73 -57
  195. package/src/recording-ghost-cursor.ts +107 -0
  196. package/src/recording-relay.ts +20 -12
  197. package/src/relay-client.ts +84 -17
  198. package/src/relay-core.test.ts +761 -583
  199. package/src/relay-navigation.test.ts +517 -484
  200. package/src/relay-session.test.ts +984 -929
  201. package/src/relay-state.test.ts +570 -0
  202. package/src/relay-state.ts +497 -0
  203. package/src/resource.md +21 -49
  204. package/src/scoped-fs.ts +9 -3
  205. package/src/screen-recording.ts +175 -31
  206. package/src/skill.md +619 -271
  207. package/src/snapshot-tools.test.ts +580 -528
  208. package/src/snapshots/shadcn-ui-accessibility-full.md +181 -183
  209. package/src/snapshots/shadcn-ui-accessibility-interactive.md +119 -121
  210. package/src/start-relay-server.ts +14 -11
  211. package/src/styles-examples.ts +8 -1
  212. package/src/styles.ts +20 -21
  213. package/src/test-declarations.ts +6 -6
  214. package/src/test-utils.ts +104 -91
  215. package/src/utils.ts +2 -1
  216. package/src/wait-for-page-load.ts +6 -1
@@ -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
- * SessionId (pw-tab-X format) is used to identify which tab to record.
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 flags = EXTENSION_IDS.map(id => `--allowlisted-extension-id=${id}`).join(' ') + ' --auto-accept-this-tab-capture'
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 error.includes('Extension has not been invoked') ||
47
- error.includes('activeTab') ||
48
- error.includes('enable recording')
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
- /** Session ID (pw-tab-X format) to identify which tab to record */
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
- /** Session ID (pw-tab-X format) to identify which tab to stop recording */
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({ sessionId, frameRate, videoBitsPerSecond, audioBitsPerSecond, audio, outputPath: absoluteOutputPath }),
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
- `For automated recording, Chrome must be restarted with special flags.\n` +
121
- `WARNING: This will close all Chrome windows. Save your work first!\n\n` +
122
- ` ${restartCmd}\n\n` +
123
- `Or click the Playwriter extension icon on the tab once to grant permission.`
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(options: StopRecordingOptions): Promise<{ path: string; duration: number; size: number }> {
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: { page: Page; sessionId?: string; relayPort?: number }): Promise<RecordingState> {
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 = sessionId
166
- ? `http://127.0.0.1:${relayPort}/recording/status?sessionId=${encodeURIComponent(sessionId)}`
167
- : `http://127.0.0.1:${relayPort}/recording/status`
168
- const response = await fetch(url)
169
- const result = await response.json() as IsRecordingResult
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: { page: Page; sessionId?: string; relayPort?: number }): Promise<void> {
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}`)