Skip to content

Commit cf33726

Browse files
committed
refactor: add TargetEventEmitter abstraction for Browser/BrowserContext
Introduce a TargetEventEmitter interface and asTargetEmitter() helper to decouple PageCollector and UniverseManager from Browser-specific types, enabling them to work with BrowserContext as well. - Add TargetEventEmitter interface in PageCollector.ts - Add asTargetEmitter() helper function for type-safe casting - Change PageCollector, NetworkCollector, UniverseManager to accept TargetEventEmitter instead of Browser - Update all test files to use asTargetEmitter()
1 parent c9691c6 commit cf33726

4 files changed

Lines changed: 81 additions & 47 deletions

File tree

src/DevtoolsUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import {PuppeteerDevToolsConnection} from './DevToolsConnectionAdapter.js';
88
import {Mutex} from './Mutex.js';
9+
import type {TargetEventEmitter} from './PageCollector.js';
910
import {DevTools} from './third_party/index.js';
1011
import type {
1112
Browser,
@@ -108,15 +109,15 @@ export interface TargetUniverse {
108109
export type TargetUniverseFactoryFn = (page: Page) => Promise<TargetUniverse>;
109110

110111
export class UniverseManager {
111-
readonly #browser: Browser;
112+
readonly #browser: TargetEventEmitter;
112113
readonly #createUniverseFor: TargetUniverseFactoryFn;
113114
readonly #universes = new WeakMap<Page, TargetUniverse>();
114115

115116
/** Guard access to #universes so we don't create unnecessary universes */
116117
readonly #mutex = new Mutex();
117118

118119
constructor(
119-
browser: Browser,
120+
browser: TargetEventEmitter,
120121
factory: TargetUniverseFactoryFn = DEFAULT_FACTORY,
121122
) {
122123
this.#browser = browser;

src/PageCollector.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,32 @@ import type {
1515
import {DevTools} from './third_party/index.js';
1616
import {
1717
type Browser,
18+
type BrowserContext,
1819
type Frame,
1920
type Handler,
2021
type HTTPRequest,
2122
type Page,
2223
type PageEvents as PuppeteerPageEvents,
2324
} from './third_party/index.js';
2425

26+
/**
27+
* Common interface for Browser and BrowserContext target events.
28+
* Both Browser and BrowserContext structurally satisfy this interface,
29+
* enabling PageCollector and UniverseManager to work with either.
30+
*/
31+
export interface TargetEventEmitter {
32+
on(type: 'targetcreated', handler: (target: Target) => void): unknown;
33+
on(type: 'targetdestroyed', handler: (target: Target) => void): unknown;
34+
off(type: 'targetcreated', handler: (target: Target) => void): unknown;
35+
off(type: 'targetdestroyed', handler: (target: Target) => void): unknown;
36+
}
37+
38+
export function asTargetEmitter(
39+
source: Browser | BrowserContext | TargetEventEmitter,
40+
): TargetEventEmitter {
41+
return source as unknown as TargetEventEmitter;
42+
}
43+
2544
export class UncaughtError {
2645
readonly details: Protocol.Runtime.ExceptionDetails;
2746
readonly targetId: string;
@@ -57,7 +76,7 @@ type WithSymbolId<T> = T & {
5776
};
5877

5978
export class PageCollector<T> {
60-
#browser: Browser;
79+
#browser: TargetEventEmitter;
6180
#listenersInitializer: (
6281
collector: (item: T) => void,
6382
) => ListenerMap<PageEvents>;
@@ -72,7 +91,7 @@ export class PageCollector<T> {
7291
protected storage = new WeakMap<Page, Array<Array<WithSymbolId<T>>>>();
7392

7493
constructor(
75-
browser: Browser,
94+
browser: TargetEventEmitter,
7695
listeners: (collector: (item: T) => void) => ListenerMap<PageEvents>,
7796
) {
7897
this.#browser = browser;
@@ -373,7 +392,7 @@ class PageEventSubscriber {
373392

374393
export class NetworkCollector extends PageCollector<HTTPRequest> {
375394
constructor(
376-
browser: Browser,
395+
browser: TargetEventEmitter,
377396
listeners: (
378397
collector: (item: HTTPRequest) => void,
379398
) => ListenerMap<PageEvents> = collect => {

tests/DevtoolsUtils.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
urlsEqual,
1515
UniverseManager,
1616
} from '../src/DevtoolsUtils.js';
17+
import {asTargetEmitter} from '../src/PageCollector.js';
1718
import {DevTools} from '../src/third_party/index.js';
1819
import type {Browser, Target} from '../src/third_party/index.js';
1920

@@ -102,7 +103,7 @@ describe('UniverseManager', () => {
102103
it('calls the factory for existing pages', async () => {
103104
const browser = getMockBrowser();
104105
const factory = sinon.stub().resolves({});
105-
const manager = new UniverseManager(browser, factory);
106+
const manager = new UniverseManager(asTargetEmitter(browser), factory);
106107
await manager.init(await browser.pages());
107108

108109
const page = (await browser.pages())[0];
@@ -115,7 +116,7 @@ describe('UniverseManager', () => {
115116
} as unknown as Browser;
116117
// eslint-disable-next-line @typescript-eslint/no-empty-function
117118
const factory = sinon.stub().returns(new Promise(() => {})); // Don't resolve.
118-
const manager = new UniverseManager(browser, factory);
119+
const manager = new UniverseManager(asTargetEmitter(browser), factory);
119120
await manager.init([]);
120121

121122
sinon.assert.notCalled(factory);
@@ -135,7 +136,7 @@ describe('UniverseManager', () => {
135136

136137
it('works with a real browser', async () => {
137138
await withBrowser(async (browser, page) => {
138-
const manager = new UniverseManager(browser);
139+
const manager = new UniverseManager(asTargetEmitter(browser));
139140
await manager.init([page]);
140141

141142
assert.notStrictEqual(manager.get(page), null);
@@ -144,7 +145,7 @@ describe('UniverseManager', () => {
144145

145146
it('ignores pauses', async () => {
146147
await withBrowser(async (browser, page) => {
147-
const manager = new UniverseManager(browser);
148+
const manager = new UniverseManager(asTargetEmitter(browser));
148149
await manager.init([page]);
149150
const targetUniverse = manager.get(page);
150151
assert.ok(targetUniverse);

tests/PageCollector.test.ts

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ConsoleCollector,
1616
NetworkCollector,
1717
PageCollector,
18+
asTargetEmitter,
1819
} from '../src/PageCollector.js';
1920
import {DevTools} from '../src/third_party/index.js';
2021

@@ -25,7 +26,7 @@ describe('PageCollector', () => {
2526
const browser = getMockBrowser();
2627
const page = (await browser.pages())[0];
2728
const request = getMockRequest();
28-
const collector = new PageCollector(browser, collect => {
29+
const collector = new PageCollector(asTargetEmitter(browser), collect => {
2930
return {
3031
request: req => {
3132
collect(req);
@@ -43,7 +44,7 @@ describe('PageCollector', () => {
4344
const page = (await browser.pages())[0];
4445
const mainFrame = page.mainFrame();
4546
const request = getMockRequest();
46-
const collector = new PageCollector(browser, collect => {
47+
const collector = new PageCollector(asTargetEmitter(browser), collect => {
4748
return {
4849
request: req => {
4950
collect(req);
@@ -63,7 +64,7 @@ describe('PageCollector', () => {
6364
const browser = getMockBrowser();
6465
const page = (await browser.pages())[0];
6566
const request = getMockRequest();
66-
const collector = new PageCollector(browser, collect => {
67+
const collector = new PageCollector(asTargetEmitter(browser), collect => {
6768
return {
6869
request: req => {
6970
collect(req);
@@ -82,7 +83,7 @@ describe('PageCollector', () => {
8283
const page = (await browser.pages())[0];
8384
const mainFrame = page.mainFrame();
8485
const request = getMockRequest();
85-
const collector = new PageCollector(browser, collect => {
86+
const collector = new PageCollector(asTargetEmitter(browser), collect => {
8687
return {
8788
request: req => {
8889
collect(req);
@@ -106,7 +107,7 @@ describe('PageCollector', () => {
106107
const browser = getMockBrowser();
107108
const page = (await browser.pages())[0];
108109
const request = getMockRequest();
109-
const collector = new PageCollector(browser, collect => {
110+
const collector = new PageCollector(asTargetEmitter(browser), collect => {
110111
return {
111112
request: req => {
112113
collect(req);
@@ -138,7 +139,7 @@ describe('PageCollector', () => {
138139
const browser = getMockBrowser();
139140
const page = (await browser.pages())[0];
140141
const request = getMockRequest();
141-
const collector = new PageCollector(browser, collect => {
142+
const collector = new PageCollector(asTargetEmitter(browser), collect => {
142143
return {
143144
request: req => {
144145
collect(req);
@@ -168,13 +169,16 @@ describe('PageCollector', () => {
168169
const page = (await browser.pages())[0];
169170
const request1 = getMockRequest();
170171
const request2 = getMockRequest();
171-
const collector = new PageCollector<HTTPRequest>(browser, collect => {
172-
return {
173-
request: req => {
174-
collect(req);
175-
},
176-
} as ListenerMap;
177-
});
172+
const collector = new PageCollector<HTTPRequest>(
173+
asTargetEmitter(browser),
174+
collect => {
175+
return {
176+
request: req => {
177+
collect(req);
178+
},
179+
} as ListenerMap;
180+
},
181+
);
178182
await collector.init([page]);
179183

180184
page.emit('request', request1);
@@ -198,7 +202,7 @@ describe('NetworkCollector', () => {
198202
frame: page.mainFrame(),
199203
});
200204
const request2 = getMockRequest();
201-
const collector = new NetworkCollector(browser);
205+
const collector = new NetworkCollector(asTargetEmitter(browser));
202206
await collector.init([page]);
203207
page.emit('request', request);
204208
page.emit('request', navRequest);
@@ -231,7 +235,7 @@ describe('NetworkCollector', () => {
231235
});
232236
const request = getMockRequest();
233237

234-
const collector = new NetworkCollector(browser);
238+
const collector = new NetworkCollector(asTargetEmitter(browser));
235239
await collector.init([page]);
236240
page.emit('request', navRequest);
237241
assert.equal(collector.getData(page)[0], navRequest);
@@ -267,7 +271,7 @@ describe('NetworkCollector', () => {
267271
});
268272
const request = getMockRequest();
269273

270-
const collector = new NetworkCollector(browser);
274+
const collector = new NetworkCollector(asTargetEmitter(browser));
271275
await collector.init([page]);
272276
page.emit('request', navRequest);
273277
assert.equal(collector.getData(page, true).length, 1);
@@ -315,13 +319,16 @@ describe('ConsoleCollector', () => {
315319

316320
page.on('issue', onIssuesListener);
317321

318-
const collector = new ConsoleCollector(browser, collect => {
319-
return {
320-
issue: issue => {
321-
collect(issue as DevTools.AggregatedIssue);
322-
},
323-
} as ListenerMap;
324-
});
322+
const collector = new ConsoleCollector(
323+
asTargetEmitter(browser),
324+
collect => {
325+
return {
326+
issue: issue => {
327+
collect(issue as DevTools.AggregatedIssue);
328+
},
329+
} as ListenerMap;
330+
},
331+
);
325332
await collector.init([page]);
326333
cdpSession.emit('Audits.issueAdded', {issue});
327334
sinon.assert.calledOnce(onIssuesListener);
@@ -336,13 +343,16 @@ describe('ConsoleCollector', () => {
336343
// @ts-expect-error internal API.
337344
const cdpSession = page._client();
338345

339-
const collector = new ConsoleCollector(browser, collect => {
340-
return {
341-
issue: issue => {
342-
collect(issue as DevTools.AggregatedIssue);
343-
},
344-
} as ListenerMap;
345-
});
346+
const collector = new ConsoleCollector(
347+
asTargetEmitter(browser),
348+
collect => {
349+
return {
350+
issue: issue => {
351+
collect(issue as DevTools.AggregatedIssue);
352+
},
353+
} as ListenerMap;
354+
},
355+
);
346356
await collector.init([page]);
347357

348358
const issue2 = {
@@ -368,13 +378,16 @@ describe('ConsoleCollector', () => {
368378
// @ts-expect-error internal API.
369379
const cdpSession = page._client();
370380

371-
const collector = new ConsoleCollector(browser, collect => {
372-
return {
373-
issue: issue => {
374-
collect(issue as DevTools.AggregatedIssue);
375-
},
376-
} as ListenerMap;
377-
});
381+
const collector = new ConsoleCollector(
382+
asTargetEmitter(browser),
383+
collect => {
384+
return {
385+
issue: issue => {
386+
collect(issue as DevTools.AggregatedIssue);
387+
},
388+
} as ListenerMap;
389+
},
390+
);
378391
await collector.init([page]);
379392

380393
cdpSession.emit('Audits.issueAdded', {issue});
@@ -393,7 +406,7 @@ describe('ConsoleCollector', () => {
393406
// @ts-expect-error internal API.
394407
const cdpSession = page._client();
395408
const onUncaughtErrorListener = sinon.spy();
396-
const collector = new ConsoleCollector(browser, () => {
409+
const collector = new ConsoleCollector(asTargetEmitter(browser), () => {
397410
return {
398411
uncaughtError: onUncaughtErrorListener,
399412
} as ListenerMap;

0 commit comments

Comments
 (0)