1616* **Idempotent across processes.** ``playwright install chromium`` is itself
1717 idempotent, and the probe skips it entirely once the binary is present, so a
1818 second *run* finds it installed and pays nothing.
19+ * **No wasted downloads on fresh Linux machines.** Before downloading on
20+ Linux, a cheap probe checks for the shared libraries Chromium needs; when
21+ any are missing, the exact remedy is printed and the launch aborts cleanly
22+ instead of downloading a browser that could not start anyway.
1923* **Opt-out for air-gapped / pre-provisioned environments.** Set
2024 ``OPENADAPT_FLOW_NO_AUTO_INSTALL=1`` to skip the auto-install; the original
2125 clear Playwright "Executable doesn't exist ... run playwright install" error
2428
2529from __future__ import annotations
2630
31+ import ctypes .util
2732import importlib .util
2833import os
2934import re
3843
3944_NOTICE = "Downloading the Chromium browser OpenAdapt needs (first run only)…"
4045
46+ #: Shared-library soname bases Playwright's Chromium needs at launch time on
47+ #: Linux. These mirror the packages ``playwright install-deps chromium``
48+ #: installs (NSS, ATK, X11 helpers, audio, GBM, …). Names are the
49+ #: ``ctypes.util.find_library`` form: no ``lib`` prefix, no version suffix.
50+ _LINUX_CHROMIUM_SONAMES = (
51+ "nss3" ,
52+ "nspr4" ,
53+ "atk-1.0" ,
54+ "atk-bridge-2.0" ,
55+ "atspi" ,
56+ "cups" ,
57+ "drm" ,
58+ "xkbcommon" ,
59+ "xcomposite" ,
60+ "xdamage" ,
61+ "xfixes" ,
62+ "xrandr" ,
63+ "gbm" ,
64+ "pango-1.0" ,
65+ "cairo" ,
66+ "asound" ,
67+ )
68+
4169
4270class BrowserSupportMissing (RuntimeError ):
4371 """The optional Playwright driver is absent for a browser operation."""
@@ -73,6 +101,53 @@ def _opted_out() -> bool:
73101 return bool (os .environ .get (NO_AUTO_INSTALL_ENV ))
74102
75103
104+ #: The Debian/Ubuntu package names matching :data:`_LINUX_CHROMIUM_SONAMES`,
105+ #: shown as the manual alternative to ``playwright install-deps``.
106+ _LINUX_APT_PACKAGES = (
107+ "libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 "
108+ "libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 "
109+ "libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2"
110+ )
111+
112+
113+ def _missing_chromium_system_libs () -> list [str ]:
114+ """Return the Chromium shared libraries missing on this Linux machine.
115+
116+ Uses ``ctypes.util.find_library`` (an ``ldconfig``-based lookup: cheap,
117+ offline, and no subprocess spawned by us). Returns an empty list on
118+ non-Linux platforms, where Playwright ships everything Chromium needs.
119+ """
120+ if sys .platform != "linux" :
121+ return []
122+ return [
123+ soname
124+ for soname in _LINUX_CHROMIUM_SONAMES
125+ if ctypes .util .find_library (soname ) is None
126+ ]
127+
128+
129+ def _require_linux_system_libs () -> None :
130+ """Refuse to download Chromium when its system libraries cannot exist.
131+
132+ Fresh Linux machines without the X11/audio/NSS stack used to download the
133+ whole browser and only then fail at launch. When libraries are missing,
134+ print the exact remedy FIRST and abort cleanly before any download.
135+ """
136+ missing = _missing_chromium_system_libs ()
137+ if not missing :
138+ return
139+ libs = ", " .join (missing )
140+ raise RuntimeError (
141+ "Chromium cannot launch on this machine yet: required system "
142+ f"libraries are missing ({ libs } ).\n \n "
143+ "Install them once with:\n \n "
144+ " sudo python -m playwright install-deps chromium\n \n "
145+ "or, on Debian/Ubuntu:\n \n "
146+ f" sudo apt-get install -y { _LINUX_APT_PACKAGES } \n \n "
147+ "Then run your command again. Nothing was downloaded."
148+ )
149+
150+
76151def _chromium_present () -> bool :
77152 """Return whether Playwright's Chromium browser binary is installed.
78153
@@ -109,11 +184,18 @@ def _chromium_present() -> bool:
109184def _install_chromium () -> None :
110185 """Run ``python -m playwright install chromium`` once, with a notice.
111186
187+ On Linux, verifies first that Chromium's shared libraries are present and
188+ aborts with the exact remedy when they are not, so no download is wasted
189+ on a browser that could not launch.
190+
112191 Raises:
113- RuntimeError: if the install subprocess fails (e.g. offline), with an
114- actionable message pointing at the manual command and the opt-out.
192+ RuntimeError: if system libraries are missing (Linux), or if the
193+ install subprocess fails (e.g. offline or behind a proxy that
194+ blocks the Playwright CDN), with an actionable message pointing
195+ at the manual command, the proxy variable, and the opt-out.
115196 """
116197 require_browser_support ()
198+ _require_linux_system_libs ()
117199 print (_NOTICE , file = sys .stderr , flush = True )
118200 try :
119201 subprocess .run (
@@ -123,11 +205,16 @@ def _install_chromium() -> None:
123205 except (subprocess .CalledProcessError , OSError ) as exc :
124206 raise RuntimeError (
125207 "openadapt-flow could not automatically download the Chromium "
126- "browser it needs. Run \n \n "
208+ "browser it needs. To install it manually, run: \n \n "
127209 " playwright install chromium\n \n "
128- "manually (you may be offline or behind a proxy), or set "
129- f"{ NO_AUTO_INSTALL_ENV } =1 to disable auto-install if the browser "
130- "is provisioned another way."
210+ "If you are behind a corporate proxy or firewall that blocks the "
211+ "Playwright download CDN, set HTTPS_PROXY first "
212+ "(for example: export HTTPS_PROXY=http://proxy.example.com:8080) "
213+ "and retry. If you are fully offline, install the browser on a "
214+ "connected machine and copy Playwright's cache directory "
215+ "(~/.cache/ms-playwright), or provision it another way. You can "
216+ f"also set { NO_AUTO_INSTALL_ENV } =1 to disable auto-install "
217+ "entirely."
131218 ) from exc
132219
133220
@@ -139,10 +226,11 @@ def ensure_chromium_installed() -> None:
139226 (subsequent calls return immediately) and is a cheap no-op when the browser
140227 is already installed.
141228
142- When the browser is missing it downloads it once via
143- ``playwright install chromium`` and prints a one-time notice. When
144- :data:`NO_AUTO_INSTALL_ENV` is set it does nothing, leaving Playwright's own
145- "browser not installed" error to surface at launch.
229+ When the browser is missing it verifies Chromium's system libraries
230+ (Linux), then downloads it once via ``playwright install chromium`` and
231+ prints a one-time notice. When :data:`NO_AUTO_INSTALL_ENV` is set it does
232+ nothing, leaving Playwright's own "browser not installed" error to surface
233+ at launch.
146234 """
147235 global _ensured
148236 require_browser_support ()
0 commit comments