Files
ark-stronghold-protocol/test/e2e/client-wait.test.js
T
sganggs bb46713718 feedback5 WZ: the waitForFunctionLong slicing test counts slices, not milliseconds (test/e2e/client-wait.test.js slicedPage)
Its fake predicate turned true 95 ms after the page was made and the test wanted at least 4 slices of 30 ms. On a loaded
machine the 30 ms timers fire late and merge the slices: the 0.2.0 candidate's full pass got 'sliced (3 calls)'. A
preload that blocks the event loop 9 ms of every 12 reproduces it 5 of 5 times. Now the predicate holds on the 4th call:
exactly 4 calls, each with the same predicate, args and polling, each slice 0–30 ms, and the wait lasted the three
slices (≥ 85 ms). It passes 5 of 5 under that lag and under 25 ms of every 27. A helper that stops at the first
timed-out slice still fails it. A test timing flake, no product change; golden unchanged.
2026-10-06 21:08:33 +08:00

109 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// test/e2e/client.mjs waitForFunctionLong (Node, no browser): a long wait of the real-server browser suites polls in
// slices shorter than the CDP protocol timeout. A plain page.waitForFunction runs its whole poll inside one
// Runtime.callFunctionOn, which the 90 s protocol timeout cut off: watch-bonds.e2e's "own battle over" wait (240 s) failed
// with a bare "Waiting failed" whenever the own battle at 1× lasted longer than 90 s (0.1.4 release candidate).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';
import { ROOT, PROTOCOL_TIMEOUT_MS, waitForFunctionLong } from './client.mjs';
const timeoutError = (ms) => Object.assign(new Error(`Waiting failed: ${ms}ms exceeded`), { name: 'TimeoutError' });
/** A page whose predicate turns true after `trueAfter` ms; each waitForFunction slice behaves like puppeteer's. */
function fakePage(trueAfter) {
const t0 = Date.now();
const calls = [];
return {
calls,
waitForFunction(fn, opts, ...args) {
calls.push({ fn, opts, args });
const left = t0 + trueAfter - Date.now();
return new Promise((resolve, reject) => {
if (left <= opts.timeout) setTimeout(() => resolve({ handle: 'ok', args }), Math.max(0, left));
else setTimeout(() => reject(timeoutError(opts.timeout)), opts.timeout);
});
},
};
}
/**
* A page whose predicate holds from its `holdsOn`-th waitForFunction call on; each earlier slice times out after its own
* timeout, like puppeteer's. Counted in calls, not in wall time: on a loaded machine late timers merge time-based slices
* (fakePage(95) with 30 ms slices returned in 3 calls instead of 4 in the 0.2.0 candidate's full pass).
*/
function slicedPage(holdsOn) {
const calls = [];
return {
calls,
waitForFunction(fn, opts, ...args) {
calls.push({ fn, opts, args });
const holds = calls.length >= holdsOn;
return new Promise((resolve, reject) => {
if (holds) setTimeout(() => resolve({ handle: 'ok', args }), 0);
else setTimeout(() => reject(timeoutError(opts.timeout)), opts.timeout);
});
},
};
}
test('a wait longer than one slice keeps polling the same predicate until it holds', async () => {
const page = slicedPage(4);
const fn = () => true;
const t0 = Date.now();
const got = await waitForFunctionLong(page, fn, { timeout: 60_000, polling: 200, slice: 30 }, 'a', 2);
assert.deepEqual(got, { handle: 'ok', args: ['a', 2] });
assert.equal(page.calls.length, 4, 'three slices ran out, the fourth held, nothing polled after it');
assert.ok(Date.now() - t0 >= 85, `the three slices waited their 30 ms each (${Date.now() - t0} ms)`);
for (const c of page.calls) {
assert.equal(c.fn, fn);
assert.deepEqual(c.args, ['a', 2]);
assert.equal(c.opts.polling, 200);
assert.ok(c.opts.timeout > 0 && c.opts.timeout <= 30, `slice ${c.opts.timeout}`);
}
});
test('the overall timeout still ends the wait with a TimeoutError naming it; the last slice is the remainder', async () => {
const page = fakePage(10_000);
const t0 = Date.now();
await assert.rejects(waitForFunctionLong(page, () => false, { timeout: 70, slice: 30 }), (e) => e.name === 'TimeoutError' && /70ms exceeded/.test(e.message));
assert.ok(Date.now() - t0 >= 65, 'not before the deadline');
assert.ok(page.calls.at(-1).opts.timeout <= 30);
assert.ok(page.calls.reduce((n, c) => n + c.opts.timeout, 0) <= 75, 'the slices add up to the timeout');
});
test('any other failure is not retried (a page error, a detached frame, the protocol timeout itself)', async () => {
for (const err of [new Error('Waiting failed'), Object.assign(new Error('boom'), { name: 'ProtocolError' })]) {
let n = 0;
const page = { waitForFunction: () => { n++; return Promise.reject(err); } };
await assert.rejects(waitForFunctionLong(page, () => true, { timeout: 1000, slice: 30 }), (e) => e === err);
assert.equal(n, 1);
}
});
test('the default slice stays well under the protocol timeout every Client launches with', async () => {
const page = fakePage(0);
await waitForFunctionLong(page, () => true, { timeout: 240000 });
assert.ok(page.calls[0].opts.timeout < PROTOCOL_TIMEOUT_MS / 2, `slice ${page.calls[0].opts.timeout}`);
const client = readFileSync(path.join(ROOT, 'test/e2e/client.mjs'), 'utf8');
assert.match(client, /protocolTimeout: PROTOCOL_TIMEOUT_MS \}/, 'Client.open launches with the exported constant');
});
test('no Client suite has a plain page.waitForFunction as long as the protocol timeout (use waitForFunctionLong)', () => {
const files = [];
for (const dir of ['test/ui', 'test/e2e']) {
for (const f of readdirSync(path.join(ROOT, dir))) if (/\.(m?js)$/.test(f)) files.push(path.join(dir, f));
}
const long = [];
for (const f of files) {
const src = readFileSync(path.join(ROOT, f), 'utf8');
if (!/from '\.\.\/e2e\/client\.mjs'|from '\.\/client\.mjs'/.test(src)) continue;
for (let i = src.indexOf('.waitForFunction('); i >= 0; i = src.indexOf('.waitForFunction(', i + 1)) {
const next = src.indexOf('.waitForFunction(', i + 1);
const m = src.slice(i, next < 0 ? undefined : next).match(/\{ timeout: ([0-9_]+)/);
if (m && Number(m[1].replace(/_/g, '')) >= PROTOCOL_TIMEOUT_MS) long.push(`${f}:${src.slice(0, i).split('\n').length} (${m[1]} ms)`);
}
}
assert.deepEqual(long, []);
});