fix: bound ConsoleCollector retention per navigation (#2773)

## Summary

`ConsoleCollector` calls `super(page, listeners)` without passing a
`maxResourcesPerNavigation` value, so the per-navigation trim that
`PageCollector` already implements never runs for console messages.
A long-lived page (or an SPA that does not navigate) can therefore
accumulate unbounded console entries in a single navigation bucket.

`NetworkCollector` already passes `MAX_REQUESTS_PER_NAVIGATION = 1_000`
and `ServiceWorkerConsoleCollector` caps its own buffer at 1 000
entries.
This change applies the same bound to `ConsoleCollector`.

## Changes

**`src/collectors/PageCollector.ts`**
- Add `static readonly MAX_MESSAGES_PER_NAVIGATION = 1_000` to
  `ConsoleCollector`.
- Accept an optional `maxMessagesPerNavigation` constructor parameter
  (defaults to the constant) and forward it to `PageCollector`.

**`tests/collectors/PageCollector.test.ts`**
- Add a test that emits more messages than a custom cap and asserts
  only the newest are retained.
- Add a test that emits more messages than the default cap and asserts
  the retained count equals `MAX_MESSAGES_PER_NAVIGATION`.

## Verification

```
npm run typecheck   # no errors
npm run build       # clean
npm run test:no-build -- tests/collectors/PageCollector.test.ts
# 22 tests pass (20 existing + 2 new)
```

Fixes #2768

---------

Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
Co-authored-by: Nicholas Roscino <nroscino@google.com>
This commit is contained in:
mehmet turac
2026-09-22 08:17:54 +00:00
committed by GitHub
co-authored by Alex Rudenko Nicholas Roscino
parent b10be8ebeb
commit e98a3ca404
2 changed files with 73 additions and 2 deletions
+4 -1
View File
@@ -196,6 +196,8 @@ export class PageCollector<T> {
export class ConsoleCollector extends PageCollector<
ConsoleMessage | Error | DevTools.AggregatedIssue | UncaughtError
> {
static readonly MAX_MESSAGES_PER_NAVIGATION = 10_000;
#subscriber?: PageEventSubscriber;
constructor(
@@ -205,8 +207,9 @@ export class ConsoleCollector extends PageCollector<
item: ConsoleMessage | Error | DevTools.AggregatedIssue | UncaughtError,
) => void,
) => ListenerMap<PageEvents>,
maxMessagesPerNavigation = ConsoleCollector.MAX_MESSAGES_PER_NAVIGATION,
) {
super(page, listeners);
super(page, listeners, maxMessagesPerNavigation);
this.#subscriber = new PageEventSubscriber(this.pptrPage);
this.#subscriber.subscribe();
}
+69 -1
View File
@@ -7,7 +7,12 @@
import assert from 'node:assert';
import {afterEach, beforeEach, describe, it} from 'node:test';
import type {Frame, HTTPRequest, Protocol} from 'puppeteer-core';
import type {
ConsoleMessage,
Frame,
HTTPRequest,
Protocol,
} from 'puppeteer-core';
import sinon from 'sinon';
import type {ListenerMap} from '../../src/collectors/PageCollector.js';
@@ -20,6 +25,14 @@ import {DevTools, FrameEvent} from '../../src/third_party/index.js';
import {getMockRequest, getMockBrowser} from '../utils.js';
function createMockConsoleMessage(text: string): ConsoleMessage {
return {
type: () => 'log',
text: () => text,
args: () => [],
} as unknown as ConsoleMessage;
}
describe('PageCollector', () => {
it('works', async () => {
const browser = getMockBrowser();
@@ -531,4 +544,59 @@ describe('ConsoleCollector', () => {
}),
);
});
it('retains only the newest messages per navigation', async () => {
const browser = getMockBrowser();
const page = (await browser.pages())[0];
const cap = 5;
const collector = new ConsoleCollector(
page,
collect => {
return {
console: (msg: ConsoleMessage) => {
collect(msg);
},
} as ListenerMap;
},
cap,
);
const messages = Array.from({length: cap + 3}, (_, i) =>
createMockConsoleMessage(`msg-${i}`),
);
for (const msg of messages) {
page.emit('console', msg);
}
const retained = collector.getData();
assert.equal(retained.length, cap);
assert.deepEqual(retained, messages.slice(messages.length - cap));
});
it('uses MAX_MESSAGES_PER_NAVIGATION as the default cap', async () => {
const browser = getMockBrowser();
const page = (await browser.pages())[0];
const collector = new ConsoleCollector(page, collect => {
return {
console: (msg: ConsoleMessage) => {
collect(msg);
},
} as ListenerMap;
});
const messages = Array.from(
{length: ConsoleCollector.MAX_MESSAGES_PER_NAVIGATION + 1},
(_, i) => createMockConsoleMessage(`msg-${i}`),
);
for (const msg of messages) {
page.emit('console', msg);
}
assert.equal(
collector.getData().length,
ConsoleCollector.MAX_MESSAGES_PER_NAVIGATION,
);
});
});