Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,8 @@ export class McpContext implements Context {
);
return waitForHelper.waitForEventsAfterAction(action);
}

getNetworkRequestStableId(request: HTTPRequest): number {
return this.#networkCollector.getIdForResource(request);
}
}
9 changes: 7 additions & 2 deletions src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down Expand Up @@ -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++;
}
Expand Down
30 changes: 26 additions & 4 deletions src/PageCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
Expand All @@ -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,
Expand Down Expand Up @@ -56,7 +71,6 @@ export class PageCollector<T> {
if (!page) {
return;
}
console.log('destro');
this.#cleanupPageDestroyed(page);
});
}
Expand All @@ -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
Expand Down Expand Up @@ -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> {
Expand Down
8 changes: 6 additions & 2 deletions src/formatters/networkFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 19 additions & 18 deletions tests/McpResponse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]`,
);
});
});
Expand Down Expand Up @@ -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]`,
);
});
});
Expand All @@ -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]`,
);
});
});
Expand Down Expand Up @@ -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]`,
);
});
});
Expand All @@ -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]`,
);
});
});
Expand Down Expand Up @@ -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]`,
);
});
});
Expand All @@ -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]`,
);
});
});
Expand Down
25 changes: 24 additions & 1 deletion tests/PageCollector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
});
});
24 changes: 12 additions & 12 deletions tests/formatters/networkFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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]',
);
});
});
Expand Down
5 changes: 4 additions & 1 deletion tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -49,6 +50,7 @@ export function getMockRequest(
hasPostData?: boolean;
postData?: string;
fetchPostData?: Promise<string>;
stableId?: number;
} = {},
): HTTPRequest {
return {
Expand Down Expand Up @@ -84,7 +86,8 @@ export function getMockRequest(
redirectChain(): HTTPRequest[] {
return [];
},
} as HTTPRequest;
[stableIdSymbol]: options.stableId ?? 1,
} as unknown as HTTPRequest;
}

export function getMockResponse(
Expand Down