ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Selenium Python 测试指南:基于 pytest 与 Bazel 的浏览器自动化测试编写与运行实践

2026/9/10 2:50:40 拓冰建站 浏览量
Selenium Python 测试指南:基于 pytest 与 Bazel 的浏览器自动化测试编写与运行实践 Selenium Python 测试指南基于 pytest 与 Bazel 的浏览器自动化测试编写与运行实践【免费下载链接】seleniumA browser automation framework and ecosystem.项目地址: https://gitcode.com/GitHub_Trending/se/seleniumSelenium 官方 Python 测试套件是一套以 pytest 为核心、以 Bazel 为构建引擎的跨浏览器测试体系。本文以仓库中的 py/TESTING.md 为主干完整梳理其测试框架约定、Bazel 目标矩阵、原生 pytest 运行方式、浏览器级跳过xfail标记、Driver 生命周期管理、核心 Fixtures 与目录组织规范并结合 py/conftest.py、py/BUILD.bazel 等源码实现讲清每条规则背后的真实机制。读完本文你将能够在新浏览器行为、新 WebDriver API 或 BiDi 协议特性落地时按照官方同款规范编写测试、精准定位目标并高效运行调试。测试框架总览Selenium Python 测试套件的技术选型非常统一测试框架使用pytest断言直接使用 pytest 标准的assert语句不引入额外断言库测试所需的 HTML 页面统一存放在common/src/web/目录下如javascriptPage.html、alerts.html、click_tests/*.html等均由测试用 Web 服务器动态提供通过pagesfixture 加载测试页面核心 API 是pages.load(pageName.html)每个测试函数的浏览器实例由driverfixture 提供并由 pytest 按所选浏览器自动参数化。一个最小的官方风格测试如下与文档示例一致import pytest from selenium.webdriver.common.by import By def test_element_is_displayed(driver, pages): pages.load(javascriptPage.html) element driver.find_element(By.ID, displayed) assert element.is_displayed() is True pytest.mark.xfail_safari(reasonSafari doesnt support this) def test_something_safari_fails(driver, pages): # Expected to fail on Safari pass从这个例子可以拆出三条核心约定测试函数签名中直接注入driver与pages页面加载统一走pages.load而不是硬编码 URL对特定浏览器的已知失败用xfail_browser标记声明而不是在测试体内写平台分支。从源码看测试的运行基石driverfixture按浏览器参数化的真实驱动driverfixture 定义在 py/conftest.py。它并非简单地yield webdriver.Chrome()而是封装了一个Driver类py/conftest.py承担了以下职责浏览器参数化当命令行指定--driver chrome firefox时pytest 通过pytest_generate_testspy/conftest.py对driverfixture 做indirect参数化同一测试会在每个浏览器上各跑一遍平台可用性检查Safari 仅在 Darwin、IE 仅在 Windows 上运行否则直接pytest.skipis_platform_validpy/conftest.py启动重试本地驱动启动失败时最多重试 3 次、间隔 1 秒DRIVER_START_RETRIES/DRIVER_START_INTERVALpy/conftest.py会话级回收stop_driver这个 session 级 autouse fixture 会在整个测试会话结束时统一quit()避免驱动进程泄漏py/conftest.py。pagesfixture 与webserverfixturepagesfixturepy/conftest.py本质是对webserverfixture 的薄封装class Pages: def url(self, name, localhostFalse): return webserver.where_is(name, localhost) def load(self, name): driver.get(self.url(name))pages.load(page.html)驱动浏览器访问该页面pages.url(page.html)只返回完整 URL适合需要先构造 URL 再使用的场景。底层的webserver是 session 级 autouse fixturepy/conftest.py启动一个由 py/test/selenium/webdriver/common/webserver.py 实现的SimpleWebServer。它基于ThreadingHTTPServer默认监听localhost:8000端口被占用时自动 1 重试以common/src/web/为静态根目录并提供echo_headers、echo_body、echo_json、set_cookie、basic-auth用户名postman/ 密码password等测试专用端点以及page/n动态页面生成能力。使用 Bazel 运行测试Bazel 会为每个浏览器生成独立的测试目标且默认并行执行。仓库通过 py/BUILD.bazel 中的py_test_suite宏批量生成这些目标。常用目标一览bazel test //py/... # All tests bazel test //py:unit # Unit tests (no browser) bazel test //py:test-chrome # Chrome browser tests bazel test //py:test-firefox # Firefox browser tests bazel test //py:test-chrome-common # Common (cross-browser) tests with Chrome目标命名遵循固定模式目标含义//py:unit单元测试不启动浏览器对应 py/BUILD.bazel 中的unit套件//py:test-browser某浏览器的完整聚合套件common actions 各 feature 子套件见 py/BUILD.bazel//py:test-browser-common跨浏览器公共测试test/selenium/webdriver/common/** support 该浏览器私有测试见 py/BUILD.bazel//py:test-browser-actions仅 Actions API 相关测试interactions_tests.py、w3c_interaction_tests.py等//py:test-browser-feature按功能拆分的小型子套件如fedcm、timeouts、virtual-auth、api-request、alerts、rendered、print见 py/BUILD.bazel//py:test-browser-bidiBiDi 协议测试仅 chrome / edge / firefox 支持运行单个测试文件与单个测试单文件目标名的规则是test/path/file-browser[-variant]。命名由 py/private/suite.bzl 中的py_test_suite宏生成去掉_tests.py后缀后拼接浏览器后缀。例如# 单个测试文件Chrome bazel test //py:test/selenium/webdriver/common/window_tests-chrome # 发现精确目标名 bazel query //py:all | grep window_tests在单文件目标内用-k表达式精确过滤某个测试函数BiDi 变体目标同理bazel test //py:test/selenium/webdriver/common/bidi/browsing_context_tests-chrome-bidi \ --test_arg-k \ --test_argtest_get_tree_with_childbazel query //py/...可以列出py/下全部可用目标是排查目标名拼写问题的首选手段。BiDi 与远程Grid目标# 以 BiDi 协议运行 bazel test //py:test-chrome-bidi # 针对 Grid 服务器运行仅 chrome 和 firefox。 # 套件会自行启动 Selenium standalone server并通过 webdriver.Remote 与之通信。 bazel test //py:test-chrome-remote # classic protocol bazel test //py:test-chrome-remote-bidi # BiDi over Grids websocket proxy bazel test //py:test-remote # every classic remote suite bazel test //py:test-remote-bidi # every BiDi remote suite这些聚合目标的组成在 py/BUILD.bazel 中定义test-remote聚合 chrome/firefox 的-remote-common、-remote-actions与各-remote-feature子套件而test-remote-bidi特意独立成 job不与经典 remote 套件合并以便在 BiDi over Grid 的 WebSocket 代理仍属新能力时单独观察其稳定性py/BUILD.bazel。常用过滤与调试参数# 按标签过滤测试 bazel test //py/... --test_tag_filterschrome # 失败重试与输出控制 bazel test //py/... --flaky_test_attempts3 bazel test //py/... --test_outputall bazel test //py/... --test_outputstreamed # Live output for debugging # 无头模式 bazel test //py:test-chrome --headless说明--headless在 Bazel 目标层面透传给测试进程最终由 conftest 读取并给浏览器 Options 添加--headlessChrome/Edge或-headlessFirefox参数见 py/conftest.py。不使用 Bazel直接用 pytest 运行在本地开发迭代时也可以绕过 Bazel 直接用 pytest 运行前提是完成环境准备。环境准备# 1. 安装依赖使用锁定文件保证可复现 pip install -r py/requirements_lock.txt # 2. 构建生成文件并拷贝到本地源码树 ./go py:local_dev第二步会生成 DevTools 协议绑定、BiDi 模块等构建期生成物对应 py/BUILD.bazel 中的generate_bidi、generate_devtools等规则确保from selenium.webdriver.common.bidi import ...等导入可用。运行命令# 运行某目录下全部测试 pytest py/test/selenium/webdriver/chrome/ --driver chrome # 运行某个测试文件 pytest py/test/selenium/webdriver/common/window_tests.py # 运行单个测试函数 pytest py/test/selenium/webdriver/common/window_tests.py::test_should_get_the_size_of_the_current_window # 组合 pytest 选项 pytest py/test/selenium/webdriver/chrome/ --driver chrome --headless -v命令行选项来自 conftest所有--xxx选项都在 py/conftest.py 的pytest_addoption中注册可按需组合选项说明--driver DRIVER要运行的驱动可选chrome、edge、firefox、ie、safari、webkitgtk、wpewebkit可多次传入实现多浏览器参数化--browser BROWSER--driver的别名--browser-binary PATH指定浏览器可执行文件位置--driver-binary PATH指定 driver服务可执行文件位置--browser-args ARGS启动浏览器时附加的参数按空格拆分--headless以无头模式运行--use-lan-ip测试服务器改用局域网 IP 而非 localhostSimpleWebServer会以 LAN IP 监听--bidi启用 BiDi 协议支持--remote针对远程 Grid 服务器运行另外当传入--driver时pytest 会自动忽略与所选浏览器无关的测试目录pytest_ignore_collectpy/conftest.py例如只跑 Chrome 时不会收集 firefox 目录下的用例。BiDi 与远程的 pytest 用法Note:运行 BiDi 测试请使用--bidi标志。启用后 conftest 会给浏览器 Options 设置web_socket_url True并将unhandled_prompt_behavior设为ignore见 py/conftest.py。要针对 Grid 服务器运行请追加--remote。它会启动一个 Selenium standalone server并通过webdriver.Remote运行测试因此需要先构建 Grid jarbazel build //java/src/org/openqa/selenium/grid:selenium_server_deploy.jar--bidi --remote可组合使用Grid 会把webSocketUrlcapability 改写为自身的/session/id/se/bidi端点并将该 socket 代理转发到对应的 node。从源码看--remote路径由serverfixturepy/conftest.py承载它自动定位selenium_server_deploy.jar优先 runfiles其次bazel-bin用空闲端口启动 Java Grid 服务器并通过webdriver.Remote(command_executor...)创建驱动当同时给出--driver-binary与--browser-binary时还会生成--driver-configuration参数将驱动与浏览器固定到 Grid node从而跳过 Selenium Manager 的自动探测_pinned_grid_argspy/conftest.py。跳过测试浏览器级 xfail 标记体系跨浏览器测试中这个用例在某个浏览器上必然失败是常态Selenium 为此定义了按浏览器区分的 pytest 标记。每个标记都接受可选的reason与run参数runFalse表示完全跳过该测试而不是运行并期望失败。Marker使用场景pytest.mark.xfail_chrome预期在 Chrome 上失败pytest.mark.xfail_firefox预期在 Firefox 上失败pytest.mark.xfail_safari预期在 Safari 上失败pytest.mark.xfail_edge预期在 Edge 上失败pytest.mark.xfail_ie预期在 IE 上失败pytest.mark.xfail_remote预期在 Remote WebDriver 下失败pytest.mark.xfail_chrome(reasonNot implemented yet) pytest.mark.xfail_firefox(reasonhttps://bugzilla.mozilla.org/123) def test_something(driver, pages): pass pytest.mark.xfail_safari(runFalse) # Skip entirely instead of xfail def test_skip_safari(driver, pages): pass这些标记的注册信息位于 py/pyproject.toml并在运行时由 conftest 的_apply_xfail_markerspy/conftest.py按当前驱动名动态生效它会查找xfail_driver标记远程模式下若没有则回退到xfail_remote标记带condition参数且求值为假时该次运行不生效runFalse时直接pytest.skip而非 xfail。真实用例可参考 py/test/selenium/webdriver/common/window_tests.py该测试对 Chrome/Edge/Firefox/Remote 分别声明了失败原因如 geckodriver 的已知 issue。Driver 生命周期管理部分测试需要精细控制驱动实例的创建与销毁时机通过以下两个标记实现Marker使用场景pytest.mark.no_driver_after_test测试结束后销毁 driver强制下一个测试重新创建pytest.mark.needs_fresh_driver重启 driver 以保证测试隔离两者在driverfixture 中的行为不同py/conftest.pyneeds_fresh_driver主要用于 BiDi 测试。普通 BiDi 测试默认复用同一个 driver会话内共享仅在窗口失效时重启见ensure_valid_window而带此标记的测试会在结束时stop_driver()下一条用例拿到全新实例no_driver_after_test任意模式下测试结束后调用stop_driver()并将全局 driver 引用置空实现一次性 driver语义此外pytest_exception_interactpy/conftest.py会在测试失败时主动销毁 driver避免故障状态被后续用例复用。核心 FixturesSelenium Python 测试大量使用 pytest fixtures 来简化 setup/teardown。模块私有的 fixture 直接定义在使用它的测试文件里跨模块共享的 fixture 集中在py/conftest.pyFixture说明driverWebDriver 实例按浏览器自动参数化pages加载测试页pages.load(page.html)或pages.url(page.html)webserver测试 HTTP 服务器引用session 级 autouseclean_driver不带参数化的全新 driver 类引用clean_options全新的浏览器 Options 实例headless浏览器是否以无头模式启动用于断言无头浏览器不建模的行为如窗口焦点——无头 Chromium 会把焦点永远报告给当前窗口基于这些基础 fixtureconftest 还提供了一批用途更专一的 fixtureclean_service返回当前驱动对应的Service类实例可注入executable_pathfirefox_options/chromium_options仅在目标浏览器匹配时返回干净的 OptionsFirefox 需--driver firefoxChromium 系列需--driver chrome|edge否则自动 skipedge_service直接返回 Edge 的Service类proxy_server动态创建可自定义响应内容的 HTTP 代理服务器测试结束后自动关闭py/conftest.pydriver_executable解析--driver-binary指定的可执行文件路径含 Bazel runfiles 路径解析。测试组织与命名规范测试目录按单元 / 集成和浏览器归属双重维度组织py/test/ ├── unit/ # Unit tests (no browser) │ └── selenium/webdriver/ └── selenium/webdriver/ # Integration tests ├── common/ # Cross-browser tests ├── chrome/ ├── firefox/ ├── safari/ └── remote/要点common/下是与浏览器无关的跨浏览器测试window_tests.py、visibility_tests.py、alerts_tests.py等任何浏览器目标都会运行它们chrome/、firefox/、safari/等目录存放浏览器私有行为测试remote/下的测试需要--remote标志才会运行否则 conftest 会直接 skip_skip_unless_remotepy/conftest.pyBiDi 协议测试集中在common/bidi/如browsing_context_tests.py、network_tests.py、script_tests.py、protocol_tests.py等与common/_bidi/仅在*-bidi目标中收集。测试文件一律以_tests.py结尾如visibility_tests.py。该约定同时被两处消费Bazel 侧的 py/private/suite.bzl 用_is_test识别测试文件并自动生成目标pytest 侧的 py/pyproject.toml 通过python_files [test_*.py, *_test.py, *_tests.py]匹配。构建文件自动发现与免维护新增测试通常不需要修改任何 Bazel 构建文件py/private/suite.bzl 中的py_test_suite宏会对传入的srcs逐一过滤凡匹配test_*或*_tests.py的文件都会自动生成pytest_test目标底层封装见 py/private/pytest.bzl它生成一个调用pytest.main()的 runner 并装配 runfiles只要测试文件位于已被现有py_test_suite覆盖的目录如py/test/selenium/webdriver/common/就会自动纳入test-browser-common等套件。唯一的例外是涉及特殊依赖的测试例如alerts_tests.py与webdriverwait_tests.py依赖common_alert库、print_pdf_tests.py依赖common_print_page_options因此它们在 py/BUILD.bazel 中被显式归入独立的 feature 子套件FEATURE_TESTS以控制依赖图的最小化。小结从编写到运行的完整路径编写在py/test/selenium/webdriver/common|chrome|firefox|.../下新建xxx_tests.py测试函数注入driver、pages用 pytest 原生断言标记按需添加xfail_browser、no_driver_after_test、needs_fresh_driver等标记声明平台预期与管理驱动生命周期定位用bazel query //py:all | grep 关键词找到精确目标或直接bazel test //py:test-browser-common跑整套调试--test_outputstreamed看实时输出--flaky_test_attempts3容忍偶发失败--test_arg-k 表达式精确定位单个用例本地快跑pip install -r py/requirements_lock.txt ./go py:local_dev后用pytest ... --driver chrome --headless直接迭代。这套体系既保证了跨浏览器覆盖的广度common 套件 浏览器私有套件也通过 BiDi 目标、remote 目标和 feature 子套件实现了按协议、按拓扑、按依赖的细粒度编排是研究或贡献 Selenium Python 绑定测试时应当遵循的标准范式。【免费下载链接】seleniumA browser automation framework and ecosystem.项目地址: https://gitcode.com/GitHub_Trending/se/selenium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考