Add Playwright CDPSession compatibility and export classes
- Add ICDPSession interface for Playwright CDPSession compatibility - Add method aliases: addListener, removeListener, once, detach - Make on/off return 'this' for chaining - Export CDPSession, Editor, Debugger classes from index - Classes accept ICDPSession but use typed CDPSession internally
This commit is contained in:
@@ -3,12 +3,26 @@ import type { Page } from 'playwright-core'
|
||||
import type { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js'
|
||||
import type { CDPResponseBase, CDPEventBase } from './cdp-types.js'
|
||||
|
||||
/**
|
||||
* Common interface for CDP sessions that works with both our CDPSession
|
||||
* and Playwright's CDPSession. Use this type when you want to accept either.
|
||||
*
|
||||
* Uses loose types so Playwright's CDPSession (which uses Protocol.Events)
|
||||
* is assignable to this interface.
|
||||
*/
|
||||
export interface ICDPSession {
|
||||
send(method: string, params?: object): Promise<unknown>
|
||||
on(event: string, callback: (params: any) => void): unknown
|
||||
off(event: string, callback: (params: any) => void): unknown
|
||||
detach(): Promise<void>
|
||||
}
|
||||
|
||||
interface PendingRequest {
|
||||
resolve: (result: unknown) => void
|
||||
reject: (error: Error) => void
|
||||
}
|
||||
|
||||
export class CDPSession {
|
||||
export class CDPSession implements ICDPSession {
|
||||
private ws: WebSocket
|
||||
private pendingRequests = new Map<number, PendingRequest>()
|
||||
private eventListeners = new Map<string, Set<(params: unknown) => void>>()
|
||||
@@ -90,15 +104,48 @@ export class CDPSession {
|
||||
})
|
||||
}
|
||||
|
||||
on<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void) {
|
||||
on<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void): this {
|
||||
if (!this.eventListeners.has(event)) {
|
||||
this.eventListeners.set(event, new Set())
|
||||
}
|
||||
this.eventListeners.get(event)!.add(callback as (params: unknown) => void)
|
||||
return this
|
||||
}
|
||||
|
||||
off<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void) {
|
||||
/** Alias for `on` - matches Playwright's CDPSession interface */
|
||||
addListener<K extends keyof ProtocolMapping.Events>(
|
||||
event: K,
|
||||
callback: (params: ProtocolMapping.Events[K][0]) => void,
|
||||
): this {
|
||||
return this.on(event, callback)
|
||||
}
|
||||
|
||||
off<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void): this {
|
||||
this.eventListeners.get(event)?.delete(callback as (params: unknown) => void)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Alias for `off` - matches Playwright's CDPSession interface */
|
||||
removeListener<K extends keyof ProtocolMapping.Events>(
|
||||
event: K,
|
||||
callback: (params: ProtocolMapping.Events[K][0]) => void,
|
||||
): this {
|
||||
return this.off(event, callback)
|
||||
}
|
||||
|
||||
/** Listen for an event once, then automatically remove the listener */
|
||||
once<K extends keyof ProtocolMapping.Events>(event: K, callback: (params: ProtocolMapping.Events[K][0]) => void): this {
|
||||
const onceCallback = (params: ProtocolMapping.Events[K][0]) => {
|
||||
this.off(event, onceCallback)
|
||||
callback(params)
|
||||
}
|
||||
return this.on(event, onceCallback)
|
||||
}
|
||||
|
||||
/** Alias for `close` - matches Playwright's CDPSession interface */
|
||||
detach(): Promise<void> {
|
||||
this.close()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CDPSession } from './cdp-session.js'
|
||||
import type { ICDPSession, CDPSession } from './cdp-session.js'
|
||||
import type { Protocol } from 'devtools-protocol'
|
||||
|
||||
export interface BreakpointInfo {
|
||||
@@ -61,7 +61,8 @@ export class Debugger {
|
||||
* Creates a new Debugger instance.
|
||||
*
|
||||
* @param options - Configuration options
|
||||
* @param options.cdp - A CDPSession instance for sending CDP commands
|
||||
* @param options.cdp - A CDPSession instance for sending CDP commands (works with both
|
||||
* our CDPSession and Playwright's CDPSession)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
@@ -69,8 +70,9 @@ export class Debugger {
|
||||
* const dbg = new Debugger({ cdp })
|
||||
* ```
|
||||
*/
|
||||
constructor({ cdp }: { cdp: CDPSession }) {
|
||||
this.cdp = cdp
|
||||
constructor({ cdp }: { cdp: ICDPSession }) {
|
||||
// Cast to CDPSession for internal type safety - at runtime both are compatible
|
||||
this.cdp = cdp as CDPSession
|
||||
this.setupEventListeners()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CDPSession } from './cdp-session.js'
|
||||
import type { ICDPSession, CDPSession } from './cdp-session.js'
|
||||
|
||||
export interface ReadResult {
|
||||
content: string
|
||||
@@ -52,8 +52,9 @@ export class Editor {
|
||||
private stylesheets = new Map<string, string>()
|
||||
private sourceCache = new Map<string, string>()
|
||||
|
||||
constructor({ cdp }: { cdp: CDPSession }) {
|
||||
this.cdp = cdp
|
||||
constructor({ cdp }: { cdp: ICDPSession }) {
|
||||
// Cast to CDPSession for internal type safety - at runtime both are compatible
|
||||
this.cdp = cdp as CDPSession
|
||||
this.setupEventListeners()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
export * from './cdp-relay.js'
|
||||
export * from './utils.js'
|
||||
export { CDPSession, getCDPSessionForPage } from './cdp-session.js'
|
||||
export type { ICDPSession } from './cdp-session.js'
|
||||
export { Editor } from './editor.js'
|
||||
export type { ReadResult, SearchMatch, EditResult } from './editor.js'
|
||||
export { Debugger } from './debugger.js'
|
||||
export type { BreakpointInfo, LocationInfo, EvaluateResult, ScriptInfo } from './debugger.js'
|
||||
|
||||
@@ -15,7 +15,7 @@ import { createPatch } from 'diff'
|
||||
import { getCdpUrl, LOG_FILE_PATH, VERSION, sleep } from './utils.js'
|
||||
import { killPortProcess } from 'kill-port-process'
|
||||
import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js'
|
||||
import { getCDPSessionForPage, CDPSession } from './cdp-session.js'
|
||||
import { getCDPSessionForPage, CDPSession, ICDPSession } from './cdp-session.js'
|
||||
import { Debugger } from './debugger.js'
|
||||
import { Editor } from './editor.js'
|
||||
import { getStylesForLocator, formatStylesAsText, type StylesResult } from './styles.js'
|
||||
@@ -77,8 +77,8 @@ interface VMContext {
|
||||
clearAllLogs: () => void
|
||||
waitForPageLoad: (options: WaitForPageLoadOptions) => Promise<WaitForPageLoadResult>
|
||||
getCDPSession: (options: { page: Page }) => Promise<CDPSession>
|
||||
createDebugger: (options: { cdp: CDPSession }) => Debugger
|
||||
createEditor: (options: { cdp: CDPSession }) => Editor
|
||||
createDebugger: (options: { cdp: ICDPSession }) => Debugger
|
||||
createEditor: (options: { cdp: ICDPSession }) => Editor
|
||||
getStylesForLocator: (options: { locator: any }) => Promise<StylesResult>
|
||||
formatStylesAsText: (styles: StylesResult) => string
|
||||
getReactSource: (options: { locator: any }) => Promise<ReactSourceLocation | null>
|
||||
@@ -675,11 +675,11 @@ server.tool(
|
||||
return session
|
||||
}
|
||||
|
||||
const createDebugger = (options: { cdp: CDPSession }) => {
|
||||
const createDebugger = (options: { cdp: ICDPSession }) => {
|
||||
return new Debugger(options)
|
||||
}
|
||||
|
||||
const createEditor = (options: { cdp: CDPSession }) => {
|
||||
const createEditor = (options: { cdp: ICDPSession }) => {
|
||||
return new Editor(options)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Page, Locator, ElementHandle } from 'playwright-core'
|
||||
import type { CDPSession } from './cdp-session.js'
|
||||
import type { ICDPSession, CDPSession } from './cdp-session.js'
|
||||
|
||||
export interface ReactSourceLocation {
|
||||
fileName: string | null
|
||||
@@ -25,11 +25,13 @@ function getBippyCode(): string {
|
||||
|
||||
export async function getReactSource({
|
||||
locator,
|
||||
cdp,
|
||||
cdp: cdpSession,
|
||||
}: {
|
||||
locator: Locator | ElementHandle
|
||||
cdp: CDPSession
|
||||
cdp: ICDPSession
|
||||
}): Promise<ReactSourceLocation | null> {
|
||||
// Cast to CDPSession for internal type safety - at runtime both are compatible
|
||||
const cdp = cdpSession as CDPSession
|
||||
const page: Page = 'page' in locator && typeof locator.page === 'function' ? locator.page() : (locator as any)._page
|
||||
|
||||
if (!page) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CDPSession } from './cdp-session.js'
|
||||
import type { ICDPSession, CDPSession } from './cdp-session.js'
|
||||
import type { Locator } from 'playwright-core'
|
||||
|
||||
export interface StyleSource {
|
||||
@@ -66,13 +66,15 @@ interface CSSStyleSheetHeader {
|
||||
|
||||
export async function getStylesForLocator({
|
||||
locator,
|
||||
cdp,
|
||||
cdp: cdpSession,
|
||||
includeUserAgentStyles = false,
|
||||
}: {
|
||||
locator: Locator
|
||||
cdp: CDPSession
|
||||
cdp: ICDPSession
|
||||
includeUserAgentStyles?: boolean
|
||||
}): Promise<StylesResult> {
|
||||
// Cast to CDPSession for internal type safety - at runtime both are compatible
|
||||
const cdp = cdpSession as CDPSession
|
||||
await cdp.send('DOM.enable')
|
||||
await cdp.send('CSS.enable')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user