
自动化测试
1. 测试用例
2. 代码编写
- 根据测试用例进行UI自动化测试。
- 公共属性单独放一个类,方便进行代码复用。
- 注意添加隐式等待,确保页面正确加载显示。
2.1 添加依赖pom.xml
2.2 创建常用工具类AutoTestUtil
// 常用方法
public class AutoTestUtil {
public static ChromeDriver driver = null;
// 创建谷歌驱动对象
public static ChromeDriver createDriverUtil() {
// 驱动打开谷歌浏览器
WebDriverManager.chromedriver().setup();
// 配置安全策略
ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
// 无头模式, 可以后台执行, 观察效果就关闭
// options.addArguments("--headless=new");
// 将配置添加到浏览器对象中
if (driver == null) {
driver = new ChromeDriver(options);
}
// 添加 隐式等待 确保页面元素渲染出来
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
return driver;
}
// 屏幕截图: 文件名带有类名和方法名
public static void getScreenshotAs(String classMethodName) throws IOException {
// 1. 用一个单独文件夹存放截图文件
// 2. 出问题能够快速定位到(方便查找, 见名知意)
SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat hms = new SimpleDateFormat("HH_mm_ss_SS"); // HH:mm:ss 文件名字不能带有 :, 所以使用 _
String currentYMD = ymd.format(System.currentTimeMillis());
String currentHMS = hms.format(System.currentTimeMillis());
// 截图存储的路径
String filePath = "./src/test/image/" + currentYMD + "/" + classMethodName + "_" +currentHMS + ".png";
// 截图
File screenshotAs = ( (TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
File destFile = new File(filePath);
// 保存到指定目录
FileUtils.copyFile(screenshotAs, destFile);
}
// 获取调用者方法名
public static String currentMethodName() {
// 通过跟踪堆栈信息获取方法名
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// 索引选择规则:
// stackTrace[0] -> getStackTrace() 方法本身
// stackTrace[1] -> 当前方法(currentMethodName)
// stackTrace[2] -> 调用者方法(比如main)
return stackTrace[2].getMethodName();
}
}
2.3 注册页面测试
2.3.1 注册成功
注册用户名在数据库不存在,昵称,两次密码一致,注册成功,跳转到登录界面
预期结果: 注册成功, 跳转到登录界面
代码:
@Order(1) @ParameterizedTest @CsvSource({"zyt, 周诣涛, 123456, 123456"}) void register(String username, String nickname, String password, String passwordRepeat) throws InterruptedException, IOException { driver.get("http://120.26.251.76:58080/sign-up.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#nickname")).sendKeys(nickname); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys(passwordRepeat); // 点击同意用户协议 driver.findElement(By.cssSelector("#policy")).click(); // 点击注册 driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果:与预期结果一致,注册功能测试通过
2.3.2 注册失败
注册用户名在数据库已经存在,昵称,两次密码一致,注册失败
预期结果: 提示当前用户已注册
代码:
@Order(1) @ParameterizedTest @CsvSource({"zyt, 周诣涛, 123456, 123456"}) void register(String username, String nickname, String password, String passwordRepeat) throws InterruptedException, IOException { driver.get("http://120.26.251.76:58080/sign-up.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#nickname")).sendKeys(nickname); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys(passwordRepeat); // 点击同意用户协议 driver.findElement(By.cssSelector("#policy")).click(); // 点击注册 driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果: 与预期结果一致,右下角弹出弹窗显示“用户已存在”
2.4 登录页面测试
2.4.1 登录成功
测试用户名正确,密码正确,登录成功(多个用户)
预期效果: 两个用户都登录成功
代码:
@Order(1) @ParameterizedTest @CsvSource({"xxz, 123456", "zyt, 123456"}) void login(String username, String password) throws IOException, InterruptedException { driver.get("http://120.26.251.76:58080/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); // 截图, 查看是否登录成功, 等待一秒, 防止页面没加载出来就截图了 Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果:与预期结果一致,登录功能测试通过
2.4.2 登录异常
测试用户名正确,密码错误,登录失败
测试用户名错误,密码错误,登录失败
预期结果: 提示用户名或密码错误
代码:
@Order(2) @ParameterizedTest @CsvSource({"zyt, 12345", "error1, error2"}) void noLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://120.26.251.76:58080/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果:与预期结果一致,右下角弹出弹窗显示“用户名或密码错误”
2.5 发布帖子测试
2.5.1 发布成功
登录后,输入帖子标题,不输入帖子内容,点击发布按钮,给出提示信息
预期结果: 提示: 请输入贴子内容
代码:
@Order(2) @ParameterizedTest @CsvSource({"xxz, 123456"}) void writeActicleFailByLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://120.26.251.76:58080/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement( By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")) .click(); String str1 = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div.card-body > div:nth-child(1) > label > strong")).getText(); // 断言判断是否处于发布贴子的页面 Assertions.assertEquals("版块", str1); // 输入标题 driver.findElement(By.cssSelector("#article_post_title")).sendKeys("标题"); // 不输入内容, 直接发布, 查看是否提示: 请输入文章内容 // 由于发布按钮需要向下滚动才能看到, 显示等待发布按钮加载 WebElement element = driver.findElement(By.cssSelector("#article_post_submit")); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); // 将指定元素滑动到页面顶部 ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView();", element); // 确保滚动成功 Thread.sleep(1000); element.click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果:与预期结果一致,发布帖子功能测试通过
登录后,输入帖子标题,输入帖子内容,点击发布按钮,发布成功
预期结果: 跳转回论坛主页, 且在上方显示贴子信息
代码:
@Order(3) @ParameterizedTest @CsvSource({"xxz, 123456"}) void writeActicleSuccessByLogin(String username, String password) throws IOException, InterruptedException { // 直接在刚才的基础上测试 driver.get("http://120.26.251.76:58080/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement( By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")) .click(); String str1 = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div.card-body > div:nth-child(1) > label > strong")).getText(); // 断言判断是否处于发布贴子的页面 Assertions.assertEquals("版块", str1); // 输入标题 driver.findElement(By.cssSelector("#article_post_title")).sendKeys("标题"); // 输入内容, 鼠标通过tab要先移到输入框中 Actions actions = new Actions(driver); actions.sendKeys(Keys.TAB) .sendKeys("测试内容") .perform(); // 防止执行太快, 导致文本刚输入还没被渲染完毕就点击了 Thread.sleep(1000); // 由于发布按钮需要向下滚动才能看到, 隐式等待发布按钮加载 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); WebElement element = driver.findElement(By.cssSelector("#article_post_submit")); // 将指定元素滑动到页面顶部 ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView();", element); Thread.sleep(1000); element.click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果: 与预期结果相符,发帖功能测试通过
2.5.2 发布失败
未登录状态下发布帖子,直接跳转回登录界面
预期结果: 直接跳转回登录界面
代码:
// 未登录状态访问论坛主页 @Order(1) @Test void noLoginVisitWriteActicle() { driver.get("http://120.26.251.76:58080/index.html"); String currentUrl = driver.getCurrentUrl(); String signInUrl = "http://120.26.251.76:58080/sign-in.html"; // 断言当前Url是否是登录页面的Url Assertions.assertEquals(currentUrl, signInUrl); }
实际结果: 与预期结果相符,跳转回登录页面
2.6 查看帖子测试
2.6.1 查看成功
页面基础元素: 贴子标题、贴子内容、贴子作者、点赞按钮
特殊元素: 如果是作者, 会多显示 编辑按钮 和 删除按钮
代码:
@Order(2)
@ParameterizedTest
@CsvSource({"xxz, 123456", "zyt, 123456"})
void acticleDetailByLogin(String username, String password) throws IOException, InterruptedException {
driver.get("http://120.26.251.76:58080/sign-in.html");
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
// 断言进入到首页
Assertions.assertEquals(driver.findElement(By.cssSelector("#article_list_board_title"))
.getText(), "首页");
// 进入到第一个贴子,如果页面上存在多个匹配相同定位条件的元素, 会默认返回第一个
WebElement element = driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong"));
element.click();
// 断言进入了文章详情页面
element = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div:nth-child(2) > h3"));
Assertions.assertEquals(element.getText(), "最新回复");
Thread.sleep(1000);
AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName());
}
场景1:登录后, 进入贴子详情界面, 且用户为贴子作者
预期结果: 多显示 编辑按钮 和 删除按钮
实际结果: 与预期结果相符
场景1执行后的页面
场景2:登录后, 进入贴子详情界面, 用户不为贴子作者
预期结果: 只显示 贴子标题、贴子内容、贴子作者、点赞按钮
实际结果: 与预期结果相符
场景2执行后的页面
2.6.2 查看失败
未登录状态下,进入贴子详情页,直接跳转回登录界面
预期结果: 直接跳转回登录界面
代码:
// 未登录状态访问论坛主页
@Order(1)
@Test
void noLoginVisitWriteActicle() {
driver.get("http://120.26.251.76:58080/index.html");
String currentUrl = driver.getCurrentUrl();
String signInUrl = "http://120.26.251.76:58080/sign-in.html";
// 断言当前Url是否是登录页面的Url
Assertions.assertEquals(currentUrl, signInUrl);
}
实际结果: 与预期结果相符
2.7 删除帖子测试
2.7.1 删除成功
登录后,进入帖子详情页,点击删除按钮,删除帖子成功,成功跳转回帖子列表页面
注意:当前登录用户必须为贴子作者
预期结果: 提示删除成功
代码:
@Order(2) @ParameterizedTest @CsvSource({"xxz, 123456", "zyt, 123456"}) void acticleDetailByLogin(String username, String password) throws IOException, InterruptedException { driver.get("http://120.26.251.76:58080/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); // 断言进入到首页 Assertions.assertEquals(driver.findElement(By.cssSelector("#article_list_board_title")) .getText(), "首页"); // 进入到第一个贴子,如果页面上存在多个匹配相同定位条件的元素, 会默认返回第一个 WebElement element = driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong")); element.click(); // 断言进入了文章详情页面 element = driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div:nth-child(2) > h3")); Assertions.assertEquals(element.getText(), "最新回复"); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果: 与预期结果相符
2.7.2 删除失败
未登录状态下,点击删除
预期结果: 直接跳转回登录界面
代码:
// 未登录状态访问论坛主页 @Order(1) @Test void noLoginVisitWriteActicle() { driver.get("http://120.26.251.76:58080/index.html"); String currentUrl = driver.getCurrentUrl(); String signInUrl = "http://120.26.251.76:58080/sign-in.html"; // 断言当前Url是否是登录页面的Url Assertions.assertEquals(currentUrl, signInUrl); }
实际结果: 与预期结果相符
2.8 个人简介测试
2.8.1 修改成功
登录后点击用户头像下的个人中心,输入新昵称.....并修改,观察是否修改成功
预期结果: 弹窗提示修改昵称成功
代码:
@Order(3) @ParameterizedTest @CsvSource({"xxz, 123456, 九尾www"}) void updateUserMessageByLogin(String username, String password, String nickName) throws IOException, InterruptedException { driver.get("http://120.26.251.76:58080/sign-in.html"); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); driver.findElement(By.xpath("/html/body/div[1]/header[1]/div/div/div[3]/a")).click(); driver.findElement(By.cssSelector("#index_user_settings")).click(); // 先清空 driver.findElement(By.cssSelector("#setting_input_nickname")).clear(); driver.findElement(By.cssSelector("#setting_input_nickname")).sendKeys(nickName); driver.findElement(By.cssSelector("#setting_submit_nickname")).click(); Thread.sleep(1000); AutoTestUtil.getScreenshotAs(getClass().getName() + "_" + AutoTestUtil.currentMethodName()); }
实际结果: 与预期结果相符
2.8.2 修改失败
未登录状态下,点击个人中心
预期结果: 直接跳转回登录界面
代码:
// 未登录状态访问论坛主页 @Order(1) @Test void noLoginVisitWriteActicle() { driver.get("http://120.26.251.76:58080/index.html"); String currentUrl = driver.getCurrentUrl(); String signInUrl = "http://120.26.251.76:58080/sign-in.html"; // 断言当前Url是否是登录页面的Url Assertions.assertEquals(currentUrl, signInUrl); }
实际结果: 与预期结果相符
实现方式及亮点
1. 实现方式:
① 是根据个人项目来设计的测试用例,然后根据测试用例使用selenium4自动化测试工具
Junit5单元测试框架结合来实现web自动化测试的(功能、步骤、技术一定要明确)
② 对于代码中的每个包都要进行概要介绍(公共属性[复用]、测试用例[根据每个页面来进行设计的],然后使用测试套件将所有测试类进行加载)
2. 亮点:
① 使用了JUnit5中提供的注解:避免生成过多的对象,造成资源和时间的浪费,提高了自动化的执行效率。
② 只创建一次驱动对象,避免每个用例重复创建驱动对象造成时间和资源的浪费。
③ 使用参数化:保持用例的简洁,提高代码的可读性
④ 使用测试套件:降低了测试人员的工作量,通过套件一次执行所有要运行的测试用例。
⑤ 使用了等待:提高了自动化的运行效率,提高了自动化的稳定性,减小误报的可能性。
⑥ 使用了屏幕截图:方便问题的追溯以及问题的解决。
⑦ 使用了无头模式:只注重结果,可以留出屏幕。