fix(browser)!: keep the Chromium sandbox for REA-launched browsers (#8738)

* fix(browser)!: keep the Chromium sandbox for REA-launched browsers

Playwright appends --no-sandbox unless a launch passes
chromiumSandbox: true. Neither the browser scenario launch nor the native
module resolver passed it, so every REA-launched Chromium ran without its
sandbox and REA_BROWSER_NO_SANDBOX had no effect.

Both launch sites now take the sandbox decision from ChromiumSandbox.ts:
off on Android or when REA_BROWSER_NO_SANDBOX=true, on otherwise. When
Chromium reports that it cannot start its sandbox, the launch failure's
remediation names the opt-out.

Fixes #8722

BREAKING CHANGE: hosts where Chromium cannot create its sandbox, such as
root users and Linux containers without user namespaces or a setuid
sandbox, now fail to launch until REA_BROWSER_NO_SANDBOX=true is set.

* fix(browser): name the sandbox opt-out on any sandboxed launch failure

Chromium reports an unusable sandbox through several unrelated startup
messages, including a misconfigured setuid helper and a refused namespace
move, and Playwright recognizes only some of them. Matching individual
messages missed real failures, so a sandboxed launch failure now always
names REA_BROWSER_NO_SANDBOX=true as a conditional remediation and the
retained startup diagnostics show whether the sandbox was the cause.

* fix(browser): keep default recovery for resolver failures after launch

The module resolver's outer catch also covers context, navigation and
evaluation failures after the browser launched. Attaching the sandbox
remediation there replaced their default recovery guidance. Attach the
shared launch remediation only when a sandboxed launch itself fails.

* docs(browser): note the Android sandbox exception for module traces
This commit is contained in:
김준호 authored and GitHub committed 2026-11-04 23:54:29 +08:00
1 parent b1b6cb64c2
commit 30a11af923
7 files changed
+185 -22

No files matched your search

+30 -10
View File
@@ -1,4 +1,8 @@
import { snapshotEnvironment } from "../../process/snapshotEnvironment.js";
import {
chromiumLaunchRemediation,
chromiumSandboxEnabled,
} from "../ChromiumSandbox.js";
import { isAbsolute } from "node:path";
import { z } from "zod";
import type {
@@ -73,6 +77,20 @@ export type ModuleResolverLauncher = (
const launch: ModuleResolverLauncher = async (options) =>
(await import("playwright-core")).chromium.launch(options);
const moduleResolverFailure = (
cause: unknown,
executable: string,
remediationAction: string | undefined,
): ProviderAdapterError =>
new ProviderAdapterError("native-module-resolver", OPERATION, {
cause,
...(remediationAction === undefined ? {} : { remediationAction }),
diagnostics: {
executable,
error_message: cause instanceof Error ? cause.message : String(cause),
},
});
/** Use native import.meta.resolve in an owned context with locally fulfilled requests. */
export class NativeModuleResolver implements WebModuleResolutionPort {
#tail: Promise<void> = Promise.resolve();
@@ -132,6 +150,7 @@ export class NativeModuleResolver implements WebModuleResolutionPort {
},
),
);
const chromiumSandbox = chromiumSandboxEnabled(this.environment);
const deadline = new ProviderStartupDeadline(TIMEOUT_MS, options?.signal);
let activeCall:
| { settled: boolean; interruption?: AnalysisError }
@@ -166,6 +185,7 @@ export class NativeModuleResolver implements WebModuleResolutionPort {
);
};
let lease: AnalysisCleanupLease | undefined;
let launched = false;
let cleanup: Promise<void> | undefined;
const close = (): Promise<void> => {
if (cleanup === undefined && lease !== undefined) {
@@ -197,17 +217,16 @@ export class NativeModuleResolver implements WebModuleResolutionPort {
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
chromiumSandbox,
args: [
"--disable-dev-shm-usage",
"--disable-background-networking",
"--renderer-process-limit=1",
"--js-flags=--max-old-space-size=128",
...(this.environment.REA_BROWSER_NO_SANDBOX === "true"
? ["--no-sandbox"]
: []),
],
}),
);
launched = true;
lease = new AnalysisCleanupLease(async () => {
if (
deadline.signal.aborted &&
@@ -239,14 +258,15 @@ export class NativeModuleResolver implements WebModuleResolutionPort {
cause instanceof AnalysisCapabilityUnavailableError ||
cause instanceof BrowserObservationError
? cause
: new ProviderAdapterError("native-module-resolver", OPERATION, {
: moduleResolverFailure(
cause,
diagnostics: {
executable,
error_message:
cause instanceof Error ? cause.message : String(cause),
},
}),
executable,
// Only an unresolved sandboxed launch can be a sandbox failure;
// later engine failures keep the default recovery guidance.
launched || !chromiumSandbox
? undefined
: chromiumLaunchRemediation(true),
),
);
} finally {
deadline.signal.removeEventListener("abort", onAbort);