mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(browser): report launch timeout stage and startup diagnostics
This commit is contained in:
1 parent
f2dfda4b36
commit
c765b3b413
4 files changed
+44
-24
No files matched your search
@@ -48,6 +48,9 @@ jobs:
|
||||
run: npm install --prefix "$RUNNER_TEMP/rea-web-source-map-compiler" --no-audit --no-fund --package-lock=false esbuild@0.25.10
|
||||
- run: npm run verify:browser:source-maps
|
||||
env:
|
||||
# Retain browser launch argv/PID/stderr in the verifier's captured
|
||||
# CLI failure diagnostics without changing its deadline or retrying.
|
||||
DEBUG: pw:browser
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
REA_WEB_SOURCE_MAP_COMPILER: ${{ runner.temp }}/rea-web-source-map-compiler/node_modules/esbuild/lib/main.js
|
||||
@@ -1109,6 +1109,7 @@ it("applies command_timeout_ms to the primary connect handshake when service wor
|
||||
reason: "timeout",
|
||||
userMessage: expect.stringContaining("50 ms command_timeout_ms"),
|
||||
remediationAction: expect.stringContaining("capture-browser-scenario JSON"),
|
||||
diagnostics: { phase: "handshake", command_timeout_ms: 50 },
|
||||
});
|
||||
expect(
|
||||
(failure as { remediationAction?: string }).remediationAction,
|
||||
@@ -1201,7 +1202,13 @@ it("closes a context that launches after its deadline before removing the profil
|
||||
() => undefined,
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(failure).toMatchObject({ reason: "timeout" });
|
||||
expect(failure).toMatchObject({
|
||||
reason: "timeout",
|
||||
userMessage: expect.stringContaining(
|
||||
"Browser launch and persistent-context startup",
|
||||
),
|
||||
diagnostics: { phase: "launch", command_timeout_ms: 40 },
|
||||
});
|
||||
expect(
|
||||
(failure as { remediationAction?: string }).remediationAction,
|
||||
).not.toContain("--command-timeout-ms");
|
||||
@@ -1400,6 +1407,11 @@ it("normalizes a genuine Playwright launch timeout and releases its profile", as
|
||||
reason: "timeout",
|
||||
userMessage: expect.stringContaining("4321 ms command_timeout_ms"),
|
||||
cause: failure,
|
||||
diagnostics: {
|
||||
phase: "launch",
|
||||
command_timeout_ms: 4_321,
|
||||
producer_message: "native launch deadline",
|
||||
},
|
||||
});
|
||||
expect(profilePath).toBeDefined();
|
||||
await expect(access(profilePath!)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
|
||||
@@ -872,7 +872,7 @@ export const openPlaywrightScenarioBrowser = async (
|
||||
: [],
|
||||
}),
|
||||
commandTimeoutMs,
|
||||
"handshake",
|
||||
"launch",
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
const cleanup = new AnalysisCleanupLease(async () => {
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
import type { BrowserScenario } from "../domain/browserScenario.js";
|
||||
|
||||
const OPERATION = "capture_browser_scenario" as const;
|
||||
type ScenarioCommandDeadlinePhase =
|
||||
| Extract<CdpCommandDeadlinePhase, "handshake" | "command">
|
||||
| "launch";
|
||||
|
||||
const pendingScenarioCommands = new WeakMap<
|
||||
BrowserObservationError,
|
||||
@@ -39,16 +42,19 @@ const playwrightTimedOut = (cause: unknown): cause is Error => {
|
||||
*/
|
||||
const scenarioCommandDeadlineRecovery = (
|
||||
timeoutMs: number,
|
||||
phase: Extract<CdpCommandDeadlinePhase, "handshake" | "command">,
|
||||
phase: ScenarioCommandDeadlinePhase,
|
||||
method?: string,
|
||||
): { readonly userMessage: string; readonly remediationAction: string } => {
|
||||
const shared = cdpCommandDeadlineRecovery(timeoutMs, phase, method);
|
||||
const userMessage =
|
||||
phase === "launch"
|
||||
? `Browser launch and persistent-context startup exceeded its ${String(timeoutMs)} ms command_timeout_ms deadline (deadlineExpired). This is not the caller observation window, the ${String(CDP_DISCOVERY_RESPONSE_TIMEOUT_MS)} ms CDP discovery deadline, or the ${String(CDP_CLEANUP_GRACE_MS)} ms cleanup grace.`
|
||||
: cdpCommandDeadlineRecovery(timeoutMs, phase, method).userMessage;
|
||||
const increase =
|
||||
timeoutMs < MAX_BROWSER_DEADLINE_MS
|
||||
? `Set command_timeout_ms in the capture-browser-scenario JSON above ${String(timeoutMs)} (integer milliseconds from 1 to ${String(MAX_BROWSER_DEADLINE_MS)}; omitted means ${String(DEFAULT_CDP_COMMAND_TIMEOUT_MS)}).`
|
||||
: `command_timeout_ms is already at the maximum ${String(MAX_BROWSER_DEADLINE_MS)} ms; do not increase it further.`;
|
||||
return {
|
||||
userMessage: shared.userMessage,
|
||||
userMessage,
|
||||
remediationAction: `${increase} ${browserDeadlineRetry} command_timeout_ms does not raise the fixed protocol, retained-event, or source byte budgets, the ${String(CDP_DISCOVERY_RESPONSE_TIMEOUT_MS)} ms CDP discovery response deadline, or the ${String(CDP_CLEANUP_GRACE_MS)} ms cleanup grace. Those have no runtime override.`,
|
||||
};
|
||||
};
|
||||
@@ -97,7 +103,7 @@ export const releaseLateScenarioCommand = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Bound one Playwright CDP handshake or command by the scenario command deadline.
|
||||
* Bound browser startup, a CDP handshake or a command by the scenario deadline.
|
||||
* The underlying operation stays reachable after a timeout so its owner can
|
||||
* close the returned handle or restore a mutation that lands late.
|
||||
* Playwright's own timeout, when it rejects first, is reported as that deadline.
|
||||
@@ -105,7 +111,7 @@ export const releaseLateScenarioCommand = async (
|
||||
export const withScenarioCommandDeadline = async <T>(
|
||||
work: () => Promise<T>,
|
||||
timeoutMs: number,
|
||||
phase: Extract<CdpCommandDeadlinePhase, "handshake" | "command">,
|
||||
phase: ScenarioCommandDeadlinePhase,
|
||||
method?: string,
|
||||
): Promise<T> => {
|
||||
let operation: Promise<T>;
|
||||
@@ -116,10 +122,20 @@ export const withScenarioCommandDeadline = async <T>(
|
||||
}
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
let settled = false;
|
||||
const recovery = (): {
|
||||
readonly userMessage: string;
|
||||
readonly remediationAction: string;
|
||||
} => scenarioCommandDeadlineRecovery(timeoutMs, phase, method);
|
||||
const deadlineFailure = (cause?: Error): BrowserObservationError => {
|
||||
const deadline = scenarioCommandDeadlineRecovery(timeoutMs, phase, method);
|
||||
return new BrowserObservationError(OPERATION, "timeout", {
|
||||
detail: deadline.userMessage,
|
||||
remediationAction: deadline.remediationAction,
|
||||
cause,
|
||||
diagnostics: {
|
||||
phase,
|
||||
command_timeout_ms: timeoutMs,
|
||||
...(method === undefined ? {} : { method }),
|
||||
...(cause === undefined ? {} : { producer_message: cause.message }),
|
||||
},
|
||||
});
|
||||
};
|
||||
try {
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
const fail = (cause: unknown): void => {
|
||||
@@ -128,11 +144,7 @@ export const withScenarioCommandDeadline = async <T>(
|
||||
reject(cause);
|
||||
};
|
||||
timer = setTimeout(() => {
|
||||
const deadline = recovery();
|
||||
const timeout = new BrowserObservationError(OPERATION, "timeout", {
|
||||
detail: deadline.userMessage,
|
||||
remediationAction: deadline.remediationAction,
|
||||
});
|
||||
const timeout = deadlineFailure();
|
||||
pendingScenarioCommands.set(timeout, operation);
|
||||
fail(timeout);
|
||||
}, timeoutMs);
|
||||
@@ -144,14 +156,7 @@ export const withScenarioCommandDeadline = async <T>(
|
||||
},
|
||||
(cause: unknown) => {
|
||||
if (playwrightTimedOut(cause)) {
|
||||
const deadline = recovery();
|
||||
fail(
|
||||
new BrowserObservationError(OPERATION, "timeout", {
|
||||
detail: deadline.userMessage,
|
||||
remediationAction: deadline.remediationAction,
|
||||
cause,
|
||||
}),
|
||||
);
|
||||
fail(deadlineFailure(cause));
|
||||
return;
|
||||
}
|
||||
fail(cause);
|
||||
|
||||
Reference in new issue
Block a user