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.
- package/bin.js +3 -0
- package/dist/aria-snapshot.d.ts +95 -0
- package/dist/aria-snapshot.d.ts.map +1 -0
- package/dist/aria-snapshot.js +490 -0
- package/dist/aria-snapshot.js.map +1 -0
- package/dist/bippy.js +971 -0
- package/dist/cdp-relay.d.ts +16 -0
- package/dist/cdp-relay.d.ts.map +1 -0
- package/dist/cdp-relay.js +715 -0
- package/dist/cdp-relay.js.map +1 -0
- package/dist/cdp-session.d.ts +42 -0
- package/dist/cdp-session.d.ts.map +1 -0
- package/dist/cdp-session.js +154 -0
- package/dist/cdp-session.js.map +1 -0
- package/dist/cdp-types.d.ts +63 -0
- package/dist/cdp-types.d.ts.map +1 -0
- package/dist/cdp-types.js +91 -0
- package/dist/cdp-types.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +213 -0
- package/dist/cli.js.map +1 -0
- package/dist/create-logger.d.ts +9 -0
- package/dist/create-logger.d.ts.map +1 -0
- package/dist/create-logger.js +25 -0
- package/dist/create-logger.js.map +1 -0
- package/dist/debugger-api.md +458 -0
- package/dist/debugger-examples-types.d.ts +24 -0
- package/dist/debugger-examples-types.d.ts.map +1 -0
- package/dist/debugger-examples-types.js +2 -0
- package/dist/debugger-examples-types.js.map +1 -0
- package/dist/debugger-examples.d.ts +6 -0
- package/dist/debugger-examples.d.ts.map +1 -0
- package/dist/debugger-examples.js +53 -0
- package/dist/debugger-examples.js.map +1 -0
- package/dist/debugger.d.ts +381 -0
- package/dist/debugger.d.ts.map +1 -0
- package/dist/debugger.js +633 -0
- package/dist/debugger.js.map +1 -0
- package/dist/editor-api.md +364 -0
- package/dist/editor-examples.d.ts +11 -0
- package/dist/editor-examples.d.ts.map +1 -0
- package/dist/editor-examples.js +124 -0
- package/dist/editor-examples.js.map +1 -0
- package/dist/editor.d.ts +203 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +336 -0
- package/dist/editor.js.map +1 -0
- package/dist/execute.d.ts +50 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +576 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client.d.ts +20 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +56 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp.d.ts +5 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +720 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mcp.test.d.ts +10 -0
- package/dist/mcp.test.d.ts.map +1 -0
- package/dist/mcp.test.js +2999 -0
- package/dist/mcp.test.js.map +1 -0
- package/dist/network-capture.d.ts +23 -0
- package/dist/network-capture.d.ts.map +1 -0
- package/dist/network-capture.js +98 -0
- package/dist/network-capture.js.map +1 -0
- package/dist/protocol.d.ts +54 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -0
- package/dist/react-source.d.ts +13 -0
- package/dist/react-source.d.ts.map +1 -0
- package/dist/react-source.js +68 -0
- package/dist/react-source.js.map +1 -0
- package/dist/scoped-fs.d.ts +94 -0
- package/dist/scoped-fs.d.ts.map +1 -0
- package/dist/scoped-fs.js +356 -0
- package/dist/scoped-fs.js.map +1 -0
- package/dist/selector-generator.js +8126 -0
- package/dist/start-relay-server.d.ts +6 -0
- package/dist/start-relay-server.d.ts.map +1 -0
- package/dist/start-relay-server.js +33 -0
- package/dist/start-relay-server.js.map +1 -0
- package/dist/styles-api.md +117 -0
- package/dist/styles-examples.d.ts +8 -0
- package/dist/styles-examples.d.ts.map +1 -0
- package/dist/styles-examples.js +64 -0
- package/dist/styles-examples.js.map +1 -0
- package/dist/styles.d.ts +27 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +234 -0
- package/dist/styles.js.map +1 -0
- package/dist/trace-utils.d.ts +14 -0
- package/dist/trace-utils.d.ts.map +1 -0
- package/dist/trace-utils.js +21 -0
- package/dist/trace-utils.js.map +1 -0
- package/dist/utils.d.ts +20 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +75 -0
- package/dist/utils.js.map +1 -0
- package/dist/wait-for-page-load.d.ts +16 -0
- package/dist/wait-for-page-load.d.ts.map +1 -0
- package/dist/wait-for-page-load.js +127 -0
- package/dist/wait-for-page-load.js.map +1 -0
- package/package.json +67 -0
- package/src/aria-snapshot.ts +610 -0
- package/src/assets/aria-labels-github-snapshot.txt +605 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-google-snapshot.txt +49 -0
- package/src/assets/aria-labels-google.png +0 -0
- package/src/assets/aria-labels-hacker-news-snapshot.txt +1023 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/cdp-relay.ts +925 -0
- package/src/cdp-session.ts +203 -0
- package/src/cdp-timing.md +128 -0
- package/src/cdp-types.ts +155 -0
- package/src/cli.ts +250 -0
- package/src/create-logger.ts +36 -0
- package/src/debugger-examples-types.ts +13 -0
- package/src/debugger-examples.ts +66 -0
- package/src/debugger.md +453 -0
- package/src/debugger.ts +713 -0
- package/src/editor-examples.ts +148 -0
- package/src/editor.ts +390 -0
- package/src/execute.ts +763 -0
- package/src/index.ts +10 -0
- package/src/mcp-client.ts +78 -0
- package/src/mcp.test.ts +3596 -0
- package/src/mcp.ts +876 -0
- package/src/network-capture.ts +140 -0
- package/src/prompt.bak.md +323 -0
- package/src/prompt.md +7 -0
- package/src/protocol.ts +63 -0
- package/src/react-source.ts +94 -0
- package/src/resource.md +436 -0
- package/src/scoped-fs.ts +411 -0
- package/src/snapshots/hacker-news-focused-accessibility.md +202 -0
- package/src/snapshots/hacker-news-initial-accessibility.md +11 -0
- package/src/snapshots/hacker-news-tabbed-accessibility.md +202 -0
- package/src/snapshots/shadcn-ui-accessibility.md +11 -0
- package/src/start-relay-server.ts +43 -0
- package/src/styles-examples.ts +77 -0
- package/src/styles.ts +345 -0
- package/src/trace-utils.ts +43 -0
- package/src/utils.ts +91 -0
- package/src/wait-for-page-load.ts +174 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import WebSocket from 'ws'
|
|
2
|
+
import type { Page } from 'playwright-core'
|
|
3
|
+
import type { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js'
|
|
4
|
+
import type { CDPResponseBase, CDPEventBase } from './cdp-types.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Common interface for CDP sessions that works with both our CDPSession
|
|
8
|
+
* and Playwright's CDPSession. Use this type when you want to accept either.
|
|
9
|
+
*
|
|
10
|
+
* Uses loose types so Playwright's CDPSession (which uses Protocol.Events)
|
|
11
|
+
* is assignable to this interface.
|
|
12
|
+
*/
|
|
13
|
+
export interface ICDPSession {
|
|
14
|
+
send(method: string, params?: object): Promise<unknown>
|
|
15
|
+
on(event: string, callback: (params: any) => void): unknown
|
|
16
|
+
off(event: string, callback: (params: any) => void): unknown
|
|
17
|
+
detach(): Promise<void>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PendingRequest {
|
|
21
|
+
resolve: (result: unknown) => void
|
|
22
|
+
reject: (error: Error) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class CDPSession implements ICDPSession {
|
|
26
|
+
private ws: WebSocket
|
|
27
|
+
private pendingRequests = new Map<number, PendingRequest>()
|
|
28
|
+
private eventListeners = new Map<string, Set<(params: unknown) => void>>()
|
|
29
|
+
private messageId = 0
|
|
30
|
+
private sessionId: string | null = null
|
|
31
|
+
|
|
32
|
+
constructor(ws: WebSocket) {
|
|
33
|
+
this.ws = ws
|
|
34
|
+
this.ws.on('message', (data) => {
|
|
35
|
+
try {
|
|
36
|
+
const message = JSON.parse(data.toString()) as CDPResponseBase | CDPEventBase
|
|
37
|
+
|
|
38
|
+
if ('id' in message) {
|
|
39
|
+
const response = message as CDPResponseBase
|
|
40
|
+
const pending = this.pendingRequests.get(response.id)
|
|
41
|
+
if (pending) {
|
|
42
|
+
this.pendingRequests.delete(response.id)
|
|
43
|
+
if (response.error) {
|
|
44
|
+
pending.reject(new Error(response.error.message))
|
|
45
|
+
} else {
|
|
46
|
+
pending.resolve(response.result)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} else if ('method' in message) {
|
|
50
|
+
const event = message as CDPEventBase
|
|
51
|
+
if (event.sessionId === this.sessionId || !event.sessionId) {
|
|
52
|
+
const listeners = this.eventListeners.get(event.method)
|
|
53
|
+
if (listeners) {
|
|
54
|
+
for (const listener of listeners) {
|
|
55
|
+
listener(event.params)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error('[CDPSession] Message handling error:', e)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setSessionId(sessionId: string) {
|
|
67
|
+
this.sessionId = sessionId
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
send<K extends keyof ProtocolMapping.Commands>(
|
|
71
|
+
method: K,
|
|
72
|
+
params?: ProtocolMapping.Commands[K]['paramsType'][0],
|
|
73
|
+
): Promise<ProtocolMapping.Commands[K]['returnType']> {
|
|
74
|
+
const id = ++this.messageId
|
|
75
|
+
const message: Record<string, unknown> = { id, method, params }
|
|
76
|
+
if (this.sessionId) {
|
|
77
|
+
message.sessionId = this.sessionId
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
this.pendingRequests.delete(id)
|
|
83
|
+
reject(new Error(`CDP command timeout: ${method}`))
|
|
84
|
+
}, 30000)
|
|
85
|
+
|
|
86
|
+
this.pendingRequests.set(id, {
|
|
87
|
+
resolve: (result) => {
|
|
88
|
+
clearTimeout(timeout)
|
|
89
|
+
resolve(result as ProtocolMapping.Commands[K]['returnType'])
|
|
90
|
+
},
|
|
91
|
+
reject: (error) => {
|
|
92
|
+
clearTimeout(timeout)
|
|
93
|
+
reject(error)
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
this.ws.send(JSON.stringify(message))
|
|
99
|
+
} catch (error) {
|
|
100
|
+
clearTimeout(timeout)
|
|
101
|
+
this.pendingRequests.delete(id)
|
|
102
|
+
reject(error instanceof Error ? error : new Error(String(error)))
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
on<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void): this {
|
|
108
|
+
if (!this.eventListeners.has(event)) {
|
|
109
|
+
this.eventListeners.set(event, new Set())
|
|
110
|
+
}
|
|
111
|
+
this.eventListeners.get(event)!.add(callback as (params: unknown) => void)
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Alias for `on` - matches Playwright's CDPSession interface */
|
|
116
|
+
addListener<K extends keyof ProtocolMapping.Events>(
|
|
117
|
+
event: K,
|
|
118
|
+
callback: (params: ProtocolMapping.Events[K][0]) => void,
|
|
119
|
+
): this {
|
|
120
|
+
return this.on(event, callback)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
off<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void): this {
|
|
124
|
+
this.eventListeners.get(event)?.delete(callback as (params: unknown) => void)
|
|
125
|
+
return this
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Alias for `off` - matches Playwright's CDPSession interface */
|
|
129
|
+
removeListener<K extends keyof ProtocolMapping.Events>(
|
|
130
|
+
event: K,
|
|
131
|
+
callback: (params: ProtocolMapping.Events[K][0]) => void,
|
|
132
|
+
): this {
|
|
133
|
+
return this.off(event, callback)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Listen for an event once, then automatically remove the listener */
|
|
137
|
+
once<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void): this {
|
|
138
|
+
const onceCallback = (params: ProtocolMapping.Events[K][0]) => {
|
|
139
|
+
this.off(event, onceCallback)
|
|
140
|
+
callback(params)
|
|
141
|
+
}
|
|
142
|
+
return this.on(event, onceCallback)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Alias for `close` - matches Playwright's CDPSession interface */
|
|
146
|
+
detach(): Promise<void> {
|
|
147
|
+
this.close()
|
|
148
|
+
return Promise.resolve()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
close() {
|
|
152
|
+
try {
|
|
153
|
+
for (const pending of this.pendingRequests.values()) {
|
|
154
|
+
pending.reject(new Error('CDPSession detached'))
|
|
155
|
+
}
|
|
156
|
+
this.pendingRequests.clear()
|
|
157
|
+
this.eventListeners.clear()
|
|
158
|
+
this.ws.close()
|
|
159
|
+
} catch (e) {
|
|
160
|
+
console.error('[CDPSession] WebSocket close error:', e)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function getCDPSessionForPage({ page, wsUrl }: { page: Page; wsUrl: string }): Promise<CDPSession> {
|
|
166
|
+
const ws = new WebSocket(wsUrl)
|
|
167
|
+
|
|
168
|
+
await new Promise<void>((resolve, reject) => {
|
|
169
|
+
ws.on('open', resolve)
|
|
170
|
+
ws.on('error', reject)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const cdp = new CDPSession(ws)
|
|
174
|
+
|
|
175
|
+
const pages = page.context().pages()
|
|
176
|
+
const pageIndex = pages.indexOf(page)
|
|
177
|
+
if (pageIndex === -1) {
|
|
178
|
+
cdp.close()
|
|
179
|
+
throw new Error('Page not found in context')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const { targetInfos } = await cdp.send('Target.getTargets')
|
|
183
|
+
const pageTargets = targetInfos.filter((t) => t.type === 'page')
|
|
184
|
+
|
|
185
|
+
if (pageIndex >= pageTargets.length) {
|
|
186
|
+
cdp.close()
|
|
187
|
+
throw new Error(`Page index ${pageIndex} out of bounds (${pageTargets.length} targets)`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const target = pageTargets[pageIndex]
|
|
191
|
+
if (target.url !== page.url()) {
|
|
192
|
+
cdp.close()
|
|
193
|
+
throw new Error(`URL mismatch: page has "${page.url()}" but target has "${target.url}"`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { sessionId } = await cdp.send('Target.attachToTarget', {
|
|
197
|
+
targetId: target.targetId,
|
|
198
|
+
flatten: true,
|
|
199
|
+
})
|
|
200
|
+
cdp.setSessionId(sessionId)
|
|
201
|
+
|
|
202
|
+
return cdp
|
|
203
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# CDP Event Timing and Synchronization
|
|
2
|
+
|
|
3
|
+
This document describes the timing issues we discovered and fixed in the CDP relay server, and outlines potential future improvements.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
When Playwright connects to Chrome via our CDP relay, there's a race condition between:
|
|
8
|
+
1. **Target attachment** - Extension attaches to a tab and sends `Target.attachedToTarget`
|
|
9
|
+
2. **Runtime initialization** - Playwright calls `Runtime.enable` to set up JavaScript execution contexts
|
|
10
|
+
3. **Page visibility** - `context.pages()` returns pages that are fully ready
|
|
11
|
+
|
|
12
|
+
### Symptom
|
|
13
|
+
Tests or MCP calls that run immediately after toggling the extension would fail with "page not found" because `context.pages()` didn't include the newly attached page yet.
|
|
14
|
+
|
|
15
|
+
### Root Cause
|
|
16
|
+
The `Runtime.enable` CDP command triggers `Runtime.executionContextCreated` events that tell Playwright the page's JavaScript context is ready. Without these events, pages aren't fully visible to `context.pages()`.
|
|
17
|
+
|
|
18
|
+
Previously, the extension had an arbitrary `sleep(200ms)` in the `Runtime.enable` handler to work around this. When this was increased to 400ms, tests started failing due to timing mismatches.
|
|
19
|
+
|
|
20
|
+
## The Fix
|
|
21
|
+
|
|
22
|
+
We replaced the arbitrary sleep with **event-based synchronization** in the relay server:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
case 'Runtime.enable': {
|
|
26
|
+
// Set up listener for executionContextCreated
|
|
27
|
+
const contextCreatedPromise = new Promise<void>((resolve) => {
|
|
28
|
+
const handler = ({ event }) => {
|
|
29
|
+
if (event.method === 'Runtime.executionContextCreated' &&
|
|
30
|
+
event.sessionId === sessionId) {
|
|
31
|
+
clearTimeout(timeout)
|
|
32
|
+
emitter.off('cdp:event', handler)
|
|
33
|
+
resolve()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const timeout = setTimeout(() => {
|
|
37
|
+
emitter.off('cdp:event', handler)
|
|
38
|
+
logger?.log('IMPORTANT: Runtime.enable timed out...')
|
|
39
|
+
resolve()
|
|
40
|
+
}, 3000)
|
|
41
|
+
emitter.on('cdp:event', handler)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Forward command to extension
|
|
45
|
+
const result = await sendToExtension(...)
|
|
46
|
+
|
|
47
|
+
// Wait for the event before returning
|
|
48
|
+
await contextCreatedPromise
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Why This Works
|
|
55
|
+
- When Playwright calls `Runtime.enable`, we forward it to the extension
|
|
56
|
+
- The extension enables Runtime on the Chrome tab
|
|
57
|
+
- Chrome sends `Runtime.executionContextCreated` events
|
|
58
|
+
- We wait for at least one such event before returning
|
|
59
|
+
- By the time `Runtime.enable` returns, the page's context is ready
|
|
60
|
+
|
|
61
|
+
## Event Flow
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Test toggles extension
|
|
65
|
+
↓
|
|
66
|
+
Extension attaches to tab
|
|
67
|
+
↓
|
|
68
|
+
Extension sends Target.attachedToTarget to relay
|
|
69
|
+
↓
|
|
70
|
+
Relay broadcasts to Playwright clients
|
|
71
|
+
↓
|
|
72
|
+
Playwright calls Runtime.enable ──────────────────┐
|
|
73
|
+
↓ │
|
|
74
|
+
Relay forwards to extension │
|
|
75
|
+
↓ │
|
|
76
|
+
Extension enables Runtime on Chrome tab │
|
|
77
|
+
↓ │
|
|
78
|
+
Chrome sends Runtime.executionContextCreated │
|
|
79
|
+
↓ │
|
|
80
|
+
Relay receives event, resolves promise ───────────┘
|
|
81
|
+
↓
|
|
82
|
+
Runtime.enable returns to Playwright
|
|
83
|
+
↓
|
|
84
|
+
Page is now visible in context.pages()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Future Improvements
|
|
88
|
+
|
|
89
|
+
### 1. Wait for Target Attachment
|
|
90
|
+
|
|
91
|
+
Currently, tests still need small waits after `toggleExtensionForActiveTab()` because the MCP's Playwright browser needs time to process `Target.attachedToTarget`.
|
|
92
|
+
|
|
93
|
+
A potential improvement: Track "pending" vs "ready" targets in the relay server:
|
|
94
|
+
- When `Target.attachedToTarget` arrives → mark as pending
|
|
95
|
+
- When `Runtime.executionContextCreated` arrives → mark as ready
|
|
96
|
+
- Expose an endpoint or mechanism for MCP to wait for all targets to be ready
|
|
97
|
+
|
|
98
|
+
### 2. Extension-Level Confirmation
|
|
99
|
+
|
|
100
|
+
The extension could wait for confirmation from the relay server before returning from `attachTab()`:
|
|
101
|
+
- Extension sends `Target.attachedToTarget`
|
|
102
|
+
- Relay waits for `Runtime.executionContextCreated`
|
|
103
|
+
- Relay sends acknowledgment back to extension
|
|
104
|
+
- Extension's `attachTab()` returns
|
|
105
|
+
- `toggleExtensionForActiveTab()` returns with page fully ready
|
|
106
|
+
|
|
107
|
+
This would eliminate the need for any waits in test code.
|
|
108
|
+
|
|
109
|
+
### 3. MCP-Level Page Readiness Check
|
|
110
|
+
|
|
111
|
+
The MCP could check page readiness before executing code:
|
|
112
|
+
- Before running user code, verify each page in `context.pages()` has a ready execution context
|
|
113
|
+
- Use Playwright's `page.evaluate()` with a simple expression to confirm the page is responsive
|
|
114
|
+
|
|
115
|
+
## Test Implications
|
|
116
|
+
|
|
117
|
+
The current test waits (100ms after toggle) exist for multiple reasons:
|
|
118
|
+
1. **Target attachment** - Waiting for `Target.attachedToTarget` to be processed
|
|
119
|
+
2. **Navigation completion** - Waiting for page loads/navigations
|
|
120
|
+
3. **State cleanup** - Ensuring previous test state is cleared
|
|
121
|
+
4. **Debugger synchronization** - Waiting for breakpoints/pause states
|
|
122
|
+
|
|
123
|
+
The `Runtime.enable` fix addresses reason #1 at the CDP level, but test waits are still needed for the other reasons.
|
|
124
|
+
|
|
125
|
+
## Files Changed
|
|
126
|
+
|
|
127
|
+
- `e2e-pilot/src/cdp-relay.ts` - Added event-based wait in `Runtime.enable` handler
|
|
128
|
+
- `extension/src/background.ts` - Removed arbitrary `sleep(200)` from `Runtime.enable` handler
|
package/src/cdp-types.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { Protocol } from 'devtools-protocol';
|
|
2
|
+
import type { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
|
|
3
|
+
|
|
4
|
+
export type CDPCommandFor<T extends keyof ProtocolMapping.Commands> = {
|
|
5
|
+
id: number;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
method: T;
|
|
8
|
+
params?: ProtocolMapping.Commands[T]['paramsType'][0];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type CDPCommand = {
|
|
12
|
+
[K in keyof ProtocolMapping.Commands]: CDPCommandFor<K>;
|
|
13
|
+
}[keyof ProtocolMapping.Commands];
|
|
14
|
+
|
|
15
|
+
export type CDPResponseFor<T extends keyof ProtocolMapping.Commands> = {
|
|
16
|
+
id: number;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
result?: ProtocolMapping.Commands[T]['returnType'];
|
|
19
|
+
error?: { code?: number; message: string };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type CDPResponse = {
|
|
23
|
+
[K in keyof ProtocolMapping.Commands]: CDPResponseFor<K>;
|
|
24
|
+
}[keyof ProtocolMapping.Commands];
|
|
25
|
+
|
|
26
|
+
export type CDPEventFor<T extends keyof ProtocolMapping.Events> = {
|
|
27
|
+
method: T;
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
params?: ProtocolMapping.Events[T][0];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type CDPEvent = {
|
|
33
|
+
[K in keyof ProtocolMapping.Events]: CDPEventFor<K>;
|
|
34
|
+
}[keyof ProtocolMapping.Events];
|
|
35
|
+
|
|
36
|
+
export type CDPResponseBase = {
|
|
37
|
+
id: number;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
result?: unknown;
|
|
40
|
+
error?: { code?: number; message: string };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type CDPEventBase = {
|
|
44
|
+
method: string;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
params?: unknown;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type CDPMessage = CDPCommand | CDPResponse | CDPEvent;
|
|
50
|
+
|
|
51
|
+
export type RelayServerEvents = {
|
|
52
|
+
'cdp:command': (data: { clientId: string; command: CDPCommand }) => void
|
|
53
|
+
'cdp:event': (data: { event: CDPEventBase; sessionId?: string }) => void
|
|
54
|
+
'cdp:response': (data: { clientId: string; response: CDPResponseBase; command: CDPCommand }) => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Protocol, ProtocolMapping };
|
|
58
|
+
|
|
59
|
+
// types tests. to see if types are right with some simple examples
|
|
60
|
+
if (false as any) {
|
|
61
|
+
const browserVersionCommand = {
|
|
62
|
+
id: 1,
|
|
63
|
+
method: 'Browser.getVersion',
|
|
64
|
+
} satisfies CDPCommand;
|
|
65
|
+
|
|
66
|
+
const browserVersionResponse = {
|
|
67
|
+
id: 1,
|
|
68
|
+
result: {
|
|
69
|
+
protocolVersion: '1.3',
|
|
70
|
+
product: 'Chrome',
|
|
71
|
+
revision: '123',
|
|
72
|
+
userAgent: 'Mozilla/5.0',
|
|
73
|
+
jsVersion: 'V8',
|
|
74
|
+
}
|
|
75
|
+
} satisfies CDPResponse;
|
|
76
|
+
|
|
77
|
+
const targetAttachCommand = {
|
|
78
|
+
id: 2,
|
|
79
|
+
method: 'Target.setAutoAttach',
|
|
80
|
+
params: {
|
|
81
|
+
autoAttach: true,
|
|
82
|
+
waitForDebuggerOnStart: false,
|
|
83
|
+
}
|
|
84
|
+
} satisfies CDPCommand;
|
|
85
|
+
|
|
86
|
+
const targetAttachResponse = {
|
|
87
|
+
id: 2,
|
|
88
|
+
result: undefined,
|
|
89
|
+
} satisfies CDPResponse;
|
|
90
|
+
|
|
91
|
+
const attachedToTargetEvent = {
|
|
92
|
+
method: 'Target.attachedToTarget',
|
|
93
|
+
params: {
|
|
94
|
+
sessionId: 'session-1',
|
|
95
|
+
targetInfo: {
|
|
96
|
+
targetId: 'target-1',
|
|
97
|
+
type: 'page',
|
|
98
|
+
title: 'Example',
|
|
99
|
+
url: 'https://example.com',
|
|
100
|
+
attached: true,
|
|
101
|
+
canAccessOpener: false,
|
|
102
|
+
},
|
|
103
|
+
waitingForDebugger: false,
|
|
104
|
+
}
|
|
105
|
+
} satisfies CDPEvent;
|
|
106
|
+
|
|
107
|
+
const consoleMessageEvent = {
|
|
108
|
+
method: 'Runtime.consoleAPICalled',
|
|
109
|
+
params: {
|
|
110
|
+
type: 'log',
|
|
111
|
+
args: [],
|
|
112
|
+
executionContextId: 1,
|
|
113
|
+
timestamp: 123456789,
|
|
114
|
+
}
|
|
115
|
+
} satisfies CDPEvent;
|
|
116
|
+
|
|
117
|
+
const pageNavigateCommand = {
|
|
118
|
+
id: 3,
|
|
119
|
+
method: 'Page.navigate',
|
|
120
|
+
params: {
|
|
121
|
+
url: 'https://example.com',
|
|
122
|
+
}
|
|
123
|
+
} satisfies CDPCommand;
|
|
124
|
+
|
|
125
|
+
const pageNavigateResponse = {
|
|
126
|
+
id: 3,
|
|
127
|
+
result: {
|
|
128
|
+
frameId: 'frame-1',
|
|
129
|
+
}
|
|
130
|
+
} satisfies CDPResponse;
|
|
131
|
+
|
|
132
|
+
const networkRequestEvent = {
|
|
133
|
+
method: 'Network.requestWillBeSent',
|
|
134
|
+
sessionId: 'session-1',
|
|
135
|
+
params: {
|
|
136
|
+
requestId: 'req-1',
|
|
137
|
+
loaderId: 'loader-1',
|
|
138
|
+
documentURL: 'https://example.com',
|
|
139
|
+
request: {
|
|
140
|
+
url: 'https://example.com/api',
|
|
141
|
+
method: 'GET',
|
|
142
|
+
headers: {},
|
|
143
|
+
initialPriority: 'High',
|
|
144
|
+
referrerPolicy: 'no-referrer',
|
|
145
|
+
},
|
|
146
|
+
timestamp: 123456789,
|
|
147
|
+
wallTime: 123456789,
|
|
148
|
+
initiator: {
|
|
149
|
+
type: 'other',
|
|
150
|
+
},
|
|
151
|
+
redirectHasExtraInfo: false,
|
|
152
|
+
type: 'XHR',
|
|
153
|
+
}
|
|
154
|
+
} satisfies CDPEvent;
|
|
155
|
+
}
|