说到Selenium,很多刚入行测试的小伙伴可能第一反应是“这玩意儿装起来好麻烦”,或者“代码跑起来总出各种奇奇怪怪的错”。别急,今天咱们就坐在同一个战壕里,我把这些年踩过的坑、调过的配置,还有那些能让测试脚本稳如老狗的实用技巧,掰开了揉碎了讲给你听。咱们不整那些虚头巴脑的官方文档翻译,直接上干货。
先聊聊:为什么是 Maven + Selenium 这种组合?
你可能会问,直接用 IDE(比如 IntelliJ IDEA)导个 jar 包不行吗?当然行,但那是“单兵作战”。一旦项目变大,依赖关系一复杂,手动管理 jar 包简直就是噩梦——今天缺个 selenium-server,明天爆个版本冲突,后天又发现 ChromeDriver 对不上浏览器版本。
Maven 的核心优势在于依赖管理自动化。你只需要在 pom.xml 里声明:“我要 Selenium 4.15.0 版本,顺便给我一个 Chrome 驱动支持”,Maven 就会帮你去中央仓库下载所有相关的依赖(包括那些你没明确指定但 Selenium 内部需要的包,比如 guava、jsoup 等),并且解决版本冲突。对于团队协作来说,任何人把代码 clone 下来,执行一条 mvn install,环境就自动配好了,这才是现代工程化的样子。
第一步:搭建你的 Maven 项目骨架
如果你还没有一个现成的 Maven 项目,咱们先快速建一个。在终端(Terminal)里执行以下命令:
mvn archetype:generate -DgroupId=com.example.tests -DartifactId=selenium-auto-test -DarchetypeArtifactId=maven-archetype-quickstart -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
这条命令会生成一个标准的 Maven 目录结构:src/main/java 放业务代码,src/test/java 放测试代码。咱们这次主要折腾的是测试,所以重点看 pom.xml 和 test 目录。
生成完后,用 IntelliJ IDEA 或 VS Code 打开这个项目,你会看到一个 pom.xml 文件,这是整个项目的“配置文件”。
第二步:在 pom.xml 里配置 Selenium 依赖
打开 pom.xml,在 <dependencies> 标签内加入以下内容。这里有个关键点:Selenium 4 和 Selenium 3 的配置方式差别很大,咱们以目前主流的 Selenium 4 为例,但也会提一下 3 的注意事项。
<dependencies>
<!-- Selenium Java 客户端 -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.15.0</version> <!-- 请检查 Maven Central 获取最新版本 -->
</dependency>
<!-- JUnit 5:Java 最流行的单元测试框架,用来组织测试用例 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 引擎,让 IDE 能运行测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers(可选但强烈推荐):如果你需要在 Docker 里跑浏览器,这是神器 -->
<!-- 暂时先注释掉,后面讲到高级技巧时再启用 -->
<!--
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>selenium</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
-->
<!-- Allure 报告(可选):生成漂亮的测试报告 -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>2.25.0</version>
<scope>test</scope>
</dependency>
</dependencies>
这里有个新手常犯的错误:只引入了 selenium-java,结果运行时报错找不到 WebDriverManager。selenium-java 只是核心客户端,它不包含自动下载浏览器的功能。要解决浏览器驱动版本不匹配的问题,我们需要额外引入 WebDriverManager,或者手动管理驱动(不推荐后者,因为太麻烦)。
继续修改 pom.xml,加入 WebDriverManager:
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.6.3</version>
</dependency>
为什么要用 WebDriverManager? 以前我们得去 Selenium 官方下载页面 或者 ChromeDriver 镜像站 手动下载对应版本的 chromedriver.exe 或 geckodriver,然后配置系统环境变量 webdriver.chrome.driver。一旦浏览器升级了,驱动就废了,脚本直接报错。WebDriverManager 会自动检测你本地安装的 Chrome/Firefox 版本,然后自动下载匹配的驱动并缓存起来,彻底解放双手。
第三步:编写第一个能跑的 Selenium 脚本
依赖配好后,咱们写个最简单的例子。在 src/test/java/com/example/tests/ 目录下新建一个类 BasicSeleniumTest.java。
package com.example.tests;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BasicSeleniumTest {
private WebDriver driver;
private WebDriverWait wait;
// 每个测试方法执行前运行,初始化浏览器
@BeforeEach
public void setUp() {
// 使用 WebDriverManager 自动管理 Chrome 驱动
WebDriverManager.chromedriver().setup();
// 配置 Chrome 选项:无头模式(headless)适合 CI/CD 环境,平时调试建议关闭
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized"); // 窗口最大化,方便调试
options.addArguments("--disable-gpu"); // 某些环境下禁用 GPU 加速
// options.addArguments("--headless"); // 如果需要无头模式,取消注释这行
driver = new ChromeDriver(options);
wait = new WebDriverWait(driver, Duration.ofSeconds(10)); // 显式等待超时时间 10 秒
// 打开百度首页
driver.get("https://www.baidu.com");
}
// 每个测试方法执行后运行,关闭浏览器
@AfterEach
public void tearDown() {
if (driver != null) {
driver.quit(); // 使用 quit() 而不是 close(),quit() 会关闭所有窗口并退出进程
}
}
@Test
public void testBaiduSearch() {
// 1. 定位搜索框
WebElement searchBox = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.xpath("//input[@id='kw']")
));
// 2. 输入关键词
searchBox.sendKeys("Selenium 自动化测试");
searchBox.submit(); // 按回车提交
// 3. 等待结果页加载,验证标题包含关键词
wait.until(ExpectedConditions.titleContains("Selenium"));
// 4. 断言
String title = driver.getTitle();
System.out.println("当前页面标题: " + title);
assertTrue(title.contains("Selenium"), "页面标题应包含 Selenium");
}
@Test
public void testSwitchToFirefox() {
// 演示如何切换到 Firefox,注意这里需要单独配置 Firefox
driver.quit(); // 先关掉 Chrome
WebDriverManager.firefoxdriver().setup();
FirefoxOptions options = new FirefoxOptions();
// options.addArguments("--headless"); // Firefox 无头模式
driver = new FirefoxDriver(options);
driver.get("https://www.baidu.com");
assertEquals("百度一下,你就知道", driver.getTitle().split(" - ")[0]);
}
}
代码解读:
@BeforeEach和@AfterEach:这是 JUnit 5 的注解。setUp()方法确保每个测试开始前浏览器都是干净的,tearDown()确保测试结束后浏览器被关闭,不会留下残留进程占用内存。WebDriverManager.chromedriver().setup():这是 Selenium 4 时代最友好的操作。不用手动指定驱动路径,它会自动处理。WebDriverWait和ExpectedConditions:这是 Selenium 自动化的核心灵魂。很多新手脚本失败,不是因为代码写错了,而是因为时序问题——页面还没加载完,代码就去定位元素,结果NoSuchElementException。显式等待会不断轮询(默认每 500 毫秒一次),直到元素出现或超时,这样脚本更稳定。By.xpath:定位元素的方式有很多,ID、Name、Class、Link Text、CSS Selector、XPath 等。XPath 最强大但也最慢,CSS Selector 性能更好,ID 最快。实际项目中,建议优先使用 ID 或 CSS Selector,只有当元素没有这些属性时再用 XPath。- 断言:使用 JUnit 5 的
assertEquals和assertTrue来验证测试结果。没有断言的测试是没有意义的,因为测试通过不代表功能正确。
第四步:深入理解 Selenium 4 的重大变化
如果你是从 Selenium 3 转过来的,可能会有些不适应。Selenium 4 有几个重要的变化,咱们重点说说:
1. W3C 标准兼容
Selenium 4 全面遵循 W3C WebDriver 标准。这意味着你的代码在不同浏览器、不同平台上的行为更加一致。以前在 Chrome 上能用的某些非标准 API,在 Firefox 或 Safari 上可能不行,现在统一了。
2. DevTools Protocol 支持
Selenium 4 可以直接与浏览器的 DevTools 协议交互。比如,你可以拦截网络请求、查看性能数据、获取页面资源等。这在调试复杂的前端应用时非常有用。
// 示例:获取浏览器控制台日志
driver.manage().logs().get(LogType.BROWSER).forEach(logEntry -> {
System.out.println(logEntry.getMessage());
});
3. 新增的相对定位器(Relative Locators)
Selenium 4 引入了相对定位器,可以更语义化地定位元素。比如,找某个输入框“左边”的标签,“右边”的按钮,“附近”的文字等。
import static org.openqa.selenium.support.locators.RelativeLocator.withTagName;
WebElement label = driver.findElement(By.tagName("label"));
WebElement input = label.closest(By.tagName("input"));
4. 远程执行能力增强
如果你使用 Selenium Grid 进行分布式测试,Selenium 4 的 Grid 架构更加简洁和强大,支持 Docker 容器化部署,配置更简单。
第五步:常用技巧与最佳实践
技巧 1:页面对象模型(Page Object Model, POM)
这是 Selenium 自动化测试最重要的设计模式。不要把所有的代码都写在一个测试类里,那样会导致代码高度耦合,维护成本极高。POM 的核心思想是:将页面结构和行为封装成对象。
// 定义一个页面对象类
public class BaiduPage {
private WebDriver driver;
private WebDriverWait wait;
// 封装页面元素
private By searchBox = By.xpath("//input[@id='kw']");
private By searchButton = By.xpath("//input[@id='su']");
private By resultContainer = By.id("content_left");
public BaiduPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
// 封装页面操作
public BaiduPage search(String keyword) {
wait.until(ExpectedConditions.visibilityOfElementLocated(searchBox))
.sendKeys(keyword);
driver.findElement(searchButton).click();
return this; // 返回自身,支持链式调用
}
public boolean isResultPageLoaded() {
try {
wait.until(ExpectedConditions.presenceOfElementLocated(resultContainer));
return true;
} catch (TimeoutException e) {
return false;
}
}
public String getPageTitle() {
return driver.getTitle();
}
}
// 测试类中使用页面对象
public class BaiduSearchTest {
private WebDriver driver;
private BaiduPage baiduPage;
@BeforeEach
public void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
baiduPage = new BaiduPage(driver);
driver.get("https://www.baidu.com");
}
@Test
public void testSearchKeyword() {
baiduPage.search("Selenium 自动化测试");
assertTrue(baiduPage.isResultPageLoaded(), "结果页应加载成功");
}
}
POM 的好处:
- 代码复用:同一个页面的搜索操作,可以在多个测试用例中复用。
- 维护方便:如果页面元素变了,只需要修改
BaiduPage类,而不需要修改所有的测试类。 - 可读性强:测试用例变成了一系列自然语言般的操作,比如“搜索关键词”、“验证结果页”。
技巧 2:处理动态元素和异步加载
现代 Web 应用大量使用 JavaScript 进行异步加载,元素可能不会立即出现在 DOM 中。这时候,盲目使用 Thread.sleep() 是万恶之源——它会让测试变得慢且不稳定。
正确做法:
显式等待:如前所述,使用
WebDriverWait和ExpectedConditions。隐式等待:设置一次,全局生效,但要注意它与显式等待混用可能会产生意外行为。
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));FluentWait:更精细的控制等待策略,比如忽略某些异常、设置轮询间隔。
Wait<WebDriver> wait = new FluentWait<>(driver) .withTimeout(Duration.ofSeconds(30)) .pollingEvery(Duration.ofMillis(500)) .ignoring(NoSuchElementException.class); WebElement element = wait.until(new Function<WebDriver, WebElement>() { public WebElement apply(WebDriver driver) { return driver.findElement(By.id("some-dynamic-element")); } });
技巧 3:截图与日志记录
当测试失败时,一张截图胜过千言万语。它能帮你快速定位问题是什么——是元素没找到,还是页面显示错了,还是网络请求失败了。
import org.openqa.selenium.OutputType;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public void takeScreenshot(String testName) {
try {
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
String screenshotPath = "screenshots/" + testName + "_" + System.currentTimeMillis() + ".png";
FileUtils.copyFile(srcFile, new File(screenshotPath));
System.out.println("截图已保存至: " + screenshotPath);
} catch (IOException e) {
e.printStackTrace();
}
}
// 在 @AfterEach 或 @Test 失败时调用
@AfterEach
public void tearDown() {
if (driver != null) {
// 如果测试失败,截图
if (MinecraftTestContext.get().isFailed()) { // 假设你有一个上下文管理失败状态
takeScreenshot("testBaiduSearch");
}
driver.quit();
}
}
注意:需要引入 commons-io 依赖来简化文件操作。
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
技巧 4:使用 Chrome DevTools Protocol 高级功能
Selenium 4 允许你直接与浏览器的 DevTools 交互,这在调试复杂场景时非常有用。比如,拦截网络请求、修改请求头、模拟慢速网络等。
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.v115.network.Network;
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSessionIfThereIsNotOne();
devTools.send(Network.enable(Optional.of(1000000), Optional.of(1000000), Optional.of(1000000)));
// 监听网络请求
devTools.addListener(Network.requestWillBeSent(), requestSent -> {
System.out.println("请求 URL: " + requestSent.getRequest().getUrl());
System.out.println("请求方法: " + requestSent.getRequest().getMethod());
});
技巧 5:集成 Testcontainers 实现真正的隔离环境
如果你需要在 CI/CD 流水线中运行测试,或者担心浏览器驱动版本冲突,可以使用 Testcontainers。它会启动一个 Docker 容器,里面预装了特定版本的浏览器,测试结束后容器自动销毁。
”`xml
<groupId>org.testcontainers</groupId>
<artifactId>selenium</artifactId>
<version>1.
