Web自动化测试实战
·
⾃动化实施步骤
1.编写web测试⽤例

2.⾃动化测试脚本开发
1.工具类:AutotestUtils
package com.example.blogautomatedtest.common;
// 1. 驱动管理依赖:自动下载匹配版本的ChromeDriver,无需手动配置
import io.github.bonigarcia.wdm.WebDriverManager;
// 2. 文件操作依赖:处理截图文件的复制(POM已引入commons-io)
import org.apache.commons.io.FileUtils;
// 3. Selenium核心依赖:截图功能、Chrome驱动、浏览器配置
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class AutotestUtils {
// 静态driver对象:全局复用,避免每个测试方法重复创建驱动
public static ChromeDriver driver;
// 初始化驱动方法:封装驱动创建逻辑,对外提供统一入口
public static ChromeDriver createDriver() {
// 判空:保证驱动只创建一次(单例模式),避免多次new ChromeDriver导致浏览器多开
if (driver == null) {
// 自动下载并配置ChromeDriver
WebDriverManager.chromedriver().setup();
// ChromeOptions配置:解决新版Chrome的兼容性问题
ChromeOptions options = new ChromeOptions();
options.addArguments("--no-sandbox"); // 解决Linux系统权限问题
options.addArguments("--disable-dev-shm-usage"); // 解决容器/低配机内存不足问题
options.addArguments("--remote-allow-origins=*"); // 解决新版Chrome跨域访问报错
driver = new ChromeDriver(options); // 创建驱动实例
// 隐式等待:全局等待元素加载(最多等10秒),避免“元素还没加载就定位”导致的报错
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
// 窗口最大化:避免元素因窗口大小问题定位失败
driver.manage().window().maximize();
}
return driver; // 返回驱动实例,供测试类使用
}
// 初始化驱动并打开指定URL,测试类只需调用该方法即可
public static void initDriver(String url) {
ChromeDriver driver = createDriver();
driver.get(url);
}
// 时间工具方法:生成无特殊字符的时间串,用于截图文件/目录命名
public List<String> getTime() {
Date now = new Date();
// 目录名格式:20251229
SimpleDateFormat dirFormat = new SimpleDateFormat("yyyyMMdd");
// 文件名格式:20251229123030
SimpleDateFormat fileFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String dirname = dirFormat.format(now);
String filename = fileFormat.format(now);
List<String> list = new ArrayList<>();
list.add(dirname);
list.add(filename);
return list;
}
// 截图方法:封装截图逻辑,测试类只需调用即可生成截图
public void getScreenShot(String str) throws IOException {
List<String> list = getTime(); // 获取时间串
// 截图保存路径:放到target目录(Maven构建目录),避免污染源码目录
String savePath = "./target/screenshots/" + list.get(0) + "/" + str + "-" + list.get(1) + ".png";
File destFile = new File(savePath); // 目标文件对象
// 递归创建目录:如果父目录不存在(如20251229),自动创建,避免“文件找不到路径”报错
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
// 将driver转为TakesScreenshot接口(ChromeDriver实现了该接口),生成截图文件
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
// 将临时截图文件复制到指定路径
FileUtils.copyFile(srcFile, destFile);
System.out.println("截图已保存至:" + savePath); // 打印路径,方便调试
}
// 驱动销毁方法:避免测试结束后驱动残留(浏览器进程不关闭),导致内存泄漏
public static void quitDriver() {
if (driver != null) { // 判空:避免空指针
driver.quit(); // 关闭浏览器+销毁驱动
driver = null; // 重置driver,下次调用可重新创建
}
}
}
工具类核心逻辑
| 核心步骤 | 代码实现 | 核心目的 |
|---|---|---|
| 1. 全局驱动复用 | public static ChromeDriver driver; | 所有测试类共享同一个 driver,避免重复创建浏览器进程(节省资源、提升速度) |
| 2. 驱动初始化(单例) | createDriver() + if (driver == null) | 保证驱动只创建一次,解决 “多次调用导致浏览器多开” 问题;内置 WebDriverManager 自动管理驱动版本,无需手动下载配置 |
| 3. 驱动 + 页面初始化 | initDriver(String url) | 封装 “创建驱动 + 打开指定 URL” 两步,测试类只需传 URL 即可,无需重复写驱动初始化逻辑 |
| 4. 时间工具方法 | getTime() | 生成无特殊字符的 “日期目录名 + 时间戳文件名”,解决 “路径含特殊字符报错”“文件名重复覆盖” 问题 |
| 5. 截图方法 | getScreenShot(String str) | 1. 拼接自定义保存路径;2. 递归创建目录(避免路径不存在报错);3. 调用 Selenium 原生截图生成临时文件;4. 复制临时文件到自定义路径(永久保存);5. 打印路径方便调试 |
| 6. 驱动销毁 | quitDriver() | 测试结束后关闭浏览器、销毁驱动,避免内存泄漏 / 浏览器进程残留 |
工具类设计的核心思想
- 复用性:所有通用逻辑(驱动、截图、时间、销毁)都封装在工具类,测试类只需关注 “业务验证”(比如登录是否成功、列表页是否加载),无需重复写通用代码;
- 稳定性:加了判空、隐式等待、递归创建目录、异常处理,避免 “小问题导致测试中断”;
- 可维护性:后续要改截图路径、驱动配置,只需改工具类,不用改所有测试类;
- 可追溯性:截图按 “日期目录 + 类名 + 时间戳” 命名,测试失败后能快速找到对应的截图,定位问题。
关于mkdir() 和 mkdirs()讲解
对比 mkdir() 和 mkdirs()(关键区别):
| 方法 | 作用 | 场景 |
|---|---|---|
mkdir() | 只创建最后一级目录,上层目录必须存在 | 已知父目录已存在时用 |
mkdirs() | 递归创建所有不存在的上层目录 | 不确定父目录是否存在时用 |
第一次运行时,./target/screenshots/20251229/ 整个路径中:
target/:Maven 自动创建,已存在;screenshots/:不存在;20251229/:不存在。
执行 destFile.getParentFile().mkdirs() 时:
destFile.getParentFile()拿到的是./target/screenshots/20251229/;mkdirs()会先检查screenshots/是否存在 → 不存在就创建;- 再检查
20251229/是否存在 → 不存在就创建; - 相当于一次性创建了
screenshots/和20251229/两层目录。
对应代码片段
| 代码片段 | 核心作用 |
|---|---|
str + "-" + list.get(1) | 拼接文件名(- 是普通字符,不是目录分隔符) |
mkdirs() | 递归创建所有不存在的父目录(包括 screenshots/ 和 20251229/) |
getScreenshotAs(OutputType.FILE) | 生成临时截图文件(Selenium 底层实现) |
FileUtils.copyFile(srcFile, destFile) | 把临时截图复制到自定义路径(永久保存) |
2.测试类:BlogListTest
package com.example.blogautomatedtest;
import com.example.blogautomatedtest.common.AutotestUtils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.testng.Assert; // TestNG断言:比原生assert更友好,失败时会提示详细信息
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.IOException;
import static com.example.blogautomatedtest.common.AutotestUtils.driver;
public class BlogLoginTest {
@BeforeMethod
public void setUp() {
AutotestUtils.initDriver("http://localhost:8080/blog/login");
}
// 场景1:验证登录页是否正常加载
@Test
void loginPageLoadRight() throws IOException {
// 定位登录页的核心元素(用户名/密码输入框),能定位到说明页面加载正常
driver.findElement(By.cssSelector("#username"));
driver.findElement(By.cssSelector("#password"));
// 截图保存证据
new AutotestUtils().getScreenShot(getClass().getName());
}
// 场景2:验证正确账号密码能登录成功
@Test
void loginSuc() {
try {
// 步骤1:清空输入框(避免残留数据影响测试)
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
// 步骤2:输入正确账号密码
driver.findElement(By.cssSelector("#username")).sendKeys("lisi");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
// 步骤3:点击登录按钮
driver.findElement(By.cssSelector("#submit")).click();
// 核心验证:定位列表页的特征元素,能定位到说明登录成功并跳转
driver.findElement(By.cssSelector("div.blog-list"));
// 截图:保存“登录成功”的证据,后缀区分场景
new AutotestUtils().getScreenShot(getClass().getName() + "_loginSuc");
// 回退到登录页:方便后续测试方法执行(可选)
driver.navigate().back();
} catch (IOException e) {
e.printStackTrace();
}
}
// 场景3:验证错误账号密码登录失败
@Test
void loginFail() throws IOException, InterruptedException {
// 1. 清空输入框
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
// 2. 输入错误账号密码(当前触发的是“用户不存在”提示)
driver.findElement(By.cssSelector("#username")).sendKeys("admin");
driver.findElement(By.cssSelector("#password")).sendKeys("1111");
// 3. 点击登录按钮
driver.findElement(By.cssSelector("#submit")).click();
// 4. 等待原生alert弹窗加载
Thread.sleep(2000);
// 5. 处理原生alert并获取文本
Alert alert = driver.switchTo().alert();
String actual = alert.getText(); // 实际提示文本(日志显示为“用户不存在”)
alert.accept(); // 关闭弹窗
// 6. 断言验证(注意:预期文本需与实际一致,当前实际是“用户不存在”)
String expect = "用户不存在";
Assert.assertEquals(actual, expect, "登录失败提示语不匹配");
// 7. 截图保存证据
new AutotestUtils().getScreenShot(getClass().getName() + "_loginFail");
// 8. 回退到登录页
driver.navigate().back();
}
@AfterMethod
public void tearDown() {
AutotestUtils.quitDriver();
}
}

1.判断弹窗是浏览器原生 alert 还是 前端自定义弹窗
方法一触发弹窗后看开发者工具(最准确)
- 打开页面,按
F12打开开发者工具(Elements 标签); - 触发弹窗(比如输入错误密码点击登录);
- 观察 Elements 面板:
- 如果是原生 alert:Elements 面板不会新增任何元素(原生 alert 属于浏览器层级,不在 DOM 树中);
- 如果是自定义弹窗:Elements 面板会新增弹窗对应的 DOM 元素(比如
<div class="popup">密码错误</div>)。
方法二尝试用 Selenium 的 Alert 接口测试(最直接)
@Test
void loginFail() throws IOException, InterruptedException { // 新增InterruptedException(Thread.sleep需要)
// 1. 清空输入框
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
//2. 输入错误的账号密码
driver.findElement(By.cssSelector("#username")).sendKeys("admin");
driver.findElement(By.cssSelector("#password")).sendKeys("1111");
//3. 点击登陆按钮
driver.findElement(By.cssSelector("#submit")).click();
// 【关键】先等弹窗显示(加2秒等待,避免弹窗还没出来就检测)
Thread.sleep(2000);
// 【核心】先测试弹窗类型(注释掉断言,优先看日志)
String expect = "密码错误!"; //预期提示语
String actual = ""; // 先初始化
try {
// 尝试切换到原生alert
Alert alert = driver.switchTo().alert();
actual = alert.getText(); // 获取原生弹窗文本
System.out.println("是原生alert弹窗,文本:" + actual);
alert.accept(); // 关闭弹窗
} catch (NoAlertPresentException e) {
// 走到这里说明是自定义弹窗
System.out.println("是前端自定义弹窗(非原生alert)");
// 自定义弹窗暂时先不定位,仅打印日志
}
// 【临时注释】先不做断言(等确定弹窗类型后再补)
// Assert.assertEquals(actual,expect,"登录失败提示语不匹配");
// 5. 截图:保存“登陆失败”等证据
new AutotestUtils().getScreenShot(getClass().getName() + "_loginFail");
// 6.回退到登录页
driver.navigate().back();
}
第二步:执行测试,看日志判断弹窗类型
- 运行
loginFail方法; - 看控制台输出:
- 如果显示
是原生alert弹窗,文本:密码错误!→ 是浏览器原生弹窗; - 如果显示
是前端自定义弹窗(非原生alert)→ 是前端自己写的弹窗。
- 如果显示
第三步:根据弹窗类型完善最终代码
情况 1:日志显示 “原生 alert 弹窗”
补充完整断言逻辑:
@Test
void loginFail() throws IOException, InterruptedException {
// 1-3步:清空、输入错误账号密码、点击登录 → 不变
// 点击登录后等弹窗
Thread.sleep(2000);
// 处理原生alert
String expect = "密码错误!";
Alert alert = driver.switchTo().alert();
String actual = alert.getText();
alert.accept(); // 关闭弹窗
// 断言
Assert.assertEquals(actual, expect, "登录失败提示语不匹配");
// 截图+回退 → 不变
AutotestUtils.getScreenShot(getClass().getName() + "_loginFail");
driver.navigate().back();
}
情况 2:日志显示 “自定义弹窗”
需要先定位弹窗文本元素(F12 找选择器),再补充代码:
@Test
void loginFail() throws IOException, InterruptedException {
// 1-3步:清空、输入错误账号密码、点击登录 → 不变
// 点击登录后等弹窗
Thread.sleep(2000);
// 处理自定义弹窗(替换成你找到的选择器)
String expect = "密码错误!";
// 比如找到弹窗文本的选择器是“.error-popup-text”
String actual = driver.findElement(By.cssSelector(".error-popup-text")).getText();
// (可选)点击弹窗的“确定”按钮关闭
driver.findElement(By.cssSelector(".popup-confirm-btn")).click();
// 断言
Assert.assertEquals(actual, expect, "登录失败提示语不匹配");
// 截图+回退 → 不变
AutotestUtils.getScreenShot(getClass().getName() + "_loginFail");
driver.navigate().back();
}
- 必须加
Thread.sleep(2000):点击登录后弹窗不会立刻显示,等待 2 秒确保弹窗加载完成,否则检测会出错; - 先测类型,再做断言:避免因弹窗类型判断错误导致代码直接报错;
- 自定义弹窗定位技巧:触发弹窗后,F12 开发者工具按
Ctrl+F输入 “密码错误”,找到包含该文本的元素,复制其 CSS 选择器。
开启idea中的断⾔功能

3.测试类BlogEditTest
package com.example.blogautomatedtest;
import com.example.blogautomatedtest.common.AutotestUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.Test;
import java.io.IOException;
import java.time.Duration;
import static com.example.blogautomatedtest.common.AutotestUtils.driver;
public class BlogEditTest {
@Test
void listPageOpen() {
try {
// 1. 创建显式等待对象(全局复用,超时时间10秒)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 2. 显式等待:菜单栏写博客按钮可点击,再点击
WebElement writeBlogBtn = wait.until(
ExpectedConditions.elementToBeClickable(By.cssSelector("body > div.nav > a:nth-child(5)"))
);
writeBlogBtn.click();
// 先找标题输入框
WebElement titleInput = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#title"))
);
titleInput.clear();
titleInput.sendKeys("自动化测试标题");
// 3. 显式等待:发布文章按钮可点击,再点击
WebElement submitBtn = wait.until(
ExpectedConditions.elementToBeClickable(By.cssSelector("#submit"))
);
submitBtn.click();
// 4. 发布后等待1秒(确保页面响应),再截图
Thread.sleep(1000);
new AutotestUtils().getScreenShot(getClass().getName());
} catch (IOException | InterruptedException e) { // 捕获sleep的中断异常
throw new RuntimeException("操作失败:" + e.getMessage(), e);
}
}
}
更多推荐
所有评论(0)