diff --git a/scripts/install.sh b/scripts/install.sh index 18e661bddfb..db765362ca9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1891,6 +1891,67 @@ run_browser_install_with_timeout() { fi } +# Compute the PLAYWRIGHT_HOST_PLATFORM_OVERRIDE value to retry an install with +# when Playwright's platform resolver rejects the host. ubuntu24.04 is the +# newest Linux build Playwright has shipped across recent releases and runs on +# newer apt releases (its binaries are dynamically linked); we point too-new / +# unrecognized hosts at it. Only x64/arm64 Linux have Playwright builds — emit +# nothing for anything else so the caller skips the retry. Echoes the value +# (e.g. "ubuntu24.04-x64") or nothing. +playwright_fallback_platform() { + case "$(uname -m)" in + x86_64|amd64) echo "ubuntu24.04-x64" ;; + aarch64|arm64) echo "ubuntu24.04-arm64" ;; + *) : ;; # No Playwright Linux build for this arch. + esac +} + +# Run a `playwright install ...` command, and if it fails or hangs (the +# uninterruptible "Installing Playwright Chromium with system dependencies" +# stall on apt releases Playwright doesn't recognize yet — Ubuntu 26.04, +# Debian 14, future distros — see #35166), retry it ONCE with +# PLAYWRIGHT_HOST_PLATFORM_OVERRIDE pinned to the newest known build. +# +# This is deliberately try-native-first, override-only-on-failure rather than a +# hardcoded distro/version table: when Playwright already supports the host +# (e.g. Ubuntu 26.04 on Playwright >=1.61), the first attempt succeeds and the +# override never runs — so we never force a mismatched-glibc build onto a +# release Playwright handles correctly (microsoft/playwright#35114). It is also +# self-correcting: new distro releases work the moment Playwright adds them, +# with no change here. Playwright's maintainers bless this env var as the +# supported escape hatch for unrecognized platforms (microsoft/playwright#33434). +# +# An operator-provided PLAYWRIGHT_HOST_PLATFORM_OVERRIDE is always respected: +# it is inherited by the first attempt, and the retry is skipped. +# +# Usage: run_playwright_install npx playwright install [args...] +run_playwright_install() { + local timeout_seconds="$1" + shift + + # First attempt: native platform resolution (inherits any operator override). + if run_browser_install_with_timeout "$timeout_seconds" "$@" 2>/dev/null; then + return 0 + fi + + # Operator already pinned the platform — their choice already applied to the + # attempt above; a second identical run won't help. + if [ -n "${PLAYWRIGHT_HOST_PLATFORM_OVERRIDE:-}" ]; then + return 1 + fi + + local fallback + fallback="$(playwright_fallback_platform)" + if [ -z "$fallback" ]; then + return 1 # No usable fallback build for this arch. + fi + + log_warn "Playwright didn't recognize this platform — retrying with PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=$fallback" + log_info "(common on apt releases newer than Playwright knows, e.g. Ubuntu 26.04 / Debian 14; see #35166)" + PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="$fallback" \ + run_browser_install_with_timeout "$timeout_seconds" "$@" +} + configure_browser_env_from_system_browser() { local env_file="$HERMES_HOME/.env" local browser_path="${DETECTED_BROWSER_EXECUTABLE:-}" @@ -1971,7 +2032,7 @@ install_node_deps() { # exact command the admin needs to run separately. if [ "$(id -u)" -eq 0 ] || (command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null); then log_info "Installing Playwright Chromium with system dependencies..." - cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install --with-deps chromium 2>/dev/null || { + cd "$INSTALL_DIR" && run_playwright_install 600 npx playwright install --with-deps chromium || { log_warn "Playwright browser installation failed — browser tools will not work." log_warn "Try running manually: cd $INSTALL_DIR && npx playwright install --with-deps chromium" } @@ -1981,7 +2042,7 @@ install_node_deps() { log_info " sudo npx playwright install-deps chromium" log_info " (from $INSTALL_DIR, after Node.js deps are installed)" log_info "Installing Chromium binary into this user's Playwright cache..." - cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install chromium 2>/dev/null || { + cd "$INSTALL_DIR" && run_playwright_install 600 npx playwright install chromium || { log_warn "Playwright browser installation failed — browser tools will not work." log_warn "Try running manually: cd $INSTALL_DIR && npx playwright install chromium" } @@ -2001,7 +2062,7 @@ install_node_deps() { log_warn " sudo pacman -S nss atk at-spi2-core cups libdrm libxkbcommon mesa pango cairo alsa-lib" fi fi - cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install chromium 2>/dev/null || { + cd "$INSTALL_DIR" && run_playwright_install 600 npx playwright install chromium || { log_warn "Playwright browser installation failed — browser tools will not work." } ;; @@ -2009,7 +2070,7 @@ install_node_deps() { log_warn "Playwright does not support automatic dependency installation on RPM-based systems." log_info "Install Chromium system dependencies manually before using browser tools:" log_info " sudo dnf install nss atk at-spi2-core cups-libs libdrm libxkbcommon mesa-libgbm pango cairo alsa-lib" - cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install chromium 2>/dev/null || { + cd "$INSTALL_DIR" && run_playwright_install 600 npx playwright install chromium || { log_warn "Playwright browser installation failed — install dependencies above and retry." } ;; @@ -2017,7 +2078,7 @@ install_node_deps() { log_warn "Playwright does not support automatic dependency installation on zypper-based systems." log_info "Install Chromium system dependencies manually before using browser tools:" log_info " sudo zypper install mozilla-nss libatk-1_0-0 at-spi2-core cups-libs libdrm2 libxkbcommon0 Mesa-libgbm1 pango cairo libasound2" - cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install chromium 2>/dev/null || { + cd "$INSTALL_DIR" && run_playwright_install 600 npx playwright install chromium || { log_warn "Playwright browser installation failed — install dependencies above and retry." } ;; @@ -2026,7 +2087,7 @@ install_node_deps() { log_info "Install Chromium/browser system dependencies for your distribution, then run:" log_info " cd $INSTALL_DIR && npx playwright install chromium" log_info "Browser tools will not work until dependencies are installed." - cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install chromium 2>/dev/null || true + cd "$INSTALL_DIR" && run_playwright_install 600 npx playwright install chromium || true ;; esac fi diff --git a/tests/test_install_sh_browser_install.py b/tests/test_install_sh_browser_install.py index 17476def8ff..80fb61a5efd 100644 --- a/tests/test_install_sh_browser_install.py +++ b/tests/test_install_sh_browser_install.py @@ -58,13 +58,21 @@ def test_install_script_strips_stale_snap_browser_override() -> None: def test_playwright_installs_are_timeout_guarded() -> None: text = INSTALL_SH.read_text() + # The timeout wrapper still exists and is used internally by the install + # wrapper, so every Playwright download remains bounded. assert "run_browser_install_with_timeout()" in text - assert "run_browser_install_with_timeout 600 npx playwright install chromium" in text + # Playwright installs now go through run_playwright_install(), which wraps + # run_browser_install_with_timeout (timeout-guarded) and adds an + # unrecognized-platform fallback retry. + assert "run_playwright_install 600 npx playwright install chromium" in text # --with-deps is still invoked on apt-based systems, but only when sudo # is available non-interactively (root or passwordless sudo). Non-sudo # service users fall back to the browser-only install — see # install_node_deps() in install.sh. - assert "run_browser_install_with_timeout 600 npx playwright install --with-deps chromium" in text + assert "run_playwright_install 600 npx playwright install --with-deps chromium" in text + # The wrapper still bounds the download with the timeout helper. + assert 'run_browser_install_with_timeout "$timeout_seconds" "$@"' in text + def test_install_script_supports_skip_browser_flag() -> None: @@ -86,3 +94,29 @@ def test_install_script_skips_with_deps_when_no_sudo() -> None: # service-user installs (systemd accounts, operator users, etc.). assert 'if [ "$(id -u)" -eq 0 ] || (command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null); then' in text assert "sudo npx playwright install-deps chromium" in text + + +def test_playwright_install_retries_with_platform_override_on_failure() -> None: + """Installer must self-correct when Playwright doesn't recognize the host. + + On apt releases newer than Playwright knows (Ubuntu 26.04, Debian 14, future + distros) `playwright install` hangs/fails (#35166). run_playwright_install + must retry ONCE with PLAYWRIGHT_HOST_PLATFORM_OVERRIDE pinned to the newest + known build — but only on failure (try-native-first, so a host Playwright + already supports keeps its native build and avoids the glibc mismatch in + microsoft/playwright#35114), and never when the operator pinned the value. + """ + text = INSTALL_SH.read_text() + + assert "run_playwright_install()" in text + assert "playwright_fallback_platform()" in text + # Fallback target is the newest known build, arch-aware. + assert 'echo "ubuntu24.04-x64"' in text + assert 'echo "ubuntu24.04-arm64"' in text + # Try native first: only retry after the first attempt fails. + assert 'if run_browser_install_with_timeout "$timeout_seconds" "$@" 2>/dev/null; then' in text + # Operator-pinned override is respected (retry skipped). + assert 'if [ -n "${PLAYWRIGHT_HOST_PLATFORM_OVERRIDE:-}" ]; then' in text + # The retry actually sets the override for the child process. + assert 'PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="$fallback" \\' in text +