ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux无头环境部署Chrome与Selenium自动化测试指南

Linux无头环境部署Chrome与Selenium自动化测试指南 1. 为什么要折腾无图形界面Linux环境跑Chrome1.1 从一次CI构建机上的失败说起很多搞自动化测试的朋友都有过这样的经历本地Windows或者Mac上Selenium脚本跑得行云流水浏览器窗口刷刷地开、页面元素定位精准一切岁月静好。结果代码一推到远端在CI构建机上一执行满屏的报错直接让人头皮发麻。最常见的三连击是Chrome装了但启动即崩溃、chromedriver和浏览器版本对不上、还有一堆libX11.so.6找不到之类的共享库报错。为什么会这样因为绝大多数CI服务器、测试服务器、云主机都是无图形界面的最小化Linux系统。没有桌面环境、没有X Server、没有GNOME或者KDE这些图形层组件。而Chrome本质上是一个重度依赖图形环境的应用程序它启动时需要初始化GTK、加载字体引擎、访问显示服务。你把它丢到一个没有图形界面的环境里它自然是各种不配合。这个场景不是我编出来的而是在自动化测试领域极其普遍的需求CI流水线里跑E2E测试、无人值守的定时任务做页面巡检、数据采集脚本批量渲染页面、还有容器化方案里面跑浏览器实例。这些场景的共同特点是——你不需要用眼睛盯着浏览器看甚至不需要浏览器真的显示出来你只需要它后台运行、渲染DOM、执行JavaScript、响应操作就行。所以这篇文章要解决的问题非常具体在一台没有图形界面的Linux服务器上从零开始安装Chrome并且让它配合Selenium跑通自动化测试。我会把每一步的原理、命令、坑点和验证方法都写清楚你照着操作就能跑起来不用再在搜索框里反复横跳拼凑答案。1.2 无图形界面环境与桌面环境的核心差异要理解这个问题的本质得先搞清楚Chrome启动时依赖了什么。在桌面Linux上Chrome启动时要和X Server或者Wayland通信要加载一堆图形界面相关的共享库。在无头环境里这些组件统统不存在那Chrome怎么活答案就是Chrome的headless模式。headless模式下Chrome会跳过图形界面的初始化用内存里的虚拟显示缓冲区替代真实的屏幕输出。这意味着不需要X Server不需要display manager不需要桌面环境不需要窗口管理器浏览器在后台运行所有页面渲染都在内存中完成可以正常执行JavaScript、加载CSS、渲染DOM、处理网络请求所以无头环境跑Chrome核心是两件事把Chrome需要的非图形类依赖库装全然后强制Chrome以headless模式启动。很多人在第一步就卡住了因为依赖库缺得莫名其妙而且报错信息还很隐晦。另外还有一个容易忽略的点无头环境通常是root用户操作。root用户跑Chrome会遇到沙箱权限的问题——Running as root without --no-sandbox is not supported。这个鬼打墙式的问题让不少新手一脸懵其实解决起来很简单加一个参数就行。后面我会详细说。2. 动手前的准备版本选型和环境检查2.1 Chrome、Chrome for Testing、Chromium怎么选开始安装之前先花点时间说说用哪个版本。我在实际项目里见过很多装上了但跑不稳的案例十有八九是版本选型出了问题。先说普通Chrome。日常我们的桌面浏览器用的就是它优点是功能完整、兼容性好缺点是它有自动更新机制。这个自动更新在自动化测试场景里是个灾难级的坑昨天你的chromedriver还能驱动Chrome 122今天Chrome自动更新到了123你的测试脚本瞬间报废。CI环境里的Chrome还有另一个问题就是你需要通过包管理器比如apt或yum去装而各个发行版仓库里的Chrome版本陈旧得很跟最新的chromedriver根本没法匹配。再看Chrome for Testing这个是Google专门为自动化测试场景推出的构建版本。它有几个很明显的优势版本号固定且支持按需下载不会自动更新每个版本都有对应的官方下载地址方便在脚本里动态获取和chromedriver版本严格对应包含在同一个发布体系里目录结构简单便于放到指定位置最后是Chromium。它是Chrome的开源上游功能上差异不大但默认缺少部分自动编解码器比如H.264跑一些视频类页面会有兼容问题。另外Chromium的发布节奏更快API行为可能略有差异。我把这三者做了一个对比方便你快速决策对比维度普通ChromeChrome for TestingChromium自动更新有版本容易漂移无版本固定无驱动匹配需要手动处理版本体系一致匹配简单需要手动匹配编解码器完整完整缺部分多媒体组件下载方式包管理器或官方仓库官方JSON接口按版本获取各发行版仓库或源码编译推荐场景临时脚本、本地调试CI、自动化测试标准方案有定制需求的场景针对自动化测试这个需求我的建议很简单优先选Chrome for Testing。如果只是本地快速验证一下思路临时用系统的Chrome也没问题但一旦要进CI或者做持续跑批的测试任务一定要换成Chrome for Testing。2.2 环境检查清单安装之前先花三分钟检查一下你的Linux环境。干过的老手都知道这一步能避免后面80%的坑。第一个要确认的是操作系统发行版。不同发行版用的包管理器不一样依赖库的安装命令也不同。Debian/Ubuntu用aptCentOS/RHEL 7及以下用yumCentOS/RHEL 8、Rocky Linux、AlmaLinux用dnf。强烈建议你执行一下这个命令cat /etc/os-release第二是确认系统架构。Chrome for Testing针对x86_64和arm64都有对应的构建版本你得清楚自己的机器是哪种。执行这个uname -m别看这个命令简单我遇到过真有同事在arm64的机器上下载了x86_64的包结果安装阶段各种Function not implemented报错排查了大半天才反应过来。第三是确认网络环境。Chrome for Testing的版本信息和下载文件都在Google的服务器上你需要确保服务器能正常访问。如果你有代理或者镜像源提前配置好别等到下载超时了才想起这茬。可以用一个快速的连通性测试curl -sI https://googlechromelabs.github.io/chrome-for-testing/ | head -5第四是确认权限。安装Chrome到系统目录需要root权限如果你是非root用户务必确认有sudo权限。这个也提前测一下sudo -v如果回车后没有报错说明sudo密码有效可以继续。如果我看到Sorry, try again或者sudo: command not found那就要先解决权限问题否则后面全都白搭。最后看一下可用的磁盘空间。Chrome解压安装后大概要占500MB左右加上后续的依赖库、驱动建议预留至少1GB的可用空间。df -h /opt这几项检查完毕环境就绪可以正式开始安装了。3. 无图形界面安装Chrome的完整实操3.1 获取Chrome for Testing对应版本的下载地址Chrome for Testing的版本列表和下载地址都统一维护在一个JSON接口里。这个接口设计得非常规整用脚本解析非常方便。我们先获取全部已知版本的信息curl -sL https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json -o /tmp/chrome_versions.json这个JSON文件很大它包含所有历史版本和对应的下载链接。直接看会比较费劲推荐用jq来解析没装jq的话先装一下apt install -y jq或dnf install -y jq。要获取当前最新稳定版可以这样curl -sL https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json这个接口返回的内容就简单多了类似这样的结构{ channels: { Stable: { version: 126.0.6478.126, revision: 1343674, downloads: { ... } } } }如果你需要特定的大版本比如因为你的测试代码依赖某个旧版行为可以直接在known-good-versions里搜。我会写一段简单的脚本来提取指定版本的下载链接version126.0.6478.126 url$(jq -r --arg v $version .versions[] | select(.version $v) | .downloads.chrome[] | select(.platform linux64) | .url /tmp/chrome_versions.json) echo $url如果jq返回的url为空说明你的平台linux64在该版本下没有对应的Chrome构建换一个版本即可。这里有一个细节要注意Chrome for Testing平台的命名是linux64不是linux如果你记错名字解析出来的链接永远是空的。3.2 下载、解压与目录规划拿到下载链接后直接用wget或者curl下载。安装包是.tar.xz格式体积大概150MB左右取决于具体版本。cd /opt curl -L -o chrome.tar.xz $url tar -xf chrome.tar.xz解压完成后会生成一个目录名字格式是chrome-linux64/部分旧版本叫chrome-linux/。里面就是全部的可执行文件、资源文件和依赖库。目录规划这个事很多人不上心随手解压到/tmp就完事了。我不建议这么做原因有三一是/tmp在系统重启后可能被清空你下次跑测试就傻眼二是Chrome运行时会往自己的可执行文件所在目录找资源目录被移动或删除会导致各种诡异的崩溃三是CI环境里可执行文件路径应该在固定位置否则每次都要改配置。我的习惯是把Chrome安装到/opt/chrome/有清理强迫症也能一眼找到。操作如下mkdir -p /opt/chrome mv /opt/chrome-linux64/* /opt/chrome/ rm -rf /opt/chrome-linux64 /opt/chrome.tar.xz然后建立一个符号链接让google-chrome命令全局可用自动化脚本里用起来方便ln -s /opt/chrome/chrome /usr/local/bin/chrome ln -s /opt/chrome/chrome /usr/local/bin/google-chrome关于权限我建议把/opt/chrome目录设为所有用户可读可执行chmod -R 755 /opt/chrome3.3 系统依赖库的补齐这一步是最容易踩坑的地方。Chrome在图形环境里有完整的依赖链但在最小化安装的Linux服务器上一堆共享库都是缺失的。如果你不管直接执行chrome --version大概率会看到类似这样的报错chrome: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory最直接有效的诊断方法是使用ldd命令它会列出所有依赖的共享库ldd /opt/chrome/chrome | grep not found凡是显示not found的就是要补装的。不同发行版的包名不太一样我分别说。Debian/Ubuntu系统直接一次性把常用的依赖装齐apt-get update apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \ libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 \ libasound2 libpango-1.0-0 libcairo2 fonts-liberation libu2f-udevCentOS/RHEL/Rocky系统用yum或者dnfdnf install -y nss atk at-spi2-atk cups-libs libdrm libxkbcommon \ libXcomposite libXdamage libXfixes libXrandr mesa-libgbm alsa-lib \ pango cairo liberation-fonts libXScrnSaver装完后重新跑一遍ldd检查确认没有not found了ldd /opt/chrome/chrome | grep not found如果输出为空说明依赖全部就位。这里有一个小技巧每次装完依赖后都重新执行一次ldd检查不要凭感觉。因为有的依赖装了但版本不对ldd的检查结果最诚实。3.4 验证安装是否真的成功依赖齐全后先跑版本号命令测试能不能正常加载/opt/chrome/chrome --version正常会输出类似Google Chrome for Testing 126.0.6478.126的信息。注意这个命令不仅验证了可执行文件的完整性还验证了依赖库加载的链路是否通畅。如果到这里能正常输出版本说明Chrome本身已经能在无头环境里运行了。接下来做一个真正有价值的验证——用headless模式打开一个页面并截图验证渲染管线是否正常/opt/chrome/chrome --headless --no-sandbox --disable-gpu \ --screenshot/tmp/homepage.png \ --window-size1280,720 https://example.com如果命令执行成功会看到类似Screenshot saved to /tmp/homepage.png的提示然后检查文件是否生成且大小合理ls -lh /tmp/homepage.png这一步极其重要因为它验证了Chrome在headless模式下能完成页面加载、渲染、屏幕截图的全流程。如果这一步能过自动化测试的浏览器基础就已经打好了。如果在这里报错说明还有依赖没补齐多半是字体库或者图形库问题。4. 让Selenium跑通驱动管理与无头模式配置4.1 驱动版本与浏览器版本的匹配逻辑Chrome能跑起来只是第一步自动化测试还需要一个关键的中间件——chromedriver。它的作用是把Selenium发的WebDriver协议请求翻译成Chrome能理解的DevTools协议指令。版本匹配的原则很简单粗暴chromedriver的主版本号必须和Chrome的主版本号完全一致。Chrome是126chromedriver就必须是126相差一个主版本都不行否则Selenium会直接报SessionNotCreatedException。匹配方式有三种第一种是手动下载匹配的chromedriver然后通过webdriver.Chrome(executable_path...)指定路径。这个方法理论可行但版本一多了就非常痛苦每次升级Chrome都要手动重新下载一次驱动极其反人类。第二种是使用Selenium 4.6以上版本内置的Selenium Manager。从4.6开始如果你不显式指定驱动路径Selenium Manager会自动检测Pychrome的版本然后去官方源下载匹配的驱动。这个功能极大简化了驱动管理全程自动。第三种是使用webdriver_manager这个第三方库。它和Selenium Manager功能类似但独立于Selenium存在而且提供了更灵活的配置选项比如通过代理下载、缓存版本的清理策略等。如果你用的Selenium版本较老4.6以下webdriver_manager是最佳选择。我个人的建议是能用Selenium Manager就用Selenium Manager老项目依赖不好变动的可以用webdriver_manager。两种方案我下面都会给代码示例。4.2 一个能直接跑通的最小化Selenium脚本环境准备。我用Python做示例因为Python在自动化测试领域的生态最成熟遇到问题也最好搜方案。先装依赖pip3 install selenium如果你的Selenium版本是4.6以下还需要额外装一个驱动管理器pip3 install webdriver-manager装好后写一个最小化脚本。这是Selenium 4.6配合Selenium Manager的写法from selenium import webdriver from selenium.webdriver.chrome.options import Options # 关键配置无头模式 options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) # Selenium Manager自动寻找匹配的chromedriver driver webdriver.Chrome(optionsoptions) # 访问页面验证标题 driver.get(https://example.com) print(页面标题:, driver.title) assert driver.title Example Domain # 截图确认渲染正常 driver.save_screenshot(/tmp/selenium_homepage.png) print(截图已保存) driver.quit()如果是Selenium 4.6以下版本驱动加载部分的代码需要改一下from selenium import webdriver from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) # 使用webdriver_manager自动下载匹配的chromedriver service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionsoptions)核心参数的含义我逐个解释一下--headlessnew指定使用新版无头模式。Chrome 112之后的新headless模式和旧版--headlessold在行为上更接近有头模式JavaScript兼容性更好。如果你用的是Chrome 109及更早版本这个参数要退化成--headless。--no-sandbox禁用Chrome的沙箱机制。无头环境经常以root身份运行而Chrome基于安全考虑不允许root用户启动带沙箱的实例。这个参数在CI环境里几乎是必加的。--disable-dev-shm-usage禁用/dev/shm的使用。很多容器和虚拟机的/dev/shm只有64MBChrome默认使用它做共享内存空间不足会直接导致页面崩溃或者白屏。--disable-gpu禁用GPU硬件加速。无头环境没有GPU加了反而稳定。--window-size设置浏览器窗口大小。这决定了页面的渲染视口尺寸对断言元素位置、截图尺寸都有影响。4.3 无头模式参数的最优组合与调优建议参数这东西不是越多越好。有些人习惯把网上的参数一堆全贴上去导致日志里全是警告排障时根本分不清哪些参数有效。按我的经验无头环境下的参数组合有一个黄金配置在稳定性、兼容性和日志清晰度之间比较平衡options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) options.add_argument(--disable-blink-featuresAutomationControlled) options.binary_location /opt/chrome/chrome最后一行的binary_location指定了Chrome可执行文件的位置。如果你的Chrome在/usr/local/bin/chrome下面有符号链接不指定也能通过默认路径找到。但如果你装了多个版本的Chrome比如系统自带一个、Chrome for Testing装了一个强烈建议显式指定否则你可能自己都不清楚Selenium驱动的是哪个版本。还有一个值得注意的参数--disable-blink-featuresAutomationControlled。这个参数能在一定程度上减少网页检测到自动化操作的可能性对爬数据场景尤其有用。加上它之后navigator.webdriver会返回undefined部分反爬策略会被绕过。但需要说明的是这并不代表你能绕过所有风控只是降低了一个检测维度。如果跑的是大型应用页面内存压力大可以额外加--disable-application-cache、--disable-extensions这些来减少资源占用。不过这些属于锦上添花核心参数就上面那六个。5. 常见报错与完整排查链路5.1 Chrome启动即崩溃依赖库缺失的排查这是最高频的报错。症状是执行chrome --version时报错内容通常是/opt/chrome/chrome: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory这个错误信息已经告诉你了——缺库。但问题是你可能装完一堆库之后又冒出来新的缺失。因为Chrome依赖的库有几十个不是一次性全报出来的。正确的排查链路是第一步确认哪些库缺失ldd /opt/chrome/chrome | grep not found第二步逐一把缺失的库名对应到系统包名。这一步需要一点经验我做过一个常用映射表缺失库名Debian/Ubuntu包名CentOS/RHEL系列包名libnss3.solibnss3nsslibatk-1.0.so.0libatk1.0-0atklibatk-bridge-2.0.so.0libatk-bridge2.0-0at-spi2-atklibcups.so.2libcups2cups-libslibXcomposite.so.1libxcomposite1libXcompositelibXdamage.so.1libxdamage1libXdamagelibgbm.so.1libgbm1mesa-libgbmlibasound.so.2libasound2alsa-lib第三步安装缺失的包然后回到第一步重新检查直到ldd没有not found输出。这个循环看起来简单但实际操作会有点枯燥。我建议直接用我在3.3节给的安装命令把常用依赖一次装全然后做ldd检查基本一轮就能搞定。5.2 Root用户沙箱报错的处理第二个高频报错症状是执行脚本时Selenium报Message: unknown error: Chrome failed to start: crashed. (unknown error: DevToolsActivePort file doesnt exist)这个报错信息非常具有迷惑性很多人看到DevToolsActivePort file doesnt exist就以为是端口配置问题于是去乱调--remote-debugging-port参数结果折腾半天也没解决。其实这个问题最常见的原因是两个第一个原因是root用户没有关闭沙箱。如果你在无头环境用root执行Chrome会拒绝启动。解决方案是在启动参数里加上--no-sandbox。第二个原因是/dev/shm空间不足。容器环境默认/dev/shm只有64MBChrome占满之后DevTools端口文件就没法正常创建。解决方案是加--disable-dev-shm-usage或者启动容器时用--shm-size2g扩大共享内存。我来复现一下排查这个报错的完整链路你的脚本报了DevToolsActivePort错误先确认是不是root用户执行id -u如果输出0说明是root。确认启动参数里有没有--no-sandbox用Python打印options.arguments看看。如果确实是root且没有加no-sandbox参数加上即可。如果加了还是不行检查/dev/shm大小执行df -h /dev/shm如果小于100MB果断加--disable-dev-shm-usage。如果还不行那就需要看详细的Chrome崩溃日志了。可以用以下命令手动跑一次看输出的具体崩溃原因chrome --headless --no-sandbox --disable-dev-shm-usage --disable-gpu --dump-dom https://example.com如果--dump-dom能正常输出HTML内容说明Chrome本身没问题责任在Selenium侧的参数传递上。检查你的代码是不是在options.add_argument和webdriver.Chrome(optionsoptions)之间做了奇怪的操作。5.3 版本不匹配的SessionNotCreatedException第三个典型报错selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 126 Current browser version is 125.0.6422.112这个报错已经很直白了——chromedriver和Chrome版本主版本不一致。在Selenium 4.6的Selenium Manager时代这个报错的出现频率已经大幅降低但如果你手动指定了chromedriver路径或者CI环境里缓存了旧的驱动就会踩到这个坑。排查思路查看当前Chrome版本/opt/chrome/chrome --version查看当前chromedriver版本chromedriver --version对比两者的主版本号。如果不一致让驱动管理器重新拉取匹配版本或者清掉旧的chromedriver缓存webdriver_manager缓存在~/.wdm/目录下。如果你是手动下载驱动记住这个URL规则https://storage.googleapis.com/chrome-for-testing-public/{版本号}/linux64/chromedriver-linux64.zip把版本号换成你Chrome的对应版本。5.4 白屏、超时和连接中断资源限制类问题排查第四类是运行过程中出现页面空白、元素定位超时、连接断开等稳定性问题。这类问题往往是资源限制导致的而非版本或配置错误。重点排查以下三个方面首先是内存。Chrome无头模式跑复杂页面内存占用轻松上1GB。用free -h检查可用内存如果少于2GB建议在启动参数里加--disable-extensions和--disable-application-cache减少资源消耗或者减少并发实例数。其次是临时目录。Chrome运行时会往/tmp写大量临时文件如果/tmp空间不足行为会变得非常诡异。用df -h /tmp检查空间必要时可以用--user-data-dir/tmp/chrome-profile指定一个专用的配置目录方便隔离和清理。最后是文件描述符限制。如果你的自动化测试脚本并发页面开得多会触发Too many open files错误。这种情况用ulimit -n查看当前限制临时提升可以用ulimit -n 65535永久修改需要改/etc/security/limits.conf。6. CI流水线里的实操要点与扩展方案6.1 CI环境下的安装持久化与缓存策略一次性手动安装Chrome没问题但自动化测试讲究可重复你要把这些操作固化到流水线脚本里。我的做法是写一个安装脚本每次CI构建时先检查Chrome是否存在、版本是否符合预期不符合才重新下载安装。这样比每次构建都重新下载150MB大包要高效得多也避免了对Google服务器的频繁请求。下面是一个简化的Shell脚本逻辑你可以参考#!/bin/bash set -e CHROME_DIR/opt/chrome EXPECTED_VERSION${CHROME_VERSION:-126.0.6478.126} # 版本检查如果已安装且版本一致直接跳过 if [ -x $CHROME_DIR/chrome ]; then CURRENT_VERSION$($CHROME_DIR/chrome --version | awk {print $3}) echo 当前Chrome版本: $CURRENT_VERSION if [ $CURRENT_VERSION $EXPECTED_VERSION ]; then echo Chrome版本匹配跳过安装 exit 0 fi fi # 获取下载链接并安装 curl -sL https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json -o /tmp/versions.json url$(jq -r --arg v $EXPECTED_VERSION \ .versions[] | select(.version $v) | .downloads.chrome[] | select(.platform linux64) | .url \ /tmp/versions.json) cd /tmp curl -L -o chrome.tar.xz $url tar -xf chrome.tar.xz rm -rf $CHROME_DIR mkdir -p $CHROME_DIR mv chrome-linux64/* $CHROME_DIR/ ln -sf $CHROME_DIR/chrome /usr/local/bin/chrome ln -sf $CHROME_DIR/chrome /usr/local/bin/google-chrome这个脚本里的版本号我建议用环境变量传入这样可以通过Jenkins参数、GitLab CI变量等方式动态调整不用每次改脚本。6.2 远程调试模式无头环境下的救急神器无头环境下跑测试最痛苦的事情是脚本跑了但页面长什么样、元素状态对不对你完全看不见。全凭日志和截图猜。Chrome的远程调试功能DevTools Protocol简称CDP能帮你解决这个问题。在启动参数里加上调试端口chrome --headless --no-sandbox --remote-debugging-port9222 https://example.com然后在你本地电脑的浏览器里访问http://服务器IP:9222能直接在网页端查看当前页面的DOM结构、网络请求和截图。如果你的服务器内网不能直接访问可以通过SSH端口转发ssh -L 9222:localhost:9222 root服务器IP这个操作对于调试定位元素、排查页面异常、观察前端错误日志非常有帮助。特别是那些本地跑得好好的服务器上就出错的诡异问题靠截图往往看不出所以然但打开DevTools Protocol一查具体是哪个请求挂了你心里就有数了。Selenium也支持直接连接到已经启动的Chrome实例from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_experimental_option(debuggerAddress, 127.0.0.1:9222) driver webdriver.Chrome(optionsoptions)这个能力非常适合用来调试先手动启动一个Chrome用远程调试连接进去手动操作一遍脚本再接管后续步骤实现半自动调试模式。6.3 并发执行时的资源隔离与调优自动化测试跑多了总会遇到性能瓶颈——单个Chrome实例跑测试跑完一轮要十几分钟项目越来越大这个时间让人抓狂。于是大家都会想到并发执行。无头环境下的并发有几个关键参数需要关注第一个是浏览器实例的资源隔离。每个Chrome实例的--user-data-dir参数必须不同否则多个实例共用同一个配置目录轻则Cookie串号、重则配置锁冲突导致崩溃。建议用进程ID和线程ID拼接目录名import os import tempfile profile_dir os.path.join(tempfile.mkdtemp(), chrome_profile) options.add_argument(f--user-data-dir{profile_dir})第二个是并发量度的控制。Chrome无头实例平均内存占用在300-800MB之间4GB内存的机器开两到三个并发实例就是极限了内存不足会触发OOM Killer把进程杀掉。稳妥的建议是每个实例预留1GB内存然后慢慢调优。第三个是chromedriver的连接管理。Selenium的WebDriver实例不是线程安全的多线程场景下必须每个线程创建自己的driver实例。如果用的是pytest配合pytest-xdist按进程并发天然就有隔离性比自己在项目里搞线程池要稳妥得多。6.4 容器化封装把整套环境做成镜像如果你们的CI已经容器化把Chrome装进Docker镜像是一个更干净的方案。我简单说一下我的Dockerfile思路FROM python:3.11-slim # 安装依赖库 RUN apt-get update apt-get install -y \ curl jq wget \ libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \ libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \ libgbm1 libasound2 libpango-1.0-0 libcairo2 fonts-liberation \ --no-install-recommends rm -rf /var/lib/apt/lists/* # 安装Chrome for Testing ARG CHROME_VERSION126.0.6478.126 RUN curl -sL https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json -o /tmp/versions.json \ url$(jq -r --arg v $CHROME_VERSION \ .versions[] | select(.version $v) | .downloads.chrome[] | select(.platform linux64) | .url \ /tmp/versions.json) \ curl -L -o /tmp/chrome.tar.xz $url \ tar -xf /tmp/chrome.tar.xz -C /opt \ mv /opt/chrome-linux64 /opt/chrome \ ln -s /opt/chrome/chrome /usr/local/bin/chrome \ rm -rf /tmp/versions.json /tmp/chrome.tar.xz # 安装Python依赖 RUN pip install --no-cache-dir selenium pytest pytest-xdist WORKDIR /tests CMD [pytest, -v, --distloadscope]容器方案有几个天然优势一是环境隔离彻底不会污染宿主机二是镜像构建后启动极快不用每次重新装依赖三是通过registry管理版本开发环境、测试环境、CI环境可以轻松保持完全一致。需要注意的一点是容器内跑Chrome时/dev/shm默认只有64MB这个问题依然存在。两种解决办法运行时加--shm-size2g或者在启动参数里加--disable-dev-shm-usage。前者更接近生产环境行为后者更省资源按需选择。6.5 使用Chrome DevTools协议做更深层的自动化控制Selenium之外还有一条更底层更强大的路直接用Chrome DevTools Protocol。Selenium本质上也是通过CDP驱动Chrome的但它封装了WebDriver协议抽象层级更高同时也丢掉了一些底层能力。如果你的测试场景需要获取网络请求详情、拦截和修改响应、监控性能指标、模拟弱网条件这些用Selenium实现非常吃力但用CDP的Network域和Performance域就很简单。实际操作中我不建议完全抛弃Selenium改用CDP而是建议在Selenium脚本里按需注入CDP命令。Selenium 4的driver.execute_cdp_cmd()就是干这个的# 启用网络监控 driver.execute_cdp_cmd(Network.enable, {}) # 设置请求拦截修改User-Agent driver.execute_cdp_cmd(Network.setUserAgentOverride, { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 }) # 模拟网络条件 driver.execute_cdp_cmd(Network.emulateNetworkConditions, { offline: False, latency: 100, downloadThroughput: 500 * 1024, uploadThroughput: 500 * 1024 })无损截图、生成PDF这些能力也能通过CDP实现。比如页面转PDFresult driver.execute_cdp_cmd(Page.printToPDF, { printBackground: True, format: A4 }) with open(/tmp/page.pdf, wb) as f: f.write(base64.b64decode(result[data]))这个操作在生成测试报告、保存证据文件、归档页面快照时非常实用比截图还要可靠——PDF是矢量内容缩放不糊。另一个CDP的实战场景是获取页面所有网络请求的响应状态码。这在排查某个外部资源加载失败导致测试失败时非常高效driver.execute_cdp_cmd(Network.enable, {}) # 先清空记录 driver.execute_cdp_cmd(Network.clearBrowserCache, {}) # 页面操作后获取性能条目 entries driver.execute_script( return performance.getEntriesByType(resource).map(e ({ name: e.name, duration: Math.round(e.duration), transferSize: e.transferSize })) )说实话无头Linux环境跑自动化的方向能聊的还有很多比如Puppeteer、Playwright这些更现代化的工具链。但它们底层调用的依然是你安装的Chrome浏览器你把这篇文章里关于依赖、沙箱、/dev/shm、版本匹配这些底层逻辑吃透了换成任何浏览器自动化框架都能少踩很多坑。按我自己的经验目前线上稳定运行的那套测试体系跑几千个用例每天定时触发几轮任务Chrome这个环节基本不闹脾气了。最开始的兼容问题、崩溃问题、内存问题都是靠上面这些排查链路一点点解决的。环境这关一旦过了后面就是脚本本身的稳定性问题了。希望这篇文章能帮你把环境这关顺利过掉。
返回列表