test: retain early Playwright timeout outcomes in ingestion reports

This commit is contained in:
Dotta committed 2026-11-03 22:20:38 -06:00
1 parent 81c9ffdf03
commit 7388e24d8d
4 files changed
+50 -8

No files matched your search

+24 -4
View File
@@ -20,6 +20,25 @@ test('real frozen Playwright tiny poll exposes the matcher deadline shape used b
// claim runtime qualification of H5's extended application path.
});
test('real frozen Playwright can report its configured timeout before the nominal deadline', async context => {
const require = createRequire(import.meta.url), { expect } = require('@playwright/test');
assert.equal(require('@playwright/test/package.json').version, '1.62.1');
const timeoutMs = 1200, intervalMs = 1500, startedAtMs = Date.now(), started = performance.now();
let observations = 0, failure;
try {
await expect.poll(async () => { observations++; return 0; }, { timeout: timeoutMs, intervals: [intervalMs] }).toBeGreaterThanOrEqual(1);
} catch (error) { failure = error; }
const assessedAtMs = Date.now(), elapsedMs = performance.now() - started;
assert.equal(failure?.matcherResult?.name, 'toBeGreaterThanOrEqual');
assert.equal(failure.matcherResult.pass, false);
assert.ok(failure.message.includes(`Timeout ${timeoutMs}ms exceeded while waiting on the predicate`));
assert.equal(observations, 1);
assert.ok(assessedAtMs < startedAtMs + timeoutMs, 'The real poll ends before its nominal deadline when its next interval would cross it');
context.diagnostic(JSON.stringify({ scope: 'Small real-library poll only; no app or120s runtime qualification',
playwrightVersion: '1.62.1', timeoutMs, intervalMs, startedAtMs, nominalDeadlineMs: startedAtMs + timeoutMs,
assessedAtMs, elapsedMs, observations, matcherName: failure.matcherResult.name, matcherPassed: failure.matcherResult.pass }));
});
test('H5 requires explicit mode and leaves default H4c selected', () => {
assert.equal(h5Enabled(undefined), false); assert.equal(h5Enabled(H5_MODE), true);
assert.throws(() => h5Enabled('almost-h5'));
@@ -33,19 +52,20 @@ test('first successful observation uses only the original wait with no collectio
assert.equal(value.observation.complete, true); assert.equal(value.observation.completedAtMs, 1000);
});
test('known120s miss remains failed while the same observation finishes before600s', async () => {
test('known120s miss assessed before its nominal deadline stays failed and uses only the remaining600s observation', async () => {
let clock = 1000, count = 30; const calls = [], records = [];
const value = await observeStreaming({ readCount: async () => count, target: 512, now: () => clock,
onOutcome: value => records.push(structuredClone(value)),
poll: async (read, milliseconds) => {
calls.push(milliseconds); await read();
if (calls.length === 1) { clock += milliseconds; throw timeout(milliseconds); }
if (calls.length === 1) { clock += milliseconds - 750; throw timeout(milliseconds); }
count = 512; clock += 90000; await read();
} });
assert.deepEqual(calls, [120000, 480000]);
assert.deepEqual(calls, [120000, 480750]);
assert.equal(value.original.status, 'failed'); assert.equal(value.original.lastObservedCount, 30);
assert.equal(value.original.lastObservedAtMs, 1000); assert.equal(value.original.deadlineMs, 121000);
assert.equal(value.observation.completedAtMs, 211000); assert.equal(records[0].observation.complete, false);
assert.equal(value.original.assessedAtMs, 120250); assert.ok(value.original.assessedAtMs < value.original.deadlineMs);
assert.equal(value.observation.completedAtMs, 210250); assert.equal(records[0].observation.complete, false);
assert.equal(value.original.reason, 'original_streaming_deadline');
});
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { H5_MODE, H5_PROTOCOL, H5_LIMITS, H5_WORKLOAD_SHA256, H5_BASELINE_REVISIONS, originalTerminal } from "../tests/e2e/fixtures/native-ingestion-h5.mjs";
import { H5_MODE, H5_PROTOCOL, H5_LIMITS, H5_WORKLOAD_SHA256, H5_BASELINE_REVISIONS, originalTerminal, assessH5Arm, requireH5Candidate } from "../tests/e2e/fixtures/native-ingestion-h5.mjs";
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
@@ -199,6 +199,17 @@ function h5Report(revision, loadedLatency) {
return report;
}
test("H5 assessor retains an original SLA failure assessed before its nominal streaming deadline", () => {
const report = h5Report("B0", 180000), streaming = report.cases[3].h5.streaming.original;
streaming.assessedAtMs = streaming.deadlineMs - 750;
streaming.lastObservedAtMs = streaming.assessedAtMs - 1;
const outcome = assessH5Arm(report, { status: "passed", retry: 0, errors: [], timeoutMs: H5_LIMITS.fullInvocationMs,
startedAt: "2026-11-03T00:00:00.000Z", durationMs: 500000 });
assert.equal(outcome.originalSlaPassed, false);
assert.deepEqual(outcome.misses, ["burst512:streaming120"]);
assert.throws(() => requireH5Candidate("final-b2", outcome, outcome), /every original SLA/);
});
test("H5 CLI retains baseline SLA failure, rejects final SLA misses and requires complete owning evidence", async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), "native-ingestion-h5-gates-"));
const script = new URL("./compare-native-ingestion-browser.ts", import.meta.url);
+10 -2
View File
@@ -45,8 +45,8 @@ is added. Fake-plugin streaming remains separate from real-cloud Stop proof.
The first120s poll is the original matcher. Only that exact known matcher
deadline can extend observation. Page/API errors, other assertions, terminal
failures and cleanup failures never become an allowed SLA miss. There is no
collection wait when markers are available. The600s ceiling is absolute, so a
120s miss leaves at most480s additional observation, not another600s.
collection wait when markers are available. The600s ceiling is absolute from
the original wait start; continued observation uses only its remaining budget.
Record the original wait start/deadline, actual last count and its observation
time, assessment time and error before continuing. Keep the original failure
@@ -54,6 +54,14 @@ after completion. The original120s clock starts before the common source epoch;
it is not silently rebased to epoch+120s. Derive a separate paint-deadline count
from retained2RAF timestamps; do not mislabel it as the original poll's count.
The recorded `deadlineMs` is the nominal configured boundary and
`assessedAtMs` is the actual matcher return time. Frozen Playwright1.62.1 can
report its timeout before that nominal boundary when the next polling interval
would cross it. Its original matcher outcome, known timeout error and incomplete
count establish the original failure; timestamp ordering against the nominal
deadline does not change that outcome. Retain both raw times and continue only
until the unchanged absolute600s observation ceiling.
If120s fails, original terminal processing was **not reached**. Later180s
terminal observations are separately labeled and cannot make the original
protocol pass. Keep epoch→all-terminal drainage unshortened. An additional
+4 -1
View File
@@ -58,6 +58,8 @@ export async function observeStreaming({ readCount, poll, target, now = Date.now
deadlineError = error;
}
if (readError) throw readError;
// deadlineMs is the nominal configured boundary. Playwright can report its
// timeout earlier when its next polling interval would cross that boundary.
const original = { status: deadlineError ? 'failed' : 'passed', reason: deadlineError ? 'original_streaming_deadline' : null,
startedAtMs, deadlineMs: startedAtMs + ORIGINAL_BUDGETS.streamingMs, assessedAtMs: now(),
target, lastObservedCount, lastObservedAtMs, error: deadlineError?.message ?? null };
@@ -119,7 +121,8 @@ export function assessH5Arm(report, owner) {
if (streaming.status === 'failed') {
assert.equal(streaming.reason, 'original_streaming_deadline');
assert.ok(streaming.lastObservedCount < streaming.target);
assert.ok(streaming.assessedAtMs >= streaming.deadlineMs);
// The original matcher outcome is authoritative; assessedAtMs retains
// the actual return time even when it precedes the nominal deadlineMs.
assert.equal(item.stop, false, 'Control readiness cannot inherit a streaming SLA miss');
assert.ok(streaming.error.includes('Timeout 120000ms exceeded while waiting on the predicate'));
assert.equal(value.terminalOriginal.status, 'not-reached');