Skip to content

Commit a37c9fd

Browse files
Lightning00BladeAlinaVarkki
authored andcommitted
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.
1 parent 03e6a82 commit a37c9fd

File tree

8 files changed

+102
-40
lines changed

8 files changed

+102
-40
lines changed

src/McpContext.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,8 @@ export class McpContext implements Context {
426426
);
427427
return waitForHelper.waitForEventsAfterAction(action);
428428
}
429+
430+
getNetworkRequestStableId(request: HTTPRequest): number {
431+
return this.#networkCollector.getIdForResource(request);
432+
}
429433
}

src/McpResponse.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
265265
);
266266
response.push(...data.info);
267267
for (const request of data.items) {
268-
response.push(getShortDescriptionForRequest(request));
268+
response.push(
269+
getShortDescriptionForRequest(
270+
request,
271+
context.getNetworkRequestStableId(request),
272+
),
273+
);
269274
}
270275
} else {
271276
response.push('No requests found.');
@@ -388,7 +393,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
388393
let indent = 0;
389394
for (const request of redirectChain.reverse()) {
390395
response.push(
391-
`${' '.repeat(indent)}${getShortDescriptionForRequest(request)}`,
396+
`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`,
392397
);
393398
indent++;
394399
}

src/PageCollector.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ export type ListenerMap<EventMap extends PageEvents = PageEvents> = {
1717
[K in keyof EventMap]?: (event: EventMap[K]) => void;
1818
};
1919

20+
function createIdGenerator() {
21+
let i = 1;
22+
return () => {
23+
if (i === Number.MAX_SAFE_INTEGER) {
24+
i = 0;
25+
}
26+
return i++;
27+
};
28+
}
29+
30+
export const stableIdSymbol = Symbol('stableIdSymbol');
31+
type WithSymbolId<T> = T & {
32+
[stableIdSymbol]?: number;
33+
};
34+
2035
export class PageCollector<T> {
2136
#browser: Browser;
2237
#listenersInitializer: (
@@ -28,7 +43,7 @@ export class PageCollector<T> {
2843
* As we use the reference to it.
2944
* Use methods that manipulate the array in place.
3045
*/
31-
protected storage = new WeakMap<Page, T[]>();
46+
protected storage = new WeakMap<Page, Array<WithSymbolId<T>>>();
3247

3348
constructor(
3449
browser: Browser,
@@ -56,7 +71,6 @@ export class PageCollector<T> {
5671
if (!page) {
5772
return;
5873
}
59-
console.log('destro');
6074
this.#cleanupPageDestroyed(page);
6175
});
6276
}
@@ -70,10 +84,14 @@ export class PageCollector<T> {
7084
return;
7185
}
7286

73-
const stored: T[] = [];
87+
const idGenerator = createIdGenerator();
88+
const stored: Array<WithSymbolId<T>> = [];
7489
this.storage.set(page, stored);
90+
7591
const listeners = this.#listenersInitializer(value => {
76-
stored.push(value);
92+
const withId = value as WithSymbolId<T>;
93+
withId[stableIdSymbol] = idGenerator();
94+
stored.push(withId);
7795
});
7896
listeners['framenavigated'] = (frame: Frame) => {
7997
// Only reset the storage on main frame navigation
@@ -111,6 +129,10 @@ export class PageCollector<T> {
111129
getData(page: Page): T[] {
112130
return this.storage.get(page) ?? [];
113131
}
132+
133+
getIdForResource(resource: WithSymbolId<T>): number {
134+
return resource[stableIdSymbol] ?? -1;
135+
}
114136
}
115137

116138
export class NetworkCollector extends PageCollector<HTTPRequest> {

src/formatters/networkFormatter.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ import type {HTTPRequest, HTTPResponse} from 'puppeteer-core';
1010

1111
const BODY_CONTEXT_SIZE_LIMIT = 10000;
1212

13-
export function getShortDescriptionForRequest(request: HTTPRequest): string {
14-
return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
13+
export function getShortDescriptionForRequest(
14+
request: HTTPRequest,
15+
id: number,
16+
): string {
17+
// TODO truncate the URL
18+
return `reqid ${id} - ${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
1519
}
1620

1721
export function getStatusFromRequest(request: HTTPRequest): string {

tests/McpResponse.test.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -202,15 +202,16 @@ Call handle_dialog to handle it before continuing.`,
202202
await withBrowser(async (response, context) => {
203203
response.setIncludeNetworkRequests(true);
204204
context.getNetworkRequests = () => {
205-
return [getMockRequest()];
205+
return [getMockRequest({stableId: 1}), getMockRequest({stableId: 2})];
206206
};
207207
const result = await response.handle('test', context);
208208
assert.strictEqual(
209209
result[0].text,
210210
`# test response
211211
## Network requests
212-
Showing 1-1 of 1 (Page 1 of 1).
213-
http://example.com GET [pending]`,
212+
Showing 1-2 of 2 (Page 1 of 1).
213+
reqid 1 - http://example.com GET [pending]
214+
reqid 2 - http://example.com GET [pending]`,
214215
);
215216
});
216217
});
@@ -266,7 +267,7 @@ ${JSON.stringify({request: 'body'})}
266267
${JSON.stringify({response: 'body'})}
267268
## Network requests
268269
Showing 1-1 of 1 (Page 1 of 1).
269-
http://example.com POST [success - 200]`,
270+
reqid 1 - http://example.com POST [success - 200]`,
270271
);
271272
});
272273
});
@@ -289,7 +290,7 @@ Status: [pending]
289290
- content-size:10
290291
## Network requests
291292
Showing 1-1 of 1 (Page 1 of 1).
292-
http://example.com GET [pending]`,
293+
reqid 1 - http://example.com GET [pending]`,
293294
);
294295
});
295296
});
@@ -353,8 +354,8 @@ describe('McpResponse network request filtering', () => {
353354
`# test response
354355
## Network requests
355356
Showing 1-2 of 2 (Page 1 of 1).
356-
http://example.com GET [pending]
357-
http://example.com GET [pending]`,
357+
reqid 1 - http://example.com GET [pending]
358+
reqid 1 - http://example.com GET [pending]`,
358359
);
359360
});
360361
});
@@ -377,7 +378,7 @@ http://example.com GET [pending]`,
377378
`# test response
378379
## Network requests
379380
Showing 1-1 of 1 (Page 1 of 1).
380-
http://example.com GET [pending]`,
381+
reqid 1 - http://example.com GET [pending]`,
381382
);
382383
});
383384
});
@@ -422,11 +423,11 @@ No requests found.`,
422423
`# test response
423424
## Network requests
424425
Showing 1-5 of 5 (Page 1 of 1).
425-
http://example.com GET [pending]
426-
http://example.com GET [pending]
427-
http://example.com GET [pending]
428-
http://example.com GET [pending]
429-
http://example.com GET [pending]`,
426+
reqid 1 - http://example.com GET [pending]
427+
reqid 1 - http://example.com GET [pending]
428+
reqid 1 - http://example.com GET [pending]
429+
reqid 1 - http://example.com GET [pending]
430+
reqid 1 - http://example.com GET [pending]`,
430431
);
431432
});
432433
});
@@ -451,11 +452,11 @@ http://example.com GET [pending]`,
451452
`# test response
452453
## Network requests
453454
Showing 1-5 of 5 (Page 1 of 1).
454-
http://example.com GET [pending]
455-
http://example.com GET [pending]
456-
http://example.com GET [pending]
457-
http://example.com GET [pending]
458-
http://example.com GET [pending]`,
455+
reqid 1 - http://example.com GET [pending]
456+
reqid 1 - http://example.com GET [pending]
457+
reqid 1 - http://example.com GET [pending]
458+
reqid 1 - http://example.com GET [pending]
459+
reqid 1 - http://example.com GET [pending]`,
459460
);
460461
});
461462
});

tests/PageCollector.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import assert from 'node:assert';
77
import {describe, it} from 'node:test';
88

9-
import type {Browser, Frame, Page, Target} from 'puppeteer-core';
9+
import type {Browser, Frame, HTTPRequest, Page, Target} from 'puppeteer-core';
1010

1111
import type {ListenerMap} from '../src/PageCollector.js';
1212
import {PageCollector} from '../src/PageCollector.js';
@@ -196,4 +196,27 @@ describe('PageCollector', () => {
196196

197197
assert.equal(collector.getData(page).length, 0);
198198
});
199+
200+
it('should assign ids to requests', async () => {
201+
const browser = getMockBrowser();
202+
const page = (await browser.pages())[0];
203+
const request1 = getMockRequest();
204+
const request2 = getMockRequest();
205+
const collector = new PageCollector<HTTPRequest>(browser, collect => {
206+
return {
207+
request: req => {
208+
collect(req);
209+
},
210+
} as ListenerMap;
211+
});
212+
await collector.init();
213+
214+
page.emit('request', request1);
215+
page.emit('request', request2);
216+
217+
assert.equal(collector.getData(page).length, 2);
218+
219+
assert.equal(collector.getIdForResource(request1), 1);
220+
assert.equal(collector.getIdForResource(request2), 2);
221+
});
199222
});

tests/formatters/networkFormatter.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,40 @@ describe('networkFormatter', () => {
2121
describe('getShortDescriptionForRequest', () => {
2222
it('works', async () => {
2323
const request = getMockRequest();
24-
const result = getShortDescriptionForRequest(request);
24+
const result = getShortDescriptionForRequest(request, 1);
2525

26-
assert.equal(result, 'http://example.com GET [pending]');
26+
assert.equal(result, 'reqid 1 - http://example.com GET [pending]');
2727
});
2828
it('shows correct method', async () => {
2929
const request = getMockRequest({method: 'POST'});
30-
const result = getShortDescriptionForRequest(request);
30+
const result = getShortDescriptionForRequest(request, 1);
3131

32-
assert.equal(result, 'http://example.com POST [pending]');
32+
assert.equal(result, 'reqid 1 - http://example.com POST [pending]');
3333
});
3434
it('shows correct status for request with response code in 200', async () => {
3535
const response = getMockResponse();
3636
const request = getMockRequest({response});
37-
const result = getShortDescriptionForRequest(request);
37+
const result = getShortDescriptionForRequest(request, 1);
3838

39-
assert.equal(result, 'http://example.com GET [success - 200]');
39+
assert.equal(result, 'reqid 1 - http://example.com GET [success - 200]');
4040
});
4141
it('shows correct status for request with response code in 100', async () => {
4242
const response = getMockResponse({
4343
status: 199,
4444
});
4545
const request = getMockRequest({response});
46-
const result = getShortDescriptionForRequest(request);
46+
const result = getShortDescriptionForRequest(request, 1);
4747

48-
assert.equal(result, 'http://example.com GET [failed - 199]');
48+
assert.equal(result, 'reqid 1 - http://example.com GET [failed - 199]');
4949
});
5050
it('shows correct status for request with response code above 200', async () => {
5151
const response = getMockResponse({
5252
status: 300,
5353
});
5454
const request = getMockRequest({response});
55-
const result = getShortDescriptionForRequest(request);
55+
const result = getShortDescriptionForRequest(request, 1);
5656

57-
assert.equal(result, 'http://example.com GET [failed - 300]');
57+
assert.equal(result, 'reqid 1 - http://example.com GET [failed - 300]');
5858
});
5959
it('shows correct status for request that failed', async () => {
6060
const request = getMockRequest({
@@ -64,11 +64,11 @@ describe('networkFormatter', () => {
6464
};
6565
},
6666
});
67-
const result = getShortDescriptionForRequest(request);
67+
const result = getShortDescriptionForRequest(request, 1);
6868

6969
assert.equal(
7070
result,
71-
'http://example.com GET [failed - Error in Network]',
71+
'reqid 1 - http://example.com GET [failed - Error in Network]',
7272
);
7373
});
7474
});

tests/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {HTTPRequest, HTTPResponse} from 'puppeteer-core';
1010

1111
import {McpContext} from '../src/McpContext.js';
1212
import {McpResponse} from '../src/McpResponse.js';
13+
import {stableIdSymbol} from '../src/PageCollector.js';
1314

1415
let browser: Browser | undefined;
1516

@@ -49,6 +50,7 @@ export function getMockRequest(
4950
hasPostData?: boolean;
5051
postData?: string;
5152
fetchPostData?: Promise<string>;
53+
stableId?: number;
5254
} = {},
5355
): HTTPRequest {
5456
return {
@@ -84,7 +86,8 @@ export function getMockRequest(
8486
redirectChain(): HTTPRequest[] {
8587
return [];
8688
},
87-
} as HTTPRequest;
89+
[stableIdSymbol]: options.stableId ?? 1,
90+
} as unknown as HTTPRequest;
8891
}
8992

9093
export function getMockResponse(

0 commit comments

Comments
 (0)