目录
1. 前言
前段时间, 我们学习了 WebDriver 和 selenium, 并且掌握了自动化测试的核心函数.
此外, 我们基于 Spring 完成了博客系统的开发.
因此, 我们就使用自动化技术, 对我们的博客系统进行自动化测试.
2. 自动化实施步骤
- 需求分析
- 页面分析
- 设计测试用例
- 搭建自动化环境
- 编写自动化代码
- 运行维护
设计测试用例时, 我们要根据项目中的页面来设计对应的测试用例.
3. 页面分析
博客系统一共分为以下 4 个页面:
- 博客登录页
- 博客列表页
- 博客详情页
- 博客发布(编辑)页
我们需要对这些页面来设计测试用例.
4. 设计测试用例
在编写自动化代码前, 需要先设计测试用例.
我们基于万能公式设计测试用例:
功能测试 + 性能测试 + 界面测试 + 兼容性测试 + 易用性测试 + 安全测试 (+ 弱网测试 + 安装卸载测试)
除了牢记以上 “万能公式” 外, 我们还需要充分的了解项目, 这样才能设计出优秀的测试用例.
设计的测试用例如下:
5. 搭建自动化环境
设计完测试用例后, 需要为后续编写自动化代码搭建环境.
我们依赖如下环境:
- jdk
- idea
- selenium
- WedDriverManager(驱动管理)
- 浏览器(正版)
6. 编写自动化代码
根据测试用例, 编写自动化代码.
6.1 准备工作 - Utils
创建一个 Utils 包, 存放创建 driver, 截图, 等待等操作的代码.
6.1.1 允许远程自动化 & 创建驱动
public WebDriver createDriver() {
if(driver == null) {
// 安装自动化驱动管理程序
WebDriverManager.chromedriver().setup();
// 浏览器配置
ChromeOptions options = new ChromeOptions();
// 允许来自任何源的远程连接请求
options.addArguments("--remote-allow-origins=*");
// 将配置添加到驱动中
driver = new ChromeDriver(options);
// 隐式等待页面元素 3 s, 保证代码执行前, 相关元素已存在.
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return driver;
}
6.1.2 实现自动化截图
以截图时间为所截图片的保存路径.
/**
* 屏幕截图
* String s: 在进行哪个页面的测试时, 进行的截图.
* 保存路径: ./src/test/java/com/project/bloguiautotest/images/2025-05-03/15-44-23-23.png
*/
public static void screenShot(String s) throws IOException {
// 年-月-日
SimpleDateFormat dirFormat = new SimpleDateFormat("yyyy-MM-dd");
// 时.分.秒.毫秒
SimpleDateFormat fileFormat = new SimpleDateFormat("hh.mm.ss.SSS");
String dirPath = dirFormat.format(System.currentTimeMillis());
String filePath = fileFormat.format(System.currentTimeMillis()) + ".png";
// 定义图片保存路径
String picPath = "./src/test/java/com/project/bloguiautotest/images/" + dirPath + "/" + s + "-" + filePath;
// 进行截图, 生成图片
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
// 将截图保存在指定路径下.
FileUtils.copyFile(srcFile, new File(picPath));
}
6.1.3 释放 WebDriver
/**
* 释放 driver 对象
*/
public static void exit() {
driver.quit();
}
6.2 自动化测试登录页 - LoginTest
对登录页的自动化测试流程如下:
- 使用 webDriver 打开登录页, 检查页面是否打开成功
- 针对登陆成功和登陆失败的情况, 输入账号密码, 检查是否符合预期
6.2.1 打开登陆页
将 LoginTest 继承 Utils, 将登录页的 url 传给 Utils, driver.get(url) 打开博客登陆页面.
6.2.2 检查登录页
使用 driver.get 打开登陆页后, 我们需要检查是否打开成功.
6.2.3 测试登陆
登陆页成功打开后, 接下来测试登录状态.
6.2.3.1 登录成功
输入账号密码后, 如果登录成功, 会跳转到博客详情页.
因此, 我们可以查找详情页中的元素, 如果可以查找到, 则说明登陆成功.
博客详情页中有很多元素, 这里我选择的是 “退出登录” 这个按钮.
6.2.3.2 登录失败
输入的账号密码属于以下情况任意一种时, 均会登录失败:
- 账号为空, 密码不为空
- 账号不为空, 密码为空
- 账号和密码, 都为空
- 账号正确, 密码错误
- 账号错误, 密码正确
- 账号错误, 密码错误
- 输入不合法字符
这里演示第 4 种情况.
当输入密码错误时, 会出现警告弹窗提示, 我们需要关闭弹窗:
注意:
如果对输入框进行输入操作(sendKeys), 一定要先清空输入框(clear)中的内容!!
6.3 自动化测试列表页 - ListPage
登陆成功后, 来到博客详情页.
博客列表页中有以下三部分模块:
- 个人信息模块
- 博客列表模块
- 菜单栏模块
这里就仅对博客列表模块进行测试:
public class ListPage extends Utils {
// 博客列表页 url
public static String url = "http://47.93.87.16:8080/blog_list.html";
public ListPage() {
super(url);
}
/**
* 测试博客列表页
* 1. 个人信息模块
* 2. 博客列表模块 ✅
* 3. 菜单栏模块
*/
public void checkList() {
// 获取博客标题
String blogTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();
// 获取博客发布日期
String blogDate = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText();
// 获取博客正文
String blogContent = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.desc")).getText();
// 获取 “查看全文>>” 按钮
String button = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).getText();
// 博客标题 & 博客发布日期 & 博客正文 均不能为空
assert !blogTitle.isEmpty();
assert !blogDate.isEmpty();
assert !blogContent.isEmpty();
// “查看全文>>” 按钮应符合预期
assert button.equals("查看全文>>");
// 点击 “查看全文>>” 按钮, 应跳转到博客详情页.
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
// 此时, 应来到博客详情页. 获取详情页中的标题
String jumpTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();
// 跳转后的博客标题, 应和跳转后的保持一致
assert blogTitle.equals(jumpTitle);
// 此时已经来到博客详情页. 获取这篇博客详情页的 URL.
blogDetailURL = driver.getCurrentUrl();
// System.out.println(blogDetailURL);
}
}
6.4 自动化测试详情页 - DetailPage
6.4.1 测试页面内容
对博客详情页进行测试时, 和登录页/列表页不同的是, 我们不能将一篇博客的详情页的 url 写死到属性中, 因为这篇博客随时可能会被删除, 此时这个 url 就变成了一个不存在的博客的 url.
因此, 我们可以在博客列表页中获取某一篇存在的博客, 对这篇博客的详情页进行测试.
注意: 由于博客正文内容使用的标签不固定, 有时是 p 标签, 有时是 h 标签(动态标签), 我们无法为其编写一个固定的选择器.
解决方案是:利用其结构稳定的父元素. 先通过选择器定位到稳定的父元素, 然后基于此父元素来定位其内部的动态正文内容.
博客正文, 都存在于一个静态的 div 标签中, 因此, 我们可以通过定位正文的父类即 div 标签来间接的定位博客正文.
public class DetailPage extends Utils {
// 获取父类中有效的 url
public static String url = blogDetailURL;
public DetailPage() {
super(url);
}
/**
* 测试详情页
*/
public void checkPage() {
// 博客标题
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
// 博客发布日期
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.date"));
// 博客正文
// 正文 -》 动态标签. 解决办法: 父类标签是静态的, 通过父类标签间接定位到博客正文的标签.(确定了父类, 就确定了子类)
// driver.findElement(By.cssSelector("#detail > p"));
webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#detail")));
driver.findElement(By.cssSelector("#detail")); // 父类 div 标签
// 编辑按钮
// 删除按钮
// 可能存在, 也可能不存在. 当作者是登陆用户的时候存在, 因此这里不进行测试.
}
}
6.4.2 测试编辑按钮功能
我们的预期时, 在博客详情页点击 "编辑" 按钮, 会跳转到博客编辑页, 对博客信息进行修改, 点击 "更新文章" 按钮, 文章信息随之发送改变.
我们编写自动化代码验证功能是否正常.
/**
* 检测博客详情页 编辑按钮 功能
*/
public void checkDetailEdit() throws InterruptedException {
// 获取修改前的标题
String titleBefore = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();
System.out.println("更新前的标题: " + titleBefore);
// 查找 "编辑" 按钮并点击
driver.findElement(By.cssSelector
("body > div.container > div.right > div > div.operating > button:nth-child(1)")).click();
// 此时跳转到编辑页, 更新标题 (首先清空之前的标题)
webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#title")));
driver.findElement(By.cssSelector("#title")).clear();
// 根据当前时间更新标题
webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#title")));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HHmmssSS");
String titleTime = simpleDateFormat.format(System.currentTimeMillis());
driver.findElement(By.cssSelector("#title")).sendKeys(titleTime);
//点击发布----回到列表页
driver.findElement(By.cssSelector("#submit")).click();
// 获取更新后的标题 body > div.container > div.right > div:nth-child(1) > div.title
webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.container > div.right > div > div.title")));
String titleAfter = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();
System.out.println("更新后的标题: " + titleAfter);
// titleBefore != titleAfter 更新前和更新后的标题, 应不一致.
assert !titleAfter.equals(titleBefore);
}
使断言 assert 生效需要进行一下配置:
-ea -Dfile.encoding=UTF-8
6.4.3 测试删除按钮功能
定位到博客详情页的删除按钮后进行点击, 完成删除操作.
如何验证删除操作成功: 博客列表页中, 删除操作前的博客数量应和删除操作后的博客数量不同.
/**
* 检测博客详情页 删除按钮
*/
public void checkDetailDelete() throws InterruptedException {
// 先回到列表页, 确定删除前, 一共有多少篇博客
driver.get("http://47.93.87.16:8080/blog_list.html");
List<WebElement> blogsBefore = driver.findElements(By.cssSelector("body > div.container > div.right > div"));
int blogCntBefore = blogsBefore.size();
System.out.println("删除操作前的博客篇数: " + blogCntBefore);
// 再次来到博客详情页, 定位到删除按钮并点击
driver.get(url);
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(2)")).click();
// 此时来到博客列表页, 重新查看共有多少篇博客
Thread.sleep(500);
List<WebElement> blogsAfter = driver.findElements(By.cssSelector("body > div.container > div.right > div"));
int blogCntAfter = blogsAfter.size();
System.out.println("删除操作后的博客篇数: " + blogCntAfter);
// 删除前和删除后, 列表页的博客数量应不同
assert blogCntBefore != blogCntAfter;
}
6.5 自动化测试编辑页(发布博客) - EditPage
通过 url 来到博客发布页后, 我们需要填写博客标题和博客正文.
填写博客标题时, 我们通过选择器获取 xpath 定位到后, 直接通过 sendKeys 就可以自动化填写.
但是, 填写博客正文时, 如果我们仍然通过 sendKeys 来填写内容时, 就会抛出元素不可交互异常:
ElementNotInteractableException 异常通常意味着我们通过 findElement 找到了指定的元素(findElement 成功), 但该元素当前状态无法接收键盘输入. (例如, 它可能被其他元素遮挡、被禁用、不可见, 或者它本身就不是一个设计用来直接接收输入的元素, 如: 富文本编辑器)
而接收博客正文的区域, 它恰好是一个文本编辑器, 因此我们无法通过 sendKeys 来输入内容.
此时, 我们就可以通过键盘鼠标操作来对向这个文本编辑器中输入内容.
// 通过键盘鼠标的方式, 来填写正文
WebElement contentEle = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
new Actions(driver)
.doubleClick(contentEle) // 模拟鼠标双击操作
.keyDown(Keys.DELETE) // 模拟键盘删除操作
.doubleClick() // 模拟鼠标双击操作
.keyDown(Keys.DELETE) // 模拟键盘删除操作
.sendKeys("自动化填写博客正文...") // 输入内容
.perform(); // 将结果展示到页面上
ok, 自动化输入完博客正文后, 就可以点击 "发布博客" 按钮进行发布了.
那么, 如何自动化测试博客发布成功呢?
点击发布按钮后, 此时应来到博客列表页, 而博客列表页的最后一篇博客就是我们自动化发布的博客.
那我们就可以获取列表页最后一篇博客的标题/内容, 检查是否和自动化输入的标题/内容一致, 若一致, 则说明发布成功, 反之发布失败.
如何获取选中列表页最后一篇博客呢? 我们可以采取拼接选择器/xpath的方式:
/**
* 发布博客
*/
public class EditPage extends Utils {
private static final String url = "http://47.93.87.16:8080/blog_edit.html";
public EditPage() {
// 调用父类构造, 来到博客发布页面
super(url);
}
/**
* 正常发布博客
*/
public void editSuccess() {
// 1. 填写博客标题
// 根据当前时间自动化生成博客标题
webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#title")));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-HH:mm:ss-SS");
String titleTime = simpleDateFormat.format(System.currentTimeMillis());
String titleBefore = "自动化发布博客 " + titleTime;
driver.findElement(By.cssSelector("#title")).clear();
driver.findElement(By.cssSelector("#title")).sendKeys(titleBefore);
System.out.println("发布时输入的博客名称: " + titleBefore);
// 2. 填写博客正文
// 由于正文的输入框是一个文本编辑器(不可交互), 如果通过 sendKeys 来进行输入操作, 会抛出异常.
// // ElementNotInteractableException
// WebElement contentEle = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
// contentEle.sendKeys("自动化填写博客正文...");
// 通过键盘鼠标的方式, 来填写正文
WebElement contentEle = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
new Actions(driver)
.doubleClick(contentEle) // 模拟鼠标双击操作
.keyDown(Keys.DELETE) // 模拟键盘删除操作
.doubleClick() // 模拟鼠标双击操作
.keyDown(Keys.DELETE) // 模拟键盘删除操作
.sendKeys("自动化填写博客正文...") // 输入内容
.perform(); // 将结果展示到页面上
// 3. 点击发布博客
driver.findElement(By.cssSelector("#submit")).click();
// 4. 校验博客是否发布成功
// 此时来到博客列表页
// 博客列表页的最后一篇博客, 应是我们新发布的博客, 这篇博客的名称应和我们发布时输入的名称一致
// 获取列表页的博客列表
List<WebElement> blogs = driver
.findElements(By.cssSelector("body > div.container > div.right > div"));
int size = blogs.size();
// 获取最后一篇博客的名称
String lastBlogTitle = driver.findElement(By.cssSelector
("body > div.container > div.right > div:nth-child(" + size + ") > div.title")).getText();
System.out.println("发布后的博客名称: " + lastBlogTitle);
assert lastBlogTitle.equals(titleBefore);
}
}
END