链接
首先在pom.xml上导入依赖
注意: 在导入依赖的时候要记得刷新一下maven,确保依赖真的下载下来了
<dependencies>
<dependency>
<groupId>org.seleniumhq.seleniumgroupId>
<artifactId>selenium-javaartifactId>
<version>4.0.0version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.8.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.platformgroupId>
<artifactId>junit-platform-suiteartifactId>
<version>1.8.2version>
<scope>testscope>
dependency>
dependencies>
创建出文件的目录结构,在Test包下面放测试的方法,在common包下面放一些通用的方法
这样子项目结构就会很清晰明了
在每个测试类中可能都要创建驱动对象,频繁的创建和销毁对象,要是测试类很多的话,就很消耗资源,增加运行时间,所以最好单独创建一个方法,用来创建对象,这样子就不用频繁创建驱动对象了
由于这一块是所有的测试类都要调用的,所以单独封装出一个类,作为公用的类AutoTestUtils
//创建出驱动对象
public static ChromeDriver createDriver() {
//要是还没有创建出对象,就创建出一个浏览器/驱动对象
if (chromeDriver == null) {
chromeDriver = new ChromeDriver();
//要是代码执行得很快,就会导致前端没渲染好,找不到元素的问题,所以要使用等待机制
//隐式等待---最多等待10s
chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return chromeDriver;
}
在实际的测试过程中,使用的是无头模式,根本就不会看到浏览器的页面,要是报错的话,就需要将报错的页面保存一下,方便排查问题
所以要用到屏幕截图
保存屏幕截图的时候,截图最好按照年月日 时分秒 毫秒的方式来保存,方便后面报错的时候定位错误
//设置屏幕截图的文件类型和目录类型
public List<String> getTime() {
//使用SimpleDataFormat来进行时间转换
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyyMMdd");
String fileName = simpleDateFormat1.format(System.currentTimeMillis());
String dirName = simpleDateFormat2.format(System.currentTimeMillis());
List<String> lists = new ArrayList<>();
lists.add(dirName);
lists.add(fileName);
return lists;
}
/*
获取屏幕截图,将所有的测试用例的结果都保存下来,便于后面报错发现问题
str表示是哪个类调用了屏幕截图
*/
public void getScreenShot(String str) throws IOException {
List<String> list = getTime();
//希望屏幕截图保存的名称: 目录名 + 文件名(年月日 时分秒 毫秒)
//"./src/test/java/BlogAutoTest/"
String fileName = "./src/test/java/BlogAutoTest/" + list.get(0)+"/" + str +"_" + list.get(1)+ ".png";
File srcFile = chromeDriver.getScreenshotAs(OutputType.FILE);
//将屏幕截图生成的文件放到指定的文件路径下面
FileUtils.copyFile(srcFile,new File(fileName));
}
检查登录页面的情况的时候,主要是检查 页面能不能正常显示 页面登录成功的情况 页面登录失败的情况
package BlogAutoTest.Tests;
import BlogAutoTest.common.AutoTestUtils;
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.chrome.ChromeDriver;
import java.io.IOException;
//指定测试用例的执行顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)//手动指定测试用例的执行顺序[注解]
public class BlogLoginTest extends AutoTestUtils {
//在测试登录页的时候,会有两个必做的步骤
//1.创建出驱动对象 2.访问url
//这个方法一定是是在其他的用例执行之前,就要先执行一次的,所以使用的是@BeforeAll,所以要使用static来修饰
public static ChromeDriver chromeDriver = createDriver();//创建驱动对象放到方法外面去,后面还要使用
@BeforeAll
public static void BaseControl() {
chromeDriver.get("http://47.96.166.241:8080/blog_system/login.html");
}
/*
检查页面是否能正常打开
检查点: 主页 写博客 头像
*/
@Test
@Order(1)
public void loginPageLoadSuc() throws IOException {
chromeDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
chromeDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
chromeDriver.findElement(By.cssSelector("body > div.nav > img"));
//屏幕截图,并且将类名传回去
getScreenShot(getClass().getName());
}
/*
检查页面的登陆成功的情况
*/
//测试的不止一个账号,所以要使用参数化来进行多账号验证
@ParameterizedTest
@CsvSource({"zhangsan,123","lisi,123"})
public void LoginSuc(String name, String password) throws IOException {
//当登录别的账号的时候,就要先将之前的账号密码框清空,再进行登录
chromeDriver.findElement(By.cssSelector("#username")).clear();
chromeDriver.findElement(By.cssSelector("#password")).clear();
chromeDriver.findElement(By.cssSelector("#username")).sendKeys(name);
chromeDriver.findElement(By.cssSelector("#password")).sendKeys(password);
chromeDriver.findElement(By.cssSelector("body > div.login-container > form > div > div:nth-child(4) > input")).click();
//上面的三步只是登录的基本步骤,并不能保证能登录成功,所以还要对跳转之后的页面进行检查,要是找不到元素的话就会报错
//要是能登录成功,就一定能找到"查看全文的按钮"和"登录账号名称"
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > a"));
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-left > div > h3"));
getScreenShot(getClass().getName());//屏幕截图,注意是:添加在返回之前,因为这里是测试了两个账号,所以会有两张截图
//当一个账号登录成功的时候,想再登录另一个账号的时候,此时就找不到上面的3个元素了,因为此时页面已经不再是登录页面了,
//所以要退回到登录的页面
chromeDriver.navigate().back();
}
/*
检查页面登录失败的情况
需要注意的是:此时我们验证的是登录失败的情况,所以我们给定错误的账号或者密码,来看看错误的界面是否符合预期,要是符合预期的话,测试用例跑完之后不会报错
*/
@ParameterizedTest
@CsvSource({"zhangsan1,123","lisi1,123"})//测试的是登录错误的情况,所以就要使用错误的账号/密码
@Order(2)
public void LoginFail(String name, String password) throws IOException {
chromeDriver.findElement(By.cssSelector("#username")).clear();
chromeDriver.findElement(By.cssSelector("#password")).clear();
chromeDriver.findElement(By.cssSelector("#username")).sendKeys(name);
chromeDriver.findElement(By.cssSelector("#password")).sendKeys(password);
chromeDriver.findElement(By.cssSelector("body > div.login-container > form > div > div:nth-child(4) > input")).click();
//检查登录页面跳转失败的情况,结果发现,在登录成功的页面中也有这个元素,所以仅仅只有这个元素是不能判断的,可以获取一下文本
String expectation = "用户名或者密码错误,登录失败!";
String actual = chromeDriver.findElement(By.cssSelector("body")).getText();
Assertions.assertEquals(expectation, actual);
getScreenShot(getClass().getName());//屏幕截图,注意是:添加在返回之前,因为这里是测试了两个账号,所以会有两张截图
chromeDriver.navigate().back();
}
//在执行完所有的测试方法之后都要将驱动对象销毁掉,所以使用的是@AfterAll,方法要由static来修饰
//但是由于使用的是套件,所以要在最后一个测试类中销毁驱动对象,所以不能写在这里
}
测试博客列表页,主要测试的就是页面是否能正常打开
package BlogAutoTest.Tests;
import BlogAutoTest.common.AutoTestUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
public class BlogListTest extends AutoTestUtils {
public static ChromeDriver chromeDriver = createDriver();
@BeforeAll
public static void BaseControl() {
chromeDriver.get("http://47.96.166.241:8080/blog_system/blog_list.html");
}
/*
测试列表页是否能展示出来
检查点: 头像 查看全文按钮 文章
*/
@Test
public void listPageLoadSuc() {
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-left > div > img"));
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > a"));
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-left > div > div:nth-child(4) > span:nth-child(1)"));
}
}
对于测试博客编辑页,测试的有: 页面能不能正常显示出来 能不能写出并提交博客
package BlogAutoTest.Tests;
import BlogAutoTest.common.AutoTestUtils;
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogEditTest extends AutoTestUtils {
public static ChromeDriver chromeDriver = createDriver();
@BeforeAll
public static void BaseControl() {
chromeDriver.get("http://47.96.166.241:8080/blog_system/blog_edit.html");
}
/*
检查博客编辑页有没有展示出来
检查点:发布文章 文章标题
*/
@Order(1)
@Test
public void editPageLoadSuc() {
chromeDriver.findElement(By.cssSelector("#submit"));
chromeDriver.findElement(By.cssSelector("#blog-title"));
}
/*
检查博客编辑页能不能写文章并提交
*/
@Order(2)
@Test
public void editSubmitBlog() {
String expectation = "测试发布";//博客的标题
chromeDriver.findElement(By.cssSelector("#blog-title")).sendKeys(expectation);
//因为博客系统使用的第三方(editor.md),所以不能直接获取到输入框元素,但是可以获取到一些格式
chromeDriver.findElement(By.cssSelector("#editor > div.editormd-toolbar > div > ul > li:nth-child(21) > a > i")).click();
chromeDriver.findElement(By.cssSelector("#editor > div.editormd-toolbar > div > ul > li:nth-child(5) > a > i")).click();
chromeDriver.findElement(By.cssSelector("#submit")).click();//点击提交
String actual = chromeDriver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > div.title")).getText();
//看看实际发布的博客信息与期望的博客标题是否一样
Assertions.assertEquals(expectation, actual);
}
}
package BlogAutoTest.Tests;
import BlogAutoTest.common.AutoTestUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
public class BlogDetailTest extends AutoTestUtils {
public static ChromeDriver chromeDriver = createDriver();
@BeforeAll
public static void baseControl() {
chromeDriver.get("http://47.96.166.241:8080/blog_system/blog_detail.html?blogId=10");
}
/*
测试博客详情页是否正确
检查点:时间 账号 标题
*/
@Test
public void detailPageLoadSuc() {
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-right > div > div.blog-date"));
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-left > div > h3"));
chromeDriver.findElement(By.cssSelector("body > div.container > div.container-right > div > h3"));
}
//在套件中,当同时执行了多个类,销毁一定是最后一个步骤,所以要到处搬来搬去,永远保证最后一个执行
@AfterAll
public static void quit() {
chromeDriver.quit();
}
}
想要运行以上的几个测试类,可以使用套件来指定统一运行
使用套件(suite)就能很好的管理各个测试用例的执行
package BlogAutoTest.Tests;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
//使用套件来统一执行测试用例(可以手动指定类名,也可以手动指定包名,但是只会执行包中以Test或者Tests结尾的测试类)
@Suite
@SelectClasses({BlogLoginTest.class,BlogListTest.class,BlogEditTest.class,BlogDetailTest.class})
public class runSuite {
}
关于执行web自动化测试, 需要对实际场景的分析和理解,要理清楚程序的执行步骤和执行预期结果,一旦出现问题,要学会排查错误,看看到底是自己的自动化代码写错了导致误报,还是系统本身的错误!
此次web自动化使用的是selenium4自动化测试工具和junit5单元测试框架,主要的亮点有:
使用了junit5提供的注解,避免了生成过多的对象,造成资源和时间的浪费,提高了自动化测试的效率;
只创建出一个驱动对象,避免每个测试用例重复创建对象造成时间和资源的浪费;
使用了参数化的注解,保持了用例的简洁,提高了代码的简洁性和可读性;
使用了等待机制,提高了自动化运行的效率,提升了自动化代码的稳定性;
使用了屏幕截图, 并且在设置屏幕截图的时候将截图名设置成对应的时间,方便在出现问题之后,能快速追溯到问题并解决。