mirror of
https://github.com/morluto/rea
synced 2026-10-28 23:11:11 -04:00
fix(browser): retain launch failure diagnostics (#7826)
This commit is contained in:
@@ -86,7 +86,9 @@ it("passes the selected environment to the actual browser launch boundary", asyn
|
||||
},
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("launch boundary reached");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { startup_error: "launch boundary reached" },
|
||||
});
|
||||
expect(launchedEnvironment).toEqual({
|
||||
LANG: "selected-language",
|
||||
PATH: "/selected/bin",
|
||||
@@ -126,7 +128,9 @@ it.skipIf(process.platform === "win32")(
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("fake launch boundary reached");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { startup_error: "fake launch boundary reached" },
|
||||
});
|
||||
|
||||
expect(launchedExecutable).toBe(selected);
|
||||
expect(selected).toBe(`${root}/link/../browser`);
|
||||
@@ -163,7 +167,9 @@ it("keeps Termux execution support when launching Chromium on Android", async ()
|
||||
},
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("launch boundary reached");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { startup_error: "launch boundary reached" },
|
||||
});
|
||||
|
||||
expect(launchOptions?.args).toEqual(["--no-sandbox", "--single-process"]);
|
||||
expect(launchOptions?.env).toEqual({
|
||||
@@ -705,7 +711,9 @@ it("cancels same-target queued connection before acquiring a browser", async ()
|
||||
});
|
||||
await expect(
|
||||
openPlaywrightScenarioBrowser(launch, environment, { launcher }),
|
||||
).rejects.toThrow("launch boundary reached");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { startup_error: "launch boundary reached" },
|
||||
});
|
||||
expect(launchPersistentContext).toHaveBeenCalledTimes(1);
|
||||
|
||||
if (rejectFirst === undefined)
|
||||
@@ -1054,7 +1062,9 @@ it("applies command_timeout_ms to launch startup", async () => {
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("launch boundary reached");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { startup_error: "launch boundary reached" },
|
||||
});
|
||||
expect(timeout).toBe(4_321);
|
||||
});
|
||||
|
||||
@@ -1081,7 +1091,9 @@ it("uses the default command deadline for an omitted launch timeout", async () =
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("launch boundary reached");
|
||||
).rejects.toMatchObject({
|
||||
diagnostics: { startup_error: "launch boundary reached" },
|
||||
});
|
||||
expect(timeout).toBe(DEFAULT_CDP_COMMAND_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
@@ -1399,7 +1411,14 @@ it.each([
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toBe(failure);
|
||||
).rejects.toMatchObject({
|
||||
_tag: "ProviderAdapterError",
|
||||
cause: failure,
|
||||
diagnostics: {
|
||||
executable_path: process.execPath,
|
||||
startup_error: failure.message,
|
||||
},
|
||||
});
|
||||
expect(profilePath).toBeDefined();
|
||||
await expect(access(profilePath!)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
@@ -1444,6 +1463,7 @@ it("normalizes a genuine Playwright launch timeout and releases its profile", as
|
||||
diagnostics: {
|
||||
phase: "launch",
|
||||
command_timeout_ms: 4_321,
|
||||
executable_path: process.execPath,
|
||||
producer_message: "native launch deadline",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -869,8 +869,10 @@ export const openPlaywrightScenarioBrowser = async (
|
||||
let context: BrowserContext;
|
||||
const executablePath = scenario.browser.executable_path;
|
||||
const headless = scenario.browser.headless;
|
||||
let launchStarted = false;
|
||||
try {
|
||||
await chmod(profilePath, 0o700);
|
||||
launchStarted = true;
|
||||
context = await withScenarioCommandDeadline(
|
||||
() =>
|
||||
launcher.launchPersistentContext(profilePath, {
|
||||
@@ -902,16 +904,39 @@ export const openPlaywrightScenarioBrowser = async (
|
||||
}),
|
||||
commandTimeoutMs,
|
||||
"launch",
|
||||
undefined,
|
||||
executablePath,
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
const cleanup = new AnalysisCleanupLease(async () => {
|
||||
await releaseLateScenarioCommand(cause);
|
||||
await rm(profilePath, { recursive: true, force: true, maxRetries: 3 });
|
||||
}, ["browser_profile"]);
|
||||
// Preserve Playwright's launch diagnostics before the generic provider
|
||||
// boundary discards an untyped error. Keep the original cause for late
|
||||
// command cleanup and leave typed timeout/cancellation failures intact.
|
||||
const primary =
|
||||
launchStarted && !(cause instanceof AnalysisError)
|
||||
? new ProviderAdapterError(
|
||||
PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY.id,
|
||||
OPERATION,
|
||||
{
|
||||
cause,
|
||||
userMessage: "The selected browser could not start.",
|
||||
remediationAction:
|
||||
"Review the selected executable and startup diagnostics, then correct the launch configuration before retrying.",
|
||||
diagnostics: {
|
||||
executable_path: executablePath,
|
||||
startup_error:
|
||||
cause instanceof Error ? cause.message : String(cause),
|
||||
},
|
||||
},
|
||||
)
|
||||
: cause;
|
||||
try {
|
||||
return await failBrowserScenarioOperation(cleanup, cause);
|
||||
return await failBrowserScenarioOperation(cleanup, primary);
|
||||
} catch (failure: unknown) {
|
||||
if (failure !== cause) options.retainCleanup?.(cleanup);
|
||||
if (failure !== primary) options.retainCleanup?.(cleanup);
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ export const withScenarioCommandDeadline = async <T>(
|
||||
timeoutMs: number,
|
||||
phase: ScenarioCommandDeadlinePhase,
|
||||
method?: string,
|
||||
launchExecutablePath?: string,
|
||||
): Promise<T> => {
|
||||
let operation: Promise<T>;
|
||||
try {
|
||||
@@ -131,6 +132,9 @@ export const withScenarioCommandDeadline = async <T>(
|
||||
diagnostics: {
|
||||
phase,
|
||||
command_timeout_ms: timeoutMs,
|
||||
...(launchExecutablePath === undefined
|
||||
? {}
|
||||
: { executable_path: launchExecutablePath }),
|
||||
...(method === undefined ? {} : { method }),
|
||||
// Discovered CDP URLs can carry transport userinfo in Playwright's
|
||||
// native message. Preserve that cause without projecting it here.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { chmod, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
"retains the selected browser and its startup failure through the CLI",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-browser-launch-");
|
||||
const executable = join(root, "selected-browser");
|
||||
const secret = "explicit-browser-startup-secret";
|
||||
await writeFile(
|
||||
executable,
|
||||
`#!/bin/sh\nprintf 'fixture startup failure ${secret}\\n' >&2\nexit 42\n`,
|
||||
);
|
||||
await chmod(executable, 0o700);
|
||||
for (const [path, marker] of [
|
||||
[executable, "fixture startup failure"],
|
||||
[join(root, "missing-browser"), "doesn't exist"],
|
||||
] as const) {
|
||||
const scenario = {
|
||||
browser: { mode: "launch", executable_path: path },
|
||||
start_url: { url: "http://127.0.0.1:1/" },
|
||||
secrets: [
|
||||
{
|
||||
secret_id: "credential",
|
||||
environment_variable: "BROWSER_FIXTURE_SECRET",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{ step_id: "wait", action: "wait_for_timeout", duration_ms: 1 },
|
||||
],
|
||||
};
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"capture-browser-scenario",
|
||||
JSON.stringify(scenario),
|
||||
"--json",
|
||||
],
|
||||
environment: { BROWSER_FIXTURE_SECRET: secret },
|
||||
timeoutMs: 20_000,
|
||||
});
|
||||
expect(response.exitCode, response.stdout + response.stderr).toBe(1);
|
||||
expect(response.stdout).not.toContain(secret);
|
||||
expect(response.json).toMatchObject({
|
||||
code: expect.any(String),
|
||||
details: {
|
||||
diagnostics: {
|
||||
executable_path: path,
|
||||
startup_error: expect.stringContaining(marker),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user