Skip to content

Commit 103e8c8

Browse files
fix: increase timeouts in case of Emulation
1 parent a9cf9d7 commit 103e8c8

File tree

4 files changed

+102
-14
lines changed

4 files changed

+102
-14
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ npx @modelcontextprotocol/inspector node build/src/index.js
5656

5757
Add the MCP server to your client's config.
5858

59-
```
59+
```json
6060
{
6161
"mcpServers": {
6262
"chrome-devtools": {
6363
"command": "node",
64-
"args": ["/path-to/build/src/index.js"],
64+
"args": ["/path-to/build/src/index.js"]
6565
}
6666
}
6767
}

src/McpContext.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
HTTPRequest,
1212
Page,
1313
SerializedAXNode,
14+
PredefinedNetworkConditions,
1415
} from 'puppeteer-core';
1516
import {Context} from './tools/ToolDefinition.js';
1617
import {Debugger} from 'debug';
@@ -36,6 +37,23 @@ export interface TextSnapshot {
3637
const DEFAULT_TIMEOUT = 5_000;
3738
const NAVIGATION_TIMEOUT = 10_000;
3839

40+
function getNetworkMultiplierFromString(condition: string | null): number {
41+
const puppeteerCondition =
42+
condition as keyof typeof PredefinedNetworkConditions;
43+
44+
switch (puppeteerCondition) {
45+
case 'Fast 4G':
46+
return 1;
47+
case 'Slow 4G':
48+
return 2.5;
49+
case 'Fast 3G':
50+
return 5;
51+
case 'Slow 3G':
52+
return 10;
53+
}
54+
return 1;
55+
}
56+
3957
export class McpContext implements Context {
4058
browser: Browser;
4159
logger: Debugger;
@@ -136,6 +154,7 @@ export class McpContext implements Context {
136154
} else {
137155
this.#networkConditionsMap.set(page, conditions);
138156
}
157+
this.#updateSelectedPageTimeouts();
139158
}
140159

141160
getNetworkConditions(): string | null {
@@ -146,6 +165,7 @@ export class McpContext implements Context {
146165
setCpuThrottlingRate(rate: number): void {
147166
const page = this.getSelectedPage();
148167
this.#cpuThrottlingRateMap.set(page, rate);
168+
this.#updateSelectedPageTimeouts();
149169
}
150170

151171
getCpuThrottlingRate(): number {
@@ -205,12 +225,22 @@ export class McpContext implements Context {
205225
this.#selectedPageIdx = idx;
206226
const newPage = this.getSelectedPage();
207227
newPage.on('dialog', this.#dialogHandler);
228+
this.#updateSelectedPageTimeouts();
229+
}
208230

231+
#updateSelectedPageTimeouts() {
232+
const page = this.getSelectedPage();
209233
// For waiters 5sec timeout should be sufficient.
210-
newPage.setDefaultTimeout(DEFAULT_TIMEOUT);
234+
// Increased in case we throttle the CPU
235+
const cpuMultiplier = this.getCpuThrottlingRate();
236+
page.setDefaultTimeout(DEFAULT_TIMEOUT * cpuMultiplier);
211237
// 10sec should be enough for the load event to be emitted during
212238
// navigations.
213-
newPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT);
239+
// Increased in case we throttle the network requests
240+
const networkMultiplier = getNetworkMultiplierFromString(
241+
this.getNetworkConditions(),
242+
);
243+
page.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier);
214244
}
215245

216246
async getElementByUid(uid: string): Promise<ElementHandle<Element>> {
@@ -315,9 +345,26 @@ export class McpContext implements Context {
315345
return this.#traceResults;
316346
}
317347

348+
getWaitForHelper(
349+
page: Page,
350+
cpuMultiplier: number,
351+
networkMultiplier: number,
352+
) {
353+
return new WaitForHelper(page, cpuMultiplier, networkMultiplier);
354+
}
355+
318356
waitForEventsAfterAction(action: () => Promise<unknown>): Promise<void> {
319357
const page = this.getSelectedPage();
320-
const waitForHelper = new WaitForHelper(page);
358+
const cpuMultiplier = this.getCpuThrottlingRate();
359+
const networkMultiplier = getNetworkMultiplierFromString(
360+
this.getNetworkConditions(),
361+
);
362+
363+
const waitForHelper = this.getWaitForHelper(
364+
page,
365+
cpuMultiplier,
366+
networkMultiplier,
367+
);
321368
return waitForHelper.waitForEventsAfterAction(action);
322369
}
323370
}

src/WaitForHelper.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@ import {logger} from './logger.js';
99

1010
export class WaitForHelper {
1111
#abortController = new AbortController();
12-
#genericTimeout: number;
12+
#page: CdpPage;
13+
#stableDomTimeout: number;
1314
#stableDomFor: number;
1415
#expectNavigationIn: number;
15-
#page: CdpPage;
16-
17-
constructor(page: Page) {
18-
this.#genericTimeout = 3000;
19-
this.#stableDomFor = 100;
20-
this.#expectNavigationIn = 100;
16+
#navigationTimeout: number;
17+
18+
constructor(
19+
page: Page,
20+
cpuTimeoutMultiplier: number,
21+
networkTimeoutMultiplier: number,
22+
) {
23+
this.#stableDomTimeout = 3000 * cpuTimeoutMultiplier;
24+
this.#stableDomFor = 100 * cpuTimeoutMultiplier;
25+
this.#expectNavigationIn = 100 * cpuTimeoutMultiplier;
26+
this.#navigationTimeout = 3000 * networkTimeoutMultiplier;
2127
this.#page = page as unknown as CdpPage;
2228
}
2329

@@ -69,7 +75,7 @@ export class WaitForHelper {
6975
stableDomObserver.evaluate(async observer => {
7076
return await observer.resolver.promise;
7177
}),
72-
this.timeout(this.#genericTimeout).then(() => {
78+
this.timeout(this.#stableDomTimeout).then(() => {
7379
throw new Error('Timeout');
7480
}),
7581
]);
@@ -128,7 +134,7 @@ export class WaitForHelper {
128134
const navigationStated = await navigationStartedPromise;
129135
if (navigationStated) {
130136
await this.#page.waitForNavigation({
131-
timeout: this.#genericTimeout,
137+
timeout: this.#navigationTimeout,
132138
signal: this.#abortController.signal,
133139
});
134140
}

tests/McpContext.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {describe, it} from 'node:test';
77
import assert from 'assert';
88
import {TraceResult} from '../src/trace-processing/parse.js';
99
import {withBrowser} from './utils.js';
10+
import sinon from 'sinon';
1011

1112
describe('McpContext', () => {
1213
it('list pages', async () => {
@@ -38,4 +39,38 @@ describe('McpContext', () => {
3839
assert.deepEqual(context.recordedTraces(), [fakeTrace1, fakeTrace2]);
3940
});
4041
});
42+
43+
it('should update default timeout when cpu throttling changes', async () => {
44+
await withBrowser(async (_response, context) => {
45+
const page = await context.newPage();
46+
const timeoutBefore = page.getDefaultNavigationTimeout();
47+
context.setCpuThrottlingRate(2);
48+
const timeoutAfter = page.getDefaultNavigationTimeout();
49+
assert(timeoutBefore < timeoutAfter, 'Timeout was less then expected');
50+
});
51+
});
52+
53+
it('should update default timeout when network conditions changes', async () => {
54+
await withBrowser(async (_response, context) => {
55+
const page = await context.newPage();
56+
const timeoutBefore = page.getDefaultNavigationTimeout();
57+
context.setNetworkConditions('Slow 3G');
58+
const timeoutAfter = page.getDefaultNavigationTimeout();
59+
assert(timeoutBefore < timeoutAfter, 'Timeout was less then expected');
60+
});
61+
});
62+
63+
it('should call waitForEventsAfterAction with correct multipliers', async () => {
64+
await withBrowser(async (_response, context) => {
65+
const page = await context.newPage();
66+
67+
context.setCpuThrottlingRate(2);
68+
context.setNetworkConditions('Slow 3G');
69+
const stub = sinon.spy(context, 'getWaitForHelper');
70+
71+
await context.waitForEventsAfterAction(async () => {});
72+
73+
sinon.assert.calledWithExactly(stub, page, 2, 10);
74+
});
75+
});
4176
});

0 commit comments

Comments
 (0)