mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
feat: support stable id for network requests (#375)
This is part 1, we just expose the uid here, in a follow up PR we will be used for finding the request.
This commit is contained in:
1 parent
76d5e9416d
commit
f4d7b49bb1
8 files changed
+102
-40
No files matched your search
@@ -426,4 +426,8 @@ export class McpContext implements Context {
|
||||
);
|
||||
return waitForHelper.waitForEventsAfterAction(action);
|
||||
}
|
||||
|
||||
getNetworkRequestStableId(request: HTTPRequest): number {
|
||||
return this.#networkCollector.getIdForResource(request);
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -246,7 +246,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
||||
);
|
||||
response.push(...data.info);
|
||||
for (const request of data.items) {
|
||||
response.push(getShortDescriptionForRequest(request));
|
||||
response.push(
|
||||
getShortDescriptionForRequest(
|
||||
request,
|
||||
context.getNetworkRequestStableId(request),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
response.push('No requests found.');
|
||||
@@ -347,7 +352,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
||||
let indent = 0;
|
||||
for (const request of redirectChain.reverse()) {
|
||||
response.push(
|
||||
`${' '.repeat(indent)}${getShortDescriptionForRequest(request)}`,
|
||||
`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`,
|
||||
);
|
||||
indent++;
|
||||
}
|
||||
|
||||
+26
-4
@@ -17,6 +17,21 @@ export type ListenerMap<EventMap extends PageEvents = PageEvents> = {
|
||||
[K in keyof EventMap]?: (event: EventMap[K]) => void;
|
||||
};
|
||||
|
||||
function createIdGenerator() {
|
||||
let i = 1;
|
||||
return () => {
|
||||
if (i === Number.MAX_SAFE_INTEGER) {
|
||||
i = 0;
|
||||
}
|
||||
return i++;
|
||||
};
|
||||
}
|
||||
|
||||
export const stableIdSymbol = Symbol('stableIdSymbol');
|
||||
type WithSymbolId<T> = T & {
|
||||
[stableIdSymbol]?: number;
|
||||
};
|
||||
|
||||
export class PageCollector<T> {
|
||||
#browser: Browser;
|
||||
#listenersInitializer: (
|
||||
@@ -28,7 +43,7 @@ export class PageCollector<T> {
|
||||
* As we use the reference to it.
|
||||
* Use methods that manipulate the array in place.
|
||||
*/
|
||||
protected storage = new WeakMap<Page, T[]>();
|
||||
protected storage = new WeakMap<Page, Array<WithSymbolId<T>>>();
|
||||
|
||||
constructor(
|
||||
browser: Browser,
|
||||
@@ -56,7 +71,6 @@ export class PageCollector<T> {
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
console.log('destro');
|
||||
this.#cleanupPageDestroyed(page);
|
||||
});
|
||||
}
|
||||
@@ -70,10 +84,14 @@ export class PageCollector<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
const stored: T[] = [];
|
||||
const idGenerator = createIdGenerator();
|
||||
const stored: Array<WithSymbolId<T>> = [];
|
||||
this.storage.set(page, stored);
|
||||
|
||||
const listeners = this.#listenersInitializer(value => {
|
||||
stored.push(value);
|
||||
const withId = value as WithSymbolId<T>;
|
||||
withId[stableIdSymbol] = idGenerator();
|
||||
stored.push(withId);
|
||||
});
|
||||
listeners['framenavigated'] = (frame: Frame) => {
|
||||
// Only reset the storage on main frame navigation
|
||||
@@ -111,6 +129,10 @@ export class PageCollector<T> {
|
||||
getData(page: Page): T[] {
|
||||
return this.storage.get(page) ?? [];
|
||||
}
|
||||
|
||||
getIdForResource(resource: WithSymbolId<T>): number {
|
||||
return resource[stableIdSymbol] ?? -1;
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkCollector extends PageCollector<HTTPRequest> {
|
||||
|
||||
@@ -10,8 +10,12 @@ import type {HTTPRequest, HTTPResponse} from 'puppeteer-core';
|
||||
|
||||
const BODY_CONTEXT_SIZE_LIMIT = 10000;
|
||||
|
||||
export function getShortDescriptionForRequest(request: HTTPRequest): string {
|
||||
return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
|
||||
export function getShortDescriptionForRequest(
|
||||
request: HTTPRequest,
|
||||
id: number,
|
||||
): string {
|
||||
// TODO truncate the URL
|
||||
return `reqid ${id} - ${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
|
||||
}
|
||||
|
||||
export function getStatusFromRequest(request: HTTPRequest): string {
|
||||
|
||||
+19
-18
@@ -202,15 +202,16 @@ Call handle_dialog to handle it before continuing.`,
|
||||
await withBrowser(async (response, context) => {
|
||||
response.setIncludeNetworkRequests(true);
|
||||
context.getNetworkRequests = () => {
|
||||
return [getMockRequest()];
|
||||
return [getMockRequest({stableId: 1}), getMockRequest({stableId: 2})];
|
||||
};
|
||||
const result = await response.handle('test', context);
|
||||
assert.strictEqual(
|
||||
result[0].text,
|
||||
`# test response
|
||||
## Network requests
|
||||
Showing 1-1 of 1 (Page 1 of 1).
|
||||
http://example.com GET [pending]`,
|
||||
Showing 1-2 of 2 (Page 1 of 1).
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 2 - http://example.com GET [pending]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -266,7 +267,7 @@ ${JSON.stringify({request: 'body'})}
|
||||
${JSON.stringify({response: 'body'})}
|
||||
## Network requests
|
||||
Showing 1-1 of 1 (Page 1 of 1).
|
||||
http://example.com POST [success - 200]`,
|
||||
reqid 1 - http://example.com POST [success - 200]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -289,7 +290,7 @@ Status: [pending]
|
||||
- content-size:10
|
||||
## Network requests
|
||||
Showing 1-1 of 1 (Page 1 of 1).
|
||||
http://example.com GET [pending]`,
|
||||
reqid 1 - http://example.com GET [pending]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -354,8 +355,8 @@ describe('McpResponse network request filtering', () => {
|
||||
`# test response
|
||||
## Network requests
|
||||
Showing 1-2 of 2 (Page 1 of 1).
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]`,
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -378,7 +379,7 @@ http://example.com GET [pending]`,
|
||||
`# test response
|
||||
## Network requests
|
||||
Showing 1-1 of 1 (Page 1 of 1).
|
||||
http://example.com GET [pending]`,
|
||||
reqid 1 - http://example.com GET [pending]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -423,11 +424,11 @@ No requests found.`,
|
||||
`# test response
|
||||
## Network requests
|
||||
Showing 1-5 of 5 (Page 1 of 1).
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]`,
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -452,11 +453,11 @@ http://example.com GET [pending]`,
|
||||
`# test response
|
||||
## Network requests
|
||||
Showing 1-5 of 5 (Page 1 of 1).
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]
|
||||
http://example.com GET [pending]`,
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]
|
||||
reqid 1 - http://example.com GET [pending]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import assert from 'node:assert';
|
||||
import {describe, it} from 'node:test';
|
||||
|
||||
import type {Browser, Frame, Page, Target} from 'puppeteer-core';
|
||||
import type {Browser, Frame, HTTPRequest, Page, Target} from 'puppeteer-core';
|
||||
|
||||
import type {ListenerMap} from '../src/PageCollector.js';
|
||||
import {PageCollector} from '../src/PageCollector.js';
|
||||
@@ -196,4 +196,27 @@ describe('PageCollector', () => {
|
||||
|
||||
assert.equal(collector.getData(page).length, 0);
|
||||
});
|
||||
|
||||
it('should assign ids to requests', async () => {
|
||||
const browser = getMockBrowser();
|
||||
const page = (await browser.pages())[0];
|
||||
const request1 = getMockRequest();
|
||||
const request2 = getMockRequest();
|
||||
const collector = new PageCollector<HTTPRequest>(browser, collect => {
|
||||
return {
|
||||
request: req => {
|
||||
collect(req);
|
||||
},
|
||||
} as ListenerMap;
|
||||
});
|
||||
await collector.init();
|
||||
|
||||
page.emit('request', request1);
|
||||
page.emit('request', request2);
|
||||
|
||||
assert.equal(collector.getData(page).length, 2);
|
||||
|
||||
assert.equal(collector.getIdForResource(request1), 1);
|
||||
assert.equal(collector.getIdForResource(request2), 2);
|
||||
});
|
||||
});
|
||||
@@ -21,40 +21,40 @@ describe('networkFormatter', () => {
|
||||
describe('getShortDescriptionForRequest', () => {
|
||||
it('works', async () => {
|
||||
const request = getMockRequest();
|
||||
const result = getShortDescriptionForRequest(request);
|
||||
const result = getShortDescriptionForRequest(request, 1);
|
||||
|
||||
assert.equal(result, 'http://example.com GET [pending]');
|
||||
assert.equal(result, 'reqid 1 - http://example.com GET [pending]');
|
||||
});
|
||||
it('shows correct method', async () => {
|
||||
const request = getMockRequest({method: 'POST'});
|
||||
const result = getShortDescriptionForRequest(request);
|
||||
const result = getShortDescriptionForRequest(request, 1);
|
||||
|
||||
assert.equal(result, 'http://example.com POST [pending]');
|
||||
assert.equal(result, 'reqid 1 - http://example.com POST [pending]');
|
||||
});
|
||||
it('shows correct status for request with response code in 200', async () => {
|
||||
const response = getMockResponse();
|
||||
const request = getMockRequest({response});
|
||||
const result = getShortDescriptionForRequest(request);
|
||||
const result = getShortDescriptionForRequest(request, 1);
|
||||
|
||||
assert.equal(result, 'http://example.com GET [success - 200]');
|
||||
assert.equal(result, 'reqid 1 - http://example.com GET [success - 200]');
|
||||
});
|
||||
it('shows correct status for request with response code in 100', async () => {
|
||||
const response = getMockResponse({
|
||||
status: 199,
|
||||
});
|
||||
const request = getMockRequest({response});
|
||||
const result = getShortDescriptionForRequest(request);
|
||||
const result = getShortDescriptionForRequest(request, 1);
|
||||
|
||||
assert.equal(result, 'http://example.com GET [failed - 199]');
|
||||
assert.equal(result, 'reqid 1 - http://example.com GET [failed - 199]');
|
||||
});
|
||||
it('shows correct status for request with response code above 200', async () => {
|
||||
const response = getMockResponse({
|
||||
status: 300,
|
||||
});
|
||||
const request = getMockRequest({response});
|
||||
const result = getShortDescriptionForRequest(request);
|
||||
const result = getShortDescriptionForRequest(request, 1);
|
||||
|
||||
assert.equal(result, 'http://example.com GET [failed - 300]');
|
||||
assert.equal(result, 'reqid 1 - http://example.com GET [failed - 300]');
|
||||
});
|
||||
it('shows correct status for request that failed', async () => {
|
||||
const request = getMockRequest({
|
||||
@@ -64,11 +64,11 @@ describe('networkFormatter', () => {
|
||||
};
|
||||
},
|
||||
});
|
||||
const result = getShortDescriptionForRequest(request);
|
||||
const result = getShortDescriptionForRequest(request, 1);
|
||||
|
||||
assert.equal(
|
||||
result,
|
||||
'http://example.com GET [failed - Error in Network]',
|
||||
'reqid 1 - http://example.com GET [failed - Error in Network]',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+4
-1
@@ -10,6 +10,7 @@ import type {HTTPRequest, HTTPResponse} from 'puppeteer-core';
|
||||
|
||||
import {McpContext} from '../src/McpContext.js';
|
||||
import {McpResponse} from '../src/McpResponse.js';
|
||||
import {stableIdSymbol} from '../src/PageCollector.js';
|
||||
|
||||
let browser: Browser | undefined;
|
||||
|
||||
@@ -49,6 +50,7 @@ export function getMockRequest(
|
||||
hasPostData?: boolean;
|
||||
postData?: string;
|
||||
fetchPostData?: Promise<string>;
|
||||
stableId?: number;
|
||||
} = {},
|
||||
): HTTPRequest {
|
||||
return {
|
||||
@@ -84,7 +86,8 @@ export function getMockRequest(
|
||||
redirectChain(): HTTPRequest[] {
|
||||
return [];
|
||||
},
|
||||
} as HTTPRequest;
|
||||
[stableIdSymbol]: options.stableId ?? 1,
|
||||
} as unknown as HTTPRequest;
|
||||
}
|
||||
|
||||
export function getMockResponse(
|
||||
|
||||
Reference in new issue
Block a user