playwriter 0.0.63 → 0.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a11y-client.js +18 -8
- package/dist/aria-snapshot.d.ts +41 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +134 -55
- package/dist/aria-snapshot.js.map +1 -1
- package/dist/aria-snapshot.test.js +5 -2
- package/dist/aria-snapshot.test.js.map +1 -1
- package/dist/aria-snapshot.unit.test.js +83 -41
- package/dist/aria-snapshot.unit.test.js.map +1 -1
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
- package/dist/bippy.js +1 -1
- package/dist/cdp-log.d.ts +1 -1
- package/dist/cdp-log.d.ts.map +1 -1
- package/dist/cdp-log.js +1 -1
- package/dist/cdp-log.js.map +1 -1
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +492 -298
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js.map +1 -1
- package/dist/cdp-types.d.ts.map +1 -1
- package/dist/cdp-types.js +7 -7
- package/dist/cdp-types.js.map +1 -1
- package/dist/clean-html.d.ts.map +1 -1
- package/dist/clean-html.js +4 -5
- package/dist/clean-html.js.map +1 -1
- package/dist/cli.js +45 -27
- package/dist/cli.js.map +1 -1
- package/dist/create-logger.d.ts.map +1 -1
- package/dist/create-logger.js +3 -1
- package/dist/create-logger.js.map +1 -1
- package/dist/debugger-examples-types.d.ts.map +1 -1
- package/dist/debugger.d.ts.map +1 -1
- package/dist/debugger.js +1 -3
- package/dist/debugger.js.map +1 -1
- package/dist/diff-utils.d.ts.map +1 -1
- package/dist/diff-utils.js +1 -4
- package/dist/diff-utils.js.map +1 -1
- package/dist/editor-api.md +12 -2
- package/dist/editor-examples.d.ts +1 -1
- package/dist/editor-examples.d.ts.map +1 -1
- package/dist/editor-examples.js +1 -1
- package/dist/editor-examples.js.map +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/executor.d.ts +26 -3
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +297 -64
- package/dist/executor.js.map +1 -1
- package/dist/executor.unit.test.js +38 -1
- package/dist/executor.unit.test.js.map +1 -1
- package/dist/extension-connection.test.js +139 -36
- package/dist/extension-connection.test.js.map +1 -1
- package/dist/ffmpeg.d.ts +148 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +523 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/ghost-browser.d.ts.map +1 -1
- package/dist/ghost-browser.js.map +1 -1
- package/dist/ghost-cursor-client.js +287 -0
- package/dist/ghost-cursor.d.ts +27 -0
- package/dist/ghost-cursor.d.ts.map +1 -0
- package/dist/ghost-cursor.js +63 -0
- package/dist/ghost-cursor.js.map +1 -0
- package/dist/htmlrewrite.d.ts.map +1 -1
- package/dist/htmlrewrite.js +17 -55
- package/dist/htmlrewrite.js.map +1 -1
- package/dist/htmlrewrite.test.js.map +1 -1
- package/dist/kill-port.d.ts.map +1 -1
- package/dist/kill-port.js +1 -3
- package/dist/kill-port.js.map +1 -1
- package/dist/locator-selector.test.d.ts +2 -0
- package/dist/locator-selector.test.d.ts.map +1 -0
- package/dist/locator-selector.test.js +96 -0
- package/dist/locator-selector.test.js.map +1 -0
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -3
- package/dist/mcp.js.map +1 -1
- package/dist/on-mouse-action.test.d.ts +2 -0
- package/dist/on-mouse-action.test.d.ts.map +1 -0
- package/dist/on-mouse-action.test.js +155 -0
- package/dist/on-mouse-action.test.js.map +1 -0
- package/dist/page-markdown.js +4 -4
- package/dist/page-markdown.js.map +1 -1
- package/dist/prompt.md +450 -377
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/readability.js +16 -2
- package/dist/recording-ghost-cursor.d.ts +41 -0
- package/dist/recording-ghost-cursor.d.ts.map +1 -0
- package/dist/recording-ghost-cursor.js +79 -0
- package/dist/recording-ghost-cursor.js.map +1 -0
- package/dist/recording-relay.d.ts.map +1 -1
- package/dist/recording-relay.js +8 -8
- package/dist/recording-relay.js.map +1 -1
- package/dist/relay-client.d.ts +17 -4
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +45 -11
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +515 -26
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-navigation.test.d.ts.map +1 -1
- package/dist/relay-navigation.test.js +169 -31
- package/dist/relay-navigation.test.js.map +1 -1
- package/dist/relay-session.test.d.ts.map +1 -1
- package/dist/relay-session.test.js +113 -65
- package/dist/relay-session.test.js.map +1 -1
- package/dist/relay-state.d.ts +158 -0
- package/dist/relay-state.d.ts.map +1 -0
- package/dist/relay-state.js +306 -0
- package/dist/relay-state.js.map +1 -0
- package/dist/relay-state.test.d.ts +2 -0
- package/dist/relay-state.test.d.ts.map +1 -0
- package/dist/relay-state.test.js +472 -0
- package/dist/relay-state.test.js.map +1 -0
- package/dist/scoped-fs.d.ts.map +1 -1
- package/dist/scoped-fs.js.map +1 -1
- package/dist/screen-recording.d.ts +66 -4
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +150 -13
- package/dist/screen-recording.js.map +1 -1
- package/dist/screen-recording.test.d.ts +2 -0
- package/dist/screen-recording.test.d.ts.map +1 -0
- package/dist/screen-recording.test.js +102 -0
- package/dist/screen-recording.test.js.map +1 -0
- package/dist/selector-generator.js +1 -1
- package/dist/snapshot-tools.test.js +71 -28
- package/dist/snapshot-tools.test.js.map +1 -1
- package/dist/start-relay-server.d.ts +1 -1
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +1 -1
- package/dist/start-relay-server.js.map +1 -1
- package/dist/styles-api.md +8 -1
- package/dist/styles-examples.d.ts +1 -1
- package/dist/styles-examples.d.ts.map +1 -1
- package/dist/styles-examples.js +1 -1
- package/dist/styles-examples.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +1 -3
- package/dist/styles.js.map +1 -1
- package/dist/test-declarations.d.ts.map +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +7 -5
- package/dist/test-utils.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/wait-for-page-load.d.ts.map +1 -1
- package/dist/wait-for-page-load.js +1 -1
- package/dist/wait-for-page-load.js.map +1 -1
- package/package.json +4 -3
- package/src/a11y-client.ts +5 -4
- package/src/aria-snapshot.test.ts +5 -2
- package/src/aria-snapshot.ts +306 -117
- package/src/aria-snapshot.unit.test.ts +199 -141
- package/src/aria-snapshots/github-interactive.txt +2 -0
- package/src/aria-snapshots/github-raw.txt +5 -1
- package/src/aria-snapshots/hackernews-interactive.txt +238 -241
- package/src/aria-snapshots/hackernews-raw.txt +265 -269
- package/src/assets/aria-labels-example.png +0 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
- package/src/cdp-log.ts +4 -1
- package/src/cdp-relay.ts +1059 -737
- package/src/cdp-session.ts +12 -3
- package/src/cdp-types.ts +51 -51
- package/src/clean-html.ts +4 -5
- package/src/cli.ts +82 -55
- package/src/create-logger.ts +5 -3
- package/src/debugger-examples-types.ts +4 -1
- package/src/debugger.ts +1 -5
- package/src/diff-utils.ts +2 -5
- package/src/editor-examples.ts +11 -1
- package/src/editor.ts +10 -2
- package/src/executor.ts +374 -73
- package/src/executor.unit.test.ts +48 -1
- package/src/extension-connection.test.ts +612 -488
- package/src/ffmpeg.ts +769 -0
- package/src/ghost-browser.ts +4 -6
- package/src/ghost-cursor-client.ts +369 -0
- package/src/ghost-cursor.ts +110 -0
- package/src/htmlrewrite.test.ts +6 -2
- package/src/htmlrewrite.ts +348 -386
- package/src/kill-port.ts +1 -3
- package/src/locator-selector.test.ts +115 -0
- package/src/mcp-client.ts +1 -1
- package/src/mcp.ts +21 -15
- package/src/on-mouse-action.test.ts +196 -0
- package/src/page-markdown.ts +7 -7
- package/src/protocol.ts +73 -57
- package/src/recording-ghost-cursor.ts +113 -0
- package/src/recording-relay.ts +20 -12
- package/src/relay-client.ts +85 -18
- package/src/relay-core.test.ts +1117 -578
- package/src/relay-navigation.test.ts +648 -483
- package/src/relay-session.test.ts +984 -929
- package/src/relay-state.test.ts +570 -0
- package/src/relay-state.ts +497 -0
- package/src/resource.md +21 -49
- package/src/scoped-fs.ts +9 -3
- package/src/screen-recording.test.ts +111 -0
- package/src/screen-recording.ts +256 -31
- package/src/skill.md +476 -396
- package/src/snapshot-tools.test.ts +580 -528
- package/src/snapshots/shadcn-ui-accessibility-full.md +8 -8
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +8 -8
- package/src/start-relay-server.ts +14 -11
- package/src/styles-examples.ts +8 -1
- package/src/styles.ts +20 -21
- package/src/test-declarations.ts +6 -6
- package/src/test-utils.ts +104 -91
- package/src/utils.ts +2 -1
- package/src/wait-for-page-load.ts +6 -1
|
@@ -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="
|
|
11
|
+
- role=link[name="109k"]
|
|
12
12
|
- role=button[name="Toggle theme"]
|
|
13
|
-
|
|
13
|
+
- role=link[name="New"]
|
|
14
14
|
- main:
|
|
15
|
-
- role=link[name="
|
|
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="
|
|
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-
|
|
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-
|
|
145
|
-
- button "All Sources" [id="radix-
|
|
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-
|
|
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="
|
|
11
|
+
- role=link[name="109k"]
|
|
12
12
|
- role=button[name="Toggle theme"]
|
|
13
|
-
|
|
13
|
+
- role=link[name="New"]
|
|
14
14
|
- main:
|
|
15
|
-
- role=link[name="
|
|
16
|
-
- role=link[name="
|
|
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-
|
|
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-
|
|
94
|
-
- button "All Sources" [id="radix-
|
|
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-
|
|
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
|
-
|
|
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.')
|
package/src/styles-examples.ts
CHANGED
|
@@ -74,4 +74,11 @@ async function compareStyles() {
|
|
|
74
74
|
console.log(formatStylesAsText(secondary))
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export {
|
|
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 =
|
|
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
|
}
|
package/src/test-declarations.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { ExtensionState } from 'mcp-extension/src/types.js'
|
|
2
2
|
|
|
3
3
|
declare global {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
var toggleExtensionForActiveTab: () => Promise<{ isConnected: boolean; state: ExtensionState }>
|
|
5
|
+
var getExtensionState: () => ExtensionState
|
|
6
|
+
var disconnectEverything: () => Promise<void>
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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`, {
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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>({
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
321
|
-
|
|
333
|
+
browser: Awaited<ReturnType<typeof import('@xmorse/playwright-core').chromium.connectOverCDP>>,
|
|
334
|
+
drainDelayMs = 50,
|
|
322
335
|
): Promise<void> {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
331
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
({
|
|
69
|
+
({
|
|
70
|
+
filteredDomains,
|
|
71
|
+
filteredExtensions,
|
|
72
|
+
stuckThreshold,
|
|
73
|
+
slowResourceThreshold,
|
|
74
|
+
}): {
|
|
70
75
|
ready: boolean
|
|
71
76
|
readyState: string
|
|
72
77
|
pendingRequests: string[]
|