Skip to content

Selenium UI 自动化基础 ​

T2-002必学进阶约 90 分钟JDK 21测试专项 B:接口·UI·性能 · UI 自动化

1. 本节定位 ​

接口测试测的是"服务端对外说的话";从本节起测**"用户在浏览器里看到的东西"**:

text
接口测试:发 HTTP → 断言 JSON          ← 假设"页面正确地把数据显示给用户"
UI 测试:开浏览器 → 点、填、读文本      ← 直接验证"用户真的能用"

两者的分工:接口测试覆盖"逻辑",UI 测试覆盖"呈现与交互" ✓ ——本节先解决"怎么让它跑起来" ✓

2. 学习目标 ​

  • 说出 UI 自动化的四层结构(测试代码 → 协议 → 驱动 → 浏览器)★★★;
  • 知道驱动从哪来,以及"自动获取失败"时怎么办 ★★★(含本轮的真实故障);
  • 说出 headless 与有头模式的取舍 ★★;
  • 知道环境自检该验证哪三件事 ★★★;
  • 知道 assumeTrue 跳过与"真失败"的区别 ★★(T2-018);
  • 知道"什么时候不该用 UI 自动化" ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

UI 自动化的门槛比接口测试高得多 ——先看四个"还没写测试就卡住"的位置:

#卡在哪现象
①浏览器机器上没装 / 版本不对 ✗
②浏览器驱动版本必须与浏览器匹配 ✗✗
③被测应用它得先跑起来,而且是对的那个版本 ✗
④等待时机元素还没渲染出来就去找 ✗(T2-009)

本节处理 ①②③(④ 留给 T2-009)✓

★ 为什么强调"门槛"这件事:

接口测试的失败几乎都是"业务问题";UI 测试的失败有一半是"环境问题" ✓✓

所以 UI 测试的第一课不是写断言,而是"把环境问题与业务问题分开" ✓

5. 核心概念 ​

5.1 四层结构:一次点击经过了什么 ★★★ ​

text
① 测试代码(Java + Selenium API)
      │  driver.findElement(By.id("submit")).click()
      ↓
② W3C WebDriver 协议(HTTP + JSON)
      │  POST /session/<id>/element/<eid>/click
      ↓
③ 浏览器驱动(msedgedriver / chromedriver)
      │  一个本地 HTTP 服务,把协议翻译成浏览器能懂的指令
      ↓
④ 浏览器(Edge / Chrome)
         真正执行点击、渲染、执行 JS

★ 这个结构解释了三件事:

现象原因
驱动必须与浏览器版本匹配 ★★协议在演进,驱动与浏览器是"配套"的 ✗
驱动是一个"本地服务进程" ★所以能在任务管理器里看到 msedgedriver.exe ✓
UI 测试慢每次操作都要走一遍"协议 → 驱动 → 浏览器" ✓

★ 本轮实测印证了第 2、3 条:

text
测试运行时:msedgedriver 进程 = 1,msedge 进程 = 8(headless 的多进程架构)

这 9 个进程就是"四层结构"的证据 ✓✓ ——UI 测试的成本不是"代码慢",而是"这条链本来就长" ✓

5.2 驱动从哪来:自动 vs 手动 ★★★ ​

Selenium 4.6+ 内置 Selenium Manager,会自动识别浏览器版本并下载匹配的驱动 ✓ ——这是默认做法 ✓

但它依赖网络 ✗ ——本轮就撞上了 ✓

★ 真实故障记录(原文照抄):

text
org.openqa.selenium.remote.NoSuchDriverException:
Unable to obtain: msedgedriver, error Command failed with code: 65
error sending request for url (https://msedgedriver.azureedge.net/LATEST_RELEASE_152_WINDOWS)
Driver info: driver.version: EdgeDriver

根因:Selenium Manager 要访问微软的驱动 CDN,而当前网络到不了那个域名 ✗✗

★ 关键观察:这个失败表现为 Errors,而不是 Failures ✓✓ —— 因为它是"环境异常",不是"断言不成立" ✓(T2-017 5.2 的区分)

解决方案:指定本地驱动 ✓✓

powershell
# 1. 查本机浏览器版本
(Get-Item "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe").VersionInfo.ProductVersion
# → 153.0.4234.48

# 2. 从国内镜像取同版本驱动(npmmirror 有完整归档)
#    https://registry.npmmirror.com/-/binary/edgedriver/153.0.4234.48/edgedriver_win64.zip

# 3. 解压到项目内,并在启动测试时指定
mvn test -pl ui-testing -Pui-tests "-DargLine=-Dwebdriver.edge.driver=<绝对路径>\msedgedriver.exe"

★ 两种方式的取舍:

自动(Selenium Manager)手动(本地驱动)
优点零配置 ✓离线可用、版本可控 ✓✓
缺点依赖网络 ✗✗需要自己管版本 ✗
适用开发机、能上网的 CI ✓受限网络、内网 CI ✓✓

★ 一条实践经验:

把驱动当作"环境依赖"来管理,而不是"让工具自己去搞" ✓✓

因为"自己去搞"失败时,你得到的是一个 NoSuchDriverException,而不是"网络不通" ✗

5.3 headless 与有头模式 ★★ ​

headless(无头)★有头
界面不显示窗口显示 ✓
速度快 ✓慢(要渲染)✗
调试看不到现场 ✗能看着它跑 ✓
适用CI、日常回归 ✓✓写/调测试用例时 ✓

配置方式:

java
EdgeOptions options = new EdgeOptions();
options.addArguments("--headless=new", "--disable-gpu", "--no-sandbox", "--window-size=1280,900");
WebDriver driver = new EdgeDriver(options);

★ 三个参数的作用:

参数作用
--headless=new ★新版无头模式(老写法是 --headless,行为不同 ⚠️)
--disable-gpu无头下禁用 GPU 加速(避免部分环境的异常)
--window-size固定视口大小 ——否则默认尺寸会让响应式布局出现不同元素 ★★

★ --window-size 常被忽略但很关键:

同一个页面在 1280 宽和 800 宽下,可能显示不同的元素(响应式设计)✓

不固定视口大小 → 定位结果随环境变 → flaky ✗✗

★ 一个真实的提醒:

headless 的"看不见"是双刃剑:它能跑得很快,但你得靠"截图 + 页面源码"来看现场 ✓

所以 T2-011 的"失败留证"在 headless 下是必需的,不是可选的 ✓✓

5.4 环境自检该验证哪三件事 ★★★ ​

在写任何业务断言之前,先用一个"欢迎页"验证环境 ✓ ——这样失败时能立刻区分"环境问题"与"业务问题" ✓✓

本项目就是这么做的(labs/web-basics 提供了一个极简自检页 /ui/hello):

java
@Test
@DisplayName("Edge headless 能打开页面并读到标题")
void edge_headless_should_open_page() {
    EdgeOptions options = new EdgeOptions();
    options.addArguments("--headless=new", "--disable-gpu", "--no-sandbox");
    driver = new EdgeDriver(options);            // ① 浏览器 + 驱动能起来吗
    driver.get(BASE_URL + "/ui/hello");          // ② 被测应用可达吗
    String greeting = driver.findElement(By.id("greeting")).getText();   // ③ 能读到元素吗
    assertEquals("UI 环境自检", driver.getTitle());
    assertTrue(greeting.contains("你好"));
}

三件事,各自失败的含义完全不同:

#验证失败含义
①能创建 WebDriver浏览器/驱动问题 ✗(不是被测系统的问题)
②能导航到页面应用没起 / 地址错 / 网络 ✗
③能读到元素文本页面结构或渲染问题 ⚠️

★ 本项目的实测结果:

text
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 25.45 s
  --- in cn.kb.learn.ui.UiEnvironmentCheckTest

三个自检测试全过 ✓ ——它证明的正是"浏览器 + 驱动 + 应用 + 定位"这条链是通的 ✓✓

★ 一个反面的教训:

如果不单独做自检,第一次跑测试时你会同时面对四个未知量 ✗

自检的作用是把它们逐个钉死 ✓✓

5.5 定位方式总览 ★ ​

Selenium 支持多种定位方式,本节先建立印象,T2-009 会用真实计时对比它们:

方式写法适用
id ★★By.id("submit")唯一且稳定 ✓
name ★By.name("product")表单字段 ✓
cssSelector ★★By.cssSelector("p.summary")灵活、性能好 ✓
xpathBy.xpath("//td[@class='cell-product']")能表达"文本等于"这类条件 ✓
linkText / partialLinkTextBy.linkText("登录")链接 ✓
[data-testid] ★★★By.cssSelector("[data-testid='create-submit']")测试专用钩子,与前端的样式/结构解耦 ✓✓

★ data-testid 值得单独强调:

CSS 类名会被前端为了样式而改动,id 会被为了布局而改动 ✗

data-testid 是为"测试"保留的契约 ✓ ——它变更的唯一理由就是"页面真的变了" ✓✓

本项目的页面就用了它:

html
<button type="submit" id="create-btn" data-testid="create-submit">创建订单</button>

5.6 什么时候不该用 UI 自动化 ★★★ ​

这一小节比 5.1 ~ 5.5 都重要 ——因为"用错地方"是 UI 自动化最常见的失败 ✓

UI 自动化的三个固有代价:

代价说明
慢 ★★每个操作走"协议 → 驱动 → 浏览器";本项目 22 个用例跑了 256 秒(平均 11.6 秒/例)
脆 ★★依赖 DOM、视口、异步时序、浏览器版本 ✗
贵 ★要维护浏览器与驱动版本、要维护选择器 ✗

★ 所以判断原则是:

能用接口测试覆盖的,就不要用 UI 测试 ✓✓

UI 测试只留给"只有通过界面才能验证的东西" ✓

该用 UI 测不该用 UI 测
页面能否渲染、元素是否正确显示 ✓金额计算是否正确 ✗(接口测)
表单校验提示是否出现 ✓权限校验逻辑 ✗(接口测)
点击后是否跳转/弹窗 ✓★列表排序算法 ✗(单元测)
响应式布局在不同视口的表现 ✓各种边界值 ✗(接口测,参数化更快)

★ 一个实用的数量级经验:

text
单元测试    数百~数千个     秒级
接口测试    数十~数百个      分钟级
UI 测试     几个~几十个      十分钟级   ← 只覆盖"关键用户路径"

本项目的数据印证了它 ✓:

类型用例数耗时
单元测试(TA)116秒级
接口测试(TB)421.85 s
UI 测试22256 s ✓

★ 结论:UI 测试的"性价比"最低 ——所以它必须"少而精" ✓✓

6. 递进示例 ​

6.1 最小示例:打开一个页面 ​

java
EdgeOptions options = new EdgeOptions();
options.addArguments("--headless=new");
WebDriver driver = new EdgeDriver(options);
driver.get("http://localhost:18090/ui/hello");
System.out.println(driver.getTitle());
driver.quit();          // ★ 一定要 quit,否则浏览器进程会残留

6.2 业务示例:环境自检 ​

见 5.4 的代码 ✓

6.3 反例:不做自检直接写业务断言 ​

text
第一次运行:8 个用例全红 → 分不清是"页面坏了"还是"驱动没装" ✗✗

7. 完整可运行代码与实测 ​

被测页面:labs/web-basics/src/main/java/cn/kb/learn/web/ui/UiPageController.java (提供 /ui/hello 自检页与 /ui/orders 订单管理页)

测试代码:labs/ui-testing/src/test/java/cn/kb/learn/ui/UiEnvironmentCheckTest.java

运行(脚本会自动打包、启动应用、跑测试、停应用):

powershell
pwsh -File scripts\ui-test.ps1

实测输出(真实运行结果):

text
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 25.45 s
  --- in cn.kb.learn.ui.UiEnvironmentCheckTest

★ 运行时会看到的现象(可以用任务管理器验证四层结构):

text
msedgedriver.exe    1 个      ← 驱动(本地 HTTP 服务)
msedge.exe          8 个      ← 浏览器(headless 的多进程架构)
java.exe                       ← 测试 JVM

★ 本轮踩的坑与解法:

现象根因解法
NoSuchDriverException + azureedge.net 不可达Selenium Manager 无法访问微软 CDN从 npmmirror 取同版本驱动,用 -Dwebdriver.edge.driver 指定 ✓
测试报 404,但页面代码明明写了跑的是上次留下的旧 jar脚本"总是重新打包" ✓(T2-018 说的"环境不一致")

第二条尤其值得记住:

"改了没生效"是环境类问题的头号伪装 ✓ —— UI 测试尤其容易中招,因为应用是"另一个进程",它的代码版本与测试代码的版本是两件事 ✓✓

8. Debug 观察点 ​

  1. 看任务管理器:msedgedriver 与 msedge 进程 ★ ——这是四层结构的实证 ✓;
  2. 把 --headless=new 去掉:看到浏览器窗口自己操作 ✓ ——调试定位问题时非常有用 ★★;
  3. 把 --window-size 去掉:观察响应式布局下元素是否变化 ★;
  4. 故意不 quit():观察浏览器进程残留 ⚠️(生产环境的常见事故);
  5. 把 driver.get() 的地址改成错的:观察异常类型(NoSuchDriverException?WebDriverException?)★。

9. 常见错误 ​

#错误说明
1驱动版本与浏览器不匹配 ✗✗最经典的失败(5.2)✗
2依赖自动下载而不做离线兜底 ✗受限网络直接崩(本轮)✗
3忘了 driver.quit() ✗✗浏览器进程残留、内存泄漏 ⚠️
4不固定 --window-size ✗响应式布局让定位结果不稳定 ✗(5.3)
5不做环境自检就写业务断言 ✗✗失败时分不清环境与业务(5.4)✗
6拿旧 jar 测试 ✗✗"改了没生效"(第 7 节)✗✗
7用 UI 测"接口能测的东西" ✗✗又慢又脆(5.6)✗✗

10. 动手实验 ​

目标:把"四层结构"从概念变成你亲手验证过的事实。

步骤:

  1. 跑一次 scripts\ui-test.ps1,同时打开任务管理器,记录 msedge 与 msedgedriver 的进程数 ★;
  2. 查本机浏览器版本,对比驱动版本(看是否匹配)✓;
  3. 把 --headless=new 去掉跑一次,看浏览器自己操作 ★★;
  4. 故意把驱动路径改成错的,观察异常类型与它是 Errors 还是 Failures ★★;
  5. 统计一次完整 UI 测试的耗时,与接口测试对比(本项目 256 s vs 21.85 s)✓。

验收方式:能说出"你这台机器上 UI 测试的四层分别是什么"。

扩展任务:给项目加"一键准备环境"脚本:

  • 检测浏览器版本 ✓;
  • 检测驱动版本是否匹配 ✓;
  • 不匹配时给出下载地址(提示:https://registry.npmmirror.com/-/binary/edgedriver/)✓;
  • 说明:为什么这比"让人记住版本要对"可靠(提示:把约定变成脚本)✓

11. 练习题 ​

  1. 判断:UI 测试失败说明页面有 bug。(对 / 错)
  2. 填空:UI 自动化的四层是 、、、;其中"必须与浏览器版本匹配"的是 ______。
  3. 简答:为什么"驱动获取失败"表现为 Errors 而不是 Failures?
  4. (代码阅读)
java
EdgeOptions options = new EdgeOptions();
options.addArguments("--headless=new");
WebDriver driver = new EdgeDriver(options);
driver.get("http://localhost:18090/ui/hello");
assertEquals("UI 环境自检", driver.getTitle());
// 没有 driver.quit()

这段代码有什么问题? 5. (编程)给 UiEnvironmentCheckTest 加一个测试:验证视口大小确实生效(读取 window.innerWidth 并与设置的 --window-size 对比)。

参考答案(要点)
  1. 错。可能是环境问题 ✓ ——所以要先做环境自检 ✓(5.4)
  2. 测试代码、W3C WebDriver 协议、浏览器驱动、浏览器;浏览器驱动 ✓
  3. 因为它是"外部依赖异常",不是"断言不成立" ✓ ——分类价值在于"该往哪个方向排查" ✓(T2-017 5.2)
  4. 缺少 driver.quit() ✗ ——浏览器进程会残留 ✓ ——用例一多就会堆积大量 msedge 进程,把机器拖慢 ✓★
  5. 要点:driver.manage().window().getSize(),或用 executeScript("return window.innerWidth") ✓

12. 掌握标准 ​

  • [ ] 能说出 UI 自动化的四层结构 ★★★;
  • [ ] 知道驱动必须与浏览器版本匹配,且知道自动获取失败时怎么办 ★★★;
  • [ ] 知道 headless 与有头模式的取舍,且知道要固定 --window-size ★★;
  • [ ] 会写"环境自检"测试,并区分三类失败 ★★★;
  • [ ] 知道 data-testid 为什么比 CSS 类名稳定 ★★;
  • [ ] 知道"能用接口测的不要用 UI 测" ★★★;
  • [ ] 知道"改了没生效"(旧 jar)是 UI 测试的常见伪装 ✓。

13. 扩展资源 ​

14. 下一步学习 ​

《Selenium 元素定位与显式等待》:从"能跑起来"到"能稳定地找到元素"。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引