Skip to content

Commit c80548a

Browse files
zk-khanclaude
andcommitted
test: cover waitforhelper network multiplier and navigation paths
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8028bfe commit c80548a

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

tests/WaitForHelper.test.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,33 @@
77
import assert from 'node:assert';
88
import {describe, it} from 'node:test';
99

10+
import {getNetworkMultiplierFromString} from '../src/WaitForHelper.js';
11+
12+
import {serverHooks} from './server.js';
1013
import {html, withMcpContext} from './utils.js';
1114

15+
describe('getNetworkMultiplierFromString', () => {
16+
it('maps each predefined network condition to its multiplier', () => {
17+
assert.strictEqual(getNetworkMultiplierFromString('Fast 4G'), 1);
18+
assert.strictEqual(getNetworkMultiplierFromString('Slow 4G'), 2.5);
19+
assert.strictEqual(getNetworkMultiplierFromString('Fast 3G'), 5);
20+
assert.strictEqual(getNetworkMultiplierFromString('Slow 3G'), 10);
21+
});
22+
23+
it('falls back to 1 for unknown condition strings', () => {
24+
assert.strictEqual(getNetworkMultiplierFromString('2G'), 1);
25+
assert.strictEqual(getNetworkMultiplierFromString('No emulation'), 1);
26+
assert.strictEqual(getNetworkMultiplierFromString(''), 1);
27+
});
28+
29+
it('falls back to 1 when no condition is set', () => {
30+
assert.strictEqual(getNetworkMultiplierFromString(null), 1);
31+
});
32+
});
33+
1234
describe('WaitForHelper', () => {
35+
const server = serverHooks();
36+
1337
it('does not stall when an action opens a dialog without handleDialog', async () => {
1438
await withMcpContext(async (response, context) => {
1539
const mcpPage = context.getSelectedMcpPage();
@@ -43,4 +67,125 @@ describe('WaitForHelper', () => {
4367
assert.throws(() => mcpPage.throwIfDialogOpen());
4468
});
4569
});
70+
71+
it('reports navigatedToUrl when the action starts a cross-document navigation', async () => {
72+
await withMcpContext(async (response, context) => {
73+
const mcpPage = context.getSelectedMcpPage();
74+
server.addHtmlRoute('/target.html', html`<h1>target</h1>`);
75+
const targetUrl = server.getRoute('/target.html');
76+
77+
const result = await mcpPage.waitForEventsAfterAction(async () => {
78+
await mcpPage.pptrPage.evaluate(url => {
79+
// Navigate asynchronously so the evaluate call returns before the
80+
// execution context is destroyed by the navigation.
81+
setTimeout(() => {
82+
window.location.href = url;
83+
}, 0);
84+
}, targetUrl);
85+
});
86+
87+
assert.strictEqual(result.navigatedToUrl, targetUrl);
88+
assert.strictEqual(result.dialogHandled, false);
89+
assert.strictEqual(mcpPage.pptrPage.url(), targetUrl);
90+
});
91+
});
92+
93+
it('resolves quickly without navigatedToUrl when no navigation happens', async () => {
94+
await withMcpContext(async (response, context) => {
95+
const mcpPage = context.getSelectedMcpPage();
96+
await mcpPage.pptrPage.setContent(html`<div id="root"></div>`);
97+
98+
const start = Date.now();
99+
const result = await mcpPage.waitForEventsAfterAction(async () => {
100+
await mcpPage.pptrPage.evaluate(() => {
101+
document.querySelector('#root')!.append('done');
102+
});
103+
});
104+
const elapsed = Date.now() - start;
105+
106+
assert.strictEqual(result.navigatedToUrl, undefined);
107+
assert.strictEqual(result.dialogHandled, false);
108+
// Expected wait is ~200ms (#expectNavigationIn 100ms + #stableDomFor
109+
// 100ms). Assert we stayed below #stableDomTimeout/#navigationTimeout
110+
// (3s each) to prove neither full timeout was consumed.
111+
assert.ok(
112+
elapsed < 2_000,
113+
`expected a fast return without navigation, took ${elapsed}ms`,
114+
);
115+
});
116+
});
117+
118+
it('swallows navigation timeouts and still resolves with a result', async () => {
119+
await withMcpContext(async (response, context) => {
120+
const mcpPage = context.getSelectedMcpPage();
121+
server.addRoute('/hang.html', () => {
122+
// Never respond so the started navigation cannot complete.
123+
});
124+
const hangUrl = server.getRoute('/hang.html');
125+
126+
const start = Date.now();
127+
const result = await mcpPage.waitForEventsAfterAction(
128+
async () => {
129+
await mcpPage.pptrPage.evaluate(url => {
130+
setTimeout(() => {
131+
window.location.href = url;
132+
}, 0);
133+
}, hangUrl);
134+
},
135+
{timeout: 500},
136+
);
137+
const elapsed = Date.now() - start;
138+
139+
// The navigation started but timed out; the timeout error is logged and
140+
// swallowed rather than thrown, and the pending navigation never
141+
// committed, so the URL is unchanged and no navigatedToUrl is reported.
142+
assert.strictEqual(result.navigatedToUrl, undefined);
143+
assert.strictEqual(result.dialogHandled, false);
144+
// Current behavior: the total wait is the 500ms navigation timeout plus
145+
// the full 3s #stableDomTimeout, because the stable-DOM evaluation does
146+
// not settle while the navigation is still pending (~3.5s in total).
147+
// Assert an upper bound well below protocolTimeout-scale hangs.
148+
assert.ok(
149+
elapsed < 8_000,
150+
`expected the wait to be bounded by the internal timeouts, took ${elapsed}ms`,
151+
);
152+
153+
// Note: no in-page cleanup here. Evaluations do not settle while the
154+
// navigation is pending; the harness closes the page during teardown.
155+
});
156+
});
157+
158+
it('reports same-document hash navigations via the URL comparison', async () => {
159+
await withMcpContext(async (response, context) => {
160+
const mcpPage = context.getSelectedMcpPage();
161+
server.addHtmlRoute('/page.html', html`<p>content</p>`);
162+
const pageUrl = server.getRoute('/page.html');
163+
await mcpPage.pptrPage.goto(pageUrl);
164+
165+
const result = await mcpPage.waitForEventsAfterAction(async () => {
166+
await mcpPage.pptrPage.evaluate(() => {
167+
window.location.hash = '#section';
168+
});
169+
});
170+
171+
// Same-document navigations skip the full navigation wait but are still
172+
// surfaced through the before/after URL comparison.
173+
assert.strictEqual(result.navigatedToUrl, `${pageUrl}#section`);
174+
assert.strictEqual(result.dialogHandled, false);
175+
});
176+
});
177+
178+
it('rethrows errors from the action', async () => {
179+
await withMcpContext(async (response, context) => {
180+
const mcpPage = context.getSelectedMcpPage();
181+
await mcpPage.pptrPage.setContent(html`<p>content</p>`);
182+
183+
await assert.rejects(
184+
mcpPage.waitForEventsAfterAction(() =>
185+
Promise.reject(new Error('action failed')),
186+
),
187+
/action failed/,
188+
);
189+
});
190+
});
46191
});

0 commit comments

Comments
 (0)