fix: resolve service worker IDs for console messages (#2881)

## Summary

- Fix `list_console_messages` to accept the `sw-N` service worker IDs
returned by `list_pages`. Resolve the service worker in
`McpContext.getServiceWorkerConsoleData` and use its extension ID to
retrieve console messages.
- Reject raw extension IDs, unknown IDs, and IDs for dedicated or shared
workers with `Service worker not found.`.
- Update the parameter documentation and generated CLI options to
describe the supported ID format.

Fixes #2846.

## Testing

- `npm run test -- tests/McpResponse.test.ts
tests/tools/console.test.ts` (with local Chrome): passed.
- `npm run check-format`: passed.
- `npm run test` (with local Chrome): one large full-page screenshot
test failed with `Page.captureScreenshot: Page is too large`; the same
test fails on a clean checkout of base `45c1c6c` with the same Chrome.
All other tests passed.

Note: #2800 is refactoring worker IDs; this patch is based on current
`main` and can be rebased if needed.

Co-authored-by: guolisha <guolisha@kylinos.cn>
This commit is contained in:
lisa0314andguolisha authored and GitHub committed 2026-10-16 08:00:45 +00:00
1 parent 3ea31fbab1
commit 3804a9ad52
6 files changed
+97 -5

No files matched your search

+1 -1
View File
@@ -449,7 +449,7 @@ Results are paginated (10 rules per page by default); use pageIdx to page throug
- **includeStackTraces** (boolean) _(optional)_: Set to true to include the stack trace for each message when available. Increases the response size.
- **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page.
- **pageSize** (integer) _(optional)_: Maximum number of messages to return. When omitted, returns all messages.
- **serviceWorkerId** (string) _(optional)_: Filter messages to only return messages of the specified service worker.
- **serviceWorkerId** (string) _(optional)_: Filter messages by the service worker ID shown by [`list_pages`](#list_pages) (sw-N).
- **types** (array) _(optional)_: Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.
---
+6 -1
View File
@@ -638,8 +638,13 @@ export class McpContext implements Context, BrowserlessContext {
}
getServiceWorkerConsoleData(
extensionId: string,
serviceWorkerId: string,
): Array<ConsoleMessage | UncaughtError> {
const worker = this.getWorkerById(serviceWorkerId);
if (worker?.type !== 'service_worker') {
throw new Error('Service worker not found.');
}
const extensionId = new URL(worker.url).hostname;
return this.#serviceWorkerConsoleCollector?.getData(extensionId) ?? [];
}
+1 -1
View File
@@ -1308,7 +1308,7 @@ export const commands: Commands = {
name: 'serviceWorkerId',
type: 'string',
description:
'Filter messages to only return messages of the specified service worker.',
'Filter messages by the service worker ID shown by list_pages (sw-N).',
required: false,
positional: false,
},
+1 -1
View File
@@ -90,7 +90,7 @@ export const listConsoleMessages = definePageTool(
.string()
.optional()
.describe(
'Filter messages to only return messages of the specified service worker.',
'Filter messages by the service worker ID shown by list_pages (sw-N).',
),
},
blockedByDialog: false,
+87
View File
@@ -14,6 +14,8 @@ import {pathToFileURL} from 'node:url';
import {Locator} from 'puppeteer';
import sinon from 'sinon';
import {UncaughtError} from '../src/collectors/PageCollector.js';
import {ServiceWorkerConsoleCollector} from '../src/collectors/ServiceWorkerCollector.js';
import {NetworkFormatter} from '../src/formatters/NetworkFormatter.js';
import {McpContext} from '../src/McpContext.js';
import {McpPage} from '../src/McpPage.js';
@@ -27,8 +29,10 @@ import {
} from '../src/third_party/index.js';
import type {TraceResult} from '../src/processors/PerformanceTrace.js';
import {resolveCanonicalPath} from '../src/utils/files.js';
import {stableIdSymbol} from '../src/utils/id.js';
import {
createMockMcpWorker,
createMockPuppeteerBrowser,
createMockPuppeteerPage,
createMockPuppeteerTarget,
@@ -88,6 +92,89 @@ describe('McpContext', () => {
);
}
describe('getServiceWorkerConsoleData', () => {
it('resolves the service worker ID to the collector extension ID', async () => {
const browser = createMockPuppeteerBrowser();
browser.targets.returns([]);
const context = await createConnectedMcpContext(
browser,
{
experimentalDevToolsDebugging: false,
performanceCrux: false,
heapSnapshotManager: new HeapSnapshotManager(),
},
Locator,
);
const worker = createMockMcpWorker({
id: 'sw-3',
url: 'chrome-extension://extension-id/background.js',
});
const getWorkerById = sinon
.stub(context, 'getWorkerById')
.returns(worker);
const messages = [
Object.assign(
new UncaughtError(
{
exceptionId: 1,
text: 'Service worker error',
lineNumber: 0,
columnNumber: 0,
},
'extension-id',
),
{[stableIdSymbol]: 1},
),
];
const getData = sinon
.stub(ServiceWorkerConsoleCollector.prototype, 'getData')
.returns(messages);
const result = context.getServiceWorkerConsoleData('sw-3');
sinon.assert.calledOnceWithExactly(getWorkerById, 'sw-3');
sinon.assert.calledOnceWithExactly(getData, 'extension-id');
assert.strictEqual(result, messages);
context.dispose();
});
for (const serviceWorkerId of ['sw-404', 'extension-id', 'dw-1', 'shw-2']) {
it(`rejects ${serviceWorkerId} without reading console data`, async () => {
const browser = createMockPuppeteerBrowser();
browser.targets.returns([]);
const context = await createConnectedMcpContext(
browser,
{
experimentalDevToolsDebugging: false,
performanceCrux: false,
heapSnapshotManager: new HeapSnapshotManager(),
},
Locator,
);
const getWorkerById = sinon.stub(context, 'getWorkerById');
getWorkerById
.withArgs('dw-1')
.returns(createMockMcpWorker({id: 'dw-1', type: 'dedicated_worker'}));
getWorkerById
.withArgs('shw-2')
.returns(createMockMcpWorker({id: 'shw-2', type: 'shared_worker'}));
const getData = sinon.stub(
ServiceWorkerConsoleCollector.prototype,
'getData',
);
assert.throws(
() => context.getServiceWorkerConsoleData(serviceWorkerId),
/Service worker not found\./,
);
sinon.assert.calledOnceWithExactly(getWorkerById, serviceWorkerId);
sinon.assert.notCalled(getData);
context.dispose();
});
}
});
it('list pages', async () => {
await withMcpContext(async (_response, context) => {
const page = context.getSelectedMcpPage();
+1 -1
View File
@@ -100,7 +100,7 @@ describe('console', () => {
await listConsoleMessages(args).handler(
{
params: {serviceWorkerId: extensionId},
params: {serviceWorkerId: sw.id},
page: context.getSelectedMcpPage(),
},
response2,