
开头部分写避免教科书式开场用从业者口吻快速进入主题。1. 离线安装这件事坑不在“装”而在“对齐”做自动化测试的同学迟早会撞上这么一堵墙代码写好了用例跑通了结果到了客户内网、生产环境或者那台常年不联网的测试机上pip install playwright能装但playwright install chromium直接卡死在下载阶段报错不是超时就是证书问题。更麻烦的是你以为把安装包拖过去就完事了结果浏览器起不来报一堆.so文件找不到——这时候才反应过来Playwright 的“安装包”不是一个文件而是一整套浏览器运行时 系统依赖的集合。这篇文章我会把 Playwright 离线部署的完整链路拆开讲从版本对齐、浏览器缓存目录迁移、系统依赖收集到离线机器上的环境变量配置和验证脚本全部按可操作的步骤来。适合三类人看一是要给内网测试环境搭 Playwright 的运维或测试开发二是做 CI/CD 流水线时不想让每台构建机重复下载浏览器的工程效率同学三是纯粹被“未安装 Playwright”这种报错折磨过的新手。先说结论Playwright 的离线安装核心不是“下载一个安装包”而是“把浏览器运行时和它的依赖一起搬过去并让 Playwright 找到它们”。理解了这一句后面所有操作都是顺理成章的事。2. 离线部署的整体思路为什么不能只拷 node_modules2.1 Playwright 到底由哪几部分组成很多人会把 Playwright 当成一个普通的 Python 或 JavaScript 库pip 或 npm 装完就觉得完事了。实际上 Playwright 的运行时由三块构成Drvier 层也就是 Playwright 自身的 npm 包或 pip 包里面包含 node 运行时和驱动脚本负责和浏览器进程通信、执行协议指令。浏览器运行时Playwright 不是直接用你系统里装的 Chrome而是下载自己维护的特定构建版本 Chromium、Firefox、WebKit。这些二进制文件默认缓存到本机某个目录在代码里执行chromium.launch()时自动从缓存目录找。系统依赖库Linux 环境下浏览器要跑起来还依赖一堆图形库、字体库和系统库比如 libnss3、libatk、libgbm 之类。缺了任何一个浏览器启动时就会报找不到共享库的错误。离线部署要是只盯住第一部分后面两个全漏了那装完照样跑不起来。2.2 三种典型的离线场景对应的方案我实际工作中遇到过三种场景解决方案各有侧重场景一生产内网单机部署。有一台或几台不连外网的服务器需要跑 Playwright 脚本。这种最简单把整个浏览器缓存目录压缩拷过去设置好环境变量就行。如果系统是干净的 CentOS 或 Ubuntu还需要额外收集系统依赖。场景二CI 构建机集群。每次跑流水线都在新容器或新机器上装环境如果每次都从网上下载浏览器既慢又容易被限流。这种更推荐把浏览器缓存放到共享存储所有构建机通过PLAYWRIGHT_BROWSERS_PATH指向同一份缓存。场景三开发机与测试机版本混杂。开发机上 Playwright 升级了测试机还停在旧版导致两边跑的结果不一致。这种情况下离线安装包反而变成了“版本锁”——把特定版本配套的浏览器一起归档谁要环境谁解压天然避免漂移。2.3 离线包设计的一条主线版本号必须完全一致Playwright 对浏览器版本的管理方式有点特殊每个 Playwright 版本都会绑定一批特定构建号的浏览器。你在网上随便找的 Chromium 绿色版、Chrome 安装包哪怕版本看着差不多Playwright 也未必认——它启动浏览器时会校验一个内部构建号对不上就直接报错。所以离线包设计的主线就是在联网机器上用锁定的 Playwright 版本把浏览器拉下来连同一个版本的驱动层一起归档让离线机器完全复刻这套组合。版本号从 Playwright 版本到浏览器构建号一个都不能含糊。3. 动手之前先做三件事版本锁定、依赖清单、目录梳理3.1 锁定 Playwright 版本并生成浏览器清单无论你用 Python 还是 JavaScript 生态第一步都是在联网机器上锁定依赖版本。Python 侧我一般用 pip 的 requirements.txt 锁定到精确版本pip freeze | grep playwright # 输出示例playwright1.48.0然后在联网机器上执行这句命令看看当前版本到底需要哪些浏览器及其构建号npx playwright install --dry-run输出会列出浏览器名称、构建版本和下载地址。这个清单就是离线包的“物料清单”之后去内网部署时Playwright 主版本必须跟这个一致浏览器构建号也必须对上。提示如果你用 Python 的 playwright 包对应的 CLI 命令一样可用因为 pip 包也内置了完整的驱动层。3.2 找到浏览器缓存目录的位置Playwright 浏览器默认安装位置因操作系统而异操作系统默认缓存路径Linux~/.cache/ms-playwrightmacOS~/Library/Caches/ms-playwrightWindows%USERPROFILE%\AppData\Local\ms-playwright如果设置过PLAYWRIGHT_BROWSERS_PATH环境变量路径就按环境变量走。这个目录里你会看到chromium-XXXX、firefox-XXXX、webkit-XXXX这样的子目录后面的数字就是构建号。记住这个路径因为后面“打包”和“部署”都围绕它进行。3.3 盘点 Linux 下的系统依赖别等报错再补Linux 离线环境最烦的不是浏览器本身而是系统库。Playwright 官方提供的install-deps命令能自动装全依赖但离线环境下没法直接跑。我惯用的办法是在联网机器上先跑一次install-deps --dry-run拿到完整依赖包列表或者更简单粗暴——装完浏览器后对可执行文件做一次 ldd 扫描看看需要哪些共享库。# 找到 chromium 可执行文件路径 find ~/.cache/ms-playwright -name chrome -type f # 检查缺失的共享库 ldd /path/to/chrome | grep not found这一步的输出就是离线机器上需要手工安装或用离线 apt 源安装的依赖清单。常见的依赖包括libnss3、libnspr4、libatk1.0-0、libatk-bridge2.0-0、libcups2、libdrm2、libxkbcommon0、libxcomposite1、libxdamage1、libxfixes3、libxrandr2、libgbm1、libpango-1.0-0、libcairo2等。提前收集好这些.deb或.rpm包离线机器上用dpkg -i逐个安装就能绕开apt install需要联网的问题。4. 在联网机器上把整套运行时打包带走4.1 初始化项目并安装锁定版本的 Playwright建议在联网机器上建一个干净的目录专门用来产离线包。我习惯起名playwright-offline-bundle流程如下mkdir playwright-offline-bundle cd playwright-offline-bundle # Python 项目示例 python3 -m venv venv source venv/bin/activate pip install playwright1.48.0 # 或者 Node 项目示例 npm init -y npm install playwright1.48.0注意这里安装的是 Playwright 包本身这时候浏览器还没下载。用官方命令一键拉取配套浏览器playwright install chromium # 如果需要全部浏览器则执行playwright install如果你还需要系统依赖的收集建议此时在联网机器上先执行一次依赖安装确保本地浏览器能成功启动然后再做离线打包# Debian/Ubuntu 系列 playwright install-deps chromium注意install-deps会用 apt 安装一堆系统包需要在联网且有 sudo 权限的机器上执行。生产离线机上是不会重复执行这一步的所以依赖包的收集要在这台机器上完成。4.2 让浏览器缓存目录变成一个“可移动的包”默认缓存目录在用户主目录下直接拷贝当然也行但更好的做法是设置PLAYWRIGHT_BROWSERS_PATH环境变量把浏览器安装到项目目录内这样整个目录就是自包含的离线包拷贝时不用去翻系统路径。export PLAYWRIGHT_BROWSERS_PATH./browsers playwright install chromium执行完后项目目录结构大致是这样playwright-offline-bundle/ ├── venv/ # Python 虚拟环境含 playwright 包 ├── node_modules/ # 如果用 Node则包含 playwright/test 等 ├── browsers/ # 浏览器运行时构建号一目了然 │ ├── chromium-1148/ │ └── ffmpeg-headless-shell/ └── requirements.txt # 或 package.json用于还原依赖版本把整个项目目录压缩成一个 tar 包或 zip 包这就是你真正的“离线安装包”。它和普通软件安装包最大的区别是它不仅包含可执行程序还包含浏览器运行时和环境变量约定的目录结构。4.3 做一个“部署检查脚本”提前验证打包完整性我吃过亏浏览器目录拷到一半漏了子目录到客户现场才发现只能再远程指导对方补文件。后来我养成了一个习惯打包前先在本地跑一遍“空白环境模拟”# 临时清空浏览器路径模拟离线机初始状态 export PLAYWRIGHT_BROWSERS_PATH/tmp/test-browsers # 用脚本验证启动 python3 - EOF from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(about:blank) print(Chromium launch OK) browser.close() EOF这个脚本如果能在打包机器上从零跑通说明整个包的内容是完整的到了离线机器上只要环境变量指对位置理论上也能跑通。4.4 离线依赖包的收集思路如果你要在离线机器上装系统依赖可以准备一个依赖包目录。在联网的 Debian/Ubuntu 机器上先确认 apt 源能访问然后执行mkdir deb-deps cd deb-deps apt-get download $(playwright install-deps --dry-run chromium | grep -oP lib[^ ] | sort -u)这样会把依赖的.deb文件全部拉到当前目录。到离线机器上执行dpkg -i deb-deps/*.deb提示如果依赖之间有顺序要求dpkg 可能会报依赖错误。这时可以先用dpkg -i装第一遍把报缺的依赖再补一次如果还是缺说明收集的列表不完整得回联网机器补包。5. 离线机器上的安装配置环境变量、目录放置、运行验证5.1 解压目录结构与权限离线包到达目标机器后我建议放到一个固定的位置比如/opt/playwright-runtime或用户目录下的~/playwright-env避免散落。解压时注意保留文件权限尤其可执行位mkdir -p /opt/playwright-runtime tar -xzf playwright-offline-bundle.tar.gz -C /opt/playwright-runtime chmod -R x /opt/playwright-runtime/browsers如果你用的是 Python 虚拟环境直接把 venv 目录也解压过去即可但要注意虚拟环境里的路径是写死的如果源机器和解压机器路径不一致需要修正 venv 中的脚本前缀用python3 -m venv --relocatable这类处理或干脆在目标机器上重新建 venv 再装包离线安装。更稳妥的做法在离线机器上自己建立虚拟环境把联网机器上导出的requirements.txt和 Python 安装包.whl文件拷贝过去然后离线安装# 在有网机器上导出所有 whl pip download playwright1.48.0 -d wheels/ # 在离线机器上 python3 -m venv venv source venv/bin/activate pip install --no-index --find-linkswheels/ playwright1.48.05.2 设置 PLAYWRIGHT_BROWSERS_PATH 的两种方式离线机器上的关键一步是让 Playwright 知道你解压出来的浏览器目录在哪。两种方式任选方式一环境变量指向共享路径export PLAYWRIGHT_BROWSERS_PATH/opt/playwright-runtime/browsers这种方式适合把浏览器目录和项目代码分开管理的场景多台机器可以共享同一份浏览器目录。方式二不设环境变量把浏览器目录放到默认路径。如果你不想改环境变量也可以直接把解压出来的ms-playwright目录内容放到离线机器的默认缓存路径下比如 Linux 的~/.cache/ms-playwright。这样 Playwright 什么都不用配直接能找到。我推荐方式一因为默认路径分散在用户主目录下遇到不同用户跑 Playwright 的情况会各自找各自的缓存共享性差显式设置环境变量所有用户和所有项目都能指向同一份浏览器后续升级也好控制。5.3 首次运行验证的几个检查点离线机器上部署完建议按顺序跑三个检查检查一驱动层版本python3 -c import playwright; print(playwright.__version__) # 或 npx playwright --version确认版本号和你准备离线包时锁定的版本完全一致。检查二浏览器启动。用一个最小脚本验证python3 - EOF from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() print(Browser launched:, browser.version) browser.close() EOF如果启动时报找不到可执行文件优先检查环境变量路径是否正确和目录名是否匹配。如果报共享库缺失就用ldd定位缺的库再补装离线依赖包。检查三真实页面渲染。跑一个稍微完整的用例比如打开一个本地 HTML 文件并截屏确认渲染引擎没有因缺字体或图形库而异常python3 - EOF from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.set_content(h1Hello Offline/h1pPlaywright offline test/p) page.screenshot(pathoffline-test.png) print(Screenshot saved) browser.close() EOF如果这一步能正常出图说明整套离线环境已经可用了。5.4 项目代码中如何固化配置为了避免每个开发者或每台 CI 机器都要手动设置环境变量我习惯在项目的初始化脚本或 CI 配置里显式写入浏览器路径。Python 代码里也可以提前判定import os from playwright.sync_api import sync_playwright os.environ.setdefault(PLAYWRIGHT_BROWSERS_PATH, /opt/playwright-runtime/browsers) with sync_playwright() as p: browser p.chromium.launch() ...CI 流水线里则更简单直接在 stage 的环境变量里声明env: PLAYWRIGHT_BROWSERS_PATH: /opt/playwright-runtime/browsers6. 离线部署常见问题与排查技巧实录6.1 “Executable doesnt exist” 报错这是最经典的离线部署报错。Playwright 找不到指定浏览器路径时会提示类似Executable doesnt exist at ...。排查步骤按顺序来确认浏览器目录是否真的解压到了目标机器上重点检查目录名里的构建号是否和 Playwright 版本匹配。很多人下载了旧版本包但代码用的是新版本自然对不上。确认PLAYWRIGHT_BROWSERS_PATH环境变量有没有在这个 shell 会话中生效。echo $PLAYWRIGHT_BROWSERS_PATH看一眼如果为空说明你设置的变量在另一个会话里或者启动脚本里根本没加载。确认当前运行 Playwright 的用户对浏览器目录有读和执行权限。我遇到过把包解压在 root 目录下然后用普通用户跑脚本的情况权限不足也会报这种错。注意Playwright 对浏览器构建号的匹配是硬性的不能拿 Chrome 系统安装版去顶替。哪怕你的系统 Chrome 版本比 Playwright 自带的还新它也不认。6.2 浏览器能启动但打开页面白屏或直接崩溃这种情况多半是缺 GPU/图形相关的系统库。无头模式其实也需要一定的系统库支持有的环境还缺字体库导致中文显示成方块。解决办法补装libnss3、libnspr4、libasound2等常见依赖。如果还是崩溃尝试在启动时指定chromium_sandboxFalse或用--no-sandbox参数browser p.chromium.launch(args[--no-sandbox])这通常是容器环境或受限内网环境缺少用户命名空间导致的属于环境限制而非 Playwright 本身的问题。6.3 Python 虚拟环境跨机器移植失败我在 5.1 节提过venv 里写的路径是创建时的绝对路径换机器后pip可能还能用但 Python 解释器会找不到原路径下的包。与其折腾迁移不如在离线机器上重新建 venv然后用离线 whl 包安装。这个方法最干净不会有隐藏的路径问题。也有一种省事方案直接用 Node 版本。npx playwright在 node_modules 里的路径相对固定整体拷贝后配合PLAYWRIGHT_BROWSERS_PATH更容易做到“即拷即用”。6.4 CI 流水线中每次安装都超时有的团队在 CI 里写playwright install每次构建都从网络拉浏览器速度慢且容易触发限流。这不是严格意义上的离线问题但部署思路上是相通的让浏览器目录成为一个缓存层。在 CI 机器上把PLAYWRIGHT_BROWSERS_PATH指向一个持久化目录只在 Playwright 版本升级时更新一次能省下大量构建时间。首次构建时花几分钟下载之后所有流水线都复用时间损耗趋近于零。6.5 离线机器上 npx playwright install 想跑又跑不了经常有人到了离线机器上顺手执行npx playwright install发现半天没动静或者报错。这里提醒一下离线部署一旦做好你根本不需要在离线机器上执行 install 命令。浏览器已经解压到位环境变量已指定直接跑用例就行。如果执行了 install 命令Playwright 可能会试图连网下载缺失的组件反而弄得环境半残。记住这个原则离线机器的正确姿势是“解压 配置 运行”而不是“重新安装”。7. 我把这些坑踩完之后的一点体会离线部署 Playwright 这件事说难不难说简单也不简单关键就一句话把你用的 Playwright 版本和它配套的浏览器构建号当成一个整体永远一起打包、一起迁移、一起升级。只要版本对齐、路径明确、依赖齐全内网环境跑 Playwright 和开发机上跑没有本质区别。我个人在实际操作中最受益的一个习惯是把整个离线场景沉淀成一份“环境自检脚本”放在项目根目录的scripts/check_offline_env.py里。每次到新环境先跑一遍十秒内就能把问题定位到“版本不对、路径不对、依赖缺失”这三个方向之一省掉了大量靠猜的排查时间。建议你也照着做一份哪怕只输出一句Environment OK也比面对一堆英文报错干瞪眼强得多。