离线环境Selenium自动化测试部署指南:从依赖打包到CI/CD集成
1. 项目概述:为什么我们需要一个离线的Selenium环境?
在自动化测试的日常工作中,Selenium几乎是绕不开的名字。它就像测试工程师手中的瑞士军刀,能驱动浏览器完成各种复杂的模拟操作。但不知道你有没有遇到过这样的场景:公司出于安全考虑,核心的测试服务器完全隔离在内部网络,无法访问外网;或者你需要在一个客户现场的封闭环境中快速部署一套自动化测试框架。这时候,你会发现那些“一键安装”的教程瞬间失灵,pip install selenium 这条命令变得苍白无力。
这就是我们今天要解决的问题:离线部署Selenium。这不仅仅是在一台不能上网的机器上装几个包那么简单,它是一套完整的、可复现的环境搭建方案。其核心价值在于环境可控、部署可靠、过程可追溯。对于金融、军工、政府等对网络安全有严格要求的行业,或者需要为交付给客户的软件产品配套自动化测试能力的团队来说,掌握这套技能至关重要。它意味着你的自动化测试能力不再受网络环境的制约,可以像部署一个普通应用一样,在任何地方快速拉起一套测试环境。
接下来,我将以一个典型的场景为例:你有一台能上网的Windows开发机(我们称之为“打包机”),和一台完全离线的、运行CentOS 7的Linux测试服务器(我们称之为“目标机”)。我们的目标是在目标机上,搭建一个包含Python、Selenium WebDriver以及对应浏览器(以Chrome为例)的完整自动化测试环境。整个过程,我们将手把手拆解,确保每一步你都能看懂、能操作、能成功。
2. 环境搭建的整体思路与物料准备
在开始动手之前,我们必须先理清思路。离线部署的核心矛盾在于:目标机没有网络,但安装软件又需要依赖。解决这个矛盾的标准思路是**“依赖打包,离线搬运”**。
2.1 核心思路拆解
我们的部署可以分解为三个层次,从下到上依次是:
- 系统层依赖:主要是浏览器运行所需的共享库,比如图形库、字体库等。在CentOS 7上,我们通常通过
yum管理。 - 语言运行时层:即Python环境。我们需要一个干净的Python解释器,以及pip包管理工具。
- 应用层:即Selenium库、浏览器驱动(如ChromeDriver)和浏览器本体(如Chrome)。
对应的,我们的准备工作也需要针对这三层展开。你需要在有网络的“打包机”上,提前下载好所有需要的安装包和依赖包。
2.2 物料清单与下载指南
请在联网的Windows打包机上,准备好以下所有物料。我将详细说明每一项的作用和获取方式。
1. Python离线安装包对于CentOS 7,我强烈建议使用Python 3.8或3.9版本,它们与CentOS 7自带的系统库兼容性较好。避免使用太新的Python 3.11+,可能会遇到glibc版本不兼容的问题。
- 下载地址:前往Python官网(https://www.python.org/downloads/source/)下载对应版本的源代码压缩包,例如
Python-3.8.18.tgz。选择“Source release”是为了能在目标机上编译安装,兼容性最好。
2. Selenium及其依赖的Python Wheel包Wheel是Python的一种二进制包格式,离线安装时比源码包更方便,因为它可能已经包含了编译好的二进制组件。
- 操作方法:在打包机上安装一个与目标机架构(通常是x86_64)一致的Python环境。然后使用
pip download命令。# 假设在打包机的某个空目录下操作 mkdir python_packages && cd python_packages pip download selenium urllib3 certifi --platform manylinux2014_x86_64 --python-version 38 --only-binary=:all:pip download:命令用于下载包但不安装。--platform manylinux2014_x86_64:指定Linux平台兼容的二进制格式。--python-version 38:指定为Python 3.8。--only-binary=:all::强制下载wheel包,不下载源码。- 关键依赖:
urllib3(网络请求)、certifi(CA证书)是Selenium运行时常需的。
3. Chrome浏览器及ChromeDriver离线包这是Selenium控制Chrome的核心。
- Chrome RPM包:访问Chrome企业版下载页面(https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm),直接下载最新的稳定版RPM安装包。
- ChromeDriver:访问ChromeDriver下载页(https://chromedriver.chromium.org/),下载与上述Chrome版本匹配的Linux版压缩包(如
chromedriver_linux64.zip)。版本匹配至关重要,不匹配会导致Selenium无法启动浏览器。
4. CentOS 7系统依赖包(通过Yum离线下载)这是最容易出错的一步。我们需要模拟在目标机上安装Chrome和编译Python时可能需要的所有系统库。
- 操作方法:在打包机上,你可以使用Docker启动一个干净的CentOS 7容器来模拟目标机环境,或者如果你有一台临时的、可联网的CentOS 7虚拟机更好。
# 在临时的CentOS 7机器上操作 # 1. 安装`yum-utils`工具,它包含`yumdownloader`命令 sudo yum install -y yum-utils # 2. 创建一个目录存放所有依赖包 mkdir -p /tmp/offline_yum # 3. 下载Chrome可能需要的依赖(以实际安装时的报错为准,这里是常见集合) sudo yumdownloader --resolve --destdir=/tmp/offline_yum \ google-chrome-stable \ alsa-lib \ atk \ cups-libs \ gtk3 \ libXcomposite \ libXcursor \ libXdamage \ libXext \ libXi \ libXrandr \ libXScrnSaver \ libXtst \ pango \ xorg-x11-fonts-100dpi \ xorg-x11-fonts-75dpi \ xorg-x11-fonts-cyrillic \ xorg-x11-fonts-misc \ xorg-x11-fonts-Type1 \ xorg-x11-utils # 4. 下载编译Python 3.8所需的开发工具和库 sudo yumdownloader --resolve --destdir=/tmp/offline_yum \ gcc \ make \ zlib-devel \ bzip2-devel \ openssl-devel \ ncurses-devel \ sqlite-devel \ readline-devel \ tk-devel \ libffi-devel \ xz-devel--resolve:自动下载依赖的依赖。--destdir:指定下载包存放的目录。 操作完成后,将/tmp/offline_yum目录下的所有.rpm文件打包。
注意:不同时期的CentOS 7镜像和Chrome版本,所需的依赖可能略有差异。最稳妥的方法是先在目标机(或一个干净环境)上尝试安装,根据报错信息再用
yumdownloader下载缺失的包。上述列表是一个覆盖面较广的通用清单。
5. 传输工具将所有从打包机上下载好的物料(Python源码包、Python wheel包、Chrome RPM、ChromeDriver、Yum依赖包),通过U盘、内部文件服务器或任何允许的物理介质,拷贝到离线的CentOS 7目标机上。假设我们统一放在目标机的/opt/offline_setup目录下。
3. 目标机离线环境部署实操
现在,我们正式登录到那台离线的CentOS 7服务器。接下来的操作需要root权限或sudo权限。
3.1 步骤一:部署系统层依赖
首先,我们需要解决Chrome浏览器和编译Python所需的基础系统库。我们将使用提前下载好的RPM包创建一个本地Yum仓库。
# 1. 安装创建本地仓库所需的工具`createrepo`。由于没网,我们需要从CentOS 7安装镜像中获取这个包,这里假设你已提前准备好。如果没准备,可以跳过创建仓库,直接使用`rpm -ivh *.rpm`强制安装(不推荐,易出错)。 # 假设所有依赖RPM包已在 /opt/offline_setup/yum_packages 目录下 cd /opt/offline_setup/yum_packages # 2. 安装`createrepo`包(如果你有它的rpm文件) sudo rpm -ivh createrepo-*.rpm # 3. 创建本地仓库元数据 sudo createrepo . # 4. 备份原有的Yum源配置,并配置使用本地仓库 sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup sudo tee /etc/yum.repos.d/local.repo << 'EOF' [local] name=Local Repository baseurl=file:///opt/offline_setup/yum_packages enabled=1 gpgcheck=0 EOF # 5. 清理Yum缓存并建立新缓存 sudo yum clean all sudo yum makecache # 6. 安装我们所需的开发工具和库 sudo yum install -y gcc make zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel xz-devel # 7. 安装Chrome的图形依赖库(根据之前下载的列表) sudo yum install -y alsa-lib atk cups-libs gtk3 libXcomposite libXcursor libXdamage libXext libXi libXrandr libXScrnSaver libXtst pango xorg-x11-fonts*实操心得:第6步安装开发工具时,如果本地仓库里包不全,yum会报错。这时你需要回到打包机,根据错误提示的缺失包名,用yumdownloader再次下载,补充到yum_packages目录,重新运行createrepo .和yum makecache。这个过程可能需要迭代一两次。
3.2 步骤二:编译安装Python 3.8
系统依赖就绪后,我们来安装Python。
# 1. 切换到存放安装包的目录 cd /opt/offline_setup # 2. 解压Python源码包 tar -xzf Python-3.8.18.tgz cd Python-3.8.18 # 3. 配置编译选项。--prefix指定安装路径,这里安装到/usr/local/python38,避免影响系统自带的Python2。 sudo ./configure --prefix=/usr/local/python38 --enable-optimizations --with-ssl-defaults # --enable-optimizations 会进行一些优化,但会使编译时间变长。离线环境时间充裕,建议开启。 # --with-ssl-defaults 确保SSL模块正确配置。 # 4. 编译并安装。这个过程视服务器性能,可能需要10-30分钟。 sudo make -j $(nproc) # 使用多核编译加速 sudo make altinstall # 使用altinstall而不是install,防止替换系统默认的python命令 # 5. 创建软链接,方便使用 sudo ln -sf /usr/local/python38/bin/python3.8 /usr/local/bin/python3 sudo ln -sf /usr/local/python38/bin/pip3.8 /usr/local/bin/pip3 # 6. 验证安装 python3 --version pip3 --version重要提示:使用
make altinstall是关键。它不会覆盖/usr/bin/python这个链接,而系统很多工具(如yum)依赖于Python 2.7。直接make install可能会导致系统管理工具崩溃。
3.3 步骤三:离线安装Selenium及其依赖
Python环境好了,接下来安装Selenium的Python库。
# 1. 切换到存放Python wheel包的目录 cd /opt/offline_setup/python_packages # 2. 使用pip3离线安装所有wheel包。注意安装顺序,先安装基础依赖如urllib3,再安装selenium。 sudo /usr/local/bin/pip3 install *.whl # 3. 验证Selenium是否可导入 python3 -c "import selenium; print(selenium.__version__)"如果这一步报错,提示缺少某个依赖,说明打包时漏掉了某个包。你需要回到打包机,用pip download补下那个包,再传输过来重新安装。
3.4 步骤四:安装Chrome浏览器
现在安装浏览器本体。
# 1. 进入Chrome RPM包所在目录 cd /opt/offline_setup # 2. 使用yum localinstall安装,它会自动处理本地RPM文件的依赖(依赖我们已经提前放到本地仓库了) sudo yum localinstall -y google-chrome-stable_current_x86_64.rpm # 3. 验证安装 google-chrome --version # 或 google-chrome-stable --version3.5 步骤五:安装与配置ChromeDriver
ChromeDriver是Selenium控制Chrome的桥梁。
# 1. 解压ChromeDriver cd /opt/offline_setup unzip chromedriver_linux64.zip # 2. 将ChromeDriver移动到系统PATH目录,并赋予可执行权限 sudo mv chromedriver /usr/local/bin/ sudo chmod +x /usr/local/bin/chromedriver # 3. 验证版本匹配 chromedriver --version google-chrome --version请务必核对两个命令输出的主版本号是否一致(例如,Chrome 124.0.6367.78 对应 ChromeDriver 124.0.6367.78)。如果不一致,Selenium将无法启动浏览器。
4. 验证与编写第一个离线测试脚本
环境部署完毕,是时候点火测试了。我们编写一个最简单的脚本来验证整个链路是否通畅。
在目标机上创建一个测试文件,比如test_offline_selenium.py:
#!/usr/bin/env python3 """ 离线Selenium环境验证脚本 """ from selenium import webdriver from selenium.webdriver.chrome.options import Options import time def main(): print("【1/4】开始配置Chrome选项...") # 配置Chrome选项,适应无图形界面的服务器环境 chrome_options = Options() chrome_options.add_argument('--headless') # 无头模式,不显示浏览器窗口 chrome_options.add_argument('--no-sandbox') # 在root用户下运行有时需要此参数 chrome_options.add_argument('--disable-dev-shm-usage') # 解决共享内存问题 chrome_options.add_argument('--disable-gpu') # 禁用GPU,在某些环境下更稳定 chrome_options.add_argument('--window-size=1920,1080') print("【2/4】正在启动ChromeDriver...") try: # 初始化驱动,指定我们安装的Chrome二进制文件位置(通常已自动在PATH中) driver = webdriver.Chrome(options=chrome_options) except Exception as e: print(f"启动ChromeDriver失败!错误信息:{e}") print("可能原因:1. Chrome与ChromeDriver版本不匹配;2. 缺少动态库依赖;3. 权限问题。") return print("【3/4】浏览器启动成功,尝试访问本地页面...") try: # 访问一个本地文件或about:blank来测试 driver.get('data:text/html,<h1>Hello, Offline Selenium!</h1>') time.sleep(2) # 等待2秒,观察过程 # 获取页面标题进行断言 page_title = driver.title page_source = driver.page_source print(f"页面标题:{page_title}") if "Hello, Offline Selenium!" in page_source: print("✅ 测试成功!Selenium已能离线控制Chrome。") else: print("⚠️ 页面内容加载异常。") except Exception as e: print(f"访问页面时发生错误:{e}") finally: print("【4/4】关闭浏览器,清理资源...") driver.quit() print("验证流程结束。") if __name__ == '__main__': main()运行这个脚本:
python3 test_offline_selenium.py如果一切顺利,你将在终端看到成功的输出,而不会弹出任何浏览器窗口(因为是无头模式)。这个脚本完成了从Python调用Selenium库,到Selenium启动ChromeDriver,再到ChromeDriver控制无头Chrome浏览器渲染一个简单页面的完整流程。
5. 离线环境下的常见问题与深度排查
即便按照上述步骤操作,在离线环境中你仍可能遇到一些特有的问题。这里我总结了一份“踩坑实录”和排查指南。
5.1 浏览器启动失败:版本匹配与依赖地狱
问题现象:执行脚本时报错,提示SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version...或WebDriverException: Message: unknown error: cannot find Chrome binary。
排查思路:
- 版本匹配复查:这是最高频的错误。再次运行
chromedriver --version和google-chrome --version,确保主版本号完全一致。Chrome的自动更新在离线环境不会发生,所以一旦配好就是固定的。 - Chrome二进制路径:在某些自定义安装中,可能需要显式指定Chrome路径。
from selenium.webdriver.chrome.service import Service chrome_options = Options() chrome_options.binary_location = '/usr/bin/google-chrome-stable' # 显式指定路径 service = Service('/usr/local/bin/chromedriver') driver = webdriver.Chrome(service=service, options=chrome_options) - 动态库缺失:这是离线环境第二大坑。即使RPM包安装了,某些库的链接可能仍有问题。使用
ldd命令检查。
如果发现有“not found”的库,你需要回到“打包机”,找到包含这个库的RPM包。可以使用ldd /usr/bin/google-chrome-stable | grep "not found" ldd /usr/local/bin/chromedriver | grep "not found"yum whatprovides */libxxx.so命令(在联网的CentOS 7上)查询,然后通过yumdownloader下载,传到目标机用rpm -ivh安装。
5.2 无头模式下的隐形问题
问题现象:脚本在无头模式下运行,但页面元素找不到、截图空白,或者一些JavaScript行为异常。
排查技巧:
- 增加隐式等待:网络请求在离线环境不是问题,但页面渲染的异步操作依然存在。在初始化driver后增加隐式等待是好习惯。
driver.implicitly_wait(10) # 最多等待10秒 - 使用显式等待:对于关键元素,使用
WebDriverWait配合expected_conditions是更可靠的做法。 - 截图调试:在关键步骤后截图,保存下来查看页面状态。
driver.save_screenshot('/tmp/debug_step1.png') - 打印页面源码:当元素定位失败时,打印当前的
driver.page_source,看看页面HTML是否如你所想。 - 禁用无头模式测试:如果条件允许(服务器有图形界面或可通过VNC连接),可以先注释掉
--headless参数,观察浏览器实际运行情况,这能排除很多无头模式特有的兼容性问题。
5.3 资源管理与稳定性优化
离线测试服务器可能同时运行多个测试任务,资源管理不好容易导致崩溃。
优化建议:
- 务必调用
driver.quit():在finally块或测试用例teardown中确保调用。quit()会关闭所有窗口并终止WebDriver进程,而close()只关闭当前窗口。资源泄露累积会导致内存耗尽。 - 限制并发:如果使用
pytest或unittest等框架并行运行,要根据服务器内存限制并发进程数。一个Chrome实例通常消耗200-500MB内存。 - 使用
--disable-dev-shm-usage:这个启动参数对于在Docker或小内存环境的Linux服务器上运行Chrome至关重要,它让Chrome使用/tmp而不是/dev/shm,避免共享内存空间不足。 - 日志收集:将ChromeDriver的日志输出到文件,便于后期分析。
service = Service('/usr/local/bin/chromedriver') service.log_path = '/var/log/chromedriver.log' # 需要确保有写入权限 driver = webdriver.Chrome(service=service, options=chrome_options)
5.4 离线环境下的测试数据与资源准备
你的自动化测试脚本很可能需要访问测试页面、上传文件或读取配置。在离线环境中,这些资源也需要提前准备。
解决方案:
- 搭建本地测试服务器:使用Python的
http.server模块或Nginx,在目标机上托管一个简单的静态网站作为测试目标。
然后在Selenium脚本中访问# 创建一个测试页面目录 mkdir -p /opt/test_site echo "<html><body><h1>Local Test Site</h1><input type='file' id='upload'></body></html>" > /opt/test_site/index.html # 启动一个简单的HTTP服务器(后台运行) cd /opt/test_site && nohup python3 -m http.server 8080 &http://localhost:8080。 - 文件上传测试:将测试用的文件(如图片、文档)提前放到目标机的固定目录,在脚本中使用绝对路径(如
/opt/test_data/example.jpg)进行上传操作。 - 配置管理:将数据库连接字符串、账号密码等配置信息,从代码中剥离,使用本地的配置文件(如JSON、YAML)或环境变量来管理。
6. 从搭建到集成:融入离线CI/CD流水线
环境搭建成功只是第一步,让它在离线开发测试流程中持续发挥作用才是最终目的。这里分享如何将这个离线Selenium环境集成到常见的持续集成流程中。
6.1 环境标准化与镜像制作
为了避免每次部署都重复上述繁琐步骤,最佳实践是制作一个标准化环境镜像。
方案一:Docker镜像(如果目标环境支持Docker)这是最优雅的方案。你可以基于一个干净的CentOS 7镜像,编写Dockerfile,将上述所有安装步骤固化其中。最后将构建好的镜像导出为tar文件,分发到离线环境加载即可。
# Dockerfile 示例片段 FROM centos:7 # 复制所有离线包到镜像中 COPY offline_packages /tmp/offline_packages # 执行一系列RUN命令,完成系统依赖安装、Python编译、Chrome安装等 RUN yum install -y ... && cd /tmp/offline_packages && tar -xzf ... && ... # 设置工作目录和默认命令 WORKDIR /workspace CMD ["/bin/bash"]在离线服务器上,只需
docker load < selenium_offline.tar和docker run ...,一个完整的测试环境就瞬间就绪。方案二:系统镜像或虚拟机模板如果你使用的是虚拟机(如VMware、KVM),可以在完成一台机器的完美配置后,将其转换为模板或直接克隆。这是物理机或无法使用Docker的环境下的有效选择。
6.2 与离线CI工具集成(以Jenkins为例)
假设你的内网已经部署了Jenkins。
- 节点管理:将部署好离线Selenium环境的服务器作为Jenkins的一个“永久性代理节点”。在节点配置中,要确保工具路径正确(如Python3、ChromeDriver的路径)。
- 任务配置:
- 在Jenkins任务中,指定在该代理节点上运行。
- 在“构建环境”或“构建步骤”中,使用
Execute shell或Batch command。 - 命令中,直接调用Python3运行你的测试脚本集。例如:
cd /path/to/your/test_suite # 安装项目特定的Python依赖(如果你也离线打包了) pip3 install --no-index --find-links=/path/to/offline_wheels -r requirements.txt # 运行测试,例如使用pytest python3 -m pytest tests/ --html=report.html --self-contained-html
- 测试报告:使用如
pytest-html这样的插件生成静态HTML报告。Jenkins可以配置“Post-build Actions”来发布这个HTML报告,这样团队成员就可以直接在Jenkins界面上查看测试结果,无需登录服务器。
6.3 离线依赖包的持续维护
你的项目不可能永远只用固定版本的Selenium和Chrome。如何更新?
- 建立内部物料仓库:在打包机上,定期(如每季度)执行一次依赖包更新流程。
- 更新Python包:
pip download -r new_requirements.txt ... - 更新Chrome RPM和ChromeDriver。
- 根据新的Chrome版本,在临时CentOS 7环境上测试安装,用
yumdownloader补充可能新增的系统依赖。
- 更新Python包:
- 版本管理:将
/opt/offline_setup目录用版本号命名,如offline_setup_v2.0。更新时,将新物料包传输到目标机的新目录,然后更新部署脚本或Dockerfile中的路径指向新版本。这样可以实现回滚。 - 自动化脚本:将整个打包过程脚本化。这样,当需要更新时,只需在打包机上运行这个脚本,就能自动下载所有最新物料并打包,极大减少人工操作和出错概率。
整个离线部署的过程,从最初的物料准备到最终的集成落地,确实比在线安装繁琐数倍。但它的价值在于提供了绝对的确定性和可控性。一旦这套流程跑通并固化下来,你就可以在任何网络隔离的环境中,快速、一致地复制出强大的自动化测试能力。这不仅是技术能力的体现,更是工程化思维在约束条件下解决问题的典范。
