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.
Files changed (223) hide show
  1. package/dist/a11y-client.js +18 -8
  2. package/dist/aria-snapshot.d.ts +41 -3
  3. package/dist/aria-snapshot.d.ts.map +1 -1
  4. package/dist/aria-snapshot.js +134 -55
  5. package/dist/aria-snapshot.js.map +1 -1
  6. package/dist/aria-snapshot.test.js +5 -2
  7. package/dist/aria-snapshot.test.js.map +1 -1
  8. package/dist/aria-snapshot.unit.test.js +83 -41
  9. package/dist/aria-snapshot.unit.test.js.map +1 -1
  10. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
  11. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
  12. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
  13. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
  14. package/dist/bippy.js +1 -1
  15. package/dist/cdp-log.d.ts +1 -1
  16. package/dist/cdp-log.d.ts.map +1 -1
  17. package/dist/cdp-log.js +1 -1
  18. package/dist/cdp-log.js.map +1 -1
  19. package/dist/cdp-relay.d.ts.map +1 -1
  20. package/dist/cdp-relay.js +492 -298
  21. package/dist/cdp-relay.js.map +1 -1
  22. package/dist/cdp-session.d.ts.map +1 -1
  23. package/dist/cdp-session.js.map +1 -1
  24. package/dist/cdp-types.d.ts.map +1 -1
  25. package/dist/cdp-types.js +7 -7
  26. package/dist/cdp-types.js.map +1 -1
  27. package/dist/clean-html.d.ts.map +1 -1
  28. package/dist/clean-html.js +4 -5
  29. package/dist/clean-html.js.map +1 -1
  30. package/dist/cli.js +45 -27
  31. package/dist/cli.js.map +1 -1
  32. package/dist/create-logger.d.ts.map +1 -1
  33. package/dist/create-logger.js +3 -1
  34. package/dist/create-logger.js.map +1 -1
  35. package/dist/debugger-examples-types.d.ts.map +1 -1
  36. package/dist/debugger.d.ts.map +1 -1
  37. package/dist/debugger.js +1 -3
  38. package/dist/debugger.js.map +1 -1
  39. package/dist/diff-utils.d.ts.map +1 -1
  40. package/dist/diff-utils.js +1 -4
  41. package/dist/diff-utils.js.map +1 -1
  42. package/dist/editor-api.md +12 -2
  43. package/dist/editor-examples.d.ts +1 -1
  44. package/dist/editor-examples.d.ts.map +1 -1
  45. package/dist/editor-examples.js +1 -1
  46. package/dist/editor-examples.js.map +1 -1
  47. package/dist/editor.d.ts +1 -1
  48. package/dist/editor.d.ts.map +1 -1
  49. package/dist/editor.js +1 -1
  50. package/dist/editor.js.map +1 -1
  51. package/dist/executor.d.ts +26 -3
  52. package/dist/executor.d.ts.map +1 -1
  53. package/dist/executor.js +297 -64
  54. package/dist/executor.js.map +1 -1
  55. package/dist/executor.unit.test.js +38 -1
  56. package/dist/executor.unit.test.js.map +1 -1
  57. package/dist/extension-connection.test.js +139 -36
  58. package/dist/extension-connection.test.js.map +1 -1
  59. package/dist/ffmpeg.d.ts +148 -0
  60. package/dist/ffmpeg.d.ts.map +1 -0
  61. package/dist/ffmpeg.js +523 -0
  62. package/dist/ffmpeg.js.map +1 -0
  63. package/dist/ghost-browser.d.ts.map +1 -1
  64. package/dist/ghost-browser.js.map +1 -1
  65. package/dist/ghost-cursor-client.js +287 -0
  66. package/dist/ghost-cursor.d.ts +27 -0
  67. package/dist/ghost-cursor.d.ts.map +1 -0
  68. package/dist/ghost-cursor.js +63 -0
  69. package/dist/ghost-cursor.js.map +1 -0
  70. package/dist/htmlrewrite.d.ts.map +1 -1
  71. package/dist/htmlrewrite.js +17 -55
  72. package/dist/htmlrewrite.js.map +1 -1
  73. package/dist/htmlrewrite.test.js.map +1 -1
  74. package/dist/kill-port.d.ts.map +1 -1
  75. package/dist/kill-port.js +1 -3
  76. package/dist/kill-port.js.map +1 -1
  77. package/dist/locator-selector.test.d.ts +2 -0
  78. package/dist/locator-selector.test.d.ts.map +1 -0
  79. package/dist/locator-selector.test.js +96 -0
  80. package/dist/locator-selector.test.js.map +1 -0
  81. package/dist/mcp-client.js.map +1 -1
  82. package/dist/mcp.d.ts.map +1 -1
  83. package/dist/mcp.js +8 -3
  84. package/dist/mcp.js.map +1 -1
  85. package/dist/on-mouse-action.test.d.ts +2 -0
  86. package/dist/on-mouse-action.test.d.ts.map +1 -0
  87. package/dist/on-mouse-action.test.js +155 -0
  88. package/dist/on-mouse-action.test.js.map +1 -0
  89. package/dist/page-markdown.js +4 -4
  90. package/dist/page-markdown.js.map +1 -1
  91. package/dist/prompt.md +450 -377
  92. package/dist/protocol.d.ts +4 -0
  93. package/dist/protocol.d.ts.map +1 -1
  94. package/dist/readability.js +16 -2
  95. package/dist/recording-ghost-cursor.d.ts +41 -0
  96. package/dist/recording-ghost-cursor.d.ts.map +1 -0
  97. package/dist/recording-ghost-cursor.js +79 -0
  98. package/dist/recording-ghost-cursor.js.map +1 -0
  99. package/dist/recording-relay.d.ts.map +1 -1
  100. package/dist/recording-relay.js +8 -8
  101. package/dist/recording-relay.js.map +1 -1
  102. package/dist/relay-client.d.ts +17 -4
  103. package/dist/relay-client.d.ts.map +1 -1
  104. package/dist/relay-client.js +45 -11
  105. package/dist/relay-client.js.map +1 -1
  106. package/dist/relay-core.test.d.ts.map +1 -1
  107. package/dist/relay-core.test.js +515 -26
  108. package/dist/relay-core.test.js.map +1 -1
  109. package/dist/relay-navigation.test.d.ts.map +1 -1
  110. package/dist/relay-navigation.test.js +169 -31
  111. package/dist/relay-navigation.test.js.map +1 -1
  112. package/dist/relay-session.test.d.ts.map +1 -1
  113. package/dist/relay-session.test.js +113 -65
  114. package/dist/relay-session.test.js.map +1 -1
  115. package/dist/relay-state.d.ts +158 -0
  116. package/dist/relay-state.d.ts.map +1 -0
  117. package/dist/relay-state.js +306 -0
  118. package/dist/relay-state.js.map +1 -0
  119. package/dist/relay-state.test.d.ts +2 -0
  120. package/dist/relay-state.test.d.ts.map +1 -0
  121. package/dist/relay-state.test.js +472 -0
  122. package/dist/relay-state.test.js.map +1 -0
  123. package/dist/scoped-fs.d.ts.map +1 -1
  124. package/dist/scoped-fs.js.map +1 -1
  125. package/dist/screen-recording.d.ts +66 -4
  126. package/dist/screen-recording.d.ts.map +1 -1
  127. package/dist/screen-recording.js +150 -13
  128. package/dist/screen-recording.js.map +1 -1
  129. package/dist/screen-recording.test.d.ts +2 -0
  130. package/dist/screen-recording.test.d.ts.map +1 -0
  131. package/dist/screen-recording.test.js +102 -0
  132. package/dist/screen-recording.test.js.map +1 -0
  133. package/dist/selector-generator.js +1 -1
  134. package/dist/snapshot-tools.test.js +71 -28
  135. package/dist/snapshot-tools.test.js.map +1 -1
  136. package/dist/start-relay-server.d.ts +1 -1
  137. package/dist/start-relay-server.d.ts.map +1 -1
  138. package/dist/start-relay-server.js +1 -1
  139. package/dist/start-relay-server.js.map +1 -1
  140. package/dist/styles-api.md +8 -1
  141. package/dist/styles-examples.d.ts +1 -1
  142. package/dist/styles-examples.d.ts.map +1 -1
  143. package/dist/styles-examples.js +1 -1
  144. package/dist/styles-examples.js.map +1 -1
  145. package/dist/styles.d.ts.map +1 -1
  146. package/dist/styles.js +1 -3
  147. package/dist/styles.js.map +1 -1
  148. package/dist/test-declarations.d.ts.map +1 -1
  149. package/dist/test-utils.d.ts +1 -1
  150. package/dist/test-utils.d.ts.map +1 -1
  151. package/dist/test-utils.js +7 -5
  152. package/dist/test-utils.js.map +1 -1
  153. package/dist/utils.d.ts.map +1 -1
  154. package/dist/utils.js.map +1 -1
  155. package/dist/wait-for-page-load.d.ts.map +1 -1
  156. package/dist/wait-for-page-load.js +1 -1
  157. package/dist/wait-for-page-load.js.map +1 -1
  158. package/package.json +4 -3
  159. package/src/a11y-client.ts +5 -4
  160. package/src/aria-snapshot.test.ts +5 -2
  161. package/src/aria-snapshot.ts +306 -117
  162. package/src/aria-snapshot.unit.test.ts +199 -141
  163. package/src/aria-snapshots/github-interactive.txt +2 -0
  164. package/src/aria-snapshots/github-raw.txt +5 -1
  165. package/src/aria-snapshots/hackernews-interactive.txt +238 -241
  166. package/src/aria-snapshots/hackernews-raw.txt +265 -269
  167. package/src/assets/aria-labels-example.png +0 -0
  168. package/src/assets/aria-labels-github.png +0 -0
  169. package/src/assets/aria-labels-hacker-news.png +0 -0
  170. package/src/assets/aria-labels-old-reddit.png +0 -0
  171. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
  172. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
  173. package/src/cdp-log.ts +4 -1
  174. package/src/cdp-relay.ts +1059 -737
  175. package/src/cdp-session.ts +12 -3
  176. package/src/cdp-types.ts +51 -51
  177. package/src/clean-html.ts +4 -5
  178. package/src/cli.ts +82 -55
  179. package/src/create-logger.ts +5 -3
  180. package/src/debugger-examples-types.ts +4 -1
  181. package/src/debugger.ts +1 -5
  182. package/src/diff-utils.ts +2 -5
  183. package/src/editor-examples.ts +11 -1
  184. package/src/editor.ts +10 -2
  185. package/src/executor.ts +374 -73
  186. package/src/executor.unit.test.ts +48 -1
  187. package/src/extension-connection.test.ts +612 -488
  188. package/src/ffmpeg.ts +769 -0
  189. package/src/ghost-browser.ts +4 -6
  190. package/src/ghost-cursor-client.ts +369 -0
  191. package/src/ghost-cursor.ts +110 -0
  192. package/src/htmlrewrite.test.ts +6 -2
  193. package/src/htmlrewrite.ts +348 -386
  194. package/src/kill-port.ts +1 -3
  195. package/src/locator-selector.test.ts +115 -0
  196. package/src/mcp-client.ts +1 -1
  197. package/src/mcp.ts +21 -15
  198. package/src/on-mouse-action.test.ts +196 -0
  199. package/src/page-markdown.ts +7 -7
  200. package/src/protocol.ts +73 -57
  201. package/src/recording-ghost-cursor.ts +113 -0
  202. package/src/recording-relay.ts +20 -12
  203. package/src/relay-client.ts +85 -18
  204. package/src/relay-core.test.ts +1117 -578
  205. package/src/relay-navigation.test.ts +648 -483
  206. package/src/relay-session.test.ts +984 -929
  207. package/src/relay-state.test.ts +570 -0
  208. package/src/relay-state.ts +497 -0
  209. package/src/resource.md +21 -49
  210. package/src/scoped-fs.ts +9 -3
  211. package/src/screen-recording.test.ts +111 -0
  212. package/src/screen-recording.ts +256 -31
  213. package/src/skill.md +476 -396
  214. package/src/snapshot-tools.test.ts +580 -528
  215. package/src/snapshots/shadcn-ui-accessibility-full.md +8 -8
  216. package/src/snapshots/shadcn-ui-accessibility-interactive.md +8 -8
  217. package/src/start-relay-server.ts +14 -11
  218. package/src/styles-examples.ts +8 -1
  219. package/src/styles.ts +20 -21
  220. package/src/test-declarations.ts +6 -6
  221. package/src/test-utils.ts +104 -91
  222. package/src/utils.ts +2 -1
  223. package/src/wait-for-page-load.ts +6 -1
@@ -8,15 +8,15 @@
8
8
  - role=link[name="Directory"]
9
9
  - role=link[name="Create"]
10
10
  - role=button[name="Search documentation..."]
11
- - role=link[name="107k"]
11
+ - role=link[name="109k"]
12
12
  - role=button[name="Toggle theme"]
13
- - role=link[name="New Project"]
13
+ - role=link[name="New"]
14
14
  - main:
15
- - role=link[name="RTL Support"]
15
+ - role=link[name="shadcn/skills, presets and more"]
16
16
  - heading "The Foundation for your Design System"
17
17
  - paragraph:
18
18
  - text: "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."
19
- - role=link[name="Get Started"]
19
+ - role=link[name="New Project"]
20
20
  - role=link[name="View Components"]
21
21
  - layouttable:
22
22
  - role=link[name="Examples"]
@@ -95,7 +95,7 @@
95
95
  - role=button[name="Info"] >> nth=0
96
96
  - role=textbox[name="Ask, Search or Chat..."]
97
97
  - role=button[name="Add"] >> nth=1
98
- - button "Auto" [id="radix-_R_2jlbav5uiuplb_"]
98
+ - button "Auto" [id="radix-_R_aeldbsnqbr6lb_"]
99
99
  - text: "52% used"
100
100
  - role=button[name="Send"] >> nth=0
101
101
  - role=textbox[name="@shadcn"]
@@ -141,14 +141,14 @@
141
141
  - textbox "Prompt" [id="notion-prompt"]
142
142
  - role=button[name="Add context"]
143
143
  - role=button[name="Attach file"]
144
- - button "Auto" [id="radix-_R_1e6bav5uiuplb_"]
145
- - button "All Sources" [id="radix-_R_1u6bav5uiuplb_"]
144
+ - button "Auto" [id="radix-_R_5opdbsnqbr6lb_"]
145
+ - button "All Sources" [id="radix-_R_7opdbsnqbr6lb_"]
146
146
  - role=button[name="Send"] >> nth=1
147
147
  - role=button[name="Go Back"]
148
148
  - role=button[name="Archive"]
149
149
  - role=button[name="Report"]
150
150
  - role=button[name="Snooze"]
151
- - button "More Options" [id="radix-_R_babav5uiuplb_"]
151
+ - button "More Options" [id="radix-_R_1d9dbsnqbr6lb_"]
152
152
  - labeltext:
153
153
  - "I agree to the terms and conditions" [id="checkbox-demo"]
154
154
  - labeltext:
@@ -8,12 +8,12 @@
8
8
  - role=link[name="Directory"]
9
9
  - role=link[name="Create"]
10
10
  - role=button[name="Search documentation..."]
11
- - role=link[name="107k"]
11
+ - role=link[name="109k"]
12
12
  - role=button[name="Toggle theme"]
13
- - role=link[name="New Project"]
13
+ - role=link[name="New"]
14
14
  - main:
15
- - role=link[name="RTL Support"]
16
- - role=link[name="Get Started"]
15
+ - role=link[name="shadcn/skills, presets and more"]
16
+ - role=link[name="New Project"]
17
17
  - role=link[name="View Components"]
18
18
  - role=link[name="Examples"]
19
19
  - role=link[name="Dashboard"]
@@ -59,7 +59,7 @@
59
59
  - role=button[name="Info"] >> nth=0
60
60
  - role=textbox[name="Ask, Search or Chat..."]
61
61
  - role=button[name="Add"] >> nth=1
62
- - button "Auto" [id="radix-_R_2jlbav5uiuplb_"]
62
+ - button "Auto" [id="radix-_R_aeldbsnqbr6lb_"]
63
63
  - role=button[name="Send"] >> nth=0
64
64
  - role=textbox[name="@shadcn"]
65
65
  - labeltext:
@@ -90,14 +90,14 @@
90
90
  - textbox "Prompt" [id="notion-prompt"]
91
91
  - role=button[name="Add context"]
92
92
  - role=button[name="Attach file"]
93
- - button "Auto" [id="radix-_R_1e6bav5uiuplb_"]
94
- - button "All Sources" [id="radix-_R_1u6bav5uiuplb_"]
93
+ - button "Auto" [id="radix-_R_5opdbsnqbr6lb_"]
94
+ - button "All Sources" [id="radix-_R_7opdbsnqbr6lb_"]
95
95
  - role=button[name="Send"] >> nth=1
96
96
  - role=button[name="Go Back"]
97
97
  - role=button[name="Archive"]
98
98
  - role=button[name="Report"]
99
99
  - role=button[name="Snooze"]
100
- - button "More Options" [id="radix-_R_babav5uiuplb_"]
100
+ - button "More Options" [id="radix-_R_1d9dbsnqbr6lb_"]
101
101
  - labeltext:
102
102
  - "I agree to the terms and conditions" [id="checkbox-demo"]
103
103
  - labeltext:
@@ -7,21 +7,24 @@ process.title = 'playwriter-ws-server'
7
7
  const logger = createFileLogger()
8
8
 
9
9
  process.on('uncaughtException', async (err) => {
10
- await logger.error('Uncaught Exception:', err);
11
- process.exit(1);
12
- });
10
+ await logger.error('Uncaught Exception:', err)
11
+ process.exit(1)
12
+ })
13
13
 
14
14
  process.on('unhandledRejection', async (reason) => {
15
- await logger.error('Unhandled Rejection:', reason);
16
- process.exit(1);
17
- });
15
+ await logger.error('Unhandled Rejection:', reason)
16
+ process.exit(1)
17
+ })
18
18
 
19
19
  process.on('exit', async (code) => {
20
- await logger.log(`Process exiting with code: ${code}`);
21
- });
22
-
23
-
24
- export async function startServer({ port = 19988, host = '127.0.0.1', token }: { port?: number; host?: string; token?: string } = {}) {
20
+ await logger.log(`Process exiting with code: ${code}`)
21
+ })
22
+
23
+ export async function startServer({
24
+ port = 19988,
25
+ host = '127.0.0.1',
26
+ token,
27
+ }: { port?: number; host?: string; token?: string } = {}) {
25
28
  const server = await startPlayWriterCDPRelayServer({ port, host, token, logger })
26
29
 
27
30
  console.log('CDP Relay Server running. Press Ctrl+C to stop.')
@@ -74,4 +74,11 @@ async function compareStyles() {
74
74
  console.log(formatStylesAsText(secondary))
75
75
  }
76
76
 
77
- export { getElementStyles, inspectButtonStyles, getStylesWithUserAgent, findPropertySource, checkInheritedStyles, compareStyles }
77
+ export {
78
+ getElementStyles,
79
+ inspectButtonStyles,
80
+ getStylesWithUserAgent,
81
+ findPropertySource,
82
+ checkInheritedStyles,
83
+ compareStyles,
84
+ }
package/src/styles.ts CHANGED
@@ -120,12 +120,12 @@ export async function getStylesForLocator({
120
120
  const matchedStyles = await cdp.send('CSS.getMatchedStylesForNode', { nodeId })
121
121
 
122
122
  const stylesheetUrls = new Map<string, string>()
123
-
123
+
124
124
  const processStyleSheetId = async (styleSheetId: string | undefined): Promise<StyleSource | null> => {
125
125
  if (!styleSheetId) {
126
126
  return null
127
127
  }
128
-
128
+
129
129
  if (!stylesheetUrls.has(styleSheetId)) {
130
130
  try {
131
131
  const header = await cdp.send('CSS.getStyleSheetText', { styleSheetId })
@@ -134,7 +134,7 @@ export async function getStylesForLocator({
134
134
  stylesheetUrls.set(styleSheetId, '')
135
135
  }
136
136
  }
137
-
137
+
138
138
  return null
139
139
  }
140
140
 
@@ -145,14 +145,15 @@ export async function getStylesForLocator({
145
145
  const rule = ruleMatch.rule
146
146
  const sourceRange = (rule as any).selectorList?.range as SourceRange | undefined
147
147
  const styleSheetId = rule.styleSheetId
148
-
148
+
149
149
  let source: StyleSource | null = null
150
150
  if (styleSheetId && sourceRange) {
151
151
  const styleSheet = (matchedStyles as any).cssStyleSheetHeaders?.find(
152
- (h: CSSStyleSheetHeader) => h.styleSheetId === styleSheetId
152
+ (h: CSSStyleSheetHeader) => h.styleSheetId === styleSheetId,
153
153
  )
154
- const url = styleSheet?.sourceURL || (rule as any).origin === 'user-agent' ? 'user-agent' : `stylesheet:${styleSheetId}`
155
-
154
+ const url =
155
+ styleSheet?.sourceURL || (rule as any).origin === 'user-agent' ? 'user-agent' : `stylesheet:${styleSheetId}`
156
+
156
157
  source = {
157
158
  url: (rule as any).styleSheetId ? await getStylesheetUrl(cdp, styleSheetId) : 'user-agent',
158
159
  line: sourceRange.startLine + 1,
@@ -224,9 +225,7 @@ export async function getStylesForLocator({
224
225
  }
225
226
  }
226
227
 
227
- const filteredRules = includeUserAgentStyles
228
- ? rules
229
- : rules.filter((r) => r.origin !== 'user-agent')
228
+ const filteredRules = includeUserAgentStyles ? rules : rules.filter((r) => r.origin !== 'user-agent')
230
229
 
231
230
  return {
232
231
  element: elementDescription,
@@ -239,7 +238,7 @@ function extractDeclarations(style: CSSStyle): StyleDeclarations {
239
238
  if (!style?.cssProperties) {
240
239
  return {}
241
240
  }
242
-
241
+
243
242
  const result: StyleDeclarations = {}
244
243
  for (const prop of style.cssProperties) {
245
244
  if (!prop.value || prop.value === 'initial' || prop.name.startsWith('-webkit-')) {
@@ -253,13 +252,13 @@ function extractDeclarations(style: CSSStyle): StyleDeclarations {
253
252
 
254
253
  function formatElementDescription(node: any): string {
255
254
  let desc = node.localName || node.nodeName?.toLowerCase() || 'element'
256
-
255
+
257
256
  if (node.attributes) {
258
257
  const attrs: Record<string, string> = {}
259
258
  for (let i = 0; i < node.attributes.length; i += 2) {
260
259
  attrs[node.attributes[i]] = node.attributes[i + 1]
261
260
  }
262
-
261
+
263
262
  if (attrs.id) {
264
263
  desc += `#${attrs.id}`
265
264
  }
@@ -267,7 +266,7 @@ function formatElementDescription(node: any): string {
267
266
  desc += `.${attrs.class.split(' ').join('.')}`
268
267
  }
269
268
  }
270
-
269
+
271
270
  return desc
272
271
  }
273
272
 
@@ -282,10 +281,10 @@ async function getStylesheetUrl(cdp: ICDPSession, styleSheetId: string): Promise
282
281
 
283
282
  export function formatStylesAsText(styles: StylesResult): string {
284
283
  const lines: string[] = []
285
-
284
+
286
285
  lines.push(`Element: ${styles.element}`)
287
286
  lines.push('')
288
-
287
+
289
288
  if (styles.inlineStyle) {
290
289
  lines.push('Inline styles:')
291
290
  for (const [prop, value] of Object.entries(styles.inlineStyle)) {
@@ -293,10 +292,10 @@ export function formatStylesAsText(styles: StylesResult): string {
293
292
  }
294
293
  lines.push('')
295
294
  }
296
-
295
+
297
296
  const directRules = styles.rules.filter((r) => !r.inheritedFrom)
298
297
  const inheritedRules = styles.rules.filter((r) => r.inheritedFrom)
299
-
298
+
300
299
  if (directRules.length > 0) {
301
300
  lines.push('Matched rules:')
302
301
  for (const rule of directRules) {
@@ -312,7 +311,7 @@ export function formatStylesAsText(styles: StylesResult): string {
312
311
  }
313
312
  lines.push('')
314
313
  }
315
-
314
+
316
315
  if (inheritedRules.length > 0) {
317
316
  const byAncestor = new Map<string, StyleRule[]>()
318
317
  for (const rule of inheritedRules) {
@@ -322,7 +321,7 @@ export function formatStylesAsText(styles: StylesResult): string {
322
321
  }
323
322
  byAncestor.get(key)!.push(rule)
324
323
  }
325
-
324
+
326
325
  for (const [ancestor, rules] of byAncestor) {
327
326
  lines.push(`Inherited from ${ancestor}:`)
328
327
  for (const rule of rules) {
@@ -339,6 +338,6 @@ export function formatStylesAsText(styles: StylesResult): string {
339
338
  lines.push('')
340
339
  }
341
340
  }
342
-
341
+
343
342
  return lines.join('\n')
344
343
  }
@@ -1,13 +1,13 @@
1
1
  import type { ExtensionState } from 'mcp-extension/src/types.js'
2
2
 
3
3
  declare global {
4
- var toggleExtensionForActiveTab: () => Promise<{ isConnected: boolean; state: ExtensionState }>;
5
- var getExtensionState: () => ExtensionState;
6
- var disconnectEverything: () => Promise<void>;
4
+ var toggleExtensionForActiveTab: () => Promise<{ isConnected: boolean; state: ExtensionState }>
5
+ var getExtensionState: () => ExtensionState
6
+ var disconnectEverything: () => Promise<void>
7
7
 
8
- // Browser globals used in evaluate() calls
9
- var window: any;
10
- var document: any;
8
+ // Browser globals used in evaluate() calls
9
+ var window: any
10
+ var document: any
11
11
  }
12
12
 
13
13
  export {}
package/src/test-utils.ts CHANGED
@@ -21,10 +21,15 @@ async function buildExtension({ port, distDir }: { port: number; distDir: string
21
21
  })
22
22
  .then(async () => {
23
23
  // Build into a per-port dist to avoid parallel test runs overwriting each other.
24
- await execAsync(`TESTING=1 PLAYWRITER_PORT=${port} PLAYWRITER_EXTENSION_DIST=${distDir} pnpm build`, { cwd: '../extension' })
24
+ await execAsync(`TESTING=1 PLAYWRITER_PORT=${port} PLAYWRITER_EXTENSION_DIST=${distDir} pnpm build`, {
25
+ cwd: '../extension',
26
+ })
25
27
  })
26
28
 
27
- extensionBuildQueues.set(distDir, buildPromise.finally(() => {}))
29
+ extensionBuildQueues.set(
30
+ distDir,
31
+ buildPromise.finally(() => {}),
32
+ )
28
33
  await buildPromise
29
34
  }
30
35
 
@@ -103,7 +108,10 @@ export async function setupTestContext({
103
108
  return { browserContext, userDataDir, relayServer }
104
109
  }
105
110
 
106
- export async function cleanupTestContext(ctx: TestContext | null, cleanup?: (() => Promise<void>) | null): Promise<void> {
111
+ export async function cleanupTestContext(
112
+ ctx: TestContext | null,
113
+ cleanup?: (() => Promise<void>) | null,
114
+ ): Promise<void> {
107
115
  if (ctx?.browserContext) {
108
116
  await ctx.browserContext.close()
109
117
  }
@@ -188,7 +196,7 @@ export async function createSseServer(): Promise<SseServer> {
188
196
  res.writeHead(200, {
189
197
  'Content-Type': 'text/event-stream',
190
198
  'Cache-Control': 'no-cache, no-transform',
191
- 'Connection': 'keep-alive',
199
+ Connection: 'keep-alive',
192
200
  })
193
201
  res.write('retry: 1000\n\n')
194
202
  res.write('data: hello\n\n')
@@ -265,129 +273,134 @@ export async function createSseServer(): Promise<SseServer> {
265
273
  resolve()
266
274
  })
267
275
  })
268
- }
276
+ },
269
277
  }
270
278
  }
271
279
 
272
- export async function withTimeout<T>({ promise, timeoutMs, errorMessage }: { promise: Promise<T>; timeoutMs: number; errorMessage: string }): Promise<T> {
273
- return await new Promise<T>((resolve, reject) => {
274
- const timeoutId = setTimeout(() => {
275
- reject(new Error(errorMessage))
276
- }, timeoutMs)
277
-
278
- promise
279
- .then((value) => {
280
- clearTimeout(timeoutId)
281
- resolve(value)
282
- })
283
- .catch((error) => {
284
- clearTimeout(timeoutId)
285
- reject(error)
286
- })
287
- })
280
+ export async function withTimeout<T>({
281
+ promise,
282
+ timeoutMs,
283
+ errorMessage,
284
+ }: {
285
+ promise: Promise<T>
286
+ timeoutMs: number
287
+ errorMessage: string
288
+ }): Promise<T> {
289
+ return await new Promise<T>((resolve, reject) => {
290
+ const timeoutId = setTimeout(() => {
291
+ reject(new Error(errorMessage))
292
+ }, timeoutMs)
293
+
294
+ promise
295
+ .then((value) => {
296
+ clearTimeout(timeoutId)
297
+ resolve(value)
298
+ })
299
+ .catch((error) => {
300
+ clearTimeout(timeoutId)
301
+ reject(error)
302
+ })
303
+ })
288
304
  }
289
305
 
290
306
  /** Tagged template for inline JS code strings used in MCP execute calls */
291
307
  export function js(strings: TemplateStringsArray, ...values: unknown[]): string {
292
- return strings.reduce(
293
- (result, str, i) => result + str + (values[i] || ''),
294
- '',
295
- )
308
+ return strings.reduce((result, str, i) => result + str + (values[i] || ''), '')
296
309
  }
297
310
 
298
311
  export function tryJsonParse(str: string) {
299
- try {
300
- return JSON.parse(str)
301
- } catch {
302
- return str
303
- }
312
+ try {
313
+ return JSON.parse(str)
314
+ } catch {
315
+ return str
316
+ }
304
317
  }
305
318
 
306
319
  /**
307
320
  * Safely close a browser connected via connectOverCDP.
308
- *
321
+ *
309
322
  * Playwright's CRConnection uses async message handling (messageWrap) that can cause
310
323
  * a race condition where _onClose() runs before all pending _onMessage() handlers complete.
311
324
  * This results in "Assertion error" from crConnection.js when a CDP response arrives
312
325
  * after callbacks were cleared by dispose().
313
- *
326
+ *
314
327
  * This helper waits for the message queue to drain before closing, avoiding the race.
315
- *
328
+ *
316
329
  * @param browser - Browser instance from chromium.connectOverCDP()
317
330
  * @param drainDelayMs - Time to wait for pending messages to be processed (default: 50ms)
318
331
  */
319
332
  export async function safeCloseCDPBrowser(
320
- browser: Awaited<ReturnType<typeof import('@xmorse/playwright-core').chromium.connectOverCDP>>,
321
- drainDelayMs = 50
333
+ browser: Awaited<ReturnType<typeof import('@xmorse/playwright-core').chromium.connectOverCDP>>,
334
+ drainDelayMs = 50,
322
335
  ): Promise<void> {
323
- // Wait for any queued message handlers to run
324
- // This gives Playwright's messageWrap time to process pending CDP responses
325
- await new Promise(r => setTimeout(r, drainDelayMs))
326
- await browser.close()
336
+ // Wait for any queued message handlers to run
337
+ // This gives Playwright's messageWrap time to process pending CDP responses
338
+ await new Promise((r) => setTimeout(r, drainDelayMs))
339
+ await browser.close()
327
340
  }
328
341
 
329
342
  export type SimpleServer = {
330
- baseUrl: string
331
- close: () => Promise<void>
343
+ baseUrl: string
344
+ close: () => Promise<void>
332
345
  }
333
346
 
334
347
  /** Minimal local HTTP server for tests that need cross-origin iframes or custom routes */
335
348
  export async function createSimpleServer({ routes }: { routes: Record<string, string> }): Promise<SimpleServer> {
336
- const openSockets: Set<net.Socket> = new Set()
337
- const server = http.createServer((req, res) => {
338
- const url = req.url || '/'
339
- const body = routes[url]
340
- if (!body) {
341
- res.writeHead(404, { 'Content-Type': 'text/plain' })
342
- res.end('not found')
343
- return
344
- }
345
- res.writeHead(200, { 'Content-Type': 'text/html' })
346
- res.end(body)
349
+ const openSockets: Set<net.Socket> = new Set()
350
+ const server = http.createServer((req, res) => {
351
+ const url = req.url || '/'
352
+ const body = routes[url]
353
+ if (!body) {
354
+ res.writeHead(404, { 'Content-Type': 'text/plain' })
355
+ res.end('not found')
356
+ return
357
+ }
358
+ res.writeHead(200, { 'Content-Type': 'text/html' })
359
+ res.end(body)
360
+ })
361
+
362
+ server.on('connection', (socket) => {
363
+ openSockets.add(socket)
364
+ socket.on('close', () => {
365
+ openSockets.delete(socket)
347
366
  })
367
+ })
348
368
 
349
- server.on('connection', (socket) => {
350
- openSockets.add(socket)
351
- socket.on('close', () => {
352
- openSockets.delete(socket)
353
- })
369
+ await new Promise<void>((resolve) => {
370
+ server.listen(0, '127.0.0.1', () => {
371
+ resolve()
354
372
  })
373
+ })
355
374
 
356
- await new Promise<void>((resolve) => {
357
- server.listen(0, '127.0.0.1', () => {
358
- resolve()
359
- })
375
+ const address = server.address()
376
+ if (!address || typeof address === 'string') {
377
+ await new Promise<void>((resolve, reject) => {
378
+ server.close((error) => {
379
+ if (error) {
380
+ reject(error)
381
+ return
382
+ }
383
+ resolve()
384
+ })
360
385
  })
386
+ throw new Error('Failed to start test server')
387
+ }
361
388
 
362
- const address = server.address()
363
- if (!address || typeof address === 'string') {
364
- await new Promise<void>((resolve, reject) => {
365
- server.close((error) => {
366
- if (error) {
367
- reject(error)
368
- return
369
- }
370
- resolve()
371
- })
389
+ return {
390
+ baseUrl: `http://127.0.0.1:${address.port}`,
391
+ close: async () => {
392
+ for (const socket of openSockets) {
393
+ socket.destroy()
394
+ }
395
+ await new Promise<void>((resolve, reject) => {
396
+ server.close((error) => {
397
+ if (error) {
398
+ reject(error)
399
+ return
400
+ }
401
+ resolve()
372
402
  })
373
- throw new Error('Failed to start test server')
374
- }
375
-
376
- return {
377
- baseUrl: `http://127.0.0.1:${address.port}`,
378
- close: async () => {
379
- for (const socket of openSockets) {
380
- socket.destroy()
381
- }
382
- await new Promise<void>((resolve, reject) => {
383
- server.close((error) => {
384
- if (error) {
385
- reject(error)
386
- return
387
- }
388
- resolve()
389
- })
390
- })
391
- },
392
- }
403
+ })
404
+ },
405
+ }
393
406
  }
package/src/utils.ts CHANGED
@@ -59,7 +59,8 @@ export function getCdpUrl({
59
59
  // Use ~/.playwriter for logs so each OS user gets their own dir (avoids permission errors on shared machines, see #44)
60
60
  const LOG_BASE_DIR = path.join(os.homedir(), '.playwriter')
61
61
  export const LOG_FILE_PATH = process.env.PLAYWRITER_LOG_FILE_PATH || path.join(LOG_BASE_DIR, 'relay-server.log')
62
- export const LOG_CDP_FILE_PATH = process.env.PLAYWRITER_CDP_LOG_FILE_PATH || path.join(path.dirname(LOG_FILE_PATH), 'cdp.jsonl')
62
+ export const LOG_CDP_FILE_PATH =
63
+ process.env.PLAYWRITER_CDP_LOG_FILE_PATH || path.join(path.dirname(LOG_FILE_PATH), 'cdp.jsonl')
63
64
 
64
65
  const packageJsonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json')
65
66
  export const VERSION = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).version as string
@@ -66,7 +66,12 @@ export async function waitForPageLoad(options: WaitForPageLoadOptions): Promise<
66
66
 
67
67
  const checkPageReady = async (): Promise<{ ready: boolean; readyState: string; pendingRequests: string[] }> => {
68
68
  const result = await page.evaluate(
69
- ({ filteredDomains, filteredExtensions, stuckThreshold, slowResourceThreshold }): {
69
+ ({
70
+ filteredDomains,
71
+ filteredExtensions,
72
+ stuckThreshold,
73
+ slowResourceThreshold,
74
+ }): {
70
75
  ready: boolean
71
76
  readyState: string
72
77
  pendingRequests: string[]