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 (测试执行)


目录


实战环境架构

                        ┌──────────────────────────────────────────────────────┐
                        │         华为云 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 自动化测试的优点

  1. 提高效率:脚本 7×24 运行,无需人工值守
  2. 保证一致性:每次执行路径完全相同
  3. 快速反馈:CI/CD 集成后,每次提交自动触发测试
  4. 降低成本:长期回归测试节省大量人力

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] 清理完毕");
    }
}

⚠️ 关键踩坑

  1. 无头模式:Linux 服务器必须用 --headless=new(Selenium 4+ 新参数),旧版 --headless 性能差
  2. --no-sandbox:root 用户运行 Chrome 必须加此参数,否则启动失败
  3. --disable-dev-shm-usage:Docker/小内存服务器避免 /dev/shm 不足崩溃
  4. 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

更多推荐