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 { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js'
|
||||||
import type { CDPResponseBase, CDPEventBase } from './cdp-types.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 {
|
interface PendingRequest {
|
||||||
resolve: (result: unknown) => void
|
resolve: (result: unknown) => void
|
||||||
reject: (error: Error) => void
|
reject: (error: Error) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CDPSession {
|
export class CDPSession implements ICDPSession {
|
||||||
private ws: WebSocket
|
private ws: WebSocket
|
||||||
private pendingRequests = new Map<number, PendingRequest>()
|
private pendingRequests = new Map<number, PendingRequest>()
|
||||||
private eventListeners = new Map<string, Set<(params: unknown) => void>>()
|
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)) {
|
if (!this.eventListeners.has(event)) {
|
||||||
this.eventListeners.set(event, new Set())
|
this.eventListeners.set(event, new Set())
|
||||||
}
|
}
|
||||||
this.eventListeners.get(event)!.add(callback as (params: unknown) => void)
|
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)
|
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() {
|
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'
|
import type { Protocol } from 'devtools-protocol'
|
||||||
|
|
||||||
export interface BreakpointInfo {
|
export interface BreakpointInfo {
|
||||||
@@ -61,7 +61,8 @@ export class Debugger {
|
|||||||
* Creates a new Debugger instance.
|
* Creates a new Debugger instance.
|
||||||
*
|
*
|
||||||
* @param options - Configuration options
|
* @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
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
@@ -69,8 +70,9 @@ export class Debugger {
|
|||||||
* const dbg = new Debugger({ cdp })
|
* const dbg = new Debugger({ cdp })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
constructor({ cdp }: { cdp: CDPSession }) {
|
constructor({ cdp }: { cdp: ICDPSession }) {
|
||||||
this.cdp = cdp
|
// Cast to CDPSession for internal type safety - at runtime both are compatible
|
||||||
|
this.cdp = cdp as CDPSession
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { CDPSession } from './cdp-session.js'
|
import type { ICDPSession, CDPSession } from './cdp-session.js'
|
||||||
|
|
||||||
export interface ReadResult {
|
export interface ReadResult {
|
||||||
content: string
|
content: string
|
||||||
@@ -52,8 +52,9 @@ export class Editor {
|
|||||||
private stylesheets = new Map<string, string>()
|
private stylesheets = new Map<string, string>()
|
||||||
private sourceCache = new Map<string, string>()
|
private sourceCache = new Map<string, string>()
|
||||||
|
|
||||||
constructor({ cdp }: { cdp: CDPSession }) {
|
constructor({ cdp }: { cdp: ICDPSession }) {
|
||||||
this.cdp = cdp
|
// Cast to CDPSession for internal type safety - at runtime both are compatible
|
||||||
|
this.cdp = cdp as CDPSession
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,8 @@
|
|||||||
export * from './cdp-relay.js'
|
export * from './cdp-relay.js'
|
||||||
export * from './utils.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 { getCdpUrl, LOG_FILE_PATH, VERSION, sleep } from './utils.js'
|
||||||
import { killPortProcess } from 'kill-port-process'
|
import { killPortProcess } from 'kill-port-process'
|
||||||
import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js'
|
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 { Debugger } from './debugger.js'
|
||||||
import { Editor } from './editor.js'
|
import { Editor } from './editor.js'
|
||||||
import { getStylesForLocator, formatStylesAsText, type StylesResult } from './styles.js'
|
import { getStylesForLocator, formatStylesAsText, type StylesResult } from './styles.js'
|
||||||
@@ -77,8 +77,8 @@ interface VMContext {
|
|||||||
clearAllLogs: () => void
|
clearAllLogs: () => void
|
||||||
waitForPageLoad: (options: WaitForPageLoadOptions) => Promise<WaitForPageLoadResult>
|
waitForPageLoad: (options: WaitForPageLoadOptions) => Promise<WaitForPageLoadResult>
|
||||||
getCDPSession: (options: { page: Page }) => Promise<CDPSession>
|
getCDPSession: (options: { page: Page }) => Promise<CDPSession>
|
||||||
createDebugger: (options: { cdp: CDPSession }) => Debugger
|
createDebugger: (options: { cdp: ICDPSession }) => Debugger
|
||||||
createEditor: (options: { cdp: CDPSession }) => Editor
|
createEditor: (options: { cdp: ICDPSession }) => Editor
|
||||||
getStylesForLocator: (options: { locator: any }) => Promise<StylesResult>
|
getStylesForLocator: (options: { locator: any }) => Promise<StylesResult>
|
||||||
formatStylesAsText: (styles: StylesResult) => string
|
formatStylesAsText: (styles: StylesResult) => string
|
||||||
getReactSource: (options: { locator: any }) => Promise<ReactSourceLocation | null>
|
getReactSource: (options: { locator: any }) => Promise<ReactSourceLocation | null>
|
||||||
@@ -675,11 +675,11 @@ server.tool(
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDebugger = (options: { cdp: CDPSession }) => {
|
const createDebugger = (options: { cdp: ICDPSession }) => {
|
||||||
return new Debugger(options)
|
return new Debugger(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEditor = (options: { cdp: CDPSession }) => {
|
const createEditor = (options: { cdp: ICDPSession }) => {
|
||||||
return new Editor(options)
|
return new Editor(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import type { Page, Locator, ElementHandle } from 'playwright-core'
|
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 {
|
export interface ReactSourceLocation {
|
||||||
fileName: string | null
|
fileName: string | null
|
||||||
@@ -25,11 +25,13 @@ function getBippyCode(): string {
|
|||||||
|
|
||||||
export async function getReactSource({
|
export async function getReactSource({
|
||||||
locator,
|
locator,
|
||||||
cdp,
|
cdp: cdpSession,
|
||||||
}: {
|
}: {
|
||||||
locator: Locator | ElementHandle
|
locator: Locator | ElementHandle
|
||||||
cdp: CDPSession
|
cdp: ICDPSession
|
||||||
}): Promise<ReactSourceLocation | null> {
|
}): 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
|
const page: Page = 'page' in locator && typeof locator.page === 'function' ? locator.page() : (locator as any)._page
|
||||||
|
|
||||||
if (!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'
|
import type { Locator } from 'playwright-core'
|
||||||
|
|
||||||
export interface StyleSource {
|
export interface StyleSource {
|
||||||
@@ -66,13 +66,15 @@ interface CSSStyleSheetHeader {
|
|||||||
|
|
||||||
export async function getStylesForLocator({
|
export async function getStylesForLocator({
|
||||||
locator,
|
locator,
|
||||||
cdp,
|
cdp: cdpSession,
|
||||||
includeUserAgentStyles = false,
|
includeUserAgentStyles = false,
|
||||||
}: {
|
}: {
|
||||||
locator: Locator
|
locator: Locator
|
||||||
cdp: CDPSession
|
cdp: ICDPSession
|
||||||
includeUserAgentStyles?: boolean
|
includeUserAgentStyles?: boolean
|
||||||
}): Promise<StylesResult> {
|
}): 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('DOM.enable')
|
||||||
await cdp.send('CSS.enable')
|
await cdp.send('CSS.enable')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user