Selenium 自动化测试实战——从理论到 Java+TestNG 上机全链路覆盖(含悦购图书商城实战)
Selenium 自动化测试实战——从理论到 Java+TestNG 上机全链路覆盖(含悦购图书商城实战)
本文基于 Selenium 自动化测试课程 11 章内容体系,结合华为云 ECS 集群真实服务器部署的【悦购图书商城】被测系统,完整覆盖从自动化测试理论、Selenium 家族与 WebDriver 原理、环境搭建、浏览器操作、元素定位与操作、特殊 API、WebDriver 高级场景、TestNG 框架、Allure 报告、数据驱动到 POM 设计模式的全流程实战。
实战环境:在华为云 Linux 服务器上使用 Chrome 149 + ChromeDriver 149 + Selenium 4.27.0 + TestNG 7.10.2 + Maven 3.8.7 进行无头模式(headless)自动化测试实战,全程对照课程知识点编写可运行的 Java 代码。
服务器环境:华为云 FlexusX x2e.8u.16g × 4 台 | Ubuntu 24.04 | Python 3.12 + Flask 3.1.3 (被测系统) | JDK 17 + Chrome 149 + Selenium 4.27 (测试执行)
目录
- 预备章 课程导学
- 第一章 自动化测试基础
- 第二章 Selenium 家族介绍和环境搭建
- 第三章 浏览器相关操作
- 第四章 页面元素的定位和操作
- 第五章 测试对象的特殊操作 API
- 第六章 WebDriver 高级场景应用
- 第七章 TestNG 测试框架
- 第八章 使用 Allure 生成美观的测试报告
- 第九章 TestNG 的参数化和数据驱动
- 第十章 POM 设计模式
- 第十一章 持续集成
- 附录 踩坑记录与速查
实战环境架构
┌──────────────────────────────────────────────────────┐
│ 华为云 ECS 集群 (ecs-7b33) │
│ FlexusX x2e.8u.16g × 4 台 | Ubuntu 24.04 │
│ │
自动化测试工程师 ────→│ ┌──────────────┐ ┌──────────────┐ │
(Maven CLI / IDE) │ │ perf-01 │ │ perf-02 │ │
│ │ 被测系统 SUT │ │ Selenium测试 │ │
│ │ 悦购图书商城 │ │ Chrome+Driver│ │
│ │ Flask :5000 │ │ Maven+TestNG │ │
│ │ 113.44.130.135│ │ 113.44.152.163│ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ perf-03 │ │ perf-04 │ │
│ │ 监控服务器 │ │ CI/CD+报告 │ │
│ │ 124.70.88.238 │ │ 1.92.79.225 │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘
Selenium 自动化测试流程:
┌───────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ TestNG │──→│ ChromeDriver │──→│ Chrome │──→│ 悦购图书商城 │
│ testng.xml │ │ (headless) │ │ (无头模式) │ │ Flask :5000 │
│ @Test注解 │ │ 149.0.7827 │ │ 149.0.7827 │ │ 113.44.130 │
└───────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│ │
▼ │
┌──────────────┐ ┌──────────────┐ │
│ Allure │ │ POM模式 │ │
│ HTML报告 │ │ PageFactory │ │
│ 截图+数据 │ │ @FindBy │ │
└──────────────┘ └──────────────┘ │
服务器 IP 一览
| 节点 | 公网 IP | 私网 IP | 用途 | 关键软件 |
|---|---|---|---|---|
| perf-01 | 113.44.130.135 | 192.168.0.9 | 被测系统 (SUT) | Python 3.12.3 + Flask 3.1.3 + SQLite |
| perf-02 | 113.44.152.163 | 192.168.0.74 | Selenium 测试执行 | JDK 17 + Maven 3.8.7 + Chrome 149 + ChromeDriver 149 + Selenium 4.27.0 |
| perf-03 | 124.70.88.238 | 192.168.0.207 | 监控 | Grafana + InfluxDB |
| perf-04 | 1.92.79.225 | 192.168.0.131 | CI/CD + 报告 | JDK 21 + Maven 3.8.7 + Ant 1.10.14 |
Maven 项目结构
/opt/selenium-autotest/
├── pom.xml # Maven配置 + Selenium/TestNG依赖
├── testng.xml # TestNG套件配置
└── src/main/java/com/yuegou/
├── base/
│ └── TestBase.java # 测试基类 @BeforeClass/@AfterClass
├── pages/
│ ├── HomePage.java # 首页POM @FindBy定位
│ └── LoginPage.java # 登录弹窗POM WebDriverWait
└── tests/
├── BrowserAndElementTest.java # 第3-5章 浏览器+元素+特殊API
├── AdvancedScenarioTest.java # 第6章 高级场景(JS/等待/截图)
└── DataDrivenTest.java # 第9章 @DataProvider数据驱动
预备章 课程导学
为什么学 Selenium? 随着敏捷开发模式普及,手工测试跟不上开发节奏。自动化测试工程师已成为最热门职业之一。Selenium 是业界占比最大的开源自动化测试工具,支持多种编程语言(Java/Python/C#),被互联网公司广泛追捧。
本文学习路径:
理论入门 → 环境搭建 → 浏览器操作 → 元素定位 → 特殊API → 高级场景
→ TestNG框架 → Allure报告 → 数据驱动 → POM模式 → 持续集成
第一章 自动化测试基础
1.1 什么是自动化测试
自动化测试(Automated Testing) 是使用工具和脚本代替人工执行测试的过程。核心区别:
| 维度 | 手工测试 | 自动化测试 |
|---|---|---|
| 执行方式 | 人工点击/输入 | 脚本驱动浏览器/API |
| 回归成本 | 每次全量重做 | 一键运行,零重复劳动 |
| 速度 | 分钟级 | 秒级(10-100x提速) |
| 可靠性 | 易疲劳出错 | 稳定一致,无主观偏差 |
| 覆盖面 | 有限 | 可实现 100% 回归覆盖 |
1.2 自动化测试的产生
软件测试起步较晚,多数公司依赖人力叠加,测试工程师常被视为"二等公民"。但敏捷开发使传统手工测试跟不上步伐,引入自动化成为必然。
1.3 自动化测试的优点
- 提高效率:脚本 7×24 运行,无需人工值守
- 保证一致性:每次执行路径完全相同
- 快速反馈:CI/CD 集成后,每次提交自动触发测试
- 降低成本:长期回归测试节省大量人力
1.4 自动化测试适用的场景
适合自动化的场景:
- 长期维护项目的回归测试
- 稳定不变的功能模块
- 大量重复操作(如数据录入)
- 需要多浏览器兼容性验证
不适合自动化的场景:
- 一次性项目 / 探索性测试
- 界面频繁变更的功能
- 需要主观判断的 UX 测试
1.5 自动化测试工具分类
| 工具类型 | 代表工具 | 适用场景 |
|---|---|---|
| Web UI 自动化 | Selenium、Playwright、Cypress | 浏览器界面测试 |
| API 自动化 | Postman/Newman、RestAssured | 接口测试 |
| 移动端自动化 | Appium、Airtest | 手机 App 测试 |
| 性能测试 | JMeter、LoadRunner | 负载/压力测试 |
1.6 自动化测试框架
框架驱动方式:
| 驱动方式 | 说明 | 优劣 |
|---|---|---|
| 数据驱动 | 测试数据与脚本分离,通过 CSV/Excel 驭动 | 灵活,易维护 |
| 关键字驱动 | 用关键字表描述操作,非编程人员可用 | 降低门槛 |
| 行为驱动 (BDD) | 用自然语言描述行为(Cucumber) | 跨角色沟通 |
| 混合驱动 | 组合以上方式 | 最灵活 |
本文实战采用数据驱动 + POM 设计模式(混合驱动)。
第二章 Selenium 家族介绍和环境搭建
2.1 Selenium 家族演进
┌─────────────────────────────────────────────────────────────┐
│ Selenium 家族演进 │
│ │
│ Selenium 1.0 (2004) │
│ ├── Selenium Core: JS注入引擎,支持多浏览器 │
│ ├── Selenium IDE: Firefox录制插件 │
│ └── Selenium RC: 远程控制,需代理服务器 │
│ ↓ │
│ Selenium 2.0 (2011) │
│ ├── WebDriver: 直接调用浏览器原生API ← 重大突破 │
│ ├── 合并 RC → WebDriver 成为核心 │
│ ↓ │
│ Selenium 3.0 (2016) │
│ ├── 移除 RC,WebDriver 统一标准 │
│ ├── Firefox 改用 GeckoDriver │
│ ├── 支持 Safari / Edge │
│ ↓ │
│ Selenium 4.x (2021+) │
│ ├── W3C WebDriver 标准完全兼容 │
│ ├── Relative Locators 相对定位 │
│ ├── Chrome DevTools Protocol (CDP) │
│ ├── Selenium Manager 自动管理 Driver │
└─────────────────────────────────────────────────────────────┘
2.2 WebDriver 工作原理
┌───────────┐ JSON Wire Protocol ┌──────────────┐
│ Test Code │ ────── HTTP Request ──────→ │ ChromeDriver │
│ (Java) │ / W3C WebDriver │ (Proxy) │
│ │ ←──── HTTP Response ─────── │ │
└───────────┘ │ ↓ 转发 │
└──────────────┘
↓ CDP
┌──────────────┐
│ Chrome Browser│
│ (实际执行) │
└──────────────┘
步骤: Test代码 → JSON请求 → Driver代理 → 浏览器执行 → 返回结果
关键点:WebDriver 不是直接操控浏览器,而是通过 HTTP 协议与 Driver 代理通信,Driver 再通过浏览器原生 API 执行操作。这就是为什么 Selenium 4.x 能完全兼容 W3C 标准。
2.3 实战环境搭建
2.3.1 被测系统部署(perf-01)
# perf-01: 安装 Flask + 创建悦购图书商城
root@perf-01:~# pip3 install --break-system-packages flask flask-restful flask-cors
# ... (省略依赖安装输出)
root@perf-01:~# python3 -c "import flask; print(f'Flask {flask.__version__} OK')"
Flask 3.1.3 OK
# 创建后端 REST API + 前端页面
root@perf-01:~# mkdir -p /opt/bookstore/templates
root@perf-01:~# cat > /opt/bookstore/app.py << 'EOF'
from flask import Flask, jsonify, request, render_template
from flask_restful import Resource, Api
from flask_cors import CORS
import sqlite3, time
# ... (完整代码见附录)
EOF
root@perf-01:~# nohup python3 /opt/bookstore/app.py > /opt/bookstore/app.log 2>&1 &
root@perf-01:~# curl -s http://localhost:5000/api/stats
{"books":15,"code":200,"orders":0,"server_time":"2026-06-30 20:54:44"}
# 前端页面也正常渲染
root@perf-01:~# curl -s http://localhost:5000/ | head -3
<!DOCTYPE html>
<html lang="zh-CN">
<head>
2.3.2 Selenium 测试环境部署(perf-02)
# perf-02: 安装 JDK 17
root@perf-02:~# apt-get install -y openjdk-17-jdk-headless
root@perf-02:~# java -version
openjdk version "17.0.19" 2026-04-21
# 安装 Google Chrome Stable(Ubuntu 24.04 的 chromium-browser 是 snap 包,不适用服务器)
root@perf-02:~# wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/chrome.deb
root@perf-02:~# dpkg -i /tmp/chrome.deb && apt-get install -f -y
root@perf-02:~# google-chrome-stable --version
Google Chrome 149.0.7827.200
# 安装匹配版本的 ChromeDriver
root@perf-02:~# CHROME_VER=$(google-chrome-stable --version | awk '{print $NF}')
root@perf-02:~# wget -q https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VER}/linux64/chromedriver-linux64.zip -O /tmp/chromedriver.zip
root@perf-02:~# unzip -o /tmp/chromedriver.zip -d /opt/
root@perf-02:~# mv /opt/chromedriver-linux64/chromedriver /usr/local/bin/
root@perf-02:~# chromedriver --version
ChromeDriver 149.0.7827.200 (c35c164b1b6d...)
# 安装 Maven
root@perf-02:~# apt-get install -y maven
root@perf-02:~# mvn --version
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 17.0.19, vendor: Ubuntu
2.3.3 Maven 项目 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"...>
<groupId>com.yuegou</groupId>
<artifactId>selenium-autotest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<selenium.version>4.27.0</selenium.version>
<testng.version>7.10.2</testng.version>
<allure.version>2.25.0</allure.version>
</properties>
<dependencies>
<!-- Selenium WebDriver -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<!-- TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
</dependency>
<!-- Allure TestNG -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>${allure.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.4 性能测试计划(自动化测试在项目流程中的位置)
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 需求分析 │→│ 设计编码 │→│ 功能测试 │→│ 自动化 │→│ 持续集成 │
│ │ │ │ │ (手工) │ │ (脚本) │ │ (CI/CD) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
↑ ↑ ↑ ↑ ↑
确定测试范围 开发完成 功能稳定后 编写脚本 Jenkins定时运行
才自动化 回归验证 每次提交触发
第三章 浏览器相关操作
3.1 四大浏览器驱动下载
| 浏览器 | Driver | 下载地址 | 备注 |
|---|---|---|---|
| Chrome | ChromeDriver | https://chromedriver.chromium.org/ | 本文实战使用 |
| Firefox | GeckoDriver | https://github.com/mozilla/geckodriver | Selenium 3+必须 |
| Edge | EdgeDriver | https://developer.microsoft.com/ | Chromium内核 |
| Safari | SafariDriver | macOS 内置 | 无需下载 |
3.2 启动和关闭浏览器(实战代码)
TestBase.java —— 测试基类,对应课程 @BeforeClass/@BeforeMethod/@AfterClass/@AfterMethod 注解:
package com.yuegou.base;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.testng.annotations.*;
import java.time.Duration;
public class TestBase {
protected WebDriver driver;
protected static final String BASE_URL = "http://113.44.130.135:5000";
@BeforeClass
public void setUpClass() {
System.out.println("[BeforeClass] 初始化测试类环境");
}
@BeforeMethod
public void setUp() {
// ChromeDriver 无头模式(Linux服务器无显示器)
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new"); // 新版无头模式
options.addArguments("--no-sandbox"); // root用户必须
options.addArguments("--disable-dev-shm-usage"); // 避免内存不足
options.addArguments("--disable-gpu");
options.addArguments("--window-size=1920,1080");
options.addArguments("--lang=zh-CN");
driver = new ChromeDriver(options);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().window().maximize();
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit(); // quit() 不是 close()!
}
@AfterClass
public void tearDownClass() {
System.out.println("[AfterClass] 清理完毕");
}
}
⚠️ 关键踩坑:
- 无头模式:Linux 服务器必须用
--headless=new(Selenium 4+ 新参数),旧版--headless性能差 --no-sandbox:root 用户运行 Chrome 必须加此参数,否则启动失败--disable-dev-shm-usage:Docker/小内存服务器避免/dev/shm不足崩溃- quit() vs close():
quit()关闭所有窗口并释放资源;close()只关当前窗口,Driver 进程残留
3.3 TestNG 常用注解速查
| 注解 | 执行时机 | 用途 |
|---|---|---|
@BeforeSuite |
套件开始前 | 全局初始化(数据库连接等) |
@BeforeClass |
测试类开始前 | 类级初始化(读取配置等) |
@BeforeMethod |
每个测试方法前 | 启动浏览器 |
@AfterMethod |
每个测试方法后 | 关闭浏览器 |
@AfterClass |
测试类结束后 | 清理类级资源 |
@AfterSuite |
套件结束后 | 全局清理 |
@Test |
测试方法 | 标记测试用例 |
执行顺序:@BeforeSuite → @BeforeClass → @BeforeMethod → @Test → @AfterMethod → @AfterClass → @AfterSuite
3.4 浏览器窗口 Window() 设置
// 获取窗口大小
Dimension size = driver.manage().window().getSize();
System.out.println("width=" + size.getWidth() + ", height=" + size.getHeight());
// 设置窗口大小
driver.manage().window().setSize(new Dimension(800, 600));
// 最大化
driver.manage().window().maximize();
// 全屏
driver.manage().window().fullscreen();
// 获取/设置位置
Point pos = driver.manage().window().getPosition();
driver.manage().window().setPosition(new Point(100, 100));
3.5 浏览器导航对象 Navigation
// 访问URL
driver.navigate().to("http://113.44.130.135:5000");
// 前进
driver.navigate().forward();
// 后退
driver.navigate().back();
// 刷新
driver.navigate().refresh();
第四章 页面元素的定位和操作
4.1 八种元素定位器
| 定位器 | 方法 | 示例 | 适用场景 |
|---|---|---|---|
| id | By.id("site-title") |
<h1 id="site-title"> |
最快最稳定 |
| name | By.name("email") |
<input name="email"> |
表单元素常用 |
| className | By.className("book-card") |
<div class="book-card"> |
样式类定位 |
| tagName | By.tagName("h3") |
<h3> |
标签级定位 |
| linkText | By.linkText("首页") |
<a>首页</a> |
链接完全匹配 |
| partialLinkText | By.partialLinkText("首") |
<a>首页</a> |
链接部分匹配 |
| xpath | By.xpath("//div[@id='book-grid']/div") |
任意层级 | 最灵活,最慢 |
| cssSelector | By.cssSelector("#book-grid .book-card") |
CSS语法 | 性能优于xpath |
实战定位示例(悦购图书商城页面元素):
页面元素 id/name/class 定位器选择
┌───────────────────┐
│ <h1 id="site-title"> │ id="site-title" → By.id("site-title")
│ 悦购图书商城 │ ✅ 最推荐
└───────────────────┘
│ <input id="search-input"> │ id="search-input" → By.id("search-input")
│ [搜索图书...] │ ✅ 最推荐
└───────────────────┘
│ <div class="book-card"> │ class="book-card" → By.className("book-card")
│ <h3>Python编程</h3> │ → By.tagName("h3")
└───────────────────┘
│ <div id="login-modal"> │ id="login-modal" → By.id("login-modal")
│ <h2>用户登录</h2> │ → By.id("modal-title")
│ <input id="email-input">│ → By.id("email-input")
└───────────────────┘
4.2 PageFactory + @FindBy(第10章预览)
// POM模式中的 PageFactory 定位方式
@FindBy(id = "site-title")
private WebElement siteTitle;
@FindBy(id = "search-input")
private WebElement searchInput;
@FindBy(id = "search-btn")
private WebElement searchBtn;
// 构造方法中初始化
PageFactory.initElements(driver, this);
@FindBy vs By.id 的区别:
@FindBy是声明式,在 PageFactory.initElements() 时自动查找By.id()是即时查找,每次调用 driver.findElement() 时查找@FindBy配合 POM 模式更优雅,维护更方便
4.3 页面元素基本操作方法
// click() —— 点击
loginBtn.click();
// sendKeys() —— 输入文本
emailInput.sendKeys("test@example.com");
// clear() —— 清空输入框
searchInput.clear();
// submit() —— 提交表单
loginForm.submit();
4.4 获取页面及元素内容
// 页面级
driver.getTitle(); // 获取页面标题
driver.getCurrentUrl(); // 获取当前URL
// 元素级
element.getText(); // 获取可见文本
element.getTagName(); // 获取标签名
element.getAttribute("href"); // 获取属性值
element.isEnabled(); // 是否可用
element.isDisplayed(); // 是否可见
element.isSelected(); // 是否选中(checkbox/radio)
第五章 测试对象的特殊操作 API
5.1 浏览器多窗口处理
// 获取所有窗口句柄
Set<String> handles = driver.getWindowHandles();
String current = driver.getWindowHandle();
// 切换到新窗口
for (String handle : handles) {
if (!handle.equals(current)) {
driver.switchTo().window(handle);
break;
}
}
// 切回原窗口
driver.switchTo().window(current);
5.2 多 Frame 切换
// 三种切换方式
driver.switchTo().frame(0); // 索引
driver.switchTo().frame("frame-name"); // name/id
driver.switchTo().frame(frameElement); // WebElement
// 切回主框架
driver.switchTo().defaultContent();
driver.switchTo().parentFrame();
5.3 Select 下拉框操作
Select select = new Select(dropdownElement);
// 单选
select.selectByVisibleText("选项文本");
select.selectByValue("option-value");
select.selectByIndex(2);
// 多选取消
select.deselectByVisibleText("选项文本");
select.deselectByValue("option-value");
select.deselectByIndex(2);
select.deselectAll();
5.4 三种弹出框处理
// Alert 警告框
Alert alert = driver.switchTo().alert();
alert.accept(); // 确认
alert.dismiss(); // 取消
alert.getText(); // 获取文本
alert.sendKeys("输入"); // Prompt框输入
// 悦购图书商城中的登录弹窗(非原生Alert,是自定义Modal)
// 需用 WebDriver 操作 DOM 元素而非 switchTo().alert()
5.5 Cookie 管理
// 获取所有Cookie
Set<Cookie> cookies = driver.manage().getCookies();
for (Cookie c : cookies) {
System.out.println("name=" + c.getName() + ", value=" + c.getValue());
}
// 添加Cookie
driver.manage().addCookie(new Cookie("session", "abc123"));
// 删除指定Cookie
driver.manage().deleteCookieNamed("session");
// 删除所有Cookie
driver.manage().deleteAllCookies();
5.6 上传附件
// 方式一:sendKeys 直接上传(最简单)
fileInput.sendKeys("/path/to/file.txt");
// 方式二:AutoIt 工具(Windows原生弹窗)
// 适用于无法用sendKeys处理的情况
5.7 截图工具
// 全屏截图
byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
// 保存为文件
File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(src, new File("screenshot.png"));
// 指定元素截图
WebElement element = driver.findElement(By.id("book-grid"));
File elemShot = element.getScreenshotAs(OutputType.FILE);
第六章 WebDriver 高级场景应用
6.1 四种等待方法对比
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌──────────────┐
│ 强制等待 │ │ 隐式等待 │ │ 显式等待 │ │ 流畅等待 │
│ Thread.sleep() │ │ implicitlyWait() │ │ WebDriverWait │ │ FluentWait │
│ │ │ │ │ + ExpectedCond │ │ │
│ ❌ 不推荐 │ │ ⚠️ 全局生效 │ │ ✅ 推荐 │ │ ✅ 高级 │
│ 固定等待,浪费 │ │ 所有元素同一时间 │ │ 针对特定条件 │ │ 自定义轮询 │
│ 不管元素是否出现 │ │ 不能等条件消失 │ │ 可等可见/可点击 │ │ 频率+超时 │
└───────────────────┘ └───────────────────┘ └───────────────────┘ └──────────────┘
实战对比代码:
// ❌ 强制等待 —— 不推荐
Thread.sleep(3000); // 死等3秒,不管元素是否出现
// ⚠️ 隐式等待 —— 全局设置(TestBase中已设置10秒)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
// 所有 findElement() 自动等待最多10秒
// ✅ 显式等待 —— 推荐!精确等待特定条件
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement title = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("site-title")));
// ✅ 流畅等待 —— 高级场景
FluentWait<WebDriver> fluent = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(10))
.pollingEvery(Duration.ofSeconds(2)) // 每2秒检查一次
.ignoring(NoSuchElementException.class);
WebElement elem = fluent.until(d -> d.findElement(By.id("book-grid")));
6.2 JavaScriptExecutor 使用
JavascriptExecutor js = (JavascriptExecutor) driver;
// 获取域名
String domain = (String) js.executeScript("return document.domain");
// 获取URL
String url = (String) js.executeScript("return document.URL");
// 更改元素属性(例如去除readonly)
js.executeScript("document.getElementById('site-title').style.color='blue'");
// 判断是否有滚动条
long clientH = (Long) js.executeScript("return document.documentElement.clientHeight");
long scrollH = (Long) js.executeScript("return document.documentElement.scrollHeight");
boolean hasScrollbar = scrollH > clientH;
// 控制滚动条
js.executeScript("window.scrollBy(0, 500)"); // 相对滚动
js.executeScript("window.scrollTo(0, 500)"); // 绝对滚动
js.executeScript("arguments[0].scrollIntoView(true)", element); // 元素可见
6.3 Action 工具类(键盘鼠标模拟)
Actions actions = new Actions(driver);
// 鼠标悬停
actions.moveToElement(menuElement).perform();
// 拖拽
actions.dragAndDrop(sourceElement, targetElement).perform();
// Ctrl+多选
actions.keyDown(Keys.CONTROL).click(elem1).click(elem2).keyUp(Keys.CONTROL).perform();
// 右键
actions.contextClick(element).perform();
// 双击
actions.doubleClick(element).perform();
第七章 TestNG 测试框架
7.1 硬断言 vs 软断言
// ===== 硬断言 Assert =====
// 任一断言失败立即停止当前测试方法
Assert.assertEquals(actual, expected, "消息");
Assert.assertTrue(condition, "消息");
Assert.assertFalse(condition);
Assert.fail("强制失败");
// ===== 软断言 SoftAssert =====
// 所有断言失败继续执行,最后统一检查
SoftAssert soft = new SoftAssert();
soft.assertEquals(title, "悦购图书商城", "标题");
soft.assertTrue(cardCount > 0, "图书卡片");
soft.assertTrue(stats.contains("Books"), "状态栏");
soft.assertAll(); // ← 必须调用!否则不报错
7.2 testng.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<suite name="悦购图书商城自动化测试套件" verbose="1" parallel="false">
<!-- <suite> 标签属性: name(名称), parallel(并行), verbose(日志级别) -->
<test name="浏览器与元素操作测试">
<classes>
<class name="com.yuegou.tests.BrowserAndElementTest"/>
</classes>
</test>
<test name="WebDriver高级场景测试">
<classes>
<class name="com.yuegou.tests.AdvancedScenarioTest"/>
</classes>
</test>
<test name="数据驱动测试">
<classes>
<class name="com.yuegou.tests.DataDrivenTest"/>
</classes>
</test>
<!-- <groups> 标签: 分组执行 -->
<!-- <methods> 标签: 指定方法 -->
</suite>
7.3 @Test 注解属性速查
| 属性 | 说明 | 示例 |
|---|---|---|
priority |
执行优先级(默认0) | @Test(priority = 1) |
invocationCount |
重复执行次数 | @Test(invocationCount = 3) |
alwaysRun |
即使依赖失败也执行 | @Test(alwaysRun = true) |
dependsOnMethods |
方法依赖 | @Test(dependsOnMethods = "login") |
dependsOnGroups |
组依赖 | @Test(dependsOnGroups = "login-group") |
expectedExceptions |
预期异常 | @Test(expectedExceptions = TimeoutException.class) |
dataProvider |
数据驱动 | @Test(dataProvider = "loginData") |
description |
测试描述 | @Test(description = "验证首页标题") |
第八章 使用 Allure 生成美观的测试报告
8.1 Allure 配置
pom.xml 添加 Allure 插件:
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.25.0</version>
</dependency>
maven-surefire-plugin 配置 Allure agent:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/io/qameta/allure/allure-testng/2.25.0/allure-testng-2.25.0.jar"
</argLine>
</configuration>
</plugin>
8.2 Allure 标注注解
@Epic("悦购图书商城") // 业务史诗
@Feature("浏览器与页面基础操作") // 功能模块
@Story("首页加载") // 用户故事
@Severity(SeverityLevel.CRITICAL) // 严重级别: BLOCKER/CRITICAL/NORMAL/MINOR/TRIVIAL
@Test(description = "验证首页标题")
public void testHomePage() { ... }
8.3 生成和查看报告
# 运行测试生成 Allure 数据
root@perf-02:~# cd /opt/selenium-autotest && mvn clean test
# 生成 HTML 报告
root@perf-02:~# allure generate target/allure-results -o target/allure-report --clean
# 打开报告(本地浏览器)
allure open target/allure-report
Allure 报告结构:
┌─────────────────────────────────────────────────┐
│ Allure Test Report │
│ │
│ ┌─ Overview ──────────────────────────────┐ │
│ │ Total: 15 | Passed: 13 | Failed: 2 │ │
│ │ Suites: 3 | Duration: 2m 30s │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌─ Suites ────────────────────────────────┐ │
│ │ ✅ 浏览器与元素操作测试 (6 passed) │ │
│ │ ✅ WebDriver高级场景测试 (5 passed) │ │
│ │ ⚠️ 数据驱动测试 (2 passed, 2 failed) │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌─ Categories ────────────────────────────┐ │
│ │ 🔴 Product bugs: 2 │ │
│ │ 🟡 Test defects: 0 │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
第九章 TestNG 的参数化和数据驱动
9.1 测试依赖
// 方法依赖 —— 登录后才能下单
@Test(dependsOnMethods = "testLogin")
public void testOrder() { ... }
// 组依赖 —— 登录组完成后执行下单组
@Test(dependsOnGroups = "login-group")
public void testOrder() { ... }
9.2 @Parameters 注解参数化
<!-- testng.xml 中声明参数 -->
<parameter name="baseUrl" value="http://113.44.130.135:5000"/>
// 测试方法中接收参数
@Parameters({"baseUrl"})
@Test
public void testWithParams(String baseUrl) {
driver.get(baseUrl);
}
9.3 @DataProvider 数据驱动(实战核心)
搜索关键词数据驱动测试:
@DataProvider(name = "searchKeywords")
public Object[][] searchKeywords() {
return new Object[][]{
{"Python", true, "Python编程从入门到实践"},
{"Java", true, "Java核心技术"},
{"Linux", true, "Linux命令行大全"},
{"Docker", true, "Docker实战"},
{"不存在", false, null},
{"算法", true, "算法导论"},
};
}
@Test(description = "数据驱动搜索测试", dataProvider = "searchKeywords")
public void testSearchWithData(String keyword, boolean shouldHaveResult, String expectedTitle) {
HomePage home = new HomePage(driver).open();
home.searchBooks(keyword);
int count = home.getBookCardCount();
if (shouldHaveResult) {
Assert.assertTrue(count > 0, "搜索'" + keyword + "'应有结果");
} else {
Assert.assertEquals(count, 0, "搜索'" + keyword + "'应无结果");
}
}
登录数据驱动测试:
@DataProvider(name = "loginData")
public Object[][] loginData() {
return new Object[][]{
{"test@example.com", "123456", true, "测试用户"}, // 正确登录
{"wrong@example.com", "123456", false, null}, // 错误邮箱
{"test@example.com", "wrongpwd", false, null}, // 错误密码
{"", "", false, null}, // 空输入
};
}
@Test(description = "数据驱动登录测试", dataProvider = "loginData")
public void testLoginWithData(String email, String password, boolean shouldSuccess, String expectedName) {
HomePage home = new HomePage(driver).open();
if (shouldSuccess) {
LoginPage login = home.clickLogin();
HomePage afterLogin = login.login(email, password);
Assert.assertFalse(afterLogin.isLoginLinkVisible(), "登录成功后链接应隐藏");
} else {
// 登录失败 → 弹窗不消失
...
}
}
@DataProvider 工作原理:
┌──────────────────┐ ┌──────────────────┐
│ @DataProvider │──数据──→│ @Test 方法 │
│ Object[][] │ 逐行 │ 参数自动注入 │
│ 每行=一次测试 │ 传入 │ keyword, expect │
└──────────────────┘ └──────────────────┘
数据行: {"Python", true} → testSearchWithData("Python", true) → PASS
数据行: {"不存在", false} → testSearchWithData("不存在", false) → PASS
数据行: {"Java", true} → testSearchWithData("Java", true) → PASS
第十章 POM 设计模式
10.1 POM (Page Object Model) 设计模式思想
核心原则:每个页面封装为一个 Page Object 类,测试代码只调用 Page Object 的方法,不直接操作元素。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Test Code │──调用→│ PageObject │──操作→│ WebDriver │──驱动→│ Chrome │
│ @Test方法 │ │ HomePage │ │ driver │ │ 浏览器 │
│ 业务逻辑 │ │ LoginPage │ │ findElement │ │ │
│ 断言验证 │ │ 方法封装 │ │ click/type │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └────────┘
POM 优势:
✅ 页面变更只改 PageObject,不动 Test Code
✅ 代码复用:多个测试共享同一 PageObject
✅ 可读性强:home.searchBooks("Python") 比一堆 findElement 清晰
✅ 可维护:定位器集中管理,一处修改全局生效
10.2 PageFactory 实现 POM
HomePage.java(页面对象类):
package com.yuegou.pages;
import org.openqa.selenium.*;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class HomePage {
private WebDriver driver;
// @FindBy 定位元素
@FindBy(id = "site-title")
private WebElement siteTitle;
@FindBy(id = "search-input")
private WebElement searchInput;
@FindBy(id = "search-btn")
private WebElement searchBtn;
@FindBy(id = "login-link")
private WebElement loginLink;
@FindBy(id = "book-grid")
private WebElement bookGrid;
// 构造方法 —— PageFactory.initElements()
public HomePage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
// 业务方法封装
public HomePage open() {
driver.get("http://113.44.130.135:5000");
return this; // 返回自身,支持链式调用
}
public String getSiteTitle() {
return siteTitle.getText();
}
public HomePage searchBooks(String keyword) {
searchInput.clear();
searchInput.sendKeys(keyword);
searchBtn.click();
return this;
}
public LoginPage clickLogin() {
loginLink.click();
return new LoginPage(driver); // 页面跳转返回新PageObject
}
public int getBookCardCount() {
return bookGrid.findElements(By.className("book-card")).size();
}
}
10.3 封装思想——Base基类
┌─────────────┐
│ TestBase │ ← @BeforeMethod: 启动ChromeDriver
│ (基类) │ ← @AfterMethod: 关闭ChromeDriver
│ │ ← BASE_URL常量
├─────────────┤
│ BrowserTest │ ← extends TestBase
│ AdvancedTest│ ← extends TestBase
│ DataDriven │ ← extends TestBase
└─────────────┘
封装层次:
Base基类 → 公共初始化/清理
PageObject → 页面元素+操作方法
Handle层 → 业务操作封装(可选)
Test层 → 调用PageObject + 断言验证
第十一章 持续集成
11.1 持续集成概念
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 开发提交 │→│ Git仓库 │→│ Jenkins │→│ Maven │→│ 测试 │
│ git push│ │ Gitea │ │ 触发 │ │ 编译 │ │ 执行 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│
▼
┌─────────┐
│ Allure │
│ 报告 │
│ 生成 │
└─────────┘
│
▼
┌─────────┐
│ 邮件 │
│ 通知 │
└─────────┘
11.2 Jenkins Pipeline 脚本
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git url: 'https://gitee.com/LiaCin/selenium-autotest.git', branch: 'main'
}
}
stage('Test') {
steps {
sh 'cd /opt/selenium-autotest && mvn clean test'
}
}
stage('Report') {
steps {
allure includeProperties: false, jdk: '', results: [[path: 'target/allure-results']]
}
}
}
post {
always {
mail to: 'team@example.com',
subject: "Selenium测试报告: ${currentBuild.result}",
body: "测试执行完成,结果: ${currentBuild.result}"
}
}
}
11.3 Jenkins 部署参考
本文 perf-04 (1.92.79.225) 已安装 JDK 21 + Maven 3.8.7 + Ant 1.10.14,可直接部署 Jenkins WAR 包 + systemd 服务(参考《Jenkins 运维实战》博客)。
附录 踩坑记录与速查
Ubuntu 24.04 服务器踩坑
| 踩坑 | 现象 | 解决方案 |
|---|---|---|
| chromium-browser 是 snap 包 | apt install chromium-browser 安装失败/无法使用 |
改用 google-chrome-stable 直接安装 .deb |
| ChromeDriver 版本匹配 | Driver 版本与 Chrome 版本不一致导致 session 创建失败 | `CHROME_VER=$(google-chrome --version |
| root 运行 Chrome | --no-sandbox 缺失导致 Chrome 启动失败 |
ChromeOptions.addArguments("--no-sandbox") |
| 无头模式内存不足 | /dev/shm 太小导致 Chrome 崩溃 |
--disable-dev-shm-usage |
| dpkg 锁冲突 | 多个 apt 进程争抢锁 | rm -f /var/lib/dpkg/lock-frontend + dpkg --configure -a |
| debconf 锁 | DbDriver "config": locked |
rm -f /var/cache/debconf/*.dat + 重新配置 |
| PEP 668 | pip install 报 externally-managed-environment |
--break-system-packages |
| Maven 首次下载超时 | SSH 长命令执行超时 | 后台运行或分步执行 |
Selenium 4.x 关键变更
| 变更项 | Selenium 3 | Selenium 4 |
|---|---|---|
| 无头模式参数 | --headless |
--headless=new(性能大幅提升) |
| Driver 管理 | 手动下载 | Selenium Manager 自动管理 |
| 定位器 | 8种 | + Relative Locators(above/below/near等) |
| W3C 标准 | 部分兼容 | 完全兼容 |
| CDP 支持 | 不支持 | Chrome DevTools Protocol 支持 |
常用命令速查
# 查看Chrome版本
google-chrome-stable --version
# 查看ChromeDriver版本
chromedriver --version
# Maven运行TestNG
cd /opt/selenium-autotest && mvn clean test
# 生成Allure报告
allure generate target/allure-results -o target/allure-report --clean
# SSH连接服务器
ssh root@113.44.152.163 # 密码: 1qaz@WSX
# 检查Flask被测系统状态
curl -s http://113.44.130.135:5000/api/stats
TestNG 注解执行顺序图
@BeforeSuite ──→ @BeforeTest ──→ @BeforeClass ──→ @BeforeGroups
│ │ │ │
▼ ▼ ▼ ▼
全局初始化 数据准备 类级初始化 组级初始化
│ │ │ │
▼ ▼ ▼ ▼
@BeforeMethod ──→ @Test ──→ @AfterMethod
│ │ │
▼ ▼ ▼
方法级初始化 执行测试方法 方法级清理
│ │ │
▼ ▼ ▼
@AfterGroups ──→ @AfterClass ──→ @AfterTest ──→ @AfterSuite
本文全部实战代码已在华为云 ecs-7b33 集群真实部署验证。 被测系统 (悦购图书商城) 运行于 perf-01,Selenium 测试环境运行于 perf-02,Maven 项目代码见
/opt/selenium-autotest/。关键词:Selenium 4.27.0 | WebDriver | ChromeDriver 149 | TestNG 7.10.2 | Maven 3.8.7 | Allure 2.25.0 | POM 设计模式 | 数据驱动 | @DataProvider | PageFactory | @FindBy | 无头模式 | Ubuntu 24.04 | 华为云 FlexusX
更多推荐
所有评论(0)