Fix Chrome headless launch failures with --enable-unsafe-swiftshader (#915)
* Fix Chrome headless launch failures by adding --disable-gpu flag Fixes silent Chrome crashes in headless mode when GPU drivers are unavailable or restricted (common in VMs, containers, and cloud environments). ## Changes Made - **Auto-add `--disable-gpu` flag**: Automatically includes `--disable-gpu` when launching Chrome in headless mode to prevent GPU initialization crashes - **Improved error reporting**: Enhanced error messages to include Chrome's exit code when it crashes before writing DevToolsActivePort - **Better user guidance**: Added helpful hints in error messages suggesting `--no-sandbox` and `--disable-gpu` flags for troubleshooting - **Updated tests**: Added test coverage for the new `--disable-gpu` flag behavior ## Implementation Details The `--disable-gpu` flag is only added in headless mode (when `options.headless && !has_extensions`), preserving GPU acceleration for non-headless usage. The error handling now captures Chrome's exit code and provides actionable debugging information when Chrome fails silently. Fixes #914 * Use --enable-unsafe-swiftshader instead of --disable-gpu for Playwright parity --disable-gpu disables all GPU acceleration and breaks WebGL on Chrome 130+. Playwright uses --enable-unsafe-swiftshader to enable CPU-based software rendering via SwiftShader, which prevents GPU-driver crashes while preserving WebGL support. This matches the behavior from v0.19 (Playwright-based daemon). --------- Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
This commit is contained in:
@@ -133,6 +133,11 @@ fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
|
||||
// injected in headless mode). Skip --headless when extensions are loaded.
|
||||
if options.headless && !has_extensions {
|
||||
args.push("--headless=new".to_string());
|
||||
// Enable SwiftShader software rendering in headless mode. This
|
||||
// prevents silent crashes in environments where GPU drivers are
|
||||
// missing or restricted (VMs, containers, some cloud machines)
|
||||
// while preserving WebGL support. Playwright uses the same flag.
|
||||
args.push("--enable-unsafe-swiftshader".to_string());
|
||||
}
|
||||
|
||||
if let Some(ref proxy) = options.proxy {
|
||||
@@ -303,9 +308,17 @@ fn wait_for_devtools_active_port(
|
||||
let poll_interval = Duration::from_millis(50);
|
||||
|
||||
while std::time::Instant::now() <= deadline {
|
||||
if let Ok(Some(_status)) = child.try_wait() {
|
||||
// If Chrome already exited, stop waiting.
|
||||
break;
|
||||
if let Ok(Some(status)) = child.try_wait() {
|
||||
// Chrome exited before writing DevToolsActivePort -- report the
|
||||
// exit code so the caller can surface it alongside stderr output.
|
||||
let code = status
|
||||
.code()
|
||||
.map(|c| format!("{}", c))
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
return Err(format!(
|
||||
"Chrome exited early (exit code: {}) without writing DevToolsActivePort",
|
||||
code
|
||||
));
|
||||
}
|
||||
|
||||
if let Some((port, ws_path)) = read_devtools_active_port(user_data_dir) {
|
||||
@@ -364,7 +377,10 @@ fn chrome_launch_error(message: &str, stderr_lines: &[String]) -> String {
|
||||
|
||||
if relevant.is_empty() {
|
||||
if stderr_lines.is_empty() {
|
||||
return format!("{} (no stderr output from Chrome)", message);
|
||||
return format!(
|
||||
"{} (no stderr output from Chrome)\nHint: try passing --args \"--no-sandbox\" if Chrome crashes silently in your environment",
|
||||
message
|
||||
);
|
||||
}
|
||||
let last_lines: Vec<&String> = stderr_lines.iter().rev().take(5).collect();
|
||||
return format!(
|
||||
@@ -794,6 +810,8 @@ mod tests {
|
||||
fn test_chrome_launch_error_no_stderr() {
|
||||
let msg = chrome_launch_error("Chrome exited", &[]);
|
||||
assert!(msg.contains("no stderr output"));
|
||||
assert!(msg.contains("Hint:"));
|
||||
assert!(msg.contains("--no-sandbox"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -831,6 +849,10 @@ mod tests {
|
||||
};
|
||||
let result = build_chrome_args(&opts).unwrap();
|
||||
assert!(result.args.iter().any(|a| a == "--headless=new"));
|
||||
assert!(result
|
||||
.args
|
||||
.iter()
|
||||
.any(|a| a == "--enable-unsafe-swiftshader"));
|
||||
assert!(result.args.iter().any(|a| a == "--window-size=1280,720"));
|
||||
// Temp dir created when no profile
|
||||
assert!(result.temp_user_data_dir.is_some());
|
||||
@@ -847,6 +869,10 @@ mod tests {
|
||||
};
|
||||
let result = build_chrome_args(&opts).unwrap();
|
||||
assert!(!result.args.iter().any(|a| a.contains("--headless")));
|
||||
assert!(!result
|
||||
.args
|
||||
.iter()
|
||||
.any(|a| a == "--enable-unsafe-swiftshader"));
|
||||
assert!(!result.args.iter().any(|a| a.starts_with("--window-size=")));
|
||||
// Temp dir created when no profile
|
||||
assert!(result.temp_user_data_dir.is_some());
|
||||
|
||||
Reference in New Issue
Block a user