fix: do not enable the DevTools frontend Audits subscription (#2625)

## Summary

Puppeteer already subscribes to `Audits.issueAdded`.
`overrideDevToolsGlobals` did not stop the DevTools frontend from
enabling its own `Audits` domain, so a second consumer of the same
stream was created on session setup. Against a target holding a large
retained issue backlog, that second subscription replays the entire
backlog through `IssuesManager`, logging `No handler registered for
issue code PerformanceIssue` per unsupported entry and delaying
unrelated page work.

This stubs `Audits.invoke_enable` on the agent prototype exactly the way
the Network emulation commands are already stubbed, so the redundant
subscription is never enabled. The four identical no-op command bodies
are collapsed into one `noopAgentCommand` helper while adding the
fourth.

## Why this matters

Closes #2556

The guard added in #2535 covers `PageEventSubscriber.#onIssueAdded`, the
collector's own subscription. It does not cover the frontend's
`IssuesManager` path, which is the other consumer of the same CDP event,
so the warning and the backlog replay both survive it. Fixing it at the
`invoke_enable` boundary means the frontend never receives the replay at
all, rather than filtering each issue after the fact.

## Testing

`node scripts/test.js tests/devtools/DevtoolsUtils.test.ts` -> 20
passing. `npm run build` and `npm run typecheck` both clean.

The added case builds a real 500-issue `PerformanceIssue` backlog on a
page, detaches, then constructs the target universe and asserts the
warning is never emitted while `Runtime.evaluate` still works.

It pins the defect rather than just covering the path. Reverting only
`src/devtools/DevtoolsUtils.ts`, rebuilding, and re-running:

```
warn('No handler registered for issue code PerformanceIssue') at createIssuesFromProtocolIssue
  (build/third_party/devtools-frontend/front_end/models/issues_manager/IssuesManager.js:164:13)
  ... (repeated)
at assert2.<computed> [as neverCalledWithMatch]
✖ createTargetUniverse (1533.363458ms)
  '1 subtest failed'
```

Not tested: no measurement of the tool-call timeout the report describes
on a long-lived session. The test asserts the backlog is no longer
replayed, which is the mechanism, not the end-to-end latency.

AI was used for assistance.
This commit is contained in:
Matt Van Horn
2026-08-28 06:51:13 +00:00
committed by GitHub
parent d1baa90e2b
commit d1e73ffd7e
2 changed files with 78 additions and 20 deletions
+26 -20
View File
@@ -36,6 +36,12 @@ export function overrideDevToolsGlobals({
// DevTools CDP errors can get noisy.
DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
const noopAgentCommand = () => {
return Promise.resolve({
getError: () => undefined,
});
};
// Stub out Network emulation commands on the DevTools Agent prototype globally.
// This prevents the DevTools Frontend from ever resetting/clearing Puppeteer's
// active network blocking/throttling rules during target setup or session lifetime.
@@ -63,42 +69,42 @@ export function overrideDevToolsGlobals({
networkAgentPrototype,
'invoke_overrideNetworkState',
{
value: () => {
return Promise.resolve({
getError: () => undefined,
});
},
value: noopAgentCommand,
writable: true,
configurable: true,
enumerable: true,
},
);
Object.defineProperty(networkAgentPrototype, 'invoke_enable', {
value: () => {
return Promise.resolve({
getError: () => undefined,
});
},
value: noopAgentCommand,
writable: true,
configurable: true,
enumerable: true,
});
Object.defineProperty(networkAgentPrototype, 'invoke_disable', {
value: () => {
return Promise.resolve({
getError: () => undefined,
});
},
value: noopAgentCommand,
writable: true,
configurable: true,
enumerable: true,
});
Object.defineProperty(networkAgentPrototype, 'invoke_setBlockedURLs', {
value: () => {
return Promise.resolve({
getError: () => undefined,
});
},
value: noopAgentCommand,
writable: true,
configurable: true,
enumerable: true,
});
}
// Puppeteer already collects issues from its own Audits subscription. Avoid
// enabling the DevTools Frontend's redundant subscription, which can replay
// a large retained issue backlog and delay unrelated page work.
const auditsAgentPrototype =
DevTools.ProtocolClient.InspectorBackend.inspectorBackend.agentPrototypes.get(
'Audits',
);
if (auditsAgentPrototype) {
Object.defineProperty(auditsAgentPrototype, 'invoke_enable', {
value: noopAgentCommand,
writable: true,
configurable: true,
enumerable: true,
+52
View File
@@ -100,6 +100,58 @@ describe('createTargetUniverse', () => {
sinon.assert.notCalled(requestStartedSpy);
});
});
it('does not replay retained Audits issues', async () => {
server.addHtmlRoute('/audits', html`<div>Audits</div>`);
await withBrowser(async (browser, page) => {
await page.goto(server.getRoute('/audits'));
const auditsSession = await page.createCDPSession();
await auditsSession.send('Audits.enable');
const backlogCreated = Promise.withResolvers<void>();
let performanceIssueCount = 0;
auditsSession.on('Audits.issueAdded', event => {
if (
event.issue.code === 'PerformanceIssue' &&
event.issue.details.performanceIssueDetails?.performanceIssueType ===
'DocumentCookie'
) {
performanceIssueCount++;
if (performanceIssueCount === 500) {
backlogCreated.resolve();
}
}
});
await page.evaluate(() => {
document.cookie = 'test=value';
for (let i = 0; i < 500; i++) {
void document.cookie;
}
});
await backlogCreated.promise;
assert.strictEqual(performanceIssueCount, 500);
await auditsSession.detach();
const warnStub = sinon.stub(console, 'warn');
const targetUniverse = await createTargetUniverse(
await page.createCDPSession(),
);
assert.ok(targetUniverse.target.model(DevTools.DebuggerModel));
assert.strictEqual(await page.evaluate('1 + 1'), 2);
await targetUniverse.session.send('Runtime.evaluate', {
expression: '1 + 1',
});
sinon.assert.neverCalledWithMatch(
warnStub,
'No handler registered for issue code PerformanceIssue',
);
});
});
});
describe('SymbolizedError', () => {