广告:Codex Token 低价中转站稳定接口 · 快速接入 · 开发者备用通道
Engineering article

我在大厂用Selenium:自动化部署 | 面试高频

有次在大厂用Selenium做自动化部署,从头到尾几乎没用过官方文档。因为真实场景里,浏览器版本和操作系统不一致,页面加载超时、元素定位不准、脚本执行失败的概率极高。我最终选择用Python+PyTest+Page Object Model的组合,把测试脚本分成模块,每个模块专注一个页面。在配置时,我强制统一使用ChromeDriver 1

我在大厂用Selenium:自动化部署 | 面试高频
配图来源于网络和AI生成,仅供参考。
▌ 技术引导

有次在大厂用Selenium做自动化部署,从头到尾几乎没用过官方文档。因为真实场景里,浏览器版本和操作系统不一致,页面加载超时、元素定位不准、脚本执行失败的概率极高。我最终选择用Python+PyTest+Page Object Model的组合,把测试脚本分成模块,每个模块专注一个页面。在配置时,我强制统一使用ChromeDriver 116版本,配合无头模式和headless参数,避免图形界面干扰。执行部署时,会先用requests库模拟登录,然后再用Selenium去操作页面,这样能减少某些页面依赖登录态的情况。最关键的是,我通过定时器和日志记录工具,把每个操作步骤的时间戳记录下来,方便后续定位问题。结果发现,某些测试用例在特定时间运行会挂掉,后来才知道是浏览器缓存和网络延迟的问题。这个过程让我意识到,Selenium在大厂级的自动化部署里,不是万能的,但合理配置和结构设计能让它变得稳定。

我见过很多团队在用Selenium做部署自动化时,直接把所有代码写在一起,结果维护起来像一团乱麻。后来我引入了playwright,发现它比Selenium轻量,而且自带录制功能,能自动帮你生成测试代码。用playwright的时候,我习惯在脚本开头定义浏览器配置,比如user-agent、代理、视窗大小这些参数,这样能模拟不同用户环境。另外,我通过设置headless=False,让浏览器可视化执行,这样能更直观地看到问题,比如元素加载异常或者JS执行错误。部署脚本里我用async来并发执行多个任务,这样能节省时间,但需要注意资源占用问题。偶尔还会用到selenium-wire替代原生的Selenium,因为它能拦截和修改HTTP请求,对某些接口调试有很大帮助。

还有些时候,我发现Selenium页面加载太慢,尤其是在处理动态内容时。这时候我会用WebDriverWait来等待元素出现,而不是单纯的sleep。不过sleep反而让测试不准确,容易出错。我用过chromedriver的excludeSwitches参数来关闭一些默认启用的选项,比如自动下载文件,避免脚本被中断。另外,我也尝试过用docker来部署Selenium服务,这样能保证环境一致性,减少版本差异带来的问题。一个坑就是docker里的浏览器会定期弹出更新提示,必须用--no-sandbox和--disable-gpu参数来规避。在部署过程中,我还会用到git hooks和CI/CD管道,确保每次提交都会触发测试,而不是手动执行。总之,Selenium在大厂用,不能只靠它,还得结合其他工具和策略,才能不被环境和配置拖后腿。

▌ 技术参考

在大厂级的自动化部署中,Selenium通常作为浏览器自动化工具,配合CI/CD流程实现页面级测试。核心概念包括WebDriver、元素定位、浏览器配置、脚本执行策略等。实际使用时,会遇到页面加载延迟、元素定位失败、脚本执行超时等问题,这些都需要针对性处理。在配置时,必须确保浏览器版本、操作系统、网络环境和代理参数一致,否则测试结果会有偏差。

我用Python写Selenium脚本时,会优先使用PyTest来做框架,因为它支持参数化测试和并发执行,能大幅提升测试效率。脚本结构上,我会把页面元素封装成类,每个类对应一个页面,减少重复代码。比如,定义一个LoginPage类,包含用户登录的入口元素和操作函数。在测试用例中,只需要实例化这个类并调用方法即可,这样维护起来更简单。实际运行时,用PyTest的命令行参数指定不同的测试模块,比如pytest -k login_test,这样可以灵活控制执行范围。

在浏览器配置方面,我通常使用ChromeOptions来设置参数,比如添加--headless参数启动无头模式,或者用--disable-gpu避免GPU加速问题。另外,我会设置user-agent参数,模拟不同设备访问。有时候还会用到--no-sandbox来绕过沙盒限制,特别是在docker环境里。需要注意的是,某些参数可能无法在无头模式下正常工作,比如--enable-automation会触发页面的anti-bot检测,必须提前在测试前关闭。

部署脚本中,我经常用requests库预处理登录态,这样在Selenium执行操作时就不需要每次都重新登录。比如,先用requests.post发送登录请求,获取到cookie,再用Selenium的add_cookie方法加载到浏览器中。这样能减少测试时间,同时避免某些页面需要登录才能操作的问题。在实际操作中,我还会用到selenium-wire来拦截HTTP请求,比如获取登录后返回的token,注入到后续请求中,确保页面状态正确。

遇到页面加载延迟时,我常用WebDriverWait来等待元素出现,而不是直接sleep。比如,在等待登录按钮出现时,代码写成:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.XPATH, '//button[@id="login"]')))

这样能提高测试的准确性,避免因为元素未加载而报错。不过,有时候页面加载过慢,我也会用page_load_strategy参数设置为eager,让浏览器在加载页面时立即返回,而不是等全部内容加载完毕。

在部署环境中,我见过很多团队直接使用docker部署Selenium服务,这样能确保环境一致性。常见的做法是用chromium-browser和chromedriver镜像组合,通过docker-compose.yml文件定义服务。不过,一个问题就是docker里的浏览器会定期弹出更新提示,必须用--no-sandbox参数来关闭。此外,我还用过Selenium Grid来分发测试任务,这样能同时运行多个浏览器实例,加快测试速度。但Grid的配置相对复杂,需要管理节点和hub的地址,以及浏览器版本匹配问题。

有些测试用例在特定时间运行会挂掉,这时候我通常会用日志记录工具,比如logging模块,把每一步操作的时间戳记录下来。比如在脚本开头加入:

import logging
logging.basicConfig(level=logging.DEBUG)

这样能清晰看到每个步骤执行的时间,帮助定位问题。另外,我也会用到pytest的日志插件,比如pytest-log_helper,让日志更易读。有时候会遇到浏览器无法启动的问题,这时候我会检查docker的日志,发现是某个环境变量配置错误,比如DISPLAY没有正确设置,或者缺少某些依赖库。

我还用过PyTest的parametrize功能,让同一个测试函数执行不同的参数组合。比如,定义一个测试函数,然后用@pytest.mark.parametrize传入不同的登录用户名和密码,这样能覆盖更多测试场景。不过需要注意参数化时的依赖关系,比如某些测试可能需要特定的登录状态,不能随意组合参数。我也会用到pytest的fixture来管理前置条件,比如登录、初始化页面、设置环境变量等。

针对某些页面需要验证码的情况,我无法用Selenium自动处理,只能用第三方OCR工具,比如Tesseract,配合截图和文本识别。有时候会用pyautogui来模拟点击验证码,但这样会增加脚本的不稳定性。我见过有团队用Selenium的执行速度和稳定性做对比,发现Selenium在某些场景下效率比playwright低,特别是在处理复杂的DOM结构时。但Selenium对某些页面的兼容性更好,比如某些基于旧版浏览器的页面。

在实际部署过程中,我采用多线程来执行测试用例,这样能并行处理多个任务。比如用concurrent.futures.ThreadPoolExecutor来管理线程,每个线程对应一个测试任务。但要注意线程池的大小,避免资源竞争。此外,我也会用到异步框架,比如asyncio,来并发执行多个测试用例,但需要确保每个测试用例是异步安全的,避免共享资源导致冲突。

我还遇到过一些浏览器弹窗导致脚本卡住的情况,比如下载提示、权限请求等。这时候会用到Selenium的alert处理函数,比如driver.switch_to.alert.accept()来跳过弹窗。有时候会用到execute_script方法,直接操作浏览器的JS代码,比如关闭弹窗或者跳过某些操作。但这样做容易让脚本变得不安全,需要在测试环境中严格控制。

在测试异常处理方面,我通常用try-except块来捕获错误,记录日志并跳过失败用例。比如:

try:
element = driver.find_element(By.XPATH, '//div[@class="error"]')
except Exception as e:
logging.error("元素定位失败: %s", e)
continue

这种方式能确保测试脚本不因单个错误而中断,提高整体的健壮性。另外,我也会用到pytest的skip和xfail标记,来标记某些不稳定的用例,方便后续优化。

我还用过一些浏览器扩展来辅助测试,比如屏蔽某些广告或弹窗,这样测试环境更干净。不过这些扩展可能会影响页面加载,需要提前在测试环境安装并配置。有时候会用到浏览器的开发者工具,比如Chrome DevTools Protocol,通过它来获取页面状态、网络请求等信息,帮助调试问题。

在部署日志中,我见过有些团队使用Selenium的log方法来查看浏览器日志,比如:

log = driver.get_log('browser')
for entry in log:
print(entry)

但这种方式在无头模式下可能无效,需要手动配置日志输出路径。另外,我也会用到ELK(Elasticsearch, Logstash, Kibana)来收集和分析测试日志,帮助快速定位问题。有时候会遇到测试脚本执行后没有清理环境的情况,导致后续测试出错,所以必须用driver.quit()来关闭浏览器,而不是driver.close()。

我见过有些团队在用Selenium做自动化部署时,把所有代码写在一起,导致维护困难。后来改用模块化设计,每个页面单独一个模块,这样修改更方便。比如,把LoginPage、HomePage、ProfilePage分别封装成类,每个类包含对应的元素和操作函数。这样不仅代码整洁,还能提高复用性。在实际执行时,我也会用到pytest-xdist来并行执行测试,加快整体测试速度。

有时测试脚本会因为页面元素变化而失效,这时候我会用Selenium的Wait机制来动态等待元素出现,而不是硬编码等待时间。比如用WebDriverWait配合expected_conditions,让脚本在元素出现后再执行下一步。这种方式比sleep更智能,也更高效。在某些情况下,我还会用到Selenium的重新定位元素功能,比如在页面重新加载后,使用find_element重新查找元素,避免定位失效。

我还用过一些异步测试框架,比如pytest-asyncio,来处理复杂的测试流程。比如,用async def定义测试函数,配合await关键字执行异步任务。这种方式能更灵活地控制测试顺序,但需要确保所有依赖都是异步兼容的。在实际部署中,这种模式能提升测试效率,尤其是在处理大量页面请求时。不过要小心处理异步状态,避免并发问题。