@@ -7,6 +7,7 @@ import fs from 'node:fs/promises';
77import os from 'node:os' ;
88import path from 'node:path' ;
99
10+ import { extractUrlLikeFromDevToolsTitle , urlsEqual } from './DevtoolsUtils.js' ;
1011import type { ListenerMap } from './PageCollector.js' ;
1112import { NetworkCollector , PageCollector } from './PageCollector.js' ;
1213import { Locator } from './third_party/index.js' ;
@@ -40,8 +41,8 @@ export interface TextSnapshot {
4041}
4142
4243interface McpContextOptions {
43- // Whether the DevTools windows are exposed as pages.
44- devtools : boolean ;
44+ // Whether the DevTools windows are exposed as pages for debugging of DevTools .
45+ experimentalDevToolsDebugging : boolean ;
4546}
4647
4748const DEFAULT_TIMEOUT = 5_000 ;
@@ -82,6 +83,7 @@ export class McpContext implements Context {
8283
8384 // The most recent page state.
8485 #pages: Page [ ] = [ ] ;
86+ #pageToDevToolsPage = new Map < Page , Page > ( ) ;
8587 #selectedPageIdx = 0 ;
8688 // The most recent snapshot.
8789 #textSnapshot: TextSnapshot | null = null ;
@@ -324,19 +326,57 @@ export class McpContext implements Context {
324326 * Creates a snapshot of the pages.
325327 */
326328 async createPagesSnapshot ( ) : Promise < Page [ ] > {
327- this . #pages = ( await this . browser . pages ( ) ) . filter ( page => {
328- if ( page . url ( ) . startsWith ( 'devtools://' ) ) {
329- return this . #options. devtools ;
329+ const allPages = await this . browser . pages ( ) ;
330+
331+ this . #pages = allPages . filter ( page => {
332+ // If we allow debugging DevTools windows, return all pages.
333+ if ( this . #options. experimentalDevToolsDebugging ) {
334+ return true ;
330335 }
331- return true ;
336+ // If we are in regular mode, the user should only see non-DevTools page.
337+ return ! page . url ( ) . startsWith ( 'devtools://' ) ;
332338 } ) ;
339+
340+ await this . #detectOpenDevToolsWindows( allPages ) ;
341+
333342 return this . #pages;
334343 }
335344
345+ async #detectOpenDevToolsWindows( pages : Page [ ] ) {
346+ this . #pageToDevToolsPage = new Map < Page , Page > ( ) ;
347+ for ( const devToolsPage of pages ) {
348+ if ( devToolsPage . url ( ) . startsWith ( 'devtools://' ) ) {
349+ try {
350+ const data = await devToolsPage
351+ // @ts -expect-error no types for _client().
352+ . _client ( )
353+ . send ( 'Target.getTargetInfo' ) ;
354+ const devtoolsPageTitle = data . targetInfo . title ;
355+ const urlLike = extractUrlLikeFromDevToolsTitle ( devtoolsPageTitle ) ;
356+ if ( ! urlLike ) {
357+ continue ;
358+ }
359+ // TODO: lookup without a loop.
360+ for ( const page of this . #pages) {
361+ if ( urlsEqual ( page . url ( ) , urlLike ) ) {
362+ this . #pageToDevToolsPage. set ( page , devToolsPage ) ;
363+ }
364+ }
365+ } catch {
366+ // no-op
367+ }
368+ }
369+ }
370+ }
371+
336372 getPages ( ) : Page [ ] {
337373 return this . #pages;
338374 }
339375
376+ getDevToolsPage ( page : Page ) : Page | undefined {
377+ return this . #pageToDevToolsPage. get ( page ) ;
378+ }
379+
340380 /**
341381 * Creates a text snapshot of a page.
342382 */
0 commit comments