Skip to content

Commit 0412878

Browse files
fix: PageCollector subscribing multiple times (#241)
We were setting the storage inside the first collect callback which mean that we double subscribed to the events. This affected Network request and Console messages
1 parent d61e151 commit 0412878

File tree

2 files changed

+69
-18
lines changed

2 files changed

+69
-18
lines changed

src/PageCollector.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import type {Browser, HTTPRequest, Page} from 'puppeteer-core';
99
export class PageCollector<T> {
1010
#browser: Browser;
1111
#initializer: (page: Page, collector: (item: T) => void) => void;
12+
/**
13+
* The Array in this map should only be set once
14+
* As we use the reference to it.
15+
* Use methods that manipulate the array in place.
16+
*/
1217
protected storage = new WeakMap<Page, T[]>();
1318

1419
constructor(
@@ -30,7 +35,6 @@ export class PageCollector<T> {
3035
if (!page) {
3136
return;
3237
}
33-
3438
this.#initializePage(page);
3539
});
3640
}
@@ -44,6 +48,9 @@ export class PageCollector<T> {
4448
return;
4549
}
4650

51+
const stored: T[] = [];
52+
this.storage.set(page, stored);
53+
4754
page.on('framenavigated', frame => {
4855
// Only reset the storage on main frame navigation
4956
if (frame !== page.mainFrame()) {
@@ -52,16 +59,16 @@ export class PageCollector<T> {
5259
this.cleanup(page);
5360
});
5461
this.#initializer(page, value => {
55-
const stored = this.storage.get(page) ?? [];
5662
stored.push(value);
57-
this.storage.set(page, stored);
5863
});
5964
}
6065

6166
protected cleanup(page: Page) {
62-
const collection = this.storage.get(page) ?? [];
63-
// Keep the reference alive
64-
collection.length = 0;
67+
const collection = this.storage.get(page);
68+
if (collection) {
69+
// Keep the reference alive
70+
collection.length = 0;
71+
}
6572
}
6673

6774
getData(page: Page): T[] {
@@ -72,13 +79,17 @@ export class PageCollector<T> {
7279
export class NetworkCollector extends PageCollector<HTTPRequest> {
7380
override cleanup(page: Page) {
7481
const requests = this.storage.get(page) ?? [];
82+
if (!requests) {
83+
return;
84+
}
7585
const lastRequestIdx = requests.findLastIndex(request => {
7686
return request.frame() === page.mainFrame()
7787
? request.isNavigationRequest()
7888
: false;
7989
});
8090
// Keep all requests since the last navigation request including that
8191
// navigation request itself.
82-
this.storage.set(page, requests.slice(Math.max(lastRequestIdx, 0)));
92+
// Keep the reference
93+
requests.splice(0, Math.max(lastRequestIdx, 0));
8394
}
8495
}

tests/PageCollector.test.ts

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,37 @@
66
import assert from 'node:assert';
77
import {describe, it} from 'node:test';
88

9-
import type {Browser, Frame, Page, PageEvents} from 'puppeteer-core';
9+
import type {Browser, Frame, Page, Target} from 'puppeteer-core';
1010

1111
import {PageCollector} from '../src/PageCollector.js';
1212

1313
import {getMockRequest} from './utils.js';
1414

15-
function getMockPage(): Page {
16-
const listeners: Record<keyof PageEvents, (data: unknown) => void> = {};
17-
const mainFrame = {} as Frame;
15+
function mockListener() {
16+
const listeners: Record<string, Array<(data: unknown) => void>> = {};
1817
return {
19-
on(eventName, listener) {
20-
listeners[eventName] = listener;
18+
on(eventName: string, listener: (data: unknown) => void) {
19+
if (listeners[eventName]) {
20+
listeners[eventName].push(listener);
21+
} else {
22+
listeners[eventName] = [listener];
23+
}
2124
},
22-
emit(eventName, data) {
23-
listeners[eventName]?.(data);
25+
emit(eventName: string, data: unknown) {
26+
for (const listener of listeners[eventName] ?? []) {
27+
listener(data);
28+
}
2429
},
30+
};
31+
}
32+
33+
function getMockPage(): Page {
34+
const mainFrame = {} as Frame;
35+
return {
2536
mainFrame() {
2637
return mainFrame;
2738
},
39+
...mockListener(),
2840
} as Page;
2941
}
3042

@@ -34,9 +46,7 @@ function getMockBrowser(): Browser {
3446
pages() {
3547
return Promise.resolve(pages);
3648
},
37-
on(_type, _handler) {
38-
// Mock
39-
},
49+
...mockListener(),
4050
} as Browser;
4151
}
4252

@@ -113,4 +123,34 @@ describe('PageCollector', () => {
113123

114124
assert.equal(collector.getData(page).length, 1);
115125
});
126+
127+
it('should only subscribe once', async () => {
128+
const browser = getMockBrowser();
129+
const page = (await browser.pages())[0];
130+
const request = getMockRequest();
131+
const collector = new PageCollector(browser, (pageListener, collect) => {
132+
pageListener.on('request', req => {
133+
collect(req);
134+
});
135+
});
136+
await collector.init();
137+
browser.emit('targetcreated', {
138+
page() {
139+
return Promise.resolve(page);
140+
},
141+
} as Target);
142+
143+
// The page inside part is async so we need to await some time
144+
await new Promise<void>(res => res());
145+
146+
assert.equal(collector.getData(page).length, 0);
147+
148+
page.emit('request', request);
149+
150+
assert.equal(collector.getData(page).length, 1);
151+
152+
page.emit('request', request);
153+
154+
assert.equal(collector.getData(page).length, 2);
155+
});
116156
});

0 commit comments

Comments
 (0)