e2e-pilot 0.0.69

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 (152) hide show
  1. package/bin.js +3 -0
  2. package/dist/aria-snapshot.d.ts +95 -0
  3. package/dist/aria-snapshot.d.ts.map +1 -0
  4. package/dist/aria-snapshot.js +490 -0
  5. package/dist/aria-snapshot.js.map +1 -0
  6. package/dist/bippy.js +971 -0
  7. package/dist/cdp-relay.d.ts +16 -0
  8. package/dist/cdp-relay.d.ts.map +1 -0
  9. package/dist/cdp-relay.js +715 -0
  10. package/dist/cdp-relay.js.map +1 -0
  11. package/dist/cdp-session.d.ts +42 -0
  12. package/dist/cdp-session.d.ts.map +1 -0
  13. package/dist/cdp-session.js +154 -0
  14. package/dist/cdp-session.js.map +1 -0
  15. package/dist/cdp-types.d.ts +63 -0
  16. package/dist/cdp-types.d.ts.map +1 -0
  17. package/dist/cdp-types.js +91 -0
  18. package/dist/cdp-types.js.map +1 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +213 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/create-logger.d.ts +9 -0
  24. package/dist/create-logger.d.ts.map +1 -0
  25. package/dist/create-logger.js +25 -0
  26. package/dist/create-logger.js.map +1 -0
  27. package/dist/debugger-api.md +458 -0
  28. package/dist/debugger-examples-types.d.ts +24 -0
  29. package/dist/debugger-examples-types.d.ts.map +1 -0
  30. package/dist/debugger-examples-types.js +2 -0
  31. package/dist/debugger-examples-types.js.map +1 -0
  32. package/dist/debugger-examples.d.ts +6 -0
  33. package/dist/debugger-examples.d.ts.map +1 -0
  34. package/dist/debugger-examples.js +53 -0
  35. package/dist/debugger-examples.js.map +1 -0
  36. package/dist/debugger.d.ts +381 -0
  37. package/dist/debugger.d.ts.map +1 -0
  38. package/dist/debugger.js +633 -0
  39. package/dist/debugger.js.map +1 -0
  40. package/dist/editor-api.md +364 -0
  41. package/dist/editor-examples.d.ts +11 -0
  42. package/dist/editor-examples.d.ts.map +1 -0
  43. package/dist/editor-examples.js +124 -0
  44. package/dist/editor-examples.js.map +1 -0
  45. package/dist/editor.d.ts +203 -0
  46. package/dist/editor.d.ts.map +1 -0
  47. package/dist/editor.js +336 -0
  48. package/dist/editor.js.map +1 -0
  49. package/dist/execute.d.ts +50 -0
  50. package/dist/execute.d.ts.map +1 -0
  51. package/dist/execute.js +576 -0
  52. package/dist/execute.js.map +1 -0
  53. package/dist/index.d.ts +11 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/mcp-client.d.ts +20 -0
  58. package/dist/mcp-client.d.ts.map +1 -0
  59. package/dist/mcp-client.js +56 -0
  60. package/dist/mcp-client.js.map +1 -0
  61. package/dist/mcp.d.ts +5 -0
  62. package/dist/mcp.d.ts.map +1 -0
  63. package/dist/mcp.js +720 -0
  64. package/dist/mcp.js.map +1 -0
  65. package/dist/mcp.test.d.ts +10 -0
  66. package/dist/mcp.test.d.ts.map +1 -0
  67. package/dist/mcp.test.js +2999 -0
  68. package/dist/mcp.test.js.map +1 -0
  69. package/dist/network-capture.d.ts +23 -0
  70. package/dist/network-capture.d.ts.map +1 -0
  71. package/dist/network-capture.js +98 -0
  72. package/dist/network-capture.js.map +1 -0
  73. package/dist/protocol.d.ts +54 -0
  74. package/dist/protocol.d.ts.map +1 -0
  75. package/dist/protocol.js +2 -0
  76. package/dist/protocol.js.map +1 -0
  77. package/dist/react-source.d.ts +13 -0
  78. package/dist/react-source.d.ts.map +1 -0
  79. package/dist/react-source.js +68 -0
  80. package/dist/react-source.js.map +1 -0
  81. package/dist/scoped-fs.d.ts +94 -0
  82. package/dist/scoped-fs.d.ts.map +1 -0
  83. package/dist/scoped-fs.js +356 -0
  84. package/dist/scoped-fs.js.map +1 -0
  85. package/dist/selector-generator.js +8126 -0
  86. package/dist/start-relay-server.d.ts +6 -0
  87. package/dist/start-relay-server.d.ts.map +1 -0
  88. package/dist/start-relay-server.js +33 -0
  89. package/dist/start-relay-server.js.map +1 -0
  90. package/dist/styles-api.md +117 -0
  91. package/dist/styles-examples.d.ts +8 -0
  92. package/dist/styles-examples.d.ts.map +1 -0
  93. package/dist/styles-examples.js +64 -0
  94. package/dist/styles-examples.js.map +1 -0
  95. package/dist/styles.d.ts +27 -0
  96. package/dist/styles.d.ts.map +1 -0
  97. package/dist/styles.js +234 -0
  98. package/dist/styles.js.map +1 -0
  99. package/dist/trace-utils.d.ts +14 -0
  100. package/dist/trace-utils.d.ts.map +1 -0
  101. package/dist/trace-utils.js +21 -0
  102. package/dist/trace-utils.js.map +1 -0
  103. package/dist/utils.d.ts +20 -0
  104. package/dist/utils.d.ts.map +1 -0
  105. package/dist/utils.js +75 -0
  106. package/dist/utils.js.map +1 -0
  107. package/dist/wait-for-page-load.d.ts +16 -0
  108. package/dist/wait-for-page-load.d.ts.map +1 -0
  109. package/dist/wait-for-page-load.js +127 -0
  110. package/dist/wait-for-page-load.js.map +1 -0
  111. package/package.json +67 -0
  112. package/src/aria-snapshot.ts +610 -0
  113. package/src/assets/aria-labels-github-snapshot.txt +605 -0
  114. package/src/assets/aria-labels-github.png +0 -0
  115. package/src/assets/aria-labels-google-snapshot.txt +49 -0
  116. package/src/assets/aria-labels-google.png +0 -0
  117. package/src/assets/aria-labels-hacker-news-snapshot.txt +1023 -0
  118. package/src/assets/aria-labels-hacker-news.png +0 -0
  119. package/src/cdp-relay.ts +925 -0
  120. package/src/cdp-session.ts +203 -0
  121. package/src/cdp-timing.md +128 -0
  122. package/src/cdp-types.ts +155 -0
  123. package/src/cli.ts +250 -0
  124. package/src/create-logger.ts +36 -0
  125. package/src/debugger-examples-types.ts +13 -0
  126. package/src/debugger-examples.ts +66 -0
  127. package/src/debugger.md +453 -0
  128. package/src/debugger.ts +713 -0
  129. package/src/editor-examples.ts +148 -0
  130. package/src/editor.ts +390 -0
  131. package/src/execute.ts +763 -0
  132. package/src/index.ts +10 -0
  133. package/src/mcp-client.ts +78 -0
  134. package/src/mcp.test.ts +3596 -0
  135. package/src/mcp.ts +876 -0
  136. package/src/network-capture.ts +140 -0
  137. package/src/prompt.bak.md +323 -0
  138. package/src/prompt.md +7 -0
  139. package/src/protocol.ts +63 -0
  140. package/src/react-source.ts +94 -0
  141. package/src/resource.md +436 -0
  142. package/src/scoped-fs.ts +411 -0
  143. package/src/snapshots/hacker-news-focused-accessibility.md +202 -0
  144. package/src/snapshots/hacker-news-initial-accessibility.md +11 -0
  145. package/src/snapshots/hacker-news-tabbed-accessibility.md +202 -0
  146. package/src/snapshots/shadcn-ui-accessibility.md +11 -0
  147. package/src/start-relay-server.ts +43 -0
  148. package/src/styles-examples.ts +77 -0
  149. package/src/styles.ts +345 -0
  150. package/src/trace-utils.ts +43 -0
  151. package/src/utils.ts +91 -0
  152. package/src/wait-for-page-load.ts +174 -0
@@ -0,0 +1,202 @@
1
+ - table [ref=e3]:
2
+ - rowgroup [ref=e4]:
3
+ - row "Hacker Newsnew | past | comments | ask | show | jobs | submit login" [ref=e5]:
4
+ - cell "Hacker Newsnew | past | comments | ask | show | jobs | submit login" [ref=e6]:
5
+ - table [ref=e7]:
6
+ - rowgroup [ref=e8]:
7
+ - row "Hacker Newsnew | past | comments | ask | show | jobs | submit login" [ref=e9]:
8
+ - cell [ref=e10]:
9
+ - link [ref=e11] [cursor=pointer]:
10
+ - /url: https://news.ycombinator.com
11
+ - img [ref=e12] [cursor=pointer]
12
+ - cell "Hacker Newsnew | past | comments | ask | show | jobs | submit" [ref=e13]:
13
+ - generic [ref=e14]:
14
+ - link "Hacker News" [active] [ref=e16] [cursor=pointer]:
15
+ - /url: news
16
+ - link "new" [ref=e17] [cursor=pointer]:
17
+ - /url: newest
18
+ - text: "|"
19
+ - link "past" [ref=e18] [cursor=pointer]:
20
+ - /url: front
21
+ - text: "|"
22
+ - link "comments" [ref=e19] [cursor=pointer]:
23
+ - /url: newcomments
24
+ - text: "|"
25
+ - link "ask" [ref=e20] [cursor=pointer]:
26
+ - /url: ask
27
+ - text: "|"
28
+ - link "show" [ref=e21] [cursor=pointer]:
29
+ - /url: show
30
+ - text: "|"
31
+ - link "jobs" [ref=e22] [cursor=pointer]:
32
+ - /url: jobs
33
+ - text: "|"
34
+ - link "submit" [ref=e23] [cursor=pointer]:
35
+ - /url: submit
36
+ - cell "login" [ref=e24]:
37
+ - link "login" [ref=e26] [cursor=pointer]:
38
+ - /url: login?goto=item%3Fid%3D1
39
+ - row [ref=e27]
40
+ - row "upvote Y Combinator (ycombinator.com) 57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road? upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e28]:
41
+ - cell "upvote Y Combinator (ycombinator.com) 57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road? upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e29]:
42
+ - table [ref=e30]:
43
+ - rowgroup [ref=e31]:
44
+ - row "upvote Y Combinator (ycombinator.com)" [ref=e32]:
45
+ - cell [ref=e33]
46
+ - cell "upvote" [ref=e35]:
47
+ - link "upvote" [ref=e37] [cursor=pointer]:
48
+ - /url: vote?id=1&how=up&goto=item%3Fid%3D1
49
+ - cell "Y Combinator (ycombinator.com)" [ref=e39]:
50
+ - generic [ref=e40]:
51
+ - link "Y Combinator" [ref=e41] [cursor=pointer]:
52
+ - /url: http://ycombinator.com
53
+ - generic [ref=e42]:
54
+ - text: (
55
+ - link "ycombinator.com" [ref=e43] [cursor=pointer]:
56
+ - /url: from?site=ycombinator.com
57
+ - generic [ref=e44] [cursor=pointer]: ycombinator.com
58
+ - text: )
59
+ - row "57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments" [ref=e45]:
60
+ - cell [ref=e46]
61
+ - cell "57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments" [ref=e47]:
62
+ - generic [ref=e48]:
63
+ - generic [ref=e49]: 57 points
64
+ - text: by
65
+ - link "pg" [ref=e50] [cursor=pointer]:
66
+ - /url: user?id=pg
67
+ - link "on Oct 9, 2006" [ref=e52] [cursor=pointer]:
68
+ - /url: item?id=1
69
+ - text: "|"
70
+ - link "hide" [ref=e54] [cursor=pointer]:
71
+ - /url: hide?id=1&goto=item%3Fid%3D1
72
+ - text: "|"
73
+ - link "past" [ref=e55] [cursor=pointer]:
74
+ - /url: https://hn.algolia.com/?query=Y%20Combinator&type=story&dateRange=all&sort=byDate&storyText=false&prefix&page=0
75
+ - text: "|"
76
+ - link "favorite" [ref=e56] [cursor=pointer]:
77
+ - /url: fave?id=1&auth=5328fcfde7333d68e67a8a2334e25acee5599932
78
+ - text: "|"
79
+ - link "3 comments" [ref=e57] [cursor=pointer]:
80
+ - /url: item?id=1
81
+ - row [ref=e58]:
82
+ - cell [ref=e59]
83
+ - cell [ref=e60]
84
+ - table [ref=e63]:
85
+ - rowgroup [ref=e64]:
86
+ - row "upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e65]:
87
+ - cell "upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e66]:
88
+ - table [ref=e67]:
89
+ - rowgroup [ref=e68]:
90
+ - row "upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e69]:
91
+ - cell [ref=e70]:
92
+ - img
93
+ - cell "upvote" [ref=e72]:
94
+ - link "upvote" [ref=e74] [cursor=pointer]:
95
+ - /url: vote?id=15&how=up&goto=item%3Fid%3D1
96
+ - cell "sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e76]:
97
+ - generic [ref=e78]:
98
+ - link "sama" [ref=e79] [cursor=pointer]:
99
+ - /url: user?id=sama
100
+ - link "on Oct 9, 2006" [ref=e81] [cursor=pointer]:
101
+ - /url: item?id=15
102
+ - link "[–]" [ref=e84] [cursor=pointer]:
103
+ - /url: javascript:void(0)
104
+ - generic [ref=e87]:
105
+ - generic [ref=e88]: "\"the rising star of venture capital\" -unknown VC eating lunch on SHR"
106
+ - generic:
107
+ - paragraph
108
+ - row "upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e92]:
109
+ - cell "upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e93]:
110
+ - table [ref=e94]:
111
+ - rowgroup [ref=e95]:
112
+ - row "upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e96]:
113
+ - cell [ref=e97]:
114
+ - img [ref=e98]
115
+ - cell "upvote" [ref=e99]:
116
+ - link "upvote" [ref=e101] [cursor=pointer]:
117
+ - /url: vote?id=17&how=up&goto=item%3Fid%3D1
118
+ - cell "pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e103]:
119
+ - generic [ref=e105]:
120
+ - link "pg" [ref=e106] [cursor=pointer]:
121
+ - /url: user?id=pg
122
+ - link "on Oct 9, 2006" [ref=e108] [cursor=pointer]:
123
+ - /url: item?id=17
124
+ - generic [ref=e110]:
125
+ - text: "|"
126
+ - link [ref=e111] [cursor=pointer]:
127
+ - /url: "#15"
128
+ - text: parent
129
+ - link "[–]" [ref=e112] [cursor=pointer]:
130
+ - /url: javascript:void(0)
131
+ - generic [ref=e115]:
132
+ - generic [ref=e116]: Is there anywhere to eat on Sandhill Road?
133
+ - generic:
134
+ - paragraph
135
+ - row "upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e120]:
136
+ - cell "upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e121]:
137
+ - table [ref=e122]:
138
+ - rowgroup [ref=e123]:
139
+ - row "upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e124]:
140
+ - cell [ref=e125]:
141
+ - img [ref=e126]
142
+ - cell "upvote" [ref=e127]:
143
+ - link "upvote" [ref=e129] [cursor=pointer]:
144
+ - /url: vote?id=1079&how=up&goto=item%3Fid%3D1
145
+ - cell "dmon on Feb 25, 2007 | | [–] sure" [ref=e131]:
146
+ - generic [ref=e133]:
147
+ - link "dmon" [ref=e134] [cursor=pointer]:
148
+ - /url: user?id=dmon
149
+ - link "on Feb 25, 2007" [ref=e136] [cursor=pointer]:
150
+ - /url: item?id=1079
151
+ - generic [ref=e138]:
152
+ - text: "|"
153
+ - link [ref=e139] [cursor=pointer]:
154
+ - /url: "#15"
155
+ - text: root
156
+ - text: "|"
157
+ - link [ref=e140] [cursor=pointer]:
158
+ - /url: "#17"
159
+ - text: parent
160
+ - link "[–]" [ref=e141] [cursor=pointer]:
161
+ - /url: javascript:void(0)
162
+ - generic [ref=e144]:
163
+ - generic [ref=e145]: sure
164
+ - generic:
165
+ - paragraph
166
+ - row "Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4 Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:" [ref=e151]:
167
+ - cell "Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4 Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:" [ref=e152]:
168
+ - img
169
+ - table [ref=e154]:
170
+ - rowgroup [ref=e155]:
171
+ - row [ref=e156]:
172
+ - cell [ref=e157]
173
+ - link "Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4" [ref=e160] [cursor=pointer]:
174
+ - /url: https://www.ycombinator.com/apply/
175
+ - generic [ref=e162]:
176
+ - generic [ref=e163]:
177
+ - link "Guidelines" [ref=e164] [cursor=pointer]:
178
+ - /url: newsguidelines.html
179
+ - text: "|"
180
+ - link "FAQ" [ref=e165] [cursor=pointer]:
181
+ - /url: newsfaq.html
182
+ - text: "|"
183
+ - link "Lists" [ref=e166] [cursor=pointer]:
184
+ - /url: lists
185
+ - text: "|"
186
+ - link "API" [ref=e167] [cursor=pointer]:
187
+ - /url: https://github.com/HackerNews/API
188
+ - text: "|"
189
+ - link "Security" [ref=e168] [cursor=pointer]:
190
+ - /url: security.html
191
+ - text: "|"
192
+ - link "Legal" [ref=e169] [cursor=pointer]:
193
+ - /url: https://www.ycombinator.com/legal/
194
+ - text: "|"
195
+ - link "Apply to YC" [ref=e170] [cursor=pointer]:
196
+ - /url: https://www.ycombinator.com/apply/
197
+ - text: "|"
198
+ - link "Contact" [ref=e171] [cursor=pointer]:
199
+ - /url: mailto:hn@ycombinator.com
200
+ - generic [ref=e174]:
201
+ - text: "Search:"
202
+ - textbox [ref=e175]
@@ -0,0 +1,11 @@
1
+ MCP error -32602: Input validation error: Invalid arguments for tool execute: [
2
+ {
3
+ "code": "invalid_type",
4
+ "expected": "string",
5
+ "received": "undefined",
6
+ "path": [
7
+ "intend"
8
+ ],
9
+ "message": "Required"
10
+ }
11
+ ]
@@ -0,0 +1,43 @@
1
+ import { startE2EPilotCDPRelayServer } from './cdp-relay.js'
2
+ import { createFileLogger } from './create-logger.js'
3
+
4
+ process.title = 'e2e-pilot-ws-server'
5
+
6
+ const logger = createFileLogger()
7
+
8
+ process.on('uncaughtException', async (err) => {
9
+ await logger.error('Uncaught Exception:', err);
10
+ process.exit(1);
11
+ });
12
+
13
+ process.on('unhandledRejection', async (reason) => {
14
+ await logger.error('Unhandled Rejection:', reason);
15
+ process.exit(1);
16
+ });
17
+
18
+ process.on('exit', async (code) => {
19
+ await logger.log(`Process exiting with code: ${code}`);
20
+ });
21
+
22
+
23
+ export async function startServer({ port = 19988, host = '127.0.0.1', token }: { port?: number; host?: string; token?: string } = {}) {
24
+ const server = await startE2EPilotCDPRelayServer({ port, host, token, logger })
25
+
26
+ console.log('CDP Relay Server running. Press Ctrl+C to stop.')
27
+ console.log('Logs are being written to:', logger.logFilePath)
28
+
29
+ process.on('SIGINT', () => {
30
+ console.log('\nShutting down...')
31
+ server.close()
32
+ process.exit(0)
33
+ })
34
+
35
+ process.on('SIGTERM', () => {
36
+ console.log('\nShutting down...')
37
+ server.close()
38
+ process.exit(0)
39
+ })
40
+
41
+ return server
42
+ }
43
+ startServer().catch(logger.error)
@@ -0,0 +1,77 @@
1
+ import { page, getStylesForLocator, formatStylesAsText, console } from './debugger-examples-types.js'
2
+
3
+ // Example: Get styles for an element and display them
4
+ async function getElementStyles() {
5
+ const loc = page.locator('.my-button')
6
+ const styles = await getStylesForLocator({ locator: loc })
7
+ console.log(formatStylesAsText(styles))
8
+ }
9
+
10
+ // Example: Inspect computed styles for a specific element
11
+ async function inspectButtonStyles() {
12
+ const button = page.getByRole('button', { name: 'Submit' })
13
+ const styles = await getStylesForLocator({ locator: button })
14
+
15
+ console.log('Element:', styles.element)
16
+
17
+ if (styles.inlineStyle) {
18
+ console.log('Inline styles:', styles.inlineStyle)
19
+ }
20
+
21
+ for (const rule of styles.rules) {
22
+ console.log(`${rule.selector}: ${JSON.stringify(rule.declarations)}`)
23
+ if (rule.source) {
24
+ console.log(` Source: ${rule.source.url}:${rule.source.line}`)
25
+ }
26
+ }
27
+ }
28
+
29
+ // Example: Include browser default (user-agent) styles
30
+ async function getStylesWithUserAgent() {
31
+ const loc = page.locator('input[type="text"]')
32
+ const styles = await getStylesForLocator({
33
+ locator: loc,
34
+ includeUserAgentStyles: true,
35
+ })
36
+ console.log(formatStylesAsText(styles))
37
+ }
38
+
39
+ // Example: Find where a CSS property is defined
40
+ async function findPropertySource() {
41
+ const loc = page.locator('.card')
42
+ const styles = await getStylesForLocator({ locator: loc })
43
+
44
+ const backgroundRule = styles.rules.find((r) => 'background-color' in r.declarations)
45
+ if (backgroundRule) {
46
+ console.log('background-color defined by:', backgroundRule.selector)
47
+ if (backgroundRule.source) {
48
+ console.log(` at ${backgroundRule.source.url}:${backgroundRule.source.line}`)
49
+ }
50
+ }
51
+ }
52
+
53
+ // Example: Check inherited styles
54
+ async function checkInheritedStyles() {
55
+ const loc = page.locator('.nested-text')
56
+ const styles = await getStylesForLocator({ locator: loc })
57
+
58
+ const inheritedRules = styles.rules.filter((r) => r.inheritedFrom)
59
+ for (const rule of inheritedRules) {
60
+ console.log(`Inherited from ${rule.inheritedFrom}: ${rule.selector}`)
61
+ console.log(' Properties:', rule.declarations)
62
+ }
63
+ }
64
+
65
+ // Example: Compare styles between two elements
66
+ async function compareStyles() {
67
+ const primary = await getStylesForLocator({ locator: page.locator('.btn-primary') })
68
+ const secondary = await getStylesForLocator({ locator: page.locator('.btn-secondary') })
69
+
70
+ console.log('Primary button:')
71
+ console.log(formatStylesAsText(primary))
72
+
73
+ console.log('Secondary button:')
74
+ console.log(formatStylesAsText(secondary))
75
+ }
76
+
77
+ export { getElementStyles, inspectButtonStyles, getStylesWithUserAgent, findPropertySource, checkInheritedStyles, compareStyles }
package/src/styles.ts ADDED
@@ -0,0 +1,345 @@
1
+ import type { ICDPSession, CDPSession } from './cdp-session.js'
2
+ import type { Locator } from 'playwright-core'
3
+
4
+ export interface StyleSource {
5
+ url: string
6
+ line: number
7
+ column: number
8
+ }
9
+
10
+ export type StyleDeclarations = Record<string, string>
11
+
12
+ export interface StyleRule {
13
+ selector: string
14
+ source: StyleSource | null
15
+ origin: 'regular' | 'user-agent' | 'injected' | 'inspector'
16
+ declarations: StyleDeclarations
17
+ inheritedFrom: string | null
18
+ }
19
+
20
+ export interface StylesResult {
21
+ element: string
22
+ inlineStyle: StyleDeclarations | null
23
+ rules: StyleRule[]
24
+ }
25
+
26
+ interface CSSProperty {
27
+ name: string
28
+ value: string
29
+ important?: boolean
30
+ }
31
+
32
+ interface CSSStyle {
33
+ cssProperties: CSSProperty[]
34
+ cssText?: string
35
+ }
36
+
37
+ interface CSSRule {
38
+ selectorList: { text: string }
39
+ style: CSSStyle
40
+ styleSheetId?: string
41
+ origin: string
42
+ }
43
+
44
+ interface RuleMatch {
45
+ rule: CSSRule
46
+ matchingSelectors: number[]
47
+ }
48
+
49
+ interface InheritedStyleEntry {
50
+ inlineStyle?: CSSStyle
51
+ matchedCSSRules: RuleMatch[]
52
+ }
53
+
54
+ interface SourceRange {
55
+ startLine: number
56
+ startColumn: number
57
+ endLine: number
58
+ endColumn: number
59
+ }
60
+
61
+ interface CSSStyleSheetHeader {
62
+ styleSheetId: string
63
+ sourceURL?: string
64
+ origin: string
65
+ }
66
+
67
+ export async function getStylesForLocator({
68
+ locator,
69
+ cdp: cdpSession,
70
+ includeUserAgentStyles = false,
71
+ }: {
72
+ locator: Locator
73
+ cdp: ICDPSession
74
+ includeUserAgentStyles?: boolean
75
+ }): Promise<StylesResult> {
76
+ // Cast to CDPSession for internal type safety - at runtime both are compatible
77
+ const cdp = cdpSession as CDPSession
78
+ await cdp.send('DOM.enable')
79
+ await cdp.send('CSS.enable')
80
+
81
+ const elementHandle = await locator.elementHandle()
82
+ if (!elementHandle) {
83
+ throw new Error('Could not get element handle from locator')
84
+ }
85
+
86
+ const remoteObject = (elementHandle as any)._channel?.objectId
87
+ ? { objectId: (elementHandle as any)._channel.objectId }
88
+ : null
89
+
90
+ let backendNodeId: number
91
+ if (remoteObject?.objectId) {
92
+ const nodeInfo = await cdp.send('DOM.describeNode', {
93
+ objectId: remoteObject.objectId,
94
+ })
95
+ backendNodeId = nodeInfo.node.backendNodeId
96
+ } else {
97
+ const box = await elementHandle.boundingBox()
98
+ if (!box) {
99
+ throw new Error('Element has no bounding box')
100
+ }
101
+ const docResult = await cdp.send('DOM.getDocument', { depth: 0 })
102
+ const nodeAtPoint = await cdp.send('DOM.getNodeForLocation', {
103
+ x: Math.round(box.x + box.width / 2),
104
+ y: Math.round(box.y + box.height / 2),
105
+ })
106
+ backendNodeId = nodeAtPoint.backendNodeId
107
+ }
108
+
109
+ const pushResult = await cdp.send('DOM.pushNodesByBackendIdsToFrontend', {
110
+ backendNodeIds: [backendNodeId],
111
+ })
112
+ const nodeId = pushResult.nodeIds[0]
113
+
114
+ if (!nodeId) {
115
+ throw new Error('Could not get nodeId for element')
116
+ }
117
+
118
+ const nodeInfo = await cdp.send('DOM.describeNode', { nodeId })
119
+ const elementDescription = formatElementDescription(nodeInfo.node)
120
+
121
+ const matchedStyles = await cdp.send('CSS.getMatchedStylesForNode', { nodeId })
122
+
123
+ const stylesheetUrls = new Map<string, string>()
124
+
125
+ const processStyleSheetId = async (styleSheetId: string | undefined): Promise<StyleSource | null> => {
126
+ if (!styleSheetId) {
127
+ return null
128
+ }
129
+
130
+ if (!stylesheetUrls.has(styleSheetId)) {
131
+ try {
132
+ const header = await cdp.send('CSS.getStyleSheetText', { styleSheetId })
133
+ stylesheetUrls.set(styleSheetId, '')
134
+ } catch {
135
+ stylesheetUrls.set(styleSheetId, '')
136
+ }
137
+ }
138
+
139
+ return null
140
+ }
141
+
142
+ const rules: StyleRule[] = []
143
+
144
+ if (matchedStyles.matchedCSSRules) {
145
+ for (const ruleMatch of matchedStyles.matchedCSSRules) {
146
+ const rule = ruleMatch.rule
147
+ const sourceRange = (rule as any).selectorList?.range as SourceRange | undefined
148
+ const styleSheetId = rule.styleSheetId
149
+
150
+ let source: StyleSource | null = null
151
+ if (styleSheetId && sourceRange) {
152
+ const styleSheet = (matchedStyles as any).cssStyleSheetHeaders?.find(
153
+ (h: CSSStyleSheetHeader) => h.styleSheetId === styleSheetId
154
+ )
155
+ const url = styleSheet?.sourceURL || (rule as any).origin === 'user-agent' ? 'user-agent' : `stylesheet:${styleSheetId}`
156
+
157
+ source = {
158
+ url: (rule as any).styleSheetId ? await getStylesheetUrl(cdp, styleSheetId) : 'user-agent',
159
+ line: sourceRange.startLine + 1,
160
+ column: sourceRange.startColumn,
161
+ }
162
+ }
163
+
164
+ rules.push({
165
+ selector: rule.selectorList.text,
166
+ source,
167
+ origin: rule.origin as StyleRule['origin'],
168
+ declarations: extractDeclarations(rule.style),
169
+ inheritedFrom: null,
170
+ })
171
+ }
172
+ }
173
+
174
+ if (matchedStyles.inherited) {
175
+ for (let i = 0; i < matchedStyles.inherited.length; i++) {
176
+ const inheritedEntry = matchedStyles.inherited[i] as InheritedStyleEntry
177
+ const ancestorDesc = `ancestor[${i + 1}]`
178
+
179
+ if (inheritedEntry.inlineStyle) {
180
+ const declarations = extractDeclarations(inheritedEntry.inlineStyle)
181
+ if (Object.keys(declarations).length > 0) {
182
+ rules.push({
183
+ selector: 'element.style',
184
+ source: null,
185
+ origin: 'regular',
186
+ declarations,
187
+ inheritedFrom: ancestorDesc,
188
+ })
189
+ }
190
+ }
191
+
192
+ for (const ruleMatch of inheritedEntry.matchedCSSRules) {
193
+ const rule = ruleMatch.rule
194
+ const sourceRange = (rule as any).selectorList?.range as SourceRange | undefined
195
+ const styleSheetId = rule.styleSheetId
196
+
197
+ let source: StyleSource | null = null
198
+ if (styleSheetId && sourceRange) {
199
+ source = {
200
+ url: await getStylesheetUrl(cdp, styleSheetId),
201
+ line: sourceRange.startLine + 1,
202
+ column: sourceRange.startColumn,
203
+ }
204
+ }
205
+
206
+ const declarations = extractDeclarations(rule.style)
207
+ if (Object.keys(declarations).length > 0) {
208
+ rules.push({
209
+ selector: rule.selectorList.text,
210
+ source,
211
+ origin: rule.origin as StyleRule['origin'],
212
+ declarations,
213
+ inheritedFrom: ancestorDesc,
214
+ })
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ let inlineStyle: StyleDeclarations | null = null
221
+ if (matchedStyles.inlineStyle) {
222
+ const declarations = extractDeclarations(matchedStyles.inlineStyle as CSSStyle)
223
+ if (Object.keys(declarations).length > 0) {
224
+ inlineStyle = declarations
225
+ }
226
+ }
227
+
228
+ const filteredRules = includeUserAgentStyles
229
+ ? rules
230
+ : rules.filter((r) => r.origin !== 'user-agent')
231
+
232
+ return {
233
+ element: elementDescription,
234
+ inlineStyle,
235
+ rules: filteredRules,
236
+ }
237
+ }
238
+
239
+ function extractDeclarations(style: CSSStyle): StyleDeclarations {
240
+ if (!style?.cssProperties) {
241
+ return {}
242
+ }
243
+
244
+ const result: StyleDeclarations = {}
245
+ for (const prop of style.cssProperties) {
246
+ if (!prop.value || prop.value === 'initial' || prop.name.startsWith('-webkit-')) {
247
+ continue
248
+ }
249
+ const value = prop.important ? `${prop.value} !important` : prop.value
250
+ result[prop.name] = value
251
+ }
252
+ return result
253
+ }
254
+
255
+ function formatElementDescription(node: any): string {
256
+ let desc = node.localName || node.nodeName?.toLowerCase() || 'element'
257
+
258
+ if (node.attributes) {
259
+ const attrs: Record<string, string> = {}
260
+ for (let i = 0; i < node.attributes.length; i += 2) {
261
+ attrs[node.attributes[i]] = node.attributes[i + 1]
262
+ }
263
+
264
+ if (attrs.id) {
265
+ desc += `#${attrs.id}`
266
+ }
267
+ if (attrs.class) {
268
+ desc += `.${attrs.class.split(' ').join('.')}`
269
+ }
270
+ }
271
+
272
+ return desc
273
+ }
274
+
275
+ async function getStylesheetUrl(cdp: CDPSession, styleSheetId: string): Promise<string> {
276
+ try {
277
+ await cdp.send('CSS.getStyleSheetText', { styleSheetId })
278
+ return `stylesheet:${styleSheetId}`
279
+ } catch {
280
+ return `stylesheet:${styleSheetId}`
281
+ }
282
+ }
283
+
284
+ export function formatStylesAsText(styles: StylesResult): string {
285
+ const lines: string[] = []
286
+
287
+ lines.push(`Element: ${styles.element}`)
288
+ lines.push('')
289
+
290
+ if (styles.inlineStyle) {
291
+ lines.push('Inline styles:')
292
+ for (const [prop, value] of Object.entries(styles.inlineStyle)) {
293
+ lines.push(` ${prop}: ${value}`)
294
+ }
295
+ lines.push('')
296
+ }
297
+
298
+ const directRules = styles.rules.filter((r) => !r.inheritedFrom)
299
+ const inheritedRules = styles.rules.filter((r) => r.inheritedFrom)
300
+
301
+ if (directRules.length > 0) {
302
+ lines.push('Matched rules:')
303
+ for (const rule of directRules) {
304
+ lines.push(` ${rule.selector} {`)
305
+ const sourceInfo = rule.source ? ` /* ${rule.source.url}:${rule.source.line}:${rule.source.column} */` : ''
306
+ if (sourceInfo) {
307
+ lines.push(` ${sourceInfo}`)
308
+ }
309
+ for (const [prop, value] of Object.entries(rule.declarations)) {
310
+ lines.push(` ${prop}: ${value};`)
311
+ }
312
+ lines.push(' }')
313
+ }
314
+ lines.push('')
315
+ }
316
+
317
+ if (inheritedRules.length > 0) {
318
+ const byAncestor = new Map<string, StyleRule[]>()
319
+ for (const rule of inheritedRules) {
320
+ const key = rule.inheritedFrom!
321
+ if (!byAncestor.has(key)) {
322
+ byAncestor.set(key, [])
323
+ }
324
+ byAncestor.get(key)!.push(rule)
325
+ }
326
+
327
+ for (const [ancestor, rules] of byAncestor) {
328
+ lines.push(`Inherited from ${ancestor}:`)
329
+ for (const rule of rules) {
330
+ lines.push(` ${rule.selector} {`)
331
+ const sourceInfo = rule.source ? ` /* ${rule.source.url}:${rule.source.line}:${rule.source.column} */` : ''
332
+ if (sourceInfo) {
333
+ lines.push(` ${sourceInfo}`)
334
+ }
335
+ for (const [prop, value] of Object.entries(rule.declarations)) {
336
+ lines.push(` ${prop}: ${value};`)
337
+ }
338
+ lines.push(' }')
339
+ }
340
+ lines.push('')
341
+ }
342
+ }
343
+
344
+ return lines.join('\n')
345
+ }