掘金 后端 ( ) • 2024-04-04 10:59

本篇文章将记录作者和带领读者,使用Java+Selenium+Junit5测试框架,对个人博客系统进行自动化测试。作者的个人博客系统分为前台系统和后台系统,这次测试主要测试前台的注册、首页、详情页、个人中心界面,后台的登录和标签管理界面。下图是整个系统测试用例的设计。 博客系统自动化.png

由于掘金上传视频不方便,本篇文章中把视频转为gif展示了,有一点点糊🥲

想看视频的朋友点这里👉 【Java+Selenium+Junit5对个人博客进行自动化测试(包含代码和视频) - CSDN App】http://t.csdnimg.cn/Xevqv

后台系统

后台指的是:管理个人博客的后台系统

登录界面测试

1、编写测试用例

image-20240401093630863

2、准备工作

(1)新建maven工程

image-20240401094159139

(2)引入依赖

Selenium、Junit5、用于截图的工具包

 <dependency>
     <groupId>org.seleniumhq.selenium</groupId>
     <artifactId>selenium-java</artifactId>
     <version>4.18.1</version>
 </dependency>
 <!--   保存屏幕截图文件需要用到的包     -->
 <dependency>
     <groupId>commons-io</groupId>
     <artifactId>commons-io</artifactId>
     <version>2.6</version>
 </dependency>
 <dependency>
     <groupId>org.junit.jupiter</groupId>
     <artifactId>junit-jupiter</artifactId>
     <version>5.8.2</version>
     <scope>test</scope>
 </dependency>
 <dependency>
     <groupId>org.junit.platform</groupId>
     <artifactId>junit-platform-suite</artifactId>
     <version>1.8.2</version>
     <scope>test</scope>
 </dependency>

(3)在test包下创建包的结构

image-20240401094610368

(4)准备一个工具类,用于创建driver驱动和隐式等待

image-20240401095614835

  • selenium浏览器驱动安装不再赘述,网上有很多资料
  • 隐式等待的原因是:我们在自动化测试的时候要频繁获取页面中的元素,很多时候页面元素的加载速度赶不上自动化代码的执行速度,会导致找不到元素的情况。为避免这种情况,我们进行隐式等待,让程序等我们一会。注意:隐式等待作用于WebDriver整个生命周期,只要没有执行driver.quit,即没有退出浏览器,隐式等待都是一直存在的
 public class AutoTestUtils {
     public static ChromeDriver driver;
 ​
     public static ChromeDriver createDriver() {
         if(driver == null){
             System.setProperty("webdriver.chrome.driver","D:\JavaSet\jdk\myjdk8\bin\chromedriver.exe");
             ChromeOptions options = new ChromeOptions();
             options.addArguments("--remote-allow-origins=*");
             driver = new ChromeDriver(options);
             // 设置隐式等待时间
             driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
             System.out.println("ChromeDriver创建完毕......");
         }
         return driver;
     }
 }

3、测试页面显示是否正确

image-20240401100315566

  • @TestMethodOrder注解使用 OrderAnnotation 方法排序器,它会按照 @Order 注解中指定的顺序来执行测试方法。数字越小越先执行。
  • 在执行之后的测试方法之前,我们要先打开博客后台登录页面。
  • 通过页面的一些元素定位,加上断言进行验证页面显示是否正确。

4、测试登录失败的情况

image-20240401101037454

  • @ParameterizedTest + @CsvSource 注解用于参数化测试。减少重复的测试代码,提高测试的可维护性。
  • 由于WebDriver只能在一个页面上对元素识别与定位,而错误提示信息是隐藏弹窗,无法定位无法获取。这里采用强制等待+Js代码来定位隐藏的div弹窗。这里不用隐式等待的原因是: 作用不了非HTML页面的元素的,所以弹窗无法等待,看下是否在切换到弹窗之前弹窗还没有出现,终端报的错误是不是noalert。这里不用显示等待的原因是: 我们的登录界面测试类继承了AutoTestUtils类,这个类实现了隐式等待,隐式等待和显示等待十分不推荐共同使用,会带来意想不到的错误!

5、测试登录正常的情况

image-20240401102537181

  • 通过保存屏幕截图,可以更直观地了解测试执行时页面的状态,有助于定位问题和进行故障排查。

6、测试代码和视频

 package BlogAdmin;
 ​
 import common.AutoTestUtils;
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.openqa.selenium.*;
 import org.openqa.selenium.chrome.ChromeDriver;
 import java.io.File;
 import java.io.IOException;
 import java.time.Duration;
 ​
 /**
  * @author 3A87
  * @description 后台登录界面测试
  */
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class) // 说明当前该类下面的测试方法要按一定的顺序执行
 public class LoginTest extends AutoTestUtils {
     public static ChromeDriver driver = createDriver();
     @Test
     @BeforeAll // 被@BeforeAll修饰的方法要是静态的
     static void init() {
         // 跳转到博客登录页面
         driver.get("http://123.xx.xx.xxx:xxxx/");
     }
     /**
      * 检查登录页面是否正常显示
      */
     @Test
     @Order(1)
     void loginPageTest(){
         // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
         // 利用断言判断登录的文本内容显示是否正确
         String expect = "Login Form";
         String actual = driver.findElement(By.cssSelector(".title")).getText(); // 检查登录页面的登录文本是否存在
         //System.out.println(actual);
         Assertions.assertEquals(expect, actual);
         // 检查提交按钮是否存在
         driver.findElement(By.cssSelector("[type='button']"));
     }
 ​
     /**
      * 检查登录失败的情况,每写一个测试用例就测试一下
      */
     @Order(2)
     @ParameterizedTest // 多个参数,不用加@Test
     @CsvSource({"mie,666666","admin, 1234"})
     void loginFailTest(String username, String password) throws InterruptedException {
         // 把之前默认填充内容清空
         driver.findElement(By.cssSelector("[name='userName']")).clear();
         driver.findElement(By.cssSelector("[name='password']")).clear();
         //输入用户名和密码
         driver.findElement(By.cssSelector("[name='userName']")).sendKeys(username);
         driver.findElement(By.cssSelector("[name='password']")).sendKeys(password);
         //点击登录
         driver.findElement(By.cssSelector("[type='button']")).click();
         Thread.sleep(2000);
         // 使用 JavascriptExecutor 执行 JavaScript 代码来操作隐藏的 div 弹窗
         JavascriptExecutor js = driver;
         WebElement hiddenDiv = (WebElement) js.executeScript("return document.querySelector("div[role='alert'][class='el-message el-message--error']")");
 ​
         System.out.println(hiddenDiv.getText());
     }
 ​
 ​
     /**
      * 检查正常登录的情况
      */
     @Order(3)
     @ParameterizedTest
     @CsvSource({"mie, 1234"})
     void loginRightTest(String username, String password) throws InterruptedException, IOException {
         // 把之前的输入的内容清空
         driver.findElement(By.cssSelector("[name='userName']")).clear();
         driver.findElement(By.cssSelector("[name='password']")).clear();
         //输入用户名和密码
         driver.findElement(By.cssSelector("[name='userName']")).sendKeys(username);
         driver.findElement(By.cssSelector("[name='password']")).sendKeys(password);
         //点击登录
         driver.findElement(By.cssSelector("[type='button']")).click();
         Thread.sleep(1000);
         // 上述步骤只是说明输入了账号和密码,但还不知道点击提交后是否会跳转到博客列表页
         String expect = "http://123.56.154.146:8094/#/dashboard";
         String actual = driver.getCurrentUrl();
         Assertions.assertEquals(expect, actual); // 查看当前的url是否在博客详情页面
         // 进行截图,看当前是否跳转到了登录界面
         File srcFile =  driver.getScreenshotAs(OutputType.FILE);
         String fileName = "loginRightTest.png";
         FileUtils.copyFile(srcFile, new File(fileName));
     }
     // 这里我们不关闭driver驱动,因为我们要保证登录状态
 }

登录2.gif

标签模块功能测试

我们测试方法的顺序是增、改、查、删,这样保证最后不对已上线的博客系统造成影响

1、准备工作

(1)测试用例

增加标签:image-20240402202703327

修改标签:

image-20240402202732536

查询标签:

image-20240402202758574

删除标签:

image-20240402202827894

(2)添加测试套件

通过测试套件可以同时执行多个类的测试用例。

在测试标签模块功能前,必须先进行登录,才可以进行对标签的增删改查,这里可以使用测试套件确保先登录。

junit5测试套件注解有两种:

 // 第一种方法: @Suite && @SelectClasses
 // 第二种方法: @Suite && @SelectPackages -- 指定包名类运行包下所有的测试用例

image-20240402204152310

(3)进入标签页面

image-20240402204514008

  • 在定位标签管理的时候,直接用driver定位,定位不到,这里采用js的方式。在前面登录界面测试中提到过。

image-20240402204413191

2、测试新增标签功能

image-20240402204850773

  • AutoTestUtils类里增加一个检查页面是否存在标签名为tag的标签的方法,在后面测试修改标签功能也会用到。image-20240402205034559
  • 在上面这个方法,我采用的方法是定位页面中标签名的位置,提取出所有标签名,再遍历是否包含我新增的标签名。

3、测试修改标签功能

image-20240402205616730

  • 要定位上个新增标签测试方法增加的标签,需要知道该标签所在的行数。在AutoTestUtils类增加一个查询tag所在的行号的方法,因为后面删除测试方法也要用到。image-20240402210029731
  • 修改tag的名字为tag+“——修改了”,便于后面做断言。

4、测试搜索标签功能

image-20240402210136825

5、测试删除标签功能

image-20240402210251527

6、测试代码和视频

 package BlogAdmin;
 ​
 import common.AutoTestUtils;
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.openqa.selenium.*;
 ​
 import java.io.File;
 import java.io.IOException;
 ​
 /**
  * @author 3A87
  * @description 测试标签模块的增删改查功能
  */
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 public class TagTest extends AutoTestUtils {
     @Test
     @BeforeAll
     static void init() {
         driver.get("http://123.xx.xxx.xxx:xxxx/?#/dashboard");
         //点击内容管理
         driver.findElement(By.xpath("//*[@id="app"]/div/div[1]/div/div[1]/div/ul/div[13]/li/div[1]")).click();
         //点击标签管理
         //driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div/div[1]/div/ul/div[13]/li/ul/div[4]")).click();
         JavascriptExecutor js = driver;
         WebElement tagDiv = (WebElement) js.executeScript("return document.querySelector("#app > div > div.sidebar-container > div > div.scrollbar-wrapper.el-scrollbar__wrap > div > ul > div:nth-child(13) > li > ul > div:nth-child(4)")");
         tagDiv.click();
     }
 ​
 ​
     @Order(1)
     @ParameterizedTest
     @CsvSource({"测试tag1,测试remark"})
     void addTagTest(String tag,String remark) throws InterruptedException {
         //点击新增按钮
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/section/div/div[1]/div[1]/button")).click();
 ​
         Thread.sleep(100);
         String expect = "添加标签";
         String actual = driver.findElement(By.xpath("/html/body/div[2]/div/div[1]/span")).getText();
         Assertions.assertEquals(expect,actual);
 ​
         driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/form/div[1]/div/div/input")).sendKeys(tag);
         driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/form/div[2]/div/div/textarea")).sendKeys(remark);
 ​
         driver.findElement(By.xpath("/html/body/div[2]/div/div[3]/div/button[1]")).click();
 ​
         //刷新页面
         driver.navigate().refresh();
         Thread.sleep(300);
 ​
         //检查页面是否新增成功
         boolean result = AutoTestUtils.containsTag(tag);
         Assertions.assertTrue(result);
     }
 ​
     @Order(2)
     @ParameterizedTest
     @CsvSource({"测试tag1,测试remark"})
     void updateTagTest(String tag,String remark) throws InterruptedException {
         //查找新增的标签对应的行数
         int count = AutoTestUtils.getTagCount(tag);
 ​
         //System.out.println(count);
 ​
         //点击修改
         driver.findElement(By.xpath("//tbody/tr["+count+"]/td[5]/div/button[1]")).click();
         //定位tag输入框和remark输入框
         WebElement tagElement = driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/form/div[1]/div/div/input"));
         WebElement remarkElement = driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/form/div[2]/div/div/textarea"));
         //清空输入框的内容
         tagElement.clear();
         remarkElement.clear();
         //修改tag和remark
         tagElement.sendKeys(tag+"——修改了");
         remarkElement.sendKeys(remark+"——修改了");
         //点击修改
         driver.findElement(By.xpath("/html/body/div[2]/div/div[3]/div/button[1]")).click();
 ​
         Thread.sleep(300);
         //检查是否修改成功
         boolean result = AutoTestUtils.containsTag(tag+"——修改了");
         Assertions.assertTrue(result);
     }
 ​
 ​
     @Order(3)
     @ParameterizedTest
     @CsvSource({"测试tag1——修改了"})
     void searchTagByTagNameTest(String tag) throws IOException, InterruptedException {
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/section/div/form/div[1]/div/div/input")).clear();
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/section/div/form/div[1]/div/div/input")).sendKeys(tag);
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/section/div/form/div[2]/div/button")).click();
 ​
         //等搜索界面加载一会
         Thread.sleep(300);
         // 进行截图,看当前是否搜索到了名字为tag的标签记录
         File srcFile =  driver.getScreenshotAs(OutputType.FILE);
         String fileName = "searchTagTest.png";
         FileUtils.copyFile(srcFile, new File(fileName));
     }
 ​
     @Order(4)
     @ParameterizedTest
     @CsvSource({"测试tag1——修改了"})
     void deleteTagTest(String tag) throws InterruptedException {
         //查找新增的标签对应的行数
         int count = AutoTestUtils.getTagCount(tag);
         //点击删除
         driver.findElement(By.xpath("//tbody/tr["+count+"]/td[5]/div/button[2]")).click();
 ​
         JavascriptExecutor js = driver;
         WebElement confirmButton = (WebElement) js.executeScript("return document.querySelector("body > div.el-message-box__wrapper > div > div.el-message-box__btns > button.el-button.el-button--default.el-button--small.el-button--primary")");
         confirmButton.click();
 ​
         Thread.sleep(1000);
         boolean result = AutoTestUtils.containsTag(tag);
         Assertions.assertFalse(result);
     }
 ​
 ​
     @Test
     @AfterAll
     static void exit() {
         driver.quit();
     }
 }
 ​

标签模块2.gif

前台系统

前台指的是:展示个人博客的前台系统

注册界面测试

1、编写测试用例

image-20240401114023730

2、测试页面显示是否正常

image-20240401151155110

3、测试注册成功的情况

image-20240401151304191

  • 注册成功后会跳转到登录页面,这里添加一个断言。
  • 跳转到登录页面后,为继续测试注册失败的情况,我们需要再跳转回注册页面。driver.navigate.back()类似于浏览器中点击浏览器的“后退”按钮。

4、测试注册失败的情况

image-20240401151627242

  • 因为注册失败的测试用例较多,我们将测试用例写在csv文件下,采用@CsvFileSource注解把它们拿过来。image-20240401151822672

  • 这里测试出来我的前台注册功能的两个bug

    • 两次输入不一致的密码,仍可注册成功!大漏洞!
    • 邮箱后缀不加.仍可以注册成功
  • 这里遇到了一个小问题:当我测试密码为空的用例时,浏览器好像会复用上一个用例的123123密码,导致注册成功

5、测试代码和视频

 import common.AutoTestUtils;
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvFileSource;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.openqa.selenium.By;
 import org.openqa.selenium.OutputType;
 import org.openqa.selenium.chrome.ChromeDriver;
 ​
 import java.io.File;
 import java.io.IOException;
 ​
 /**
  * 注册界面的自动化测试
  */
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 public class regTest extends AutoTestUtils {
     public static ChromeDriver driver = new ChromeDriver();
     @Test
     @BeforeAll // 带有BeforeAll注解的方法会在当前类下的所有测试用例之前(方法)执行一次,注意只是执行一次
     public static void init() {
         // 既然是对注册界面的测试,自然要先跳转到该界面
         driver.get("http://www.hello3a87.com/#/Login?login=0");
     }
 ​
     /**
      * 对页面内容的完整性进行测试
      */
     @Test
     @Order(1)
     public void regPageTest() {
         // 利用断言验证页面显示的文本是否正确
         String expect = "注册";
         String actual = driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[1]/h1")).getText();
         Assertions.assertEquals(expect, actual);
 ​
         // 检查博客登录页的主页超链接是否存在
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[1]/p/a"));
         // 检查提交按钮是否存在
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[12]"));
     }
 ​
     /**
      * 正常注册
      */
     @Order(2)
     @ParameterizedTest
     @CsvSource({"懒羊羊,lyy,[email protected],123123,123123"})
     public void regRightTest(String username,String nickname,String email,String password1, String password2) throws InterruptedException, IOException {
         // 每次都要提前把之前输入框的内容给清除(不管有没有内容)
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[3]/input")).clear(); //用户名
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[5]/input")).clear(); //昵称
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[6]/input")).clear(); //邮箱
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[8]/input")).clear(); //用户密码
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[10]/input")).clear(); //确认密码
 ​
         // 将信息填入输入框
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[3]/input")).sendKeys(username); //用户名
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[5]/input")).sendKeys(nickname); //昵称
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[6]/input")).sendKeys(email); //邮箱
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[8]/input")).sendKeys(password1); //用户密码
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[10]/input")).sendKeys(password2); //确认密码
         // 找到提交按钮,并点击提交
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[12]")).click();
         // 注册成功后,应该会跳转到登录页面
         Thread.sleep(100);
         String expectURL = "http://www.hello3a87.com/#/Login?login=1";
         String actualURL = driver.getCurrentUrl(); // 获取当前页面的URL
         Assertions.assertEquals(expectURL, actualURL);
         // 获取此时的屏幕截图,此时应该以及跳转到了登录页面
         File srcFile = driver.getScreenshotAs(OutputType.FILE);
         String fileName = "regRightTest.png";
         FileUtils.copyFile(srcFile, new File(fileName));
         // 因为注册成功会跳转到登录界面,所以但接下来我们还有在注册界面测试,所以要回退到注册界面
         driver.navigate().back();
     }
     /**
      * 测试注册失败的情况
      */
     @ParameterizedTest
     @Order(3)
     @CsvFileSource(resources = "/regUsers.csv")
     public void regFailTest(String username,String nickname,String email,String password1, String password2) throws InterruptedException {
         // 每次都要提前把之前输入框的内容给清除(不管有没有内容)
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[3]/input")).clear(); //用户名
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[5]/input")).clear(); //昵称
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[6]/input")).clear(); //邮箱
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[8]/input")).clear(); //用户密码
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[10]/input")).clear(); //确认密码
 ​
         // 将信息填入输入框
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[3]/input")).sendKeys(username); //用户名
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[5]/input")).sendKeys(nickname); //昵称
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[6]/input")).sendKeys(email); //邮箱
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[8]/input")).sendKeys(password1); //用户密码
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[10]/input")).sendKeys(password2); //确认密码
         // 找到提交按钮,并点击提交
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[12]")).click();
         Thread.sleep(100);
     }
 ​
     /**
      * 关闭注册弹窗
      */
     @Test
     @AfterAll  // 带有AfterAll注解的方法会在当前类下的所有测试用例(方法)执行之后 执行一次,注意只是执行一次
     public static void close() {
         driver.quit();
     }
 ​
 }
 ​

前台注册2.gif

博客首页

1、编写测试用例

image-20240402215314586

2、测试首页页面的完整性

image-20240403143119407

3、测试代码和视频

 import common.AutoTestUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.openqa.selenium.By;
 import org.openqa.selenium.chrome.ChromeDriver;
 ​
 import java.time.Duration;
 ​
 /**
  * @author 3A87
  * @description 测试博客首页
  */
 public class PageTest extends AutoTestUtils {
     public static ChromeDriver driver = new ChromeDriver();
     @Test
     @BeforeAll
     static void init() {
         driver.get("http://www.hello3a87.com/#/Home");
     }
     @Test
     void pageTest() {
         // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
         // 测试右侧的用户名是否能正常显示
         String expect = "3A87";
         String actual = driver.findElement(By.cssSelector("#app > div > div.container > div > div.el-col.el-col-24.el-col-sm-24.el-col-md-8 > div > section:nth-child(1) > div.r1-body > p")).getText();
         Assertions.assertEquals(expect,actual);
         // 测试博客主页格言是否能正常显示
         String expect1 = "人生人山人海人来人往,自己自尊自爱自由自在";
         String actual1 = driver.findElement(By.cssSelector("#app > div > div:nth-child(1) > div.headImgBox > div.h-information > h2")).getText();
         Assertions.assertEquals(expect1, actual1);
         // 测试菜单栏的首页、分类、赞赏、友链是否能正常显示
         String expect2 = "首页";
         String actual2 = driver.findElement(By.xpath("//*[@id="app"]/div/div[1]/div[1]/div/div/div/ul/li[1]")).getText();
         Assertions.assertEquals(expect2, actual2);
         String expect3 = "分类";
         String actual3 = driver.findElement(By.xpath("//*[@id="app"]/div/div[1]/div[1]/div/div/div/ul/li[2]/div")).getText();
         Assertions.assertEquals(expect3, actual3);
         String expect4 = "赞赏";
         String actual4 = driver.findElement(By.xpath("//*[@id="app"]/div/div[1]/div[1]/div/div/div/ul/li[3]")).getText();
         Assertions.assertEquals(expect4, actual4);
         String expect5 = "友链";
         String actual5 = driver.findElement(By.xpath("//*[@id="app"]/div/div[1]/div[1]/div/div/div/ul/li[4]")).getText();
         Assertions.assertEquals(expect5, actual5);
         // 测试热门文章是否能正常显示
         String expect6 = "热门文章";
         String actual6 = driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div/div[2]/div/section[2]/h2")).getText();
         Assertions.assertEquals(expect6,actual6);
     }
     @Test
     @AfterAll
     static void exit() {
         driver.quit();
     }
 }

首页2.gif

文章详情页

1、编写测试用例

image-20240403145601360

2、测试文章详情页面的完整性

image-20240403144825520

3、测试代码和视频

 import common.AutoTestUtils;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.openqa.selenium.By;
 import org.openqa.selenium.chrome.ChromeDriver;
 ​
 /**
  * @author 3A87
  * @description 测试博客文章详情页
  */
 public class ArticleTest extends AutoTestUtils {
     public static ChromeDriver driver = new ChromeDriver();
     @Test
     @BeforeAll
     static void init() {
         driver.get("http://www.hello3a87.com/#/DetailArticle?aid=16");
     }
 ​
     @Test
     void pageTest(){
         //查看文章标题显示内容是否和期待的一致
         String expect = "Jmeter实战——编写博客标签模块增删改查自动化脚本和压测";
         String actual = driver.findElement(By.xpath("//*[@id="detail"]/div/div[1]/div[1]/header/h1/a")).getText();
         Assertions.assertEquals(expect,actual);
 ​
         //评论区是否正常显示
         String expect1 = "发表评论";
         String actual1 = driver.findElement(By.xpath("//*[@id="detail"]/div/div[1]/div[2]/div[1]/h3")).getText();
         Assertions.assertEquals(expect1,actual1);
     }
 }

文章详情页2.gif

个人中心页面

1、编写测试用例

image-20240403193208826

2、准备工作 - 测试前台登录功能

image-20240403193259798

  • 若无等待,程序会找不到页面元素

3、测试个人中心页面的完整性

image-20240403201350316

  • 进入个人中心时鼠标悬浮,选择个人中心image-20240403150757604

4、测试编辑功能

image-20240403201651368

  • 强制等待,等upload.exe文件执行完,要不然程序太快,exe还没执行完就点击提交了

  • 这里上传头像采用的方式是AutoIt3工具,此外

    • 如果上传图片的前端代码是用input时,可以使用sendKeys的方式,注意路径名必须是绝对路径! (亲测)

       driver.findElement(By.xpath("定位index的xpath的路径")).sendKeys("图片的绝对路径");
      
    • 还有使用Robot类的方式,但是我觉得过于繁琐。

    • 其他方式的具体使用可以参考我放在文末的文章链接。

利用AutoIt3工具上传头像使用步骤:

(1)官网下载AutoIt3工具AutoIt Downloads - AutoIt (autoitscript.com)image-20240403201852673

(2)下载完成后,解压,双击exe文件,安装

image-20240403201934433

(3)打开SciTE Script Editor编写脚本image-20240403202318293

(4)设置字符集

File→Encoding→UTF-8

防止中文乱码问题

image-20240403202440530

(5)编写脚本

;是注释

 ; 等待 "打开" 对话框出现
 WinWait("打开", "")
 ​
 ; 将焦点设置在 "打开" 对话框中的 "Edit1" 控件上
 ControlFocus("打开","","Edit1")
 ​
 ; 在 "打开" 对话框中的 "Edit1" 控件中设置文本,即文件路径
 ControlSetText("打开", "", "Edit1", "D:\picture\美羊羊.png")
 ​
 ; 在 "打开" 对话框中点击 "Button1" 控件,即确定按钮
 ControlClick("打开", "", "Button1")

我编写脚本的办法有两种:1、和chatgpt对话 2、查阅api文档

打开AutoIt Help File,用好查找的功能

image-20240403203123995

(6)保存为au3格式的脚本

File -> save

(7)将au3格式转为exe文件

打开Compile Script to .exe

image-20240403203419984

(8)在测试代码中用引入

 Runtime.getRuntime().exec("C:\Users\liuxi\Desktop\upload.exe");

5、测试代码和视频

 import common.AutoTestUtils;
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.openqa.selenium.By;
 import org.openqa.selenium.OutputType;
 import org.openqa.selenium.chrome.ChromeDriver;
 import org.openqa.selenium.interactions.Actions;
 ​
 import java.io.File;
 import java.io.IOException;
 import java.time.Duration;
 ​
 /**
  * @author 3A87
  * @description 测试个人中心功能
  */
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 public class MyPageTest extends AutoTestUtils {
     public static ChromeDriver driver = new ChromeDriver();
     @Test
     @BeforeAll
     static void init() {
         driver.get("http://www.hello3a87.com/#/Home");
     }
 ​
     @Order(1)
     @ParameterizedTest
     @CsvSource({"美羊羊,123123"})
     void LoginTest(String username,String pwd){
         driver.findElement(By.xpath("//*[@id="app"]/div/div[1]/div[1]/div/div/div/ul/div/div[1]/a[1]")).click();
         // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[3]/input")).sendKeys(username);
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[4]/input")).sendKeys(pwd);
 ​
         driver.findElement(By.xpath("//*[@id="app"]/div/div/div/div/div[5]")).click();
     }
 ​
     @Order(2)
     @Test
     void MyPageTest() throws InterruptedException {
         // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
         //鼠标悬浮在 小头像 元素上面
         Actions action = new Actions(driver);
         action.moveToElement(driver.findElement(By.xpath("//div[@class='haslogin']"))).perform();
         Thread.sleep(100);
         //进入个人中心
         driver.findElement(By.linkText("个人中心")).click();
 ​
         Thread.sleep(300);
         //检查页面显示是否完整
         String expect1 = "编辑";
         String button = driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[2]/header/h1/span")).getText();
         Assertions.assertEquals(expect1,button);
         String expect2 = "电子邮件";
         String email = driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[2]/section/ul/li[3]/span[1]")).getText();
         Assertions.assertEquals(expect2,email);
     }
 ​
 ​
     @Order(3)
     @Test
     void editMyPageTest() throws IOException, InterruptedException {
         // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询
         driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
         //点击编辑按钮
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[2]/header/h1/span")).click();
         //上传图片
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[1]/section/ul/li[1]/div/div[1]")).click();
         Runtime.getRuntime().exec("C:\Users\liuxi\Desktop\upload.exe");
         Thread.sleep(3000);
         //修改昵称
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[1]/section/ul/li[2]/div/input")).clear();
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[1]/section/ul/li[2]/div/input")).sendKeys("美羊羊");
         //点击提交
         driver.findElement(By.xpath("//*[@id="app"]/div/div[2]/div[1]/section/div/a[2]")).click();
 ​
         Thread.sleep(300);
         // 进行截图,看是否更新了头像和昵称
         File srcFile =  driver.getScreenshotAs(OutputType.FILE);
         String fileName = "MyPageEditTest.png";
         FileUtils.copyFile(srcFile, new File(fileName));
 ​
     }
 ​
     @Test
     @AfterAll
     static void exit() {
         driver.quit();
     }
 ​
 }
 ​

个人中心2.gif

总结

通过这次对我的个人博客系统进行自动化测试,

  • 我上手了Junit5测试框架

    • 套件的使用
    • 执行顺序的控制
    • 参数化(单值,批量,多个测试用例)
  • 我熟悉了selenium的使用

    • 对等待的理解更深,隐式等待、显示等待、强制等待
    • 元素定位的方法,xpath,css
    • 隐藏元素的定位,弹窗的定位
    • 断言的使用
    • 截图的使用
    • 鼠标悬浮
    • 刷新、回退页面
  • 我接触了新工具AutoIt3

参考文章

对个人博客系统进行web自动化测试(包含测试代码和测试的详细过程)_测试博客-CSDN博客

Selenium之定位弹窗元素 - xiaowangzi3668 - 博客园 (cnblogs.com)

关于selenium的等待-CSDN博客

Selenium UI自动化测试中元素定位不到的原因和解决方法汇总_selenium 获取不到元素有哪些可能-CSDN博客

selenium处理文件或图片上传弹窗的三种方式(input框,robot类,autoIT3工具)_selenium 上传文件 弹框 如何关闭-CSDN博客

Selenium元素获取异常之 ElementNotInteractableException:element not interactable-CSDN博客

selenium+java利用AutoIT实现文件上传 - YunMan - 博客园 (cnblogs.com)

全网最全AutoIt3基础教程及实战案例-CSDN博客

selenium java 高级技巧篇(必学)上传文件处理(三)_java selenium 上传图片-CSDN博客