SeleniumBase 视觉回归测试实战:用 Case Plan 复现 check_window 布局变更检测(python.org Donate 按钮移除示例)
【免费下载链接】SeleniumBaseAPIs for browser automation, testing, and bypassing bot-detection. Includes CDP Mode: A stealthy configuration for chromium that passes every bot detection test.项目地址: https://gitcode.com/GitHub_Trending/se/SeleniumBase
导读
本文以 SeleniumBase 仓库中 examples/visual_testing/case_plans/test_layout_fail.VisualLayout_FixtureTests.test_python_home_change.md 这份 Case Plan 为骨架,完整讲解self.check_window()驱动的视觉回归测试如何工作。你将掌握:Case Plan 与测试代码的对应关系、baseline=True基线建立、remove_element()修改页面 DOM、level=3严格比对模式,以及失败时side_by_side.html对比报告的生成原理。读完即可独立复现"移除元素导致视觉回归失败"的完整流程,并理解底层源码实现。
一、Case Plan 是什么:一段测试计划的 Markdown 表格
在深入视觉测试细节之前,先认识这份文档本身。SeleniumBase 的Case Plans(测试用例计划)是一种基于 Markdown 表格的测试管理方式,可以直接在代码托管平台上渲染展示。其完整机制记录在 help_docs/case_plans.md 中:
- 每个 Case Plan 以
测试文件.py::测试类::测试方法的格式作为标题行; - 正文是一张
# | Step Description | Expected Result三列表格(#为步骤序号,-为分隔行); - 单个 Case Plan 存放在测试文件所在目录的
case_plans/文件夹中; - 多个 Case Plan 可通过
sbase caseplans图形界面汇总生成case_summary.md摘要文件(仓库中已有实例:examples/case_summary.md)。
本文主角这份 Case Plan 原文如下,它精确描述了test_layout_fail.py中VisualLayout_FixtureTests::test_python_home_change这个测试的两步行为:
| # | Step Description | Expected Result | | - | ---------------- | --------------- | | 1 | Open https://python.org/.
Callcheck_window()withbaseline=True. | | | 2 | Remove theDonatebutton usingremove_element(SELECTOR).
Callcheck_window()withlevel=3. | The test fails because theDonatebutton was removed.
Aside_by_side.htmlfile appears in the specificlatest_logs/folder of the test. |
Markdown 表格书写有严格约定:表头分隔行必须由|、-、空格按正确位置排列;步骤内需要换行时使用<br />;空步骤在两个竖线之间留空格(| |)。这些约定同样记录在 help_docs/case_plans.md 中,是生成可渲染 Case Plan 的前提。
二、Case Plan 对应的测试源码:Fixture 风格与类风格两种写法
该 Case Plan 对应的真实测试位于 examples/visual_testing/test_layout_fail.py。该文件同时示范了 SeleniumBase 两种主流语法格式:
"""Visual Layout Testing with different Syntax Formats""" from seleniumbase import BaseCase BaseCase.main(__name__, __file__) class VisualLayout_FixtureTests: def test_python_home_change(self, sb): sb.goto("https://python.org/") print('\nCreating baseline in "visual_baseline" folder.') sb.check_window(name="python_home", baseline=True) # Remove the "Donate" button sb.remove_element("a.donate-button") print("(This test should fail)") # due to missing button sb.check_window(name="python_home", level=3) class VisualLayoutFailureTests(BaseCase): def test_xkcd_logo_change(self): self.goto("https://xkcd.com/554/") print('\nCreating baseline in "visual_baseline" folder.') self.check_window(name="xkcd_554", baseline=True) # Change height: (83 -> 110) , Change width: (185 -> 120) self.set_attribute('[alt="xkcd.com logo"]', "height", "110") self.set_attribute('[alt="xkcd.com logo"]', "width", "120") print("(This test should fail)") # due to a resized logo self.check_window(name="xkcd_554", level=3)两种写法对同一套 API 的调用完全一致:
- Fixture 风格(
VisualLayout_FixtureTests):类不继承BaseCase,方法签名接收sb参数,通过 pytest fixture 注入 SeleniumBase 实例。文件顶部的BaseCase.main(__name__, __file__)让脚本既可以直接运行,也可被 pytest 收集。 - 类风格(
VisualLayoutFailureTests):继承BaseCase,使用self.前缀调用同名方法。同文件的test_xkcd_logo_change还演示了用set_attribute()篡改 logo 尺寸的另一种回归触发方式,与remove_element()殊途同归。
三、建立视觉基线:check_window(baseline=True) 做了什么
第 1 步sb.check_window(name="python_home", baseline=True)的作用是为名为 "python_home" 的窗口建立视觉基线。首次以某个唯一name调用check_window()时,SeleniumBase 会自动创建基线文件夹,seleniumbase/fixtures/base_case.py 中的方法 docstring 明确了基线目录的生成逻辑:
- 基线文件夹以
测试名 + name 参数命名,同一个测试可以存放多个不同name的基线; - 文件夹保存在
visual_baseline/目录下(存储目录常量VisualBaseline.STORAGE_FOLDER = "visual_baseline"定义于 seleniumbase/fixtures/constants.py); - 目录初始化由 seleniumbase/core/visual_helper.py 的
visual_baseline_folder_setup()完成,多线程场景下并发创建由except Exception容错。
基线文件夹建立后会生成 5 个文件(详见 examples/visual_testing/ReadMe.md):
| 文件 | 内容 |
|---|---|
page_url.txt | 当前窗口的 URL |
baseline.png | 基线截图(PNG) |
tags_level1.txt | 页面 HTML 标签 |
tags_level2.txt | HTML 标签 + 属性名 |
tags_level3.txt | HTML 标签 + 属性名/属性值 |
关键设计点:视觉回归比对的不是像素,而是 HTML 标签与属性的结构。因此页面纯文本变化不会触发失败,而 DOM 结构、属性名或属性值的改变才会被检出。这正是"文本变更不影响视觉比对"一说的来源。
四、制造变更:remove_element() 如何删除 Donate 按钮
第 2 步先用sb.remove_element("a.donate-button")移除 python.org 首页的 Donate 按钮,再执行sb.check_window(name="python_home", level=3)触发严格比对。
remove_element()的实现位于 seleniumbase/fixtures/base_case.py,其底层并非 Selenium 点击操作,而是通过 JavaScript 直接操作 DOM:
- 先等待
body可见,再以timeout=0.5短暂等待目标元素出现; - 将传入选择器统一转换为 CSS 选择器(
convert_to_css_selector); - 普通 CSS 选择器走
document.querySelector()+parentElement.removeChild()移除首个匹配元素; - 含
:contains(的 jQuery 风格选择器则退化为jQuery(...).remove()或元素级删除; - 删除前会对选择器做
re.escape转义与引号处理,保证特殊字符安全。
与之配套的remove_elements()(seleniumbase/fixtures/base_case.py)则用querySelectorAll遍历删除全部匹配元素。此外,文件还演示了set_attribute()修改元素属性来制造差异,说明"变更页面"的手段是开放的——只要改变 DOM 结构或属性即可。
五、触发失败:check_window(level=3) 的比对机制
check_window()的完整签名(seleniumbase/fixtures/base_case.py):
def check_window( self, name="default", level=0, baseline=False, check_domain=True, full_diff=False, ):调用前方法会先执行wait_for_ready_state_complete()并等待body可见,确保页面稳定;若处于 Demo Mode 还会输出警告(Demo Mode 的 HTML 改动可能干扰比对)。随后参数被规整为 0/1/2/3 四个合法值,非法值直接抛异常。
level 严格度体系(同时记录于 seleniumbase/fixtures/base_case.py 与 examples/visual_testing/ReadMe.md):
| level | 比对内容 | 行为 |
|---|---|---|
level=0 | 仅干跑 | 与基线比对并打印差异,但不失败 |
level=1 | HTML 标签 | 对照tags_level1.txt |
level=2 | 标签 + 属性名 | 对照tags_level1.txt与tags_level2.txt |
level=3 | 标签 + 属性名/值 | 对照全部三个tags_level*.txt,最严格 |
注意 level 的累积语义:level=2实际同时比对 level1 与 level2,level=3则三层全比。基线建立(baseline=True)时 level 参数不参与比对,仅用于后续运行。
两个重要防线:
- 域名校验:当前页面域名与基线 URL 域名不一致时,抛出 "Page Domain Mismatch Failure";可通过
check_domain=False关闭。 - 同测试内自比对:在同一个测试中对同一
name多次调用check_window()时,第一次传baseline=True即可将当次页面快照作为基线,与后续版本对比——这正是本 Case Plan 的核心用法。
另外full_diff=True可让报错输出列出全部差异元素,默认(False)只显示第一个差异元素。
六、失败产物:side_by_side.html 与 latest_logs 目录
按 Case Plan 的预期结果,level=3比对必然失败(Donate 按钮已被移除),此时测试在该测试专属的latest_logs/目录下生成side_by_side.html对比报告。
报告生成链路清晰可查:
- 文件名常量
SideBySide.HTML_FILE = "side_by_side.html"定义于 seleniumbase/fixtures/constants.py; - HTML 由 seleniumbase/core/visual_helper.py 的
get_sbs_html()拼装,包含 baseline 与失败截图并排对比的表格结构(get_sbs_table_row()生成左右两列<img>); - 写入逻辑在 seleniumbase/fixtures/base_case.py:将 HTML 写入
test_logpath(即该测试的latest_logs/目录),同时基线 PNG 与最新截图latest.png也会被拷贝到该目录,方便人工核对基线是否需要重置。
典型的level=3失败输出(来自 examples/visual_testing/ReadMe.md,对应 python.org 场景)形如:
AssertionError: First differing element 33: ['a', [['class', ['donate-button']], ['href', '/psf/donations/']]] ['div', [['class', ['options-bar']]]] ... *** Exception: <Level 3> Visual Diff Failure: * HTML tag attribute values don't match the baseline!AssertionError中的First differing element精确指出是第 33 个结构元素——基线中是a.donate-button锚点,当前页面中该位置已变成div.options-bar。这是"结构比对而非像素比对"最直观的验证:差异来自 DOM 节点缺失导致的序列错位。
七、运行与基线维护
运行测试
在 examples/visual_testing 目录下执行:
pytest test_layout_fail.py --html=report.html--html=report.html会同时生成 pytest HTML 报告。注意VisualLayout_FixtureTests依赖 pytest fixture 注入sb,因此必须经 pytest 运行;文件顶部的BaseCase.main(__name__, __file__)也支持直接python test_layout_fail.py方式执行。
重置视觉基线
当被测网站发生预期的布局改版时,需要重置基线以避免误报。在命令行追加参数即可:
pytest test_layout_fail.py --visual_baseline只要带--visual_baseline运行,check_window()就不会失败——它会重建视觉基线而非与旧基线比对。这是官网文档与源码 docstring 共同确认的官方用法。
适用边界
check_window()对动态内容网站效果有限:动态内容会改变页面布局与结构导致误报。对这类站点建议改用常规功能测试,或在比对前先清除动态元素(例如用ad_block()移除广告等动态内容)再执行比对。
八、延伸:把 Case Plan 纳入团队流程
回到 Case Plan 本身,它是 SeleniumBase 测试管理的一部分:
- 创建:运行
sbase caseplans(实现在 seleniumbase/console_scripts/sb_caseplans.py)启动图形界面,选择需要 Case Plan 的测试,一键为缺失者生成带默认表格的样板文件;支持-k、-m、指定文件或目录等与 pytest 一致的选择规则; - 汇总:通过界面按钮生成
case_summary.md摘要,汇总所有case_plans/目录下的计划。摘要文件生成于启动 GUI 的目录,而单个 Case Plan 生成于测试所在目录的case_plans/下——两者位置不同,多测试目录时会出现多个case_plans/文件夹; - 协作价值:Case Plan 让视觉回归测试的"操作步骤 + 预期结果"以人类可读的 Markdown 表格沉淀在代码库中,既可作为测试文档,也可作为评审与排期依据。
总结
从一份两行的 Case Plan 出发,本文完整还原了 SeleniumBase 视觉回归测试的闭环:check_window(baseline=True)建立结构化基线 →remove_element()以 JS 修改 DOM →check_window(level=3)做最严格的结构比对 → 失败时在latest_logs/生成side_by_side.html并输出差异元素定位。整个过程的核心设计是用 HTML 标签与属性替代像素截图做对比,配合level严格度分级、--visual_baseline基线重置与 Case Plan 文档化机制,为网页布局回归提供了一套可审计、可复现、低误报的自动化方案。
【免费下载链接】SeleniumBaseAPIs for browser automation, testing, and bypassing bot-detection. Includes CDP Mode: A stealthy configuration for chromium that passes every bot detection test.项目地址: https://gitcode.com/GitHub_Trending/se/SeleniumBase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考