From f4b792c202235804e13fef252f6617b82a58011f Mon Sep 17 00:00:00 2001 From: ZayanKhan-12 Date: Sat, 8 Aug 2026 17:19:29 -0400 Subject: [PATCH 1/2] test: cover waitforhelper network multiplier and navigation paths --- tests/WaitForHelper.test.ts | 145 ++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/tests/WaitForHelper.test.ts b/tests/WaitForHelper.test.ts index 00d5e583a..516d5727f 100644 --- a/tests/WaitForHelper.test.ts +++ b/tests/WaitForHelper.test.ts @@ -7,9 +7,33 @@ import assert from 'node:assert'; import {describe, it} from 'node:test'; +import {getNetworkMultiplierFromString} from '../src/WaitForHelper.js'; + +import {serverHooks} from './server.js'; import {html, withMcpContext} from './utils.js'; +describe('getNetworkMultiplierFromString', () => { + it('maps each predefined network condition to its multiplier', () => { + assert.strictEqual(getNetworkMultiplierFromString('Fast 4G'), 1); + assert.strictEqual(getNetworkMultiplierFromString('Slow 4G'), 2.5); + assert.strictEqual(getNetworkMultiplierFromString('Fast 3G'), 5); + assert.strictEqual(getNetworkMultiplierFromString('Slow 3G'), 10); + }); + + it('falls back to 1 for unknown condition strings', () => { + assert.strictEqual(getNetworkMultiplierFromString('2G'), 1); + assert.strictEqual(getNetworkMultiplierFromString('No emulation'), 1); + assert.strictEqual(getNetworkMultiplierFromString(''), 1); + }); + + it('falls back to 1 when no condition is set', () => { + assert.strictEqual(getNetworkMultiplierFromString(null), 1); + }); +}); + describe('WaitForHelper', () => { + const server = serverHooks(); + it('does not stall when an action opens a dialog without handleDialog', async () => { await withMcpContext(async (response, context) => { const mcpPage = context.getSelectedMcpPage(); @@ -43,4 +67,125 @@ describe('WaitForHelper', () => { assert.throws(() => mcpPage.throwIfDialogOpen()); }); }); + + it('reports navigatedToUrl when the action starts a cross-document navigation', async () => { + await withMcpContext(async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + server.addHtmlRoute('/target.html', html`

target

`); + const targetUrl = server.getRoute('/target.html'); + + const result = await mcpPage.waitForEventsAfterAction(async () => { + await mcpPage.pptrPage.evaluate(url => { + // Navigate asynchronously so the evaluate call returns before the + // execution context is destroyed by the navigation. + setTimeout(() => { + window.location.href = url; + }, 0); + }, targetUrl); + }); + + assert.strictEqual(result.navigatedToUrl, targetUrl); + assert.strictEqual(result.dialogHandled, false); + assert.strictEqual(mcpPage.pptrPage.url(), targetUrl); + }); + }); + + it('resolves quickly without navigatedToUrl when no navigation happens', async () => { + await withMcpContext(async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + await mcpPage.pptrPage.setContent(html`
`); + + const start = Date.now(); + const result = await mcpPage.waitForEventsAfterAction(async () => { + await mcpPage.pptrPage.evaluate(() => { + document.querySelector('#root')!.append('done'); + }); + }); + const elapsed = Date.now() - start; + + assert.strictEqual(result.navigatedToUrl, undefined); + assert.strictEqual(result.dialogHandled, false); + // Expected wait is ~200ms (#expectNavigationIn 100ms + #stableDomFor + // 100ms). Assert we stayed below #stableDomTimeout/#navigationTimeout + // (3s each) to prove neither full timeout was consumed. + assert.ok( + elapsed < 2_000, + `expected a fast return without navigation, took ${elapsed}ms`, + ); + }); + }); + + it('swallows navigation timeouts and still resolves with a result', async () => { + await withMcpContext(async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + server.addRoute('/hang.html', () => { + // Never respond so the started navigation cannot complete. + }); + const hangUrl = server.getRoute('/hang.html'); + + const start = Date.now(); + const result = await mcpPage.waitForEventsAfterAction( + async () => { + await mcpPage.pptrPage.evaluate(url => { + setTimeout(() => { + window.location.href = url; + }, 0); + }, hangUrl); + }, + {timeout: 500}, + ); + const elapsed = Date.now() - start; + + // The navigation started but timed out; the timeout error is logged and + // swallowed rather than thrown, and the pending navigation never + // committed, so the URL is unchanged and no navigatedToUrl is reported. + assert.strictEqual(result.navigatedToUrl, undefined); + assert.strictEqual(result.dialogHandled, false); + // Current behavior: the total wait is the 500ms navigation timeout plus + // the full 3s #stableDomTimeout, because the stable-DOM evaluation does + // not settle while the navigation is still pending (~3.5s in total). + // Assert an upper bound well below protocolTimeout-scale hangs. + assert.ok( + elapsed < 8_000, + `expected the wait to be bounded by the internal timeouts, took ${elapsed}ms`, + ); + + // Note: no in-page cleanup here. Evaluations do not settle while the + // navigation is pending; the harness closes the page during teardown. + }); + }); + + it('reports same-document hash navigations via the URL comparison', async () => { + await withMcpContext(async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + server.addHtmlRoute('/page.html', html`

content

`); + const pageUrl = server.getRoute('/page.html'); + await mcpPage.pptrPage.goto(pageUrl); + + const result = await mcpPage.waitForEventsAfterAction(async () => { + await mcpPage.pptrPage.evaluate(() => { + window.location.hash = '#section'; + }); + }); + + // Same-document navigations skip the full navigation wait but are still + // surfaced through the before/after URL comparison. + assert.strictEqual(result.navigatedToUrl, `${pageUrl}#section`); + assert.strictEqual(result.dialogHandled, false); + }); + }); + + it('rethrows errors from the action', async () => { + await withMcpContext(async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + await mcpPage.pptrPage.setContent(html`

content

`); + + await assert.rejects( + mcpPage.waitForEventsAfterAction(() => + Promise.reject(new Error('action failed')), + ), + /action failed/, + ); + }); + }); }); From a2f6763954f53cf88fa302afb93e426b01b7fe94 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Fri, 21 Aug 2026 21:09:10 +0200 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> --- tests/WaitForHelper.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/WaitForHelper.test.ts b/tests/WaitForHelper.test.ts index 516d5727f..7b6ab7027 100644 --- a/tests/WaitForHelper.test.ts +++ b/tests/WaitForHelper.test.ts @@ -150,8 +150,6 @@ describe('WaitForHelper', () => { `expected the wait to be bounded by the internal timeouts, took ${elapsed}ms`, ); - // Note: no in-page cleanup here. Evaluations do not settle while the - // navigation is pending; the harness closes the page during teardown. }); }); @@ -168,8 +166,6 @@ describe('WaitForHelper', () => { }); }); - // Same-document navigations skip the full navigation wait but are still - // surfaced through the before/after URL comparison. assert.strictEqual(result.navigatedToUrl, `${pageUrl}#section`); assert.strictEqual(result.dialogHandled, false); });