playwriter 0.0.89 → 0.0.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aria-snapshot.d.ts +7 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +76 -18
- package/dist/aria-snapshot.js.map +1 -1
- package/dist/aria-snapshot.test.js +94 -0
- package/dist/aria-snapshot.test.js.map +1 -1
- package/dist/bippy.js +1 -1
- package/dist/browser-config.d.ts +12 -1
- package/dist/browser-config.d.ts.map +1 -1
- package/dist/browser-config.js +101 -38
- package/dist/browser-config.js.map +1 -1
- package/dist/browser-launch.d.ts +17 -0
- package/dist/browser-launch.d.ts.map +1 -0
- package/dist/browser-launch.js +44 -0
- package/dist/browser-launch.js.map +1 -0
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +34 -10
- package/dist/cdp-relay.js.map +1 -1
- package/dist/channel-owner-inspect.test.d.ts +2 -0
- package/dist/channel-owner-inspect.test.d.ts.map +1 -0
- package/dist/channel-owner-inspect.test.js +75 -0
- package/dist/channel-owner-inspect.test.js.map +1 -0
- package/dist/chrome-discovery.d.ts +65 -0
- package/dist/chrome-discovery.d.ts.map +1 -0
- package/dist/chrome-discovery.js +165 -0
- package/dist/chrome-discovery.js.map +1 -0
- package/dist/chrome-discovery.test.d.ts +2 -0
- package/dist/chrome-discovery.test.d.ts.map +1 -0
- package/dist/chrome-discovery.test.js +41 -0
- package/dist/chrome-discovery.test.js.map +1 -0
- package/dist/cli.js +357 -53
- package/dist/cli.js.map +1 -1
- package/dist/executor.d.ts +44 -11
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +121 -55
- package/dist/executor.js.map +1 -1
- package/dist/executor.unit.test.js +29 -1
- package/dist/executor.unit.test.js.map +1 -1
- package/dist/extension/background.js +1704 -0
- package/dist/extension/icons/SolarCursorSquareBold.png +0 -0
- package/dist/extension/icons/icon-black-128.png +0 -0
- package/dist/extension/icons/icon-black-16.png +0 -0
- package/dist/extension/icons/icon-black-32.png +0 -0
- package/dist/extension/icons/icon-black-48.png +0 -0
- package/dist/extension/icons/icon-gray-128.png +0 -0
- package/dist/extension/icons/icon-gray-16.png +0 -0
- package/dist/extension/icons/icon-gray-32.png +0 -0
- package/dist/extension/icons/icon-gray-48.png +0 -0
- package/dist/extension/icons/icon-green-128.png +0 -0
- package/dist/extension/icons/icon-green-16.png +0 -0
- package/dist/extension/icons/icon-green-32.png +0 -0
- package/dist/extension/icons/icon-green-48.png +0 -0
- package/dist/extension/icons/playwriter-icon-black.png +0 -0
- package/dist/extension/icons/playwriter-icon-gray-disabled.png +0 -0
- package/dist/extension/icons/playwriter-icon-gray-disabled.svg +1 -0
- package/dist/extension/manifest.json +40 -0
- package/dist/extension/offscreen.js +214 -0
- package/dist/extension/src/offscreen.html +9 -0
- package/dist/extension/src/prism-bash.min.js +1 -0
- package/dist/extension/src/prism.min.js +1 -0
- package/dist/extension/src/welcome.html +399 -0
- package/dist/kitty-graphics.d.ts +22 -0
- package/dist/kitty-graphics.d.ts.map +1 -0
- package/dist/kitty-graphics.js +71 -0
- package/dist/kitty-graphics.js.map +1 -0
- package/dist/kitty-graphics.test.d.ts +2 -0
- package/dist/kitty-graphics.test.d.ts.map +1 -0
- package/dist/kitty-graphics.test.js +125 -0
- package/dist/kitty-graphics.test.js.map +1 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +76 -12
- package/dist/mcp.js.map +1 -1
- package/dist/package-paths.d.ts +3 -0
- package/dist/package-paths.d.ts.map +1 -0
- package/dist/package-paths.js +29 -0
- package/dist/package-paths.js.map +1 -0
- package/dist/prompt.md +4 -2
- package/dist/readability.js +1 -1
- package/dist/relay-core.test.js +14 -4
- 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 +95 -1
- package/dist/relay-navigation.test.js.map +1 -1
- package/dist/scoped-fs.d.ts +5 -4
- package/dist/scoped-fs.d.ts.map +1 -1
- package/dist/scoped-fs.js +11 -9
- package/dist/scoped-fs.js.map +1 -1
- package/dist/scoped-fs.test.d.ts +5 -0
- package/dist/scoped-fs.test.d.ts.map +1 -0
- package/dist/scoped-fs.test.js +70 -0
- package/dist/scoped-fs.test.js.map +1 -0
- package/dist/screen-recording.d.ts +3 -6
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +7 -21
- package/dist/screen-recording.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/dist/snapshot-tools.test.js +3 -4
- package/dist/snapshot-tools.test.js.map +1 -1
- package/dist/test-utils.d.ts +3 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +24 -12
- package/dist/test-utils.js.map +1 -1
- package/package.json +6 -5
- package/src/aria-snapshot.test.ts +110 -0
- package/src/aria-snapshot.ts +86 -19
- package/src/aria-snapshots/contenteditable-interactive.txt +7 -0
- package/src/aria-snapshots/contenteditable-raw.txt +14 -0
- package/src/aria-snapshots/github-interactive.txt +0 -2
- package/src/aria-snapshots/github-raw.txt +0 -4
- package/src/aria-snapshots/hackernews-interactive.txt +240 -237
- package/src/aria-snapshots/hackernews-raw.txt +262 -258
- package/src/aria-snapshots/prosemirror-interactive.txt +63 -0
- package/src/aria-snapshots/prosemirror-raw.txt +141 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/browser-config.ts +143 -37
- package/src/browser-launch.ts +75 -0
- package/src/cdp-relay.ts +46 -11
- package/src/channel-owner-inspect.test.ts +94 -0
- package/src/chrome-discovery.test.ts +50 -0
- package/src/chrome-discovery.ts +211 -0
- package/src/cli.ts +432 -59
- package/src/executor.ts +171 -72
- package/src/executor.unit.test.ts +32 -1
- package/src/kitty-graphics.test.ts +139 -0
- package/src/kitty-graphics.ts +79 -0
- package/src/mcp.ts +82 -12
- package/src/package-paths.ts +36 -0
- package/src/relay-core.test.ts +14 -4
- package/src/relay-navigation.test.ts +106 -1
- package/src/scoped-fs.test.ts +77 -0
- package/src/scoped-fs.ts +12 -9
- package/src/screen-recording.ts +7 -22
- package/src/skill.md +32 -2
- package/src/snapshot-tools.test.ts +3 -4
- package/src/snapshots/shadcn-ui-accessibility-full.md +181 -183
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +119 -121
- package/src/test-utils.ts +25 -11
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for https://github.com/remorses/playwriter/issues/82
|
|
3
|
+
*
|
|
4
|
+
* User reported that running:
|
|
5
|
+
* playwriter -s 1 -e 'await page.goto("https://example.com")'
|
|
6
|
+
* printed "all env vars" to the terminal.
|
|
7
|
+
*
|
|
8
|
+
* Root cause: `await page.goto(...)` auto-returns the Playwright Response.
|
|
9
|
+
* util.inspect with depth:4 traversed Response → _connection → _platform →
|
|
10
|
+
* env = process.env and dumped every environment variable.
|
|
11
|
+
*
|
|
12
|
+
* Fix: custom util.inspect handler on ChannelOwner and on the channel proxy
|
|
13
|
+
* target. See playwright/packages/playwright-core/src/client/channelOwner.ts
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect } from 'vitest'
|
|
16
|
+
import util from 'node:util'
|
|
17
|
+
|
|
18
|
+
describe('issue #82 - env var leak via util.inspect on Playwright objects', () => {
|
|
19
|
+
it('channel proxy target must NOT leak _platform.env via util.inspect', () => {
|
|
20
|
+
// Simulate the channel proxy structure from ChannelOwner._createChannel:
|
|
21
|
+
// a bare EventEmitter target wrapped in a Proxy, with _platform.env set
|
|
22
|
+
// to process.env. Before the fix, util.inspect traversed this.
|
|
23
|
+
const SECRET_KEY = 'ISSUE_82_TEST_SECRET_KEY'
|
|
24
|
+
const SECRET_VALUE = 'this-should-never-appear-in-inspect-output'
|
|
25
|
+
|
|
26
|
+
const base: any = {
|
|
27
|
+
_events: {},
|
|
28
|
+
_eventsCount: 0,
|
|
29
|
+
_maxListeners: undefined,
|
|
30
|
+
_platform: {
|
|
31
|
+
env: { [SECRET_KEY]: SECRET_VALUE, HOME: '/home/user' },
|
|
32
|
+
colors: {},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Simulate the fix: custom inspect set directly on the base target.
|
|
37
|
+
base[Symbol.for('nodejs.util.inspect.custom')] = function () {
|
|
38
|
+
return 'Channel<Response@test-guid>'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const proxy = new Proxy(base, { get: (obj, prop) => obj[prop] })
|
|
42
|
+
|
|
43
|
+
// Inspect both directly and nested inside another object (as happens
|
|
44
|
+
// when auto-returning a Response whose _initializer.request is a channel)
|
|
45
|
+
const directInspect = util.inspect(proxy, { depth: 4 })
|
|
46
|
+
const nestedInspect = util.inspect(
|
|
47
|
+
{ request: proxy, url: 'https://example.com' },
|
|
48
|
+
{ depth: 4 },
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
expect(directInspect).not.toContain(SECRET_VALUE)
|
|
52
|
+
expect(directInspect).not.toContain(SECRET_KEY)
|
|
53
|
+
expect(nestedInspect).not.toContain(SECRET_VALUE)
|
|
54
|
+
expect(nestedInspect).not.toContain(SECRET_KEY)
|
|
55
|
+
|
|
56
|
+
expect(directInspect).toMatchInlineSnapshot(`"Channel<Response@test-guid>"`)
|
|
57
|
+
expect(nestedInspect).toMatchInlineSnapshot(`"{ request: Channel<Response@test-guid>, url: 'https://example.com' }"`)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('ChannelOwner instance must NOT leak _platform.env via util.inspect', () => {
|
|
61
|
+
// Simulate a ChannelOwner instance — this is what the user actually
|
|
62
|
+
// auto-returns from `await page.goto(url)`.
|
|
63
|
+
const SECRET_VALUE = 'issue-82-channelowner-secret'
|
|
64
|
+
|
|
65
|
+
// Simulate a ChannelOwner instance via a plain object. The real fix in
|
|
66
|
+
// playwright-core adds a computed Symbol method to the ChannelOwner class.
|
|
67
|
+
const instance: any = {
|
|
68
|
+
_type: 'Response',
|
|
69
|
+
_guid: 'response@abc123',
|
|
70
|
+
_connection: { _platform: { env: { LEAKED: SECRET_VALUE } } },
|
|
71
|
+
_initializer: {
|
|
72
|
+
url: 'https://example.com/',
|
|
73
|
+
status: 200,
|
|
74
|
+
statusText: 'OK',
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
instance[Symbol.for('nodejs.util.inspect.custom')] = function (
|
|
78
|
+
_depth: number,
|
|
79
|
+
options: any,
|
|
80
|
+
inspect: (value: any, opts: any) => string,
|
|
81
|
+
) {
|
|
82
|
+
const header = `${this._type}@${this._guid}`
|
|
83
|
+
const initializerDepth =
|
|
84
|
+
typeof options?.depth === 'number' ? Math.max(options.depth - 1, 0) : 2
|
|
85
|
+
return `${header} ${inspect(this._initializer, { ...options, depth: initializerDepth })}`
|
|
86
|
+
}
|
|
87
|
+
const output = util.inspect(instance, { depth: 4 })
|
|
88
|
+
|
|
89
|
+
expect(output).not.toContain(SECRET_VALUE)
|
|
90
|
+
expect(output).toMatchInlineSnapshot(
|
|
91
|
+
`"Response@response@abc123 { url: 'https://example.com/', status: 200, statusText: 'OK' }"`,
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest'
|
|
2
|
+
import { parseDevToolsActivePort } from './chrome-discovery.js'
|
|
3
|
+
|
|
4
|
+
describe('parseDevToolsActivePort', () => {
|
|
5
|
+
test('parses valid contents', () => {
|
|
6
|
+
const result = parseDevToolsActivePort('9222\n/devtools/browser/abc-123-def\n')
|
|
7
|
+
expect(result).toMatchInlineSnapshot(`
|
|
8
|
+
{
|
|
9
|
+
"port": 9222,
|
|
10
|
+
"wsPath": "/devtools/browser/abc-123-def",
|
|
11
|
+
}
|
|
12
|
+
`)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('parses with extra whitespace', () => {
|
|
16
|
+
const result = parseDevToolsActivePort(' 9222 \n /devtools/browser/abc \n')
|
|
17
|
+
expect(result).toMatchInlineSnapshot(`
|
|
18
|
+
{
|
|
19
|
+
"port": 9222,
|
|
20
|
+
"wsPath": "/devtools/browser/abc",
|
|
21
|
+
}
|
|
22
|
+
`)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('returns null for single line', () => {
|
|
26
|
+
expect(parseDevToolsActivePort('9222')).toBeNull()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('returns null for empty string', () => {
|
|
30
|
+
expect(parseDevToolsActivePort('')).toBeNull()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('returns null for invalid port', () => {
|
|
34
|
+
expect(parseDevToolsActivePort('abc\n/devtools/browser/123')).toBeNull()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('returns null for port 0', () => {
|
|
38
|
+
expect(parseDevToolsActivePort('0\n/devtools/browser/123')).toBeNull()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('returns null for port > 65535', () => {
|
|
42
|
+
expect(parseDevToolsActivePort('99999\n/devtools/browser/123')).toBeNull()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('returns null for invalid ws path', () => {
|
|
46
|
+
expect(parseDevToolsActivePort('9222\n/some/other/path')).toBeNull()
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome instance discovery for direct CDP connections.
|
|
3
|
+
*
|
|
4
|
+
* Probes the default CDP port (9222) via GET /json/version.
|
|
5
|
+
* If Chrome responds with a valid webSocketDebuggerUrl, the instance is usable.
|
|
6
|
+
*
|
|
7
|
+
* Chrome 136+ with chrome://inspect debugging returns 404 on all HTTP endpoints
|
|
8
|
+
* but still accepts WebSocket connections on /devtools/browser/*. In that case
|
|
9
|
+
* (status 'blocked'), we return a synthetic wsUrl — the actual WS connection
|
|
10
|
+
* (which may trigger Chrome's approval dialog) only happens when the user
|
|
11
|
+
* explicitly runs a command, not during discovery.
|
|
12
|
+
*
|
|
13
|
+
* For non-default ports or remote hosts, pass an explicit endpoint with
|
|
14
|
+
* --direct host:port or --direct ws://....
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface DiscoveredInstance {
|
|
18
|
+
browser: string
|
|
19
|
+
port: number
|
|
20
|
+
wsUrl: string
|
|
21
|
+
profiles: Array<{ name: string; email: string }>
|
|
22
|
+
dataDir: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ParsedActivePort {
|
|
26
|
+
port: number
|
|
27
|
+
wsPath: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse the contents of a DevToolsActivePort file.
|
|
32
|
+
* Format: line 1 = port number, line 2 = /devtools/browser/{guid}
|
|
33
|
+
*/
|
|
34
|
+
export function parseDevToolsActivePort(contents: string): ParsedActivePort | null {
|
|
35
|
+
const lines = contents.trim().split('\n')
|
|
36
|
+
if (lines.length < 2) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
const port = parseInt(lines[0].trim(), 10)
|
|
40
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
const wsPath = lines[1].trim()
|
|
44
|
+
if (!wsPath.startsWith('/devtools/browser/')) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
return { port, wsPath }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface JsonVersionResponse {
|
|
51
|
+
Browser?: string
|
|
52
|
+
webSocketDebuggerUrl?: string
|
|
53
|
+
'Protocol-Version'?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type PortProbeStatus =
|
|
57
|
+
| { type: 'live'; wsUrl: string; browser: string }
|
|
58
|
+
/** HTTP response received but no CDP data (Chrome 136+ returns 404 on all HTTP
|
|
59
|
+
* endpoints but still accepts WS on /devtools/browser/*). We can't get browser
|
|
60
|
+
* info without a WS connection which would trigger Chrome's approval dialog. */
|
|
61
|
+
| { type: 'blocked'; port: number; hostname: string }
|
|
62
|
+
/** ECONNREFUSED or timeout — nothing is listening */
|
|
63
|
+
| { type: 'dead' }
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Probe a port via GET /json/version.
|
|
67
|
+
*
|
|
68
|
+
* Returns:
|
|
69
|
+
* - 'live' — Chrome responded with valid CDP JSON containing webSocketDebuggerUrl
|
|
70
|
+
* - 'blocked' — HTTP response received but no CDP info (Chrome 136+ default profile)
|
|
71
|
+
* - 'dead' — connection refused or timeout (nothing listening)
|
|
72
|
+
*/
|
|
73
|
+
async function probePortStatus(port: number, hostname = '127.0.0.1'): Promise<PortProbeStatus> {
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(`http://${hostname}:${port}/json/version`, {
|
|
76
|
+
signal: AbortSignal.timeout(1000),
|
|
77
|
+
})
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
await response.text() // consume body
|
|
80
|
+
return { type: 'blocked', port, hostname }
|
|
81
|
+
}
|
|
82
|
+
const data = (await response.json()) as JsonVersionResponse
|
|
83
|
+
if (data.webSocketDebuggerUrl) {
|
|
84
|
+
return { type: 'live', wsUrl: data.webSocketDebuggerUrl, browser: data.Browser || 'Unknown' }
|
|
85
|
+
}
|
|
86
|
+
return { type: 'blocked', port, hostname }
|
|
87
|
+
} catch {
|
|
88
|
+
return { type: 'dead' }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Probe a port via GET /json/version.
|
|
94
|
+
* Returns the webSocketDebuggerUrl and browser name, or null.
|
|
95
|
+
*/
|
|
96
|
+
export async function probePort(port: number, hostname = '127.0.0.1'): Promise<{ wsUrl: string; browser: string } | null> {
|
|
97
|
+
const status = await probePortStatus(port, hostname)
|
|
98
|
+
if (status.type === 'live') {
|
|
99
|
+
return { wsUrl: status.wsUrl, browser: status.browser }
|
|
100
|
+
}
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Discover Chrome on the default CDP port (9222).
|
|
106
|
+
*
|
|
107
|
+
* Probes /json/version. If Chrome responds with valid CDP JSON, returns the
|
|
108
|
+
* instance with full browser info. If Chrome returns 404 (Chrome 136+ with
|
|
109
|
+
* chrome://inspect debugging), returns a synthetic instance — the browser
|
|
110
|
+
* version is unknown until an actual WS connection is made.
|
|
111
|
+
*/
|
|
112
|
+
export async function discoverChromeInstances(): Promise<DiscoveredInstance[]> {
|
|
113
|
+
const status = await probePortStatus(9222)
|
|
114
|
+
|
|
115
|
+
if (status.type === 'live') {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
browser: parseBrowserVersion(status.browser),
|
|
119
|
+
port: 9222,
|
|
120
|
+
wsUrl: status.wsUrl,
|
|
121
|
+
profiles: [],
|
|
122
|
+
dataDir: null,
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (status.type === 'blocked') {
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
browser: 'Chrome',
|
|
131
|
+
port: status.port,
|
|
132
|
+
wsUrl: makeDirectWsUrl(status.hostname, status.port),
|
|
133
|
+
profiles: [],
|
|
134
|
+
dataDir: null,
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return []
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Build a ws:// URL for direct CDP. The path is left bare — Chrome 136+
|
|
143
|
+
* accepts any value under /devtools/browser/. Call appendSessionToWsUrl()
|
|
144
|
+
* before connecting to make the path unique per session. */
|
|
145
|
+
function makeDirectWsUrl(hostname: string, port: number): string {
|
|
146
|
+
return `ws://${hostname}:${port}/devtools/browser/`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Append a session ID to a /devtools/browser/ ws URL to make it unique.
|
|
150
|
+
* If the URL already has a full GUID path (e.g. from /json/version), return as-is. */
|
|
151
|
+
export function appendSessionToWsUrl(wsUrl: string, sessionId: string): string {
|
|
152
|
+
// Only append if the path ends with / (our synthetic URLs from blocked probes)
|
|
153
|
+
if (wsUrl.endsWith('/')) {
|
|
154
|
+
return `${wsUrl}${sessionId}`
|
|
155
|
+
}
|
|
156
|
+
return wsUrl
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resolve a --direct input value to a WebSocket URL.
|
|
161
|
+
*
|
|
162
|
+
* Accepts:
|
|
163
|
+
* - ws:// or wss:// URL — returned as-is
|
|
164
|
+
* - host:port — probes /json/version to get the webSocketDebuggerUrl
|
|
165
|
+
*
|
|
166
|
+
* Throws with a clear message if the endpoint can't be resolved.
|
|
167
|
+
*/
|
|
168
|
+
export async function resolveDirectInput(input: string): Promise<string> {
|
|
169
|
+
if (input.startsWith('ws://') || input.startsWith('wss://')) {
|
|
170
|
+
return input
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const match = input.match(/^([^:]+):(\d+)$/)
|
|
174
|
+
if (!match) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Invalid --direct value: expected a ws:// URL or host:port (e.g. localhost:9222), got: ${input}`,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const [, hostname, portStr] = match
|
|
181
|
+
const port = parseInt(portStr, 10)
|
|
182
|
+
const status = await probePortStatus(port, hostname)
|
|
183
|
+
|
|
184
|
+
if (status.type === 'live') {
|
|
185
|
+
return status.wsUrl
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (status.type === 'blocked') {
|
|
189
|
+
// Chrome 136+ returns 404 on HTTP but accepts WS on /devtools/browser/*
|
|
190
|
+
return makeDirectWsUrl(hostname, port)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Nothing found on ${hostname}:${port}. Is Chrome running with remote debugging enabled? ` +
|
|
195
|
+
`Try: chrome://inspect/#remote-debugging`,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseBrowserVersion(browserString: string): string {
|
|
200
|
+
if (browserString.startsWith('HeadlessChrome/')) {
|
|
201
|
+
return 'Chrome (Headless)'
|
|
202
|
+
}
|
|
203
|
+
if (browserString.startsWith('Chrome/')) {
|
|
204
|
+
return 'Chrome'
|
|
205
|
+
}
|
|
206
|
+
const slashIndex = browserString.indexOf('/')
|
|
207
|
+
if (slashIndex > 0) {
|
|
208
|
+
return browserString.slice(0, slashIndex)
|
|
209
|
+
}
|
|
210
|
+
return browserString
|
|
211
|
+
}
|