前言👀~
之前学完了自动化测试工具Selenium和单元测试工具JUnit,一直没有进行实战,接下来以知识百宝库系统为例结合两者测试工具,基于web项目进行完整的自动化测试实战
项目背景图
项目背景图有点纠结哪个更加好看合适,希望大家帮我选选,评论区回应1还是2,接下来就正式开始自动化测试实战了
编写Web测试用例
我们可以根据测试万能公式来设计一些测试用例,例如功能、界面、性能、兼容、安全、易用等方面入手,下面的脑图有点糊,下次换个工具试试
简易版测试用例
下面是个人稍微考虑比较多的测试用例,脑图书写的格式可能不太规范,如果再仔细思考肯定还是有很多地方没有考虑的到,毕竟测试用例是不可穷举的
自动化测试脚本开发
根据测试用例,来编写测试自动化测试脚本
自动化测试依赖
首先导入自动化测试需要用到的依赖
selenium自动化测试JUnit单元测试:
<!-- selenium驱动管理-->
<dependencies>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- selenium相关-->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<!-- 屏幕截图依赖 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- JUnit相关依赖 -->
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!--注解用到的API-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>compile</scope>
</dependency>
<!--参数化用到的API-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.8.2</version>
</dependency>
<!--测试套件依赖-->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- lombok工具包-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
注意一下JUnit使用注解的时候可能会出现以下情况,解决办法如图,如果能Import直接Import
细分测试用例
将所有的测试用例共用一个驱动对象,创建一个包存放一个java文件(每一个页面对应一个java文件),然后再创建一个公共包(存放创建驱动对象、等待等),再在test下创建一个文件夹(存放屏幕截图),为什么不是创建包呢?因为一般调包才会用,image存放的是图片,所以用不到
上述是单单使用Selenium进行自动化测试的方案,如果Selenium搭配JUnit的话,又是另外一种写法,把所有的测试都放在一个类中,然后调整顺序。两者方案各有利弊看你如何选择
公共包
创建一个公共包(存放创建驱动对象方法、等待方法、屏幕截图方法等),再在test下创建一个文件夹(存放屏幕截图),为什么不是创建包呢?因为一般调包才会用,image存放的是图片,所以用不到
代码中搭配JUnit中的@BeforeAll和@AfterAll注解创建和关闭驱动对象,这里还写了这个类的构造方法用于打开网页,后面其他类继承这个类通过传入对应的URL即可。但是,写到后面方法这个构造方法这样传递URL写会有局限性,索性就注释掉了。具体信息看如下代码
public class InitAndEnd {
public static WebDriver driver = null;
/**
* 创建驱动管理 创建驱动对象
*/
@BeforeAll
public static void initWebDriver() {
if (driver == null) {
//使用驱动管理 创建驱动程序对象 打开浏览器
WebDriverManager.chromedriver().setup();
//增加浏览器配置
ChromeOptions options = new ChromeOptions();
//强制运行访问所有链接
options.addArguments("--remote-allow-origins=*");
//设置无头模式 就是不显示浏览器进行测试
// options.addArguments("-headless");
//设置浏览器加载配置 默认值就是这个
options.setPageLoadStrategy(PageLoadStrategy.NORMAL);
driver = new ChromeDriver(options);
// driver.get("http://106.54.52.89:9999/blog_login.html");
//隐式等待 全局等待 只要是关于查找元素的操作的话
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
}
}
/**
* 关闭驱动对象
*/
@AfterAll
public static void closeWebDriver() {
if (driver != null) {
driver.quit();
}
}
/**
* 屏幕截图 接收一个参数用于补充创建文件名
* 注意时间要精确到毫秒 因为自动化测试速度太快
*/
public void getScreenShot(String str) throws IOException {
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sim2 = new SimpleDateFormat("HH-mm-ss-SS");
//转为字符串类型 根据当前时间戳
String dirTime = sim1.format(System.currentTimeMillis());
String fileTime = sim2.format(System.currentTimeMillis());
//开始路径拼接 效果 src/test/image/2024.9.9/test01-21330510.png 注意不要忘记方法名
String fileName = "src/test/image/" + dirTime + "/" + str + "-" + fileTime + ".png ";
//将屏幕截图放到指定路径下
File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File(fileName));
}
/**
* 通过构造方法 传入对应的url
* 创建驱动对象 关闭浏览器
* 后期在写的时候发现这样传递URL写会有局限性
*/
//public InitAndEnd(String url) {
//InitAndEnd.initWebDriver();
//driver.get(url);
//InitAndEnd.closeWebDriver();
}
测试类名称需要符合某些测试框架
这是我测试代码写到后面补充的,强迫症看到我的类名一直有提示,于是去查阅了一下进行修改了就没有提示了
注意:后面对测试类名称的命名要符合某些测试框架(例如JUnit或Maven Surefire Plugin)对测试类命名的约定,通常是要求测试类名称以特定的格式命名,例如以 Test
、Tests
、TestCase
或 IT
开头或结尾,下面有以下两种方法
- 修改测试类的名称,类名带上
Test
、Tests
、TestCase
或IT
开头或结尾。例如下面登录模块测试类我命名为LoginPageTestCase
- 修改配置,如果你不想改动类名,还可以在 Maven Surefire Plugin 或 JUnit 的配置中自定义正则表达式,允许其他命名格式。比如如下在 Maven 的
pom.xml
中修改 Surefire 插件配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>
<include>**/LoginPage.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
登录模块测试
在测试包下创建一个java文件用于测试登录模块,具体细节看如下代码,登录模块的相关测试都在这个类下
检查页面加载是否成功
通过检查页面中一些元素是否存在来验证页面是否加载成功,具体代码如下
忘记提了这里和下面都用到了JUnit的Order注解对测试顺序进行,前提是需要在类上加@TestMethodOrder(MethodOrderer.OrderAnnotation.class)注解
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
//通过这个注解 方法执行顺序会根据@Order注解来执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginPageTestCase extends InitAndEnd {
public static String url = "http://106.54.52.89:9999/blog_login.html";
//public LoginPageTestCase() {
//先帮子类初始化
//也就调用了子类构造方法中driver.get(url);
//super(url);
//}
/**
* 检查页面元素是否加载成功
*/
@Test
@Order(1)
public void loginPageRight() throws InterruptedException {
driver.get(url);
//放大好观察
driver.manage().window().maximize();
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("body > div.nav > span"));
driver.findElement(By.cssSelector("body > div.container-login > div"));
System.out.println("页面元素加载测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
//e.printStackTrace()会打印异常的完整调用堆栈 包括异常发生时的整个调用链
//建议使用在开发调式阶段
e.printStackTrace();
//e.getMessage()这个方法只返回异常的简短描述 通常是异常的具体错误信息
//例如 "NullPointerException: Object is null")
//在生产环境建议仅仅使用e.getMessage()更合适 这样能避免日志中打印过多信息,保持简洁
System.out.println("用户未登录状态下访问知识百宝库编辑页跳转登录界面测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下,没什么问题;补充一嘴如果异常在 try-catch
中被完全捕获和处理,而没有抛出来,JUnit 可能不会意识到该测试方法失败了,所以我在代码中加了throw e;
这样测试框架才能知道测试用例出错并报告相应的失败信息,并且屏幕截图我也放在异常捕获的情况下进行。这样可以确保在出错时抓取当前界面。后面所有代码都有加
检查登录功能–登录成功情况
如何检查登录成功情况呢?首先老规矩定位好输入框和按钮的元素后,接着我们有很多方式检查登录是否成功,例如还是像上面一样检查页面元素是否存在或者通过获取当前标题来判断,这里我们换一种方式当登录成功后,下面我们演示获取当前URL来判断和获取当前标题通过断言来判断
获取当前URL来判断代码如下,注意添加强制等待,否则自动化执行速度过快导致测试结果可能会不通过
/**
* 检查登录功能--成功登录情况
*/
@Test
@Order(2)
public void loginSuccess() throws InterruptedException {
Thread.sleep(1000);
String loginUrl = driver.getCurrentUrl();
//确保没有内容
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//测试当前url是否为知识百宝库列表页
// String currentHandle = driver.getWindowHandle();
// Set<String> windowHandles = driver.getWindowHandles();
// for (String handle : windowHandles) {
// if (handle != currentHandle) {
// //如果不是的话就执行切换为当前句柄
// driver.switchTo().window(handle);
// }
// }
Thread.sleep(2000);
String currentUrl = driver.getCurrentUrl();
//测试当前url是否为知识百宝库列表页
if (!Objects.equals(loginUrl, currentUrl)) {
System.out.println("登录功能测试通过");
System.out.println(currentUrl);
} else {
// 获取当前方法名
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("登录功能测试失败");
System.out.println(currentUrl);
throw e;
}
}
测试效果如下
补充:这里不需要切换标签页,因为标签页只有一个,当标签页有多个的时候才需要进行切换
Java断言
获取当前标题通过Java中的断言来判断或者通过JUnit提供的断言也是可以的
代码如下,同样要注意自动化执行速度过快导致测试结果可能会不通过
/**
* 检查登录功能--成功登录情况
*/
@Test
@Order(2)
public void loginSuccess() throws InterruptedException {
Thread.sleep(1000);
//确保没有内容
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.titleIs("知识百宝库列表页"));
String title = driver.getTitle();
try {
//获取当前标题通过断言来判断
assert title.equals("知识百宝库列表页");
//或者通过JUnit提供的断言也是可以的
Assertions.assertEquals(title, "知识百宝库列表页");
System.out.println("登录功能测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录功能测试失败:" + e.getMessage());
throw e;
}
测试结果
两种断言失败情况展示
如何开启idea中的断言功能呢?
检查登录功能–登录失败情况
如何检查登录失败情况呢?首先老规矩定位好输入框和按钮的元素后,接着如果用户名或密码为空、用户名或密码错误、用户名或密码长度不正确都会有一个警告弹窗
代码如下,依旧注意添加等待,否则自动化执行速度过快导致测试结果可能会不通过
/**
* 检查登录功能--登录失败情况 会有弹窗显示
* 1.先检查用户名或密码为空
* 2.用户名或密码错误
* 3.长度<3(不包括等于) 或 长度>16(不包括等于) 用户名或密码长度不正确
*/
@Test
@Order(3)
public void loginFail() throws InterruptedException {
driver.get(url);
Thread.sleep(1000);
//确保没有内容
driver.navigate().refresh();
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("");
driver.findElement(By.cssSelector("div > #password")).sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
System.out.println("用户名或密码为空测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码为空测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("1234");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
System.out.println("用户名或密码错误测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码错误测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfgh");
driver.findElement(By.cssSelector("div > #password")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait2 = new WebDriverWait(driver, Duration.ofSeconds(5));
wait2.until(ExpectedConditions.alertIsPresent());
Alert alert2 = driver.switchTo().alert();
Thread.sleep(1000);
alert2.accept();
System.out.println("用户名或密码长度不正确测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码长度不正确测试失败:" + e.getMessage());
throw e;
}
}
}
测试效果如下,下面联合测试的顺序需要变化一下,先测试登录失败的情况再测试登录成功的情况
补充:注意用例之间的依赖!!!
检查页面加载和检查登录成功和失败联合测试
在上面我们已经分别对检查 加载页面功能 和 登录成功和失败功能模块 进行测试了,都是没问题的,现在联合起来进行测试
具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
//通过这个注解 方法执行顺序会根据@Order注解来执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginPageTestCase extends InitAndEnd {
public static String url = "http://106.54.52.89:9999/blog_login.html";
// public LoginPageTestCase() {
// //先帮子类初始化
// //也就调用了子类构造方法中driver.get(url);
// super(url);
// }
/**
* 检查页面元素是否加载成功
*/
@Test
@Order(1)
public void loginPageRight() throws InterruptedException, IOException {
driver.get(url);
//放大好观察
driver.manage().window().maximize();
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("body > div.nav > span"));
driver.findElement(By.cssSelector("body > div.container-login > div"));
System.out.println("页面元素加载测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("页面元素加载测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录失败情况 会有弹窗显示
* 1.先检查用户名或密码为空
* 2.用户名或密码错误
* 3.长度<3(不包括等于) 或 长度>16(不包括等于) 用户名或密码长度不正确
*/
@Test
@Order(2)
public void loginFail() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
//确保没有内容
driver.navigate().refresh();
//上面登录后需要退出清空我们的token
// driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
// //放大好观察
// driver.manage().window().maximize();
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("");
driver.findElement(By.cssSelector("div > #password")).sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
System.out.println("用户名或密码为空测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码为空测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("1234");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
System.out.println("用户名或密码错误测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码错误测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfgh");
driver.findElement(By.cssSelector("div > #password")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait2 = new WebDriverWait(driver, Duration.ofSeconds(5));
wait2.until(ExpectedConditions.alertIsPresent());
Alert alert2 = driver.switchTo().alert();
Thread.sleep(1000);
alert2.accept();
System.out.println("用户名或密码长度不正确测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码长度不正确测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录成功情况
*/
@Test
@Order(3)
public void loginSuccess() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
// //放大好观察
// driver.manage().window().maximize();
// String loginUrl = driver.getCurrentUrl();
//确保没有内容
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//测试当前url是否为知识百宝库列表页
// String currentHandle = driver.getWindowHandle();
// Set<String> windowHandles = driver.getWindowHandles();
// for (String handle : windowHandles) {
// if (handle != currentHandle) {
// //如果不是的话就执行切换为当前句柄
// driver.switchTo().window(handle);
// }
// }
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.titleIs("知识百宝库列表页"));
String title = driver.getTitle();
try {
//获取当前标题通过断言来判断
assert title.equals("知识百宝库列表页");
// Assertions.assertEquals(title, "知识百宝库列表页");
System.out.println("登录功能测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录功能测试失败:" + e.getMessage());
throw e;
}
// Thread.sleep(3000);
// String currentUrl = driver.getCurrentUrl();
// //测试当前url是否为知识百宝库列表页
// if (!Objects.equals(loginUrl, currentUrl)) {
// System.out.println("测试通过");
// System.out.println(currentUrl);
// } else {
// System.out.println("测试失败");
// System.out.println(currentUrl);
// }
}
}
测试效果如下,自动化测试的速度可能有点快不好观察就加强制等待,后续都有加几个强制等待便于观察,你们如果想观察的更加仔细的的话可以多加几个,但是弊端很明显就是测试等待时间过久,只适用于调试阶段
注册模块测试
在测试包下创建一个java文件用于测试注册模块,具体细节看如下代码,注册模块的相关测试都在这个类下
检查登录界面注册链接相关测试
- 方法1:检查登录界面注册链接是否可点击,以及点击后链接后是否跳转至注册界面,如何验证是否跳转成功呢?方法有很多,例如通过标题、URL、注册框等
具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RegistryPageTestCase extends InitAndEnd {
public static String loginUrl = "http://106.54.52.89:9999/blog_login.html";
public static String registerUrl = "http://106.54.52.89:9999/blog_register.html";
// public RegistryPageTestCase() {
// super(url);
// }
/**
* 1.检查登录界面注册链接是否可点击
* 2.点击后是否能跳转至注册界面 可以通过标题、URL验证
*/
@Test
@Order(1)
public void registerPageRight() throws InterruptedException, IOException {
driver.get(loginUrl);
try {
Thread.sleep(1000);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//显示等待页面元素加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-login")));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-login > div")));
//定位注册链接元素 并且点击
driver.findElement(By.cssSelector("body > div.container-login > div > div:nth-child(5) > a")).click();
Thread.sleep(2000);
//验证是否跳转至注册界面
wait.until(ExpectedConditions.urlMatches(registerUrl));
System.out.println("测试登录界面点击注册链接跳转至注册界面通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("测试登录界面点击注册链接跳转至注册界面失败:" + e.getMessage());
throw e;
}
}
测试效果如下
检查注册失败相关测试
- 方法2:经过上面测试后登录界面注册按钮链接没问题以及能成功跳转注册界面,接下来测试注册功能是否正常,如何测试呢?先定位用户名输入框、密码输入框、GitHub输入框,然后测试都为空的情况是否能注册成功、以及单独输入用户名、单独输入密码、单独输入GitHub链接以上都需要测试是否能注册成功,接下来两两组合输入,首先输入用户名和Github链接、密码和GitHub链接是否能注册成功,这个方法就用于测试注册失败的方法吧,补充一下这里还需测试单独输入以及两两输入的注册长度,以及注意输入后要清空输入框不然会有意外发生!!!
具体代码如下
/**
* 续接上回 接下来测试多种注册失败情况 这样写代码清晰点 虽然可以都放在一起写但是代码量会有点多
* 1.单独输入用户、密码、GiHub输入框 并且都为空的情况是否注册成功
* 2.接着测试单独输入用户、密码、GiHub输入框的情况是否注册成功
* 3.再测试两两组合的情况 分别是输入用户和GiHub 以及输入密码和GiHub 是否注册成功
*/
@Test
@Order(2)
public void registerFail() throws IOException, InterruptedException {
//单测的时候需要 联合测试的时候就不需要了 正好测试能不能直接跳转至注册界面
driver.get(registerUrl);
try {
Thread.sleep(1000);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
driver.navigate().refresh();
//显示等待注册界面注册框加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-register > div")));
//用户输入框 输入空
WebElement userNameElement = driver.findElement(By.cssSelector("div > #register-username"));
userNameElement.sendKeys("");
WebElement submitElement = driver.findElement(By.cssSelector("#submit"));
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//用户输入框 输入长度超过指定长度
userNameElement.sendKeys("qwertyuiopasdfghj");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//上面输入完注意清空 避免不必要的情况发生
userNameElement.clear();
//密码输入框 输入空
WebElement passwordElement = driver.findElement(By.cssSelector("div > #register-password"));
passwordElement.sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//密码输入框 输入长度超过指定长度
passwordElement.sendKeys("qwertyuiopasdfghj");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
passwordElement.clear();
//Github输入框 输入空 在设计项目的时候需求中允许为空
WebElement githubElement = driver.findElement(By.cssSelector("div > #register-github"));
githubElement.sendKeys("");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
/**
* 上述是各个输入框单输入为空且长度超出指定的情况
* 接下来是两两组合输入 首先输入用户名和Github链接、密码和GitHub链接是否能注册成功
*/
userNameElement.sendKeys("kun");
githubElement.sendKeys("https://gitee.com");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
Thread.sleep(1000);
//要清空输入框
userNameElement.clear();
githubElement.clear();
passwordElement.sendKeys("kun");
githubElement.sendKeys("https://gitee.com");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//要清空输入框
passwordElement.clear();
githubElement.clear();
Thread.sleep(1000);
System.out.println("测试注册失败各种情况通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("测试注册失败各种情况失败:" + e.getMessage());
throw e;
}
}
测试效果如下
检查注册成功相关测试
- 方法3:上面是测试注册失败的方法,那么这个方法就是测试注册成功、并且测试注册成功后是否跳转回登陆界面、并且再测试注册成功后这个用户是否能正常登录。首先上面我们测试过了单独输入注册以及两两注册,以及单独输入以及两两输入的注册长度,但是呢,唯独没有对两两组合中的输入用户名和输入密码进行测试,这里我们进行测试,这里预期测试肯定是成功的,因为在需求中GitHub链接可以不是必填项,接着就是对三个输入框都进行输入进行测试,预期肯定也是测试成功的,这里我们选择都输入进行注册,两种方式注册成功后,我们还需测试注册成功是否有弹窗提示,点击确定后是否返回登录界面,这个就是验证登录界面中的元素是否存在即可。注册成功后,我们还需要测试注册成功后的用户还能不能再次注册,如果不能再次注册会有弹窗提示,反之没有
具体代码如下
/**
* 上述已经把注册失败的各个情况都测试过了
* 接下来测试注册成功的情况 就很简单了
*/
@Test
@Order(3)
public void registerSuccess() throws IOException, InterruptedException {
//单测的时候需要 联合测试的时候就不需要了 正好测试能不能直接跳转至注册界面
driver.get(registerUrl);
try {
Thread.sleep(1000);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
driver.navigate().refresh();
//显示等待注册界面注册框加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-register > div")));
//输入用户名和密码 进行注册
driver.findElement(By.cssSelector("div > #register-username")).sendKeys("kun");
driver.findElement(By.cssSelector("div > #register-password")).sendKeys("1212");
driver.findElement(By.cssSelector("div > #register-github")).sendKeys("https://gitee.com/");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
/**
* 注册成功验证 还需要测试 注册成功后的用户还能不能 再次注册
*/
//点击后跳转回登录界面 通过URL进行验证即可 直接显示等待匹配
wait.until(ExpectedConditions.urlMatches(loginUrl));
//再点击并且定位注册链接元素
driver.findElement(By.cssSelector("body > div.container-login > div > div:nth-child(5) > a")).click();
//验证是否跳转至注册界面
wait.until(ExpectedConditions.urlMatches(registerUrl));
//显示等待注册界面注册框加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-register > div")));
//输入用户名和密码 进行注册
driver.findElement(By.cssSelector("div > #register-username")).sendKeys("kunkun");
driver.findElement(By.cssSelector("div > #register-password")).sendKeys("1212");
driver.findElement(By.cssSelector("div > #register-github")).sendKeys("https://gitee.com/");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 不能再次注册会有警告弹窗
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//退回至登录界面
driver.navigate().back();
Thread.sleep(1000);
System.out.println("注册成功测试模块通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("注册成功测试模块失败:" + e.getMessage());
throw e;
}
}
测试效果如下
补充:这边测试完注册成功后返回登录界面后,还测试了注册成功是否能正常登录,但是还需要验证新注册用户是否能正常使用功能,这里就先不测试了,毕竟软件测试不可穷举!
注册功能模块联合测试
在上面我们已经分别对检查登录注册链接、注册失败、注册成功进行了相关测试,都是没问题的,现在联合起来进行测试
具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RegistryPageTestCase extends InitAndEnd {
public static String loginUrl = "http://106.54.52.89:9999/blog_login.html";
public static String registerUrl = "http://106.54.52.89:9999/blog_register.html";
// public RegistryPageTestCase() {
// super(url);
// }
/**
* 1.检查登录界面注册链接是否可点击
* 2.点击后是否能跳转至注册界面 可以通过标题、URL验证
*/
@Test
@Order(1)
public void registerPageRight() throws InterruptedException, IOException {
driver.get(loginUrl);
try {
Thread.sleep(1000);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//显示等待页面元素加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-login")));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-login > div")));
//定位注册链接元素 并且点击
driver.findElement(By.cssSelector("body > div.container-login > div > div:nth-child(5) > a")).click();
Thread.sleep(1000);
//验证是否跳转至注册界面
wait.until(ExpectedConditions.urlMatches(registerUrl));
System.out.println("测试登录界面点击注册链接跳转至注册界面通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("测试登录界面点击注册链接跳转至注册界面失败:" + e.getMessage());
throw e;
}
}
/**
* 续接上回 接下来测试多种注册失败情况 这样写代码清晰点 虽然可以都放在一起写但是代码量会有点多
* 1.单独输入用户、密码、GiHub输入框 并且都为空的情况是否注册成功
* 2.接着测试单独输入用户、密码、GiHub输入框的情况是否注册成功
* 3.再测试两两组合的情况 分别是输入用户和GiHub 以及输入密码和GiHub 是否注册成功
*/
@Test
@Order(2)
public void registerFail() throws IOException, InterruptedException {
//单测的时候需要 联合测试的时候就不需要了 正好测试能不能直接跳转至注册界面
// driver.get(registerUrl);
try {
Thread.sleep(1000);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
driver.navigate().refresh();
//显示等待注册界面注册框加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-register > div")));
//用户输入框 输入空
WebElement userNameElement = driver.findElement(By.cssSelector("div > #register-username"));
userNameElement.sendKeys("");
WebElement submitElement = driver.findElement(By.cssSelector("#submit"));
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//用户输入框 输入长度超过指定长度
userNameElement.sendKeys("qwertyuiopasdfghj");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//上面输入完注意清空 避免不必要的情况发生
userNameElement.clear();
//密码输入框 输入空
WebElement passwordElement = driver.findElement(By.cssSelector("div > #register-password"));
passwordElement.sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//密码输入框 输入长度超过指定长度
passwordElement.sendKeys("qwertyuiopasdfghj");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
passwordElement.clear();
//Github输入框 输入空 在设计项目的时候需求中允许为空
WebElement githubElement = driver.findElement(By.cssSelector("div > #register-github"));
githubElement.sendKeys("");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
/**
* 上述是各个输入框单输入为空且长度超出指定的情况
* 接下来是两两组合输入 首先输入用户名和Github链接、密码和GitHub链接是否能注册成功
*/
userNameElement.sendKeys("kun");
githubElement.sendKeys("https://gitee.com");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
Thread.sleep(1000);
//要清空输入框
userNameElement.clear();
githubElement.clear();
passwordElement.sendKeys("kun");
githubElement.sendKeys("https://gitee.com");
submitElement.click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//要清空输入框
passwordElement.clear();
githubElement.clear();
Thread.sleep(1000);
System.out.println("测试注册失败各种情况通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("测试注册失败各种情况失败:" + e.getMessage());
throw e;
}
}
/**
* 上述已经把注册失败的各个情况都测试过了
* 接下来测试注册成功的情况 就很简单了
*/
@Test
@Order(3)
public void registerSuccess() throws IOException, InterruptedException {
//单测的时候需要 联合测试的时候就不需要了 正好测试能不能直接跳转至注册界面
// driver.get(registerUrl);
try {
Thread.sleep(1000);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
driver.navigate().refresh();
//显示等待注册界面注册框加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-register > div")));
//输入用户名和密码 进行注册
driver.findElement(By.cssSelector("div > #register-username")).sendKeys("zlq");
driver.findElement(By.cssSelector("div > #register-password")).sendKeys("1212");
driver.findElement(By.cssSelector("div > #register-github")).sendKeys("https://gitee.com/");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 警告弹窗是否存在
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
/**
* 注册成功验证 还需要测试 注册成功后的用户还能不能 再次注册
*/
//点击后跳转回登录界面 通过URL进行验证即可 直接显示等待匹配
wait.until(ExpectedConditions.urlMatches(loginUrl));
//再点击并且定位注册链接元素
driver.findElement(By.cssSelector("body > div.container-login > div > div:nth-child(5) > a")).click();
//验证是否跳转至注册界面
wait.until(ExpectedConditions.urlMatches(registerUrl));
//显示等待注册界面注册框加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-register > div")));
//输入用户名和密码 进行注册
driver.findElement(By.cssSelector("div > #register-username")).sendKeys("zlq");
driver.findElement(By.cssSelector("div > #register-password")).sendKeys("1212");
driver.findElement(By.cssSelector("div > #register-github")).sendKeys("https://gitee.com/");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 不能再次注册会有警告弹窗
wait.until(ExpectedConditions.alertIsPresent());
Thread.sleep(1000);
alert.accept();
//退回至登录界面
driver.navigate().back();
//最后停留在登录界面
Thread.sleep(1000);
System.out.println("注册成功测试模块通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("注册成功测试模块失败:" + e.getMessage());
throw e;
}
}
}
测试效果如下
知识百宝库列表页模块测试
在测试包下创建一个java文件用于知识百宝库列表页模块,具体细节看如下代码,列表页模块的相关测试都在这个类下
未登录状态下 访问知识百宝库列表页
首先测试用户在未登录状态下访问知识百宝库列表页 是否会有警告弹窗,点击确定 是否会返回登录界面,代码如下,提前说一下后续会对用户在未登录状态下访问各个界面进行统一处理
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ListPageTestCase extends InitAndEnd {
public static String url = "http://106.54.52.89:9999/blog_list.html";
//public ListPageTestCase() {
//super(url);
//}
/**
* 用户未登录状态下 访问知识百宝库列表页 是否会有警告弹窗
* 点击确定 是否会返回登录界面
* 注意如果登录情况访问的话就不会有弹窗显示 我们可以先退出
* 这里已经在PageByLoginTestCase实现了 可以删除了
*/
@Test
@Order(1)
public void ListNoLogin() throws InterruptedException, IOException {
Thread.sleep(1000);
try {
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//验证是否跳转成功
String title = driver.findElement(By.cssSelector("body > div.nav>span")).getText();
assert title.equals("我的知识百宝库系统");
System.out.println("用户未登录状态下跳转登录界面测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户未登录状态下跳转登录界面测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下
登录成功情况下对知识百宝库列表页的可点击元素进行测试
这个测试比较好实现的,我们先在登录界面定位对应的输入框输入正确用户名和密码进行登录后,跳转至知识百宝库列表页对一些可点击元素进行测试,例如写知识百宝库、查看知识百宝库、主页等元素
具体代码如下
/**
* 1.我们先在登录界面定位对应的输入框输入正确用户名和密码进行登录
* 2.登录成功跳转至知识百宝库列表页对一些可点击元素进行测试 例如写知识百宝库、查看知识百宝库、主页等元素
* 3.先测试查看全文 然后回退 测试写知识百宝库 回退 测试主页 接着测试个人栏的Gitee地址 然后回退测试主页
*/
@Test
@Order(2)
public void checkListPageElement() throws InterruptedException, IOException {
//登录访问知识百宝库列表页
loginSu();
try {
Thread.sleep(1000);
//显示等待 避免不必要的等待时间 并且自动化测试速度过快可能导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//严谨点 检查知识百宝库列表页所有查看主页元素是否可点击并且跳转
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right >div > a"));
for (WebElement element : checkElements) {
//显示等待 测试查看全文是否可点击
wait.until(ExpectedConditions.elementToBeClickable(element));
//接着点击
element.click();
//强制等待 方便观察
Thread.sleep(1000);
//接着回退
driver.navigate().back();
}
//等待页面加载
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body")));
//测试写知识百宝库元素是否可点击并且跳转
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
//强制等待 方便观察
Thread.sleep(1000);
driver.navigate().back();
//测试Gitee链接是否可点击并且跳转
driver.findElement(By.cssSelector("body > div.container > div.left > div > a")).click();
driver.navigate().back();
Thread.sleep(1000);
System.out.println("知识百宝库列表页的可点击元素测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("知识百宝库列表页的可点击元素测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下
知识百宝库列表页模块联合测试
在上面我们已经分别对未登录状态下访问知识百宝库列表页、登录成功情况下对知识百宝库列表页的可点击元素进行了测试,都是没问题的,现在联合起来进行测试
具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ListPageTestCase extends InitAndEnd {
public static String loginUrl = "http://106.54.52.89:9999/blog_login.html";
public static String listUrl = "http://106.54.52.89:9999/blog_list.html";
// public ListPageTestCase() {
// super(url);
// }
/**
* 用户未登录状态下 访问知识百宝库列表页 是否会有警告弹窗
* 点击确定 是否会返回登录界面
* 注意如果登录情况访问的话就不会有弹窗显示 我们可以先退出
* 这里已经在PageByLoginTestCase实现了 可以删除了
*/
@Test
@Order(1)
public void ListNoLogin() throws InterruptedException, IOException {
//这里先打开登录界面 主要是为了屏幕最大化方便观察
driver.get(loginUrl);
try {
Thread.sleep(1000);
//屏幕最大化方便观察
driver.manage().window().maximize();
//访问知识百宝库列表页
driver.get(listUrl);
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//验证是否跳转成功
String title = driver.findElement(By.cssSelector("body > div.nav>span")).getText();
assert title.equals("我的知识百宝库系统");
System.out.println("用户未登录状态下跳转登录界面测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户未登录状态下跳转登录界面测试失败:" + e.getMessage());
throw e;
}
}
/**
* 1.我们先在登录界面定位对应的输入框输入正确用户名和密码进行登录
* 2.登录成功跳转至知识百宝库列表页对一些可点击元素进行测试 例如写知识百宝库、查看知识百宝库、主页等元素
* 3.先测试查看全文 然后回退 测试写知识百宝库 回退 测试主页 接着测试个人栏的Gitee地址 然后回退测试主页
*/
@Test
@Order(2)
public void checkListPageElement() throws InterruptedException, IOException {
//登录访问知识百宝库列表页
loginSu();
try {
Thread.sleep(1000);
//显示等待 避免不必要的等待时间 并且自动化测试速度过快可能导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//严谨点 检查知识百宝库列表页所有查看主页元素是否可点击并且跳转
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right >div > a"));
for (WebElement element : checkElements) {
//显示等待 测试查看全文是否可点击
wait.until(ExpectedConditions.elementToBeClickable(element));
//接着点击
element.click();
//强制等待 方便观察
Thread.sleep(1000);
//接着回退
driver.navigate().back();
}
//等待页面加载
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body")));
//测试写知识百宝库元素是否可点击并且跳转
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
//强制等待 方便观察
Thread.sleep(1000);
driver.navigate().back();
//测试Gitee链接是否可点击并且跳转
driver.findElement(By.cssSelector("body > div.container > div.left > div > a")).click();
driver.navigate().back();
Thread.sleep(1000);
System.out.println("知识百宝库列表页的可点击元素测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("知识百宝库列表页的可点击元素测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下
未登录状态统一测试
在测试包下创建一个java文件针对未登录模块模块,具体细节看如下代码,未登录状态的相关测试都在这个类下
代码都长的都差不多,主要测试用户在未登录状态下 访问各个知识百宝库界面 是否会有警告弹窗,点击确定 是否会返回登录界面验证跳转界面是否成功,通过定位界面元素来进行判断即可
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PageByNoLoginTestCase extends InitAndEnd {
public static String listUrl = "http://106.54.52.89:9999/blog_list.html";
public static String detailUrl = "http://106.54.52.89:9999/blog_detail.html";
public static String editUrl = "http://106.54.52.89:9999/blog_edit.html";
// public PageByNoLoginTestCase() {
// super(listUrl);
// }
/**
* 用户未登录状态下 访问知识百宝库列表页 是否会有警告弹窗
* 点击确定 是否会返回登录界面
* 注意如果登录情况访问的话就不会有弹窗显示 我们可以先退出
*/
@Test
@Order(1)
public void ListNoLogin() throws InterruptedException, IOException {
//这里先打开登录界面 主要是为了屏幕最大化方便观察
//不然直接打开列表页 会有警告弹窗没有进行处理会报错
driver.get(loginUrl);
try {
Thread.sleep(1000);
//屏幕最大化方便观察
driver.manage().window().maximize();
//访问知识百宝库列表页
driver.get(listUrl);
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//验证是否跳转成功
String title = driver.findElement(By.cssSelector("body > div.nav>span")).getText();
assert title.equals("我的知识百宝库系统");
System.out.println("用户未登录状态下访问知识百宝库列表页跳转登录界面测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户未登录状态下访问知识百宝库列表页跳转登录界面测试失败:" + e.getMessage());
throw e;
}
}
/**
* 用户未登录状态下 访问知识百宝库详情页 是否会有警告弹窗
* 点击确定 是否会返回登录界面
* 注意如果登录情况访问的话就不会有弹窗显示 我们可以先退出
*/
@Test
@Order(2)
public void detailPageNoLogin() throws InterruptedException, IOException {
driver.get(detailUrl);
Thread.sleep(1000);
try {
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//验证是否跳转成功
String title = driver.findElement(By.cssSelector("body > div.container-login > div > h3")).getText();
Assertions.assertEquals(title, "登录");
System.out.println("用户未登录状态下访问知识百宝库详情页跳转登录界面测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户未登录状态下访问知识百宝库详情页跳转登录界面测试失败:" + e.getMessage());
throw e;
}
}
/**
* 用户未登录状态下 访问知识百宝库编辑页 是否会有警告弹窗
* 点击确定 是否会返回登录界面
* 注意如果登录情况访问的话就不会有弹窗显示 我们可以先退出
*/
@Test
@Order(3)
public void editPageNoLogin() throws InterruptedException, IOException {
driver.get(editUrl);
Thread.sleep(1000);
try {
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//验证是否跳转成功
String title = driver.findElement(By.cssSelector("body > div.container-login > div > div:nth-child(5) > a")).getText();
assert title.equals("还没注册?点击注册");
System.out.println("用户未登录状态下访问知识百宝库编辑页跳转登录界面测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
//这个方法会打印异常的完整调用堆栈 包括异常发生时的整个调用链 建议使用在开发调式阶段
e.printStackTrace();
//e.getMessage()这个方法只返回异常的简短描述 通常是异常的具体错误信息
//例如 "NullPointerException: Object is null")
//在生产环境建议仅仅使用e.getMessage()更合适 这样能避免日志中打印过多信息,保持简洁
System.out.println("用户未登录状态下访问知识百宝库编辑页跳转登录界面测试失败:" + e.getMessage());
throw e;
}
}
}
测试效果如下,最后停留在登录界面,我们需要记录一下因为后续所有测试类进行测试的时候,如果各个测试类之间没处理好的会报错
检查登录功能–登录成功情况统一处理
在之前公共包下中的公共类中,添加一个方法对登录成功情况功统一处理方便后续对其他功能模块进行测试,具体细节看如下代码
代码和之前的检查登录功能–登录成功情况一样,并且也测试了跳转到知识百宝库列表页是否成功,代码如下
public static String url = "http://106.54.52.89:9999/blog_login.html";
/**
* 检查登录功能--登录成功情况统一处理
*/
public static void loginSu() throws IOException, InterruptedException {
driver.get(url);
//最大化方便有头模式观察
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2));
// String loginUrl = driver.getCurrentUrl();
try {
//确保没有内容
driver.navigate().refresh();
//显示等待页面加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container-login > div")));
//显示等待用户和密码输入框是否
WebElement userNameElement = wait.until(ExpectedConditions.elementToBeClickable(By.id("username")));
WebElement passwordElement = wait.until(ExpectedConditions.elementToBeClickable(By.id("password")));
//使用鼠标进行操作
Actions actions = new Actions(driver);
actions.click(userNameElement)
.sendKeys("Nan")
.click(passwordElement)
.sendKeys("123")
.perform();
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
wait.until(ExpectedConditions.titleIs("知识百宝库列表页"));
String title = driver.getTitle();
//获取当前标题通过断言来判断
assert title.equals("知识百宝库列表页");
// Assertions.assertEquals(title, "知识百宝库列表页");
System.out.println("登录功能测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录功能测试失败:" + e.getMessage());
throw e;
}
}
我拿下面要讲的知识百宝库编辑页模块搭配上述代码进行测试,代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ExitLoginTestCase extends InitAndEnd {
// public ExitLoginTestCase() {
// super(url);
// }
/**
* 1.首先调用登录成功方法 因为主页在没有登录情况的时候没有退出按钮
* 2.登录成功后跳转到知识百宝库列表页 通过定位页面元素验证是否跳转到知识百宝库列表页 点击退出测试是否成功跳转回登录界面
* 3.然后点击退出验证是否成功退出,如何验证呢?点击退出后会跳转回登录界面通过这个界面的元素进行验证,例如点击主页按钮跳转到知识百宝库列表页
* 4.走到这说明上面测试都是成功的 再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
* 5.并且再测试同用户名或密码登录是否能再次登录成功
*/
// @LoginSuccess
@Test
@Order(1)
public void ExitByLoginList() throws InterruptedException, IOException {
loginSu();
// driver.get(url);
Thread.sleep(2000);
try {
driver.navigate().refresh();
Thread.sleep(2000);
//点击退出 就会跳转会登录界面
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
Thread.sleep(2000);
//测试点击退出验证是否成功跳转回登录界面 通过登录界面 主页按钮跳转到知识百宝库列表页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
//走到这说明上面测试都是成功的
//再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(2000);
alert.accept();
Thread.sleep(2000);
//再测试同用户名或密码登录是否能再次登录成功
driver.findElement(By.cssSelector("#username")).sendKeys("Nan");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(2000);
//此时点击退出 测试退出模块正式通过
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
System.out.println("登录成功后跳转到知识百宝库列表页点击退出测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录成功后跳转到知识百宝库列表页点击退出测试失败:" + e.getMessage());
throw e;
}
}
基于AOP切面编程针对登录成功功能的统一处理
创建maven项目能自定义实现AOP,再登录功能测试完成后通过AOP去针对登录成功功能的统一处理用于后续的测试,后续所有测试完成后来实现一下
思路:
-
首先我们先单独创建一个包存放aop的实现,然后这里我们采取自定义注解的方式,这样比较简单可以自己定义注解的作用域、生命周期等
-
接着导入对应的依赖,毕竟我们创建的是Maven项目,包含spring-aop、spring-contex、aspectjweaver依赖
<!--以下依赖用于实现AOP-->
<!--spring-aop依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.20</version>
</dependency>
<!--spring-context依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!--aspectjweaver依赖-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
- 如何创建一个自定义注解呢?之前Spring AOP讲过,通过创建annotation注解然后实现,如下图
- 接着还需要一个切面类,已经实现了切面类其中也包含了切面,如下图
- 然而测试的时候出错了卡住了,网上查了很多也不知道如何实现,这里大家看到就当复习AOP即可,如果大家知道如何实现可以来教教我如何实现!!!
知识百宝库发布页模块测试
在测试包下创建一个java文件用于测试知识百宝库发布页模块,具体细节看如下代码,知识百宝库发布页模块的相关测试都在这个类下
测试发布知识百宝库模块总思路:
- 发布知识百宝库思路首先要登录 然后测试验证一下登录是否成功 成功接着下一步
- 下一步定位发布知识百宝库元素 点击进入 验证界面是否符合预期
- 接着符合的情况下 由于使用的是第三方组件 我们需要对对发布知识百宝库关键的元素进行测试
- 上面测试成功后 首先定位到知识百宝库内容输入框元素,再输入内容,再定位到标题 接着输入标题
- 定位到发布知识百宝库按钮 接着测试点击测试发布文章按钮是否发布成功
- 接着发布成功 是否跳转回知识百宝库列表页界面也需要进行测试
- 以及对发布是否成功也要进行测试 根据跳转回的知识百宝库列表页界的元素
- 测试知识百宝库发布是否成功思路:检查知识百宝库列表页面中是否存在刚刚发布的知识百宝库元素 确保知识百宝库的预期标题和实际标题是否匹配
- 编辑知识百宝库思路也是类似删除的话也差不多比较简单 只要实现了发布知识百宝库的思路后面都相对容易实现
检查登录情况下点击写知识百宝库相关测试
在上面我们已经完成了的用户在登录情况下点击写知识百宝库链接是否能进入知识百宝库发布页界面的测试,代码再展示一下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class EditPageTestCase extends InitAndEnd {
public static String listUrl = "http://106.54.52.89:9999/blog_list.html";
/**
* 发布知识百宝库思路首先要登录 登录成功接着下一步
* 下一步定位发布知识百宝库元素 点击链接进入 验证界面是否符合预期
*/
@Test
@Order(1)
public void openPublishPageAndPublishBlogSuccess() throws InterruptedException, IOException {
loginSu();
try {
Thread.sleep(1000);
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//定位写知识百宝库元素
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
Thread.sleep(1000);
// 定位发布文章元素
WebElement submitButton = driver.findElement(By.cssSelector("#submit"));
// 等待直到按钮的 value 属性变为 "发布知识百宝库"
wait.until(ExpectedConditions.attributeToBe(submitButton, "value", "发布文章"));
// 获取属性值
String value = submitButton.getAttribute("value");
// 验证属性值是否为 "发布知识百宝库"
Assertions.assertEquals(value, "发布文章");
//最后停留在写知识百宝库界面
Thread.sleep(3000);
System.out.println("登录情况下打开发布知识百宝库界面测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录情况下打开发布知识百宝库界面测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下
发布知识百宝库功能测试
在上面我们已经完成了的用户在登录情况下接着对发布知识百宝库功能进行测试
首先定位到知识百宝库内容输入框元素,再输入内容,代码如下
/**
* 上面测试成功后 我们定位发布知识百宝库页中的重要元素测试发布知识百宝库功能是否正常
* 接着发布成功 是否跳转回知识百宝库列表页界面也需要进行测试
*/
@Test
@Order(2)
public void publishBlogByLogin() throws IOException, InterruptedException {
loginSu();
try {
//上面已经测试过 这边直接进行跳转
driver.get(url);
Thread.sleep(1000);
//定位发布文章元素
WebElement element = 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 > span > span"));
element.sendKeys("发布知识百宝库功能测试");
System.out.println("发布知识百宝库功能测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("发布知识百宝库功能测试失败:" + e.getMessage());
throw e;
}
}
让我们看看测试效果,代码报异常了,异常信息说元素不可交互
这里又是一个知识点,当页面元素不可交互,我们可以试试使用显示等待来测试这个元素是否存在,代码如下
@Test
@Order(2)
public void publishBlogByLogin() throws IOException, InterruptedException {
InitAndEnd.loginSu();
try {
//上面已经测试过 这边直接进行跳转
driver.get(url);
Thread.sleep(1000);
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
//验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
System.out.println("内容输入框存在");
//定位发布文章元素
// WebElement element = 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 > span > span"));
// element.sendKeys("发布知识百宝库功能测试");
// System.out.println("发布知识百宝库功能测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("发布知识百宝库功能测试失败:" + e.getMessage());
throw e;
}
}
看看测试效果,输出内容输入框存在,那到底是啥问题嘞?
我们再用显示等待的其他条件测试一下,测试内容输入框是否可以点击,代码如下
@Test
@Order(2)
public void publishBlogByLogin() throws IOException, InterruptedException {
InitAndEnd.loginSu();
try {
//上面已经测试过 这边直接进行跳转
driver.get(url);
Thread.sleep(1000);
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
//验证内容输入框是否存在
wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
System.out.println("内容输入框");
//定位发布文章元素
// WebElement element = 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 > span > span"));
// element.sendKeys("发布知识百宝库功能测试");
// System.out.println("发布知识百宝库功能测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("发布知识百宝库功能测试失败:" + e.getMessage());
throw e;
}
}
看看测试效果,输出内容输入框可以点击,既然输入框内容存在并且可以点击那到底是啥问题嘞?
由于前端这里的编辑框架使用的是第三方插件,所以我们定位内容输入框直接内容是不行滴,这里就需要用到鼠标和键盘的操作来实现,具体操作以及代码实现如下
- 先通过鼠标双击输入框里的内容
- 接着通过键盘的快捷键ctrl+a,两者注意分开 a是通过sendKeys方法
- 接着释放快捷键ctrl,然后通过键盘的DELETE键删除原本的内容 最后执行**
perform()
**方法!!!
注意:ctrl+a一定要分开操作,不像人;一定要释放快捷键;最后一定要执行perform()
方法!!!
/**
* 由于使用的是第三方组件 对发布知识百宝库关键的元素进行测试
* 1.上面测试成功后 首先定位到知识百宝库内容输入框元素,再输入内容
* 2.再定位到标题 接着输入标题
* 3.定位到发布知识百宝库按钮 接着测试点击测试发布文章按钮是否发布成功
* 4.接着发布成功 是否跳转回知识百宝库列表页界面也需要进行测试
*/
@Test
@Order(2)
public void publishBlogByLogin() throws IOException, InterruptedException {
loginSu();
try {
//上面已经测试过 这边直接进行跳转
driver.get(listUrl);
Thread.sleep(1000);
//定位写知识百宝库元素
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//验证内容输入框是否可点击
// wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
//验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
//定位发布文章元素
WebElement element = 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 > span > span"));
Actions actions = new Actions(driver);
Thread.sleep(1000);
/**
* 1.先利用鼠标双击
* 2.再按快捷键ctrl+a全选内容 注意要分开操作 然后释放掉捷键ctrl
* 3.再根据键盘的DELETE删除内容 最后执行
* 注意按完快捷键后都要释放 顺序释放 否则会这个按键将会一直保持按住状态 跟我们人操作一样
*/
actions.doubleClick(element);
actions.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys(Keys.DELETE)
.sendKeys("Selenium...")
.perform();
Thread.sleep(1000);
//也可以上述操作结束后 先进行挪动再输入内容操作 但是最好是连贯操作
//actions.moveToElement(element).sendKeys("软件测试").perform();
//测试知识百宝库发布是否成功思路:检查知识百宝库列表页面中是否存在刚刚发布的知识百宝库元素 确保知识百宝库的预取标题和实际标题
//预取标题
String anticipateTitle = "软件测试";
//接着定位到标题元素 输入标题
driver.findElement(By.cssSelector("body > div.content-edit > div.push > #title")).sendKeys("软件测试");
//搞个显示等待 指定找到发布文章按钮 然后点击
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#submit")));
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//发布文章测试是否跳转回知识百宝库列表页 再测试是否发布成功
String currentUrl = driver.getCurrentUrl();
Assertions.assertEquals(url, currentUrl);
Thread.sleep(1000);
//实际标题
String actualTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();
//断言
assert anticipateTitle.equals(actualTitle);
System.out.println("发布知识百宝库测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("发布知识百宝库测试失败:" + e.getMessage());
throw e;
}
}
测试效果
上面解决了内容输入框的问题,接下来就是正式测试发布知识百宝库模块是否符合预期,思路和代码如下
/**
* 由于使用的是第三方组件 对发布知识百宝库关键的元素进行测试
* 1.上面测试成功后 首先定位到知识百宝库内容输入框元素,再输入内容
* 2.再定位到标题 接着输入标题
* 3.定位到发布知识百宝库按钮 接着测试点击测试发布文章按钮是否发布成功
* 4.接着发布成功 是否跳转回知识百宝库列表页界面也需要进行测试
* 5.测试知识百宝库发布是否成功 检查知识百宝库列表页面中是否存在刚刚发布的知识百宝库元素 确保知识百宝库的预取标题和实际标题
*/
@Test
@Order(2)
public void publishBlogByLogin() throws IOException, InterruptedException {
loginSu();
try {
//上面已经测试过 这边直接进行跳转
driver.get(listUrl);
Thread.sleep(1000);
//定位写知识百宝库元素
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//验证内容输入框是否可点击
// wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
//验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
//定位发布文章元素
WebElement element = 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 > span > span"));
Actions actions = new Actions(driver);
Thread.sleep(1000);
/**
* 1.先利用鼠标双击
* 2.再按快捷键ctrl+a全选内容 注意要分开操作 然后释放掉捷键ctrl
* 3.再根据键盘的DELETE删除内容 最后执行
* 注意按完快捷键后都要释放 顺序释放 否则会这个按键将会一直保持按住状态 跟我们人操作一样
*/
actions.doubleClick(element);
actions.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys(Keys.DELETE)
.sendKeys("软件测试基础概念...")
.perform();
Thread.sleep(1000);
//也可以上述操作结束后 先进行挪动再输入内容操作 但是最好是连贯操作
//actions.moveToElement(element).sendKeys("软件测试").perform();
//测试知识百宝库发布是否成功思路:检查知识百宝库列表页面中是否存在刚刚发布的知识百宝库元素 确保知识百宝库的预取标题和实际标题
//预取标题
String anticipateTitle = "软件测试概念";
//接着定位到标题元素 输入标题
driver.findElement(By.cssSelector("body > div.content-edit > div.push > #title")).sendKeys("软件测试概念");
//搞个显示等待 指定找到发布文章按钮 然后点击
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#submit")));
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//发布文章测试是否跳转回知识百宝库列表页 再测试是否发布成功
String currentUrl = driver.getCurrentUrl();
Assertions.assertEquals(listUrl, currentUrl);
Thread.sleep(1000);
//实际标题
String actualTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();
//断言
assert anticipateTitle.equals(actualTitle);
System.out.println("发布知识百宝库测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("发布知识百宝库测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下,最后停留在知识百宝库列表页
检查与发布知识百宝库联合测试
在上面我们已经分别对检查跳转写知识百宝库链接功能模块和发布知识百宝库功能模块进行测试了,都是没问题的,现在联合起来进行测试
具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class EditPageTestCase extends InitAndEnd {
public static String listUrl = "http://106.54.52.89:9999/blog_list.html";
// public EditPageTestCase(String url) {
// super(url);
// }
/**
* 测试发布知识百宝库模块总思路:
* 1. 发布知识百宝库思路首先要登录 然后测试验证一下登录是否成功 成功接着下一步
* 2. 下一步定位发布知识百宝库元素 点击进入 验证界面是否符合预期
* 3. 接着符合的情况下 由于使用的是第三方组件 我们需要对对发布知识百宝库关键的元素进行测试
* 4. 上面测试成功后 首先定位到知识百宝库内容输入框元素,再输入内容,再定位到标题 接着输入标题
* 5. 定位到发布知识百宝库按钮 接着测试点击测试发布文章按钮是否发布成功
* 6. 接着发布成功 是否跳转回知识百宝库列表页界面也需要进行测试
* 7. 以及对发布是否成功也要进行测试 根据跳转回的知识百宝库列表页界的元素
* 8. 测试知识百宝库发布是否成功思路:检查知识百宝库列表页面中是否存在刚刚发布的知识百宝库元素 确保知识百宝库的标题和内容匹配
* 9. 编辑知识百宝库思路也是类似删除的话也差不多比较简单 只要实现了发布知识百宝库的思路后面都相对容易实现
*/
@Test
@Order(1)
public void openPublishPageAndPublishBlogSuccess() throws InterruptedException, IOException {
loginSu();
try {
Thread.sleep(1000);
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//定位写知识百宝库元素
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
Thread.sleep(1000);
// 定位发布文章元素
WebElement submitButton = driver.findElement(By.cssSelector("#submit"));
// 等待直到按钮的 value 属性变为 "发布知识百宝库"
wait.until(ExpectedConditions.attributeToBe(submitButton, "value", "发布文章"));
// 获取属性值
String value = submitButton.getAttribute("value");
// 验证属性值是否为 "发布知识百宝库"
Assertions.assertEquals(value, "发布文章");
//最后停留在写知识百宝库界面
Thread.sleep(1000);
System.out.println("登录情况下打开发布知识百宝库界面测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录情况下打开发布知识百宝库界面测试失败:" + e.getMessage());
throw e;
}
}
/**
* 由于使用的是第三方组件 对发布知识百宝库关键的元素进行测试
* 1.上面测试成功后 首先定位到知识百宝库内容输入框元素,再输入内容
* 2.再定位到标题 接着输入标题
* 3.定位到发布知识百宝库按钮 接着测试点击测试发布文章按钮是否发布成功
* 4.接着发布成功 是否跳转回知识百宝库列表页界面也需要进行测试
*/
@Test
@Order(2)
public void publishBlogByLogin() throws IOException, InterruptedException {
// loginSu();
try {
//上面已经测试过 这边直接进行跳转
// driver.get(listUrl);
Thread.sleep(1000);
//定位写知识百宝库元素
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
//显示等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
//验证内容输入框是否可点击
// wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
//验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span > span")));
//定位发布文章元素
WebElement element = 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 > span > span"));
Actions actions = new Actions(driver);
Thread.sleep(1000);
/**
* 1.先利用鼠标双击
* 2.再按快捷键ctrl+a全选内容 注意要分开操作 然后释放掉捷键ctrl
* 3.再根据键盘的DELETE删除内容 最后执行
* 注意按完快捷键后都要释放 顺序释放 否则会这个按键将会一直保持按住状态 跟我们人操作一样
*/
actions.doubleClick(element);
actions.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys(Keys.DELETE)
.sendKeys("Java基础概念...")
.perform();
Thread.sleep(1000);
//也可以上述操作结束后 先进行挪动再输入内容操作 但是最好是连贯操作
//actions.moveToElement(element).sendKeys("软件测试").perform();
//测试知识百宝库发布是否成功思路:检查知识百宝库列表页面中是否存在刚刚发布的知识百宝库元素 确保知识百宝库的预取标题和实际标题
//预取标题
String anticipateTitle = "Java基础概念";
//接着定位到标题元素 输入标题
driver.findElement(By.cssSelector("body > div.content-edit > div.push > #title")).sendKeys("Java基础概念");
//搞个显示等待 指定找到发布文章按钮 然后点击
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#submit")));
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//发布文章测试是否跳转回知识百宝库列表页 再测试是否发布成功
String currentUrl = driver.getCurrentUrl();
Assertions.assertEquals(listUrl, currentUrl);
Thread.sleep(1000);
//实际标题
String actualTitle = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();
//断言
assert anticipateTitle.equals(actualTitle);
System.out.println("发布知识百宝库测试成功");
} catch (Exception e) {
// 获取当前方法名s
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("发布知识百宝库测试失败:" + e.getMessage());
throw e;
}
}
}
测试效果如下,上面提到过注意用例之间的依赖!!!,所以整体的代码稍微要做一些调整,但是还是取决于你对于WebDriver实例的管理。后面会有讲过,根据你的选择来修改代码逻辑
知识百宝库详情页模块测试
在测试包下创建一个java文件用于测试知识百宝库详情页模块,具体细节看如下代码,知识百宝库详情页模块的相关测试都在这个类下
编辑功能模块测试
思路:
- 首先调用登录成功方法
- 登录成功后跳转到知识百宝库列表页
- 通过知识百宝库列表页元素 校验是否跳转成功
- 上面的测试已经在登录成功方法统一进行测试了,只需要调用即可
- 现在只需要测试编辑功能模块按钮和删除功能模块即可 分别进行测试
- 先测试编辑功能模块,首先先定位查看全文按钮跳转到知识百宝库详情页先
- 更新成功后验证跳转知识百宝库详情页是否成功思路**:** 这里注意最好不要用标题进行判断 因为知识百宝库标题可能重复 我们根据发布时间进行验证是否跳转成功,更新成功后跳转到知识百宝库列表页后,定位到更新时间的元素,然后根据我们在点击提交更新后的时间戳进行比较,,所以在点击提交更新后获取一个时间戳以及设置一个变量为时差,时间上会有点差别,没事这更新时间差个几百毫秒没什么大事并且测试过有时候低至几十毫秒 时差不涉及到金钱方面就好说
- 跳转成功后 我们接着定位编辑元素,然后点击进入编辑页面,我们根据URL来判断是否跳转成功
- 然后和知识百宝库发布页中的类似,先定位标题元素然后输入内容,接着再定位内容输入框元素等一系列操作和前面一样,这里就不过多解释了,具体实现看代码
测试知识百宝库详情页编辑功能,具体代码实现如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DetailPageTestCase extends InitAndEnd {
public static String listUrl = "http://106.54.52.89:9999/blog_list.html";
public static String updateUrl = "http://106.54.52.89:9999/blog_update.html";
// public DetailPageTestCase() {
// super(url);
// }
/**
* 1. 首先调用登录成功方法
* 2. 登录成功后跳转到知识百宝库列表页
* 3. 通过知识百宝库列表页元素 校验是否跳转成功
* 4. 上面的测试已经在登录成功方法统一进行测试了,只需要调用即可
* 5. 现在只需要测试编辑功能模块按钮和删除功能模块即可 分别进行测试
* 6. 先测试编辑功能模块,首先先定位查看全文按钮跳转到知识百宝库详情页先
* 7. 更新成功后验证跳转知识百宝库详情页是否成功思路: 这里注意最好不要用标题进行判断 因为知识百宝库标题可能重复 我们根据发布时间进行验证是否跳转成功
* 8. 跳转成功后 我们接着定位编辑元素,然后点击进入编辑页面,我们根据URL来判断是否跳转成功
* 9. 然后和知识百宝库发布页中的类似,先定位标题元素然后输入内容,接着再定位内容输入框元素等一系列操作和前面一样,这里就不过多解释了,具体实现看代码
* 测试编辑功能补充思路:
* 1. 先定位查看全文元素,如果点击查看全文,如果没有编辑按钮,我们先设置一个变量记录编辑这个文本 然后使用断言的NotEquals方法来进行判断,如果成功说明这个界面不是作者的文章不能进行编辑
* 2. 接着我们界面回退,找下一篇知识百宝库,重新定位查看全文元素,重复以上操作即可 注意下面的删除按钮也是同样的逻辑
*/
@Test
@Order(1)
public void checkEditFunction() throws InterruptedException, IOException, ParseException {
loginSu();
//显示等待 避免强制等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
try {
driver.navigate().refresh();
Thread.sleep(2000);
//定位到查看全文元素 并且点击
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
Thread.sleep(2000);
//接着先定位编辑元素 并且测试点击跳转界面是否符合预期
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(1)")).click();
//验证跳转是否成功 由于跳转后我们的URL中带有知识百宝库ID,我们使用显示等待进行判断是否包含detailUrl即可 可能稍微不够严谨
wait.until(ExpectedConditions.urlContains(updateUrl));
//屏幕最大化方便观察
driver.manage().window().maximize();
//接下来的操作和发布知识百宝库功能类似就不过多解释了
//显示等待 验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span")));
//定位内容输入框
WebElement element = 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 > span"));
Actions actions = new Actions(driver);
/**
* 1.先利用鼠标双击
* 2.再按快捷键ctrl+a全选内容 注意要分开操作 然后释放掉捷键ctrl
* 3.再根据键盘的DELETE删除内容 最后执行
* 注意按完快捷键后都要释放 顺序释放 否则会这个按键将会一直保持按住状态 跟我们人操作一样
*/
actions.doubleClick(element)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys(Keys.DELETE)
.sendKeys("单元测试工具JUnit...")
.perform();
Thread.sleep(2000);
//也可以上述操作结束后 先进行挪动再输入内容操作 但是最好是连贯操作
//actions.moveToElement(element).sendKeys("软件测试").perform();
//先定位到标题元素
WebElement titleElement = driver.findElement(By.cssSelector("body > div.content-edit > div.push > #title"));
//显示等待 测试标题是否符合预期
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.content-edit > div.push > #title")));
//使用鼠标输入标题 和上面操控键盘和鼠标操作类似
actions.doubleClick(titleElement)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.sendKeys(Keys.DELETE)
.sendKeys("自动化测试项目")
.perform();
Thread.sleep(2000);
//找到发布文章按钮 然后点击
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(2000);
/**
* 1.这里注意最好不要用标题进行判断 因为知识百宝库标题可能重复 我们根据发布时间进行验证是否跳转成功
* 2.但是一定会有误差 没事这更新时间差个几百毫秒没什么大事并且测试过有时候低至几十毫秒 不涉及到金钱方面就好说
*/
//提起创建好SimpleDateFormat对象 用于解析页面上的更新时间为Date对象 指定正确的日期格式
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//获取当前时间并计算容差范围(允许误差 比如500毫秒内)
long currentTimeMillis = System.currentTimeMillis();
// 500毫秒容差范围
long tolerance = 500;
//发布文章测试是否跳转回知识百宝库列表页 再测试是否发布成功
String currentUrl = driver.getCurrentUrl();
Assertions.assertEquals(listUrl, currentUrl);
Thread.sleep(2000);
String actualUpdateTime = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText();
//解析页面上的更新时间为Date对象
Date actualUpdateDate = sim.parse(actualUpdateTime);
System.out.println(Math.abs(currentTimeMillis - actualUpdateDate.getTime()));
//比较时间差 确保实际更新时间在预期时间的容差范围内
Assertions.assertTrue(Math.abs(currentTimeMillis - actualUpdateDate.getTime()) < tolerance, "更新时间不在预期范围内");
System.out.println("编辑功能模块测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("编辑功能模块测试失败:" + e.getMessage());
throw e;
}
}
测试效果
优化版测试知识百宝库详情页编辑功能代码
补充:编辑功能有个缺陷,如果点击查看元素没有编辑按钮的话,那就会抛异常了,所以这里提供思路后续有时间进行修改,删除功能已经实现好了
测试编辑功能补充思路:
- 我们先设置一个变量记录编辑文本,因为我们要点击的是编辑按钮。并且还要设置一个变量记录标题,然后通过定位所有的查看全文元素返回一个集合,点击查看全文,定位所有的按钮元素返回一个集合,不管有没有编辑按钮, 我们先使用流操作遍历按钮集合,通过
anyMatch()
方法检查是否有符合条件(是否存在编辑按钮)这个方法会有返回值(布尔类型),接着使用一个if判断,条件为刚才使用流操作返回的值(是否存在编辑按钮)和标题是否符合预取值,符合的话我们同样使用流操作过滤掉不符合条件的元素,这里我们就两个按钮一个编辑一个删除,接着我们调用OPtioanl类
的ifPresent()
方法去找符合的元素,找到我们就点击删除按钮 - 如果在上述if判断中按钮或者标题任意一个不符合条件,我们界面都会进行回退,找下一篇知识百宝库,重新定位查看全文元素,重复以上操作即可
- 其实还可以查看全文,定位作者名称的元素来一个个进行判断这个更简单
经过这么一补充时差变长了挺多的,还是那句话没有涉及到金钱或其他方面的都好说
测试编辑功能补充代码,具体实现如下
/**
* 优化版
*/
@Test
@Order(1)
public void checkEditBestFunction() throws InterruptedException, IOException, ParseException {
loginSu();
//显示等待 避免强制等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
try {
//预取编辑按钮文本
String anticipateButton = "编辑";
//预取标题
String anticipateTitle = "自动化测试项目";
//定位知识百宝库列表界所有查看全文元素 返回值为集合
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > a"));
for (WebElement element : checkElements) {
// 显示等待 页面加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container > div.right")));
//点击查看全文
element.click();
// 显示等待页面标题和操作按钮加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container > div.right > div > div.operating")));
//获取页面中所有的按钮元素 返回值为集合
List<WebElement> buttonElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > div.operating > button"));
//定位到当前知识百宝库标题 获取到当前实际标题
WebElement titleElement = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
String actualTitle = titleElement.getText();
//流操作 选出符合条件的 返回值为布尔类型 这里使用lambda表达式 这个button即buttonElements
boolean isEditButton = buttonElements.stream().anyMatch(button -> anticipateButton.equals(button.getText()));
//接着进行判断 预取标题和实际标题是否符合条件
if (isEditButton && anticipateTitle.equals(actualTitle)) {
//流操作
buttonElements.stream()
.filter(button -> anticipateButton.equals(button.getText()))
.findFirst()
//符合条件就点击
.ifPresent(button -> button.click());
//验证跳转是否成功 由于跳转后我们的URL中带有知识百宝库ID,我们使用显示等待进行判断是否包含detailUrl即可 可能稍微不够严谨
wait.until(ExpectedConditions.urlContains(updateUrl));
//接下来的操作和发布知识百宝库功能类似就不过多解释了
//显示等待 验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span")));
//定位内容输入框
WebElement contentElement = 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 > span"));
Actions actions = new Actions(driver);
/**
* 1.先利用鼠标双击
* 2.再按快捷键ctrl+a全选内容 注意要分开操作 然后释放掉捷键ctrl
* 3.再根据键盘的DELETE删除内容 最后执行
* 注意按完快捷键后都要释放 顺序释放 否则会这个按键将会一直保持按住状态 跟我们人操作一样
*/
actions.doubleClick(contentElement)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys(Keys.DELETE)
.sendKeys("自动化测试工具Selenium...")
.perform();
//也可以上述操作结束后 先进行挪动再输入内容操作 但是最好是连贯操作
//actions.moveToElement(element).sendKeys("软件测试").perform();
//先定位到标题元素
WebElement contentTitleElement = driver.findElement(By.cssSelector("body > div.content-edit > div.push > #title"));
//显示等待 测试标题是否符合预期
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.content-edit > div.push > #title")));
//使用鼠标输入标题 和上面操控键盘和鼠标操作类似
actions.doubleClick(contentTitleElement)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.sendKeys(Keys.DELETE)
.sendKeys("自动化测试项目");
//找到发布文章按钮 然后点击
driver.findElement(By.cssSelector("#submit")).click();
/**
* 1.这里注意最好不要用标题进行判断 因为知识百宝库标题可能重复 我们根据发布时间进行验证是否跳转成功
* 2.但是一定会有误差 没事这更新时间差个几百毫秒没什么大事并且测试过有时候低至几十毫秒 不涉及到金钱方面就好说
*/
//提起创建好SimpleDateFormat对象 用于解析页面上的更新时间为Date对象 指定正确的日期格式
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//获取当前时间戳 并计算容差范围(允许误差 比如10000000毫秒内)
long currentTimeMillis = System.currentTimeMillis();
// 10000000毫秒容差范围
long tolerance = 10000000;
//发布文章测试是否跳转回知识百宝库列表页 再测试是否发布成功
wait.until(ExpectedConditions.urlMatches(listUrl));
//String currentUrl = driver.getCurrentUrl();
//Assertions.assertEquals(listUrl, currentUrl);
//获取预期更新时间
//String actualUpdateTime = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText();
//获取预期更新时间 解析页面上的更新时间为Date对象
Date actualUpdateDate = sim.parse(driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText());
System.out.println("时间差 (毫秒):" + Math.abs(currentTimeMillis - actualUpdateDate.getTime()));
//比较时间差 确保实际更新时间在预期时间的容差范围内
Assertions.assertTrue(Math.abs(currentTimeMillis - actualUpdateDate.getTime()) < tolerance, "更新时间不在预期范围内");
System.out.println("编辑功能模块测试成功");
//更新成功 直接退出循环
break;
} else {
//不符合条件 回退接着找
driver.navigate().back();
}
}
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("编辑功能模块测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下,记录一下最后停留在哪个界面
删除功能模块测试
思路:
- 首先还是调用登录成功统一方法
- 定位查看全文元素 然后跳转至知识百宝库详情页 并且验证是否跳转成功
- 定位删除按钮 点击删除按钮 然后跳转至知识百宝库列表页
- 先验证是否跳转至知识百宝库列表页 再验证是否删除成功
- 验证是否跳转至知识百宝库列表页简单
- 测试删除功能模块思路:本想通过定位知识百宝库列表页的下一篇知识百宝库的标题和时间来验证 但是如果出现同一标题同一时间呢?所以根据通过查看下一篇跳转的URL来进行验证 所以删除知识百宝库前先记录当前要删除知识百宝库的URL 然后进行比较即可
测试删除功能补充思路:
- 我们先设置一个变量记录删除文本,因为我们要点击的是删除按钮。并且还要设置一个变量记录标题,然后通过定位所有的查看全文元素返回一个集合,点击查看全文,定位所有的按钮元素返回一个集合,不管有没有删除按钮, 我们先使用流操作遍历按钮集合,通过
anyMatch()
方法检查是否有符合条件(是否存在编辑按钮)这个方法会有返回值(布尔类型),接着使用一个if判断,条件为刚才使用流操作返回的值(是否存在编辑按钮)和标题是否符合预取值,符合的话我们同样使用流操作过滤掉不符合条件的元素,这里我们就两个按钮一个编辑一个删除,接着我们调用OPtioanl类
的ifPresent()
方法去找符合的元素,找到我们就点击删除按钮 - 如果在上述if判断中按钮或者标题任意一个不符合条件,我们界面都会进行回退,找下一篇知识百宝库,重新定位查看全文元素,重复以上操作即可
- 其实还可以查看全文,定位作者名称的元素来一个个进行判断这个更简单
首先展示我一开始写的测试知识百宝库详情页删除功能的代码,仔细观察会有很多冗余的地方并且不优雅代码臃肿,但是还是能实现的,需要注意的是少用强制等待sleep()方法,之前提过耗费测试时间等,主要用于调试观察,所以我才这么写
/**
* 测试删除功能模块是否正常
* 1.首先还是调用登录成功统一方法
* 2.定位查看全文元素 然后跳转至知识百宝库详情页 并且验证是否跳转成功
* 3.定位删除按钮 点击删除按钮 然后跳转至知识百宝库列表页
* 4.先验证是否跳转至知识百宝库列表页 再验证是否删除成功
* 6.验证是否跳转至知识百宝库列表页简单
* 5.验证是否删除成功 本想通过定位知识百宝库列表页的下一篇知识百宝库的标题和时间来验证 但是如果出现同一标题同一时间呢
* 所以根据通过查看下一篇跳转的URL来进行验证 所以删除知识百宝库前先记录当前要删除知识百宝库的URL 然后进行比较即可
*/
@Test
@Order(2)
public void checkDeleteFunction() throws InterruptedException, IOException {
loginSu();
Thread.sleep(2000);
try {
driver.navigate().refresh();
//预期的删除按钮
String anticipateButton = "删除";
//预期要删除的标题
String anticipateTitle = "自动化实战";
//获取界面中查看全文元素
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right>div>a"));
Thread.sleep(2000);
for (WebElement element : checkElements) {
boolean flg = false;
//点击查看全文元素
element.click();
Thread.sleep(2000);
//点击查看全文后获取界面中编辑按钮和删除按钮元素 然后进行判断
List<WebElement> buttonElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > div.operating > button"));
String actualTitle = null;
for (WebElement buttonElement : buttonElements) {
if (buttonElement == null) {
break;
}
String text = buttonElement.getText();
//如果是删除按钮 置为true
if (anticipateButton.equals(text)) {
flg = true;
}
//定位到标题元素
WebElement webElement = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
//获取到标题文本
actualTitle = webElement.getText();
//找到删除按钮后并且验证是否标题是否符合预取 点击删除并且直接退出循环
if (flg && anticipateTitle.equals(actualTitle)) {
//满足条件点击删除按钮
buttonElement.click();
break;
}
}
Thread.sleep(2000);
//不管是否删除是否成功都会跳转至知识百宝库列表页
String currentUrl = driver.getCurrentUrl();
//找到删除按钮后 验证是否跳转至知识百宝库列表页 并且验证标题是否符合预期
if (flg && listUrl.equals(currentUrl) && anticipateTitle.equals(actualTitle)) {
break;
} else {
driver.navigate().back();
}
}
Thread.sleep(2000);
System.out.println("删除功能模块测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("删除功能模块测试失败:" + e.getMessage());
throw e;
}
}
测试效果
优化版测试知识百宝库详情页删除功能代码
具体实现如下,注意这是单个进行测试并不代表没有问题,后面编辑和删除两者联合进行测试可能就会出现问题
/**
* 优化版
*/
@Test
@Order(2)
public void checkDeleteBestFunction() throws InterruptedException, IOException {
loginSu();
//使用显式等待代替Thread.sleep 避免使用固定的等待时间,这样测试更加稳定
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
driver.navigate().refresh();
//预取删除按钮
String anticipateButton = "删除";
//预取标题
String anticipateTitle = "自动化实战";
// 获取界面中查看全文元素
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right>div>a"));
for (WebElement element : checkElements) {
element.click();
// 等待页面标题和操作按钮加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container > div.right > div > div.operating")));
// 获取页面上的按钮和标题
List<WebElement> buttonElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > div.operating > button"));
WebElement titleElement = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
String actualTitle = titleElement.getText();
/**
* 流操作
* 1.将List<WebElement>集合转为数据流 帮助我们遍历集合并且处理 省去使用forEach 代码更加优雅简洁
* 2.anyMatch()方法遍历流中的每一个元素 检查任意一个元素是否符合我们给定的条件(也就是预取值) 找到返回true 反之false
* 3.anyMatch()方法使用的是lambda表达式 它定义了检查每个 button(即 WebElement)的文本内容是否等于 anticipateButton
* 4 简单说 就是它会遍历每个 button 检查它的文本是否是 "删除"
* 整个逻辑的意思是: 检查按钮集合中的任意一个按钮 它的文本是否等于“删除” 如果找到这样的按钮 属性会被设置为 true 反之false
*/
boolean isDeleteButtonPresent = buttonElements.stream().anyMatch(button -> anticipateButton.equals(button.getText()));
/**
* 流操作和上面一样 将List<WebElement>集合转为数据流 帮助我们遍历集合并且处理 省去使用forEach 代码更加优雅简洁
* 1.首先filter()方法先过滤掉不符合条件的元素 留下符合条件的元素 也就是保留文本为删除的按钮 也是使用lambda表达式 和上面差不多
* 2.接着findFirst()方法属于Optional类中方法 从过滤后的流中取出第一个符合条件的元素(如果有多个符合条件的元素 它只会返回第一个 这里我们也只有编辑和删除按钮) 这个方法会返回一个Optional类型
* 3.最后通过Optional类调用ifPresent()方法如果找到了符合条件的元素 (即 findFirst() 成功返回了一个值)那么就执行 ifPresent() 里面的逻辑
* 4.WebElement::click是简写 叫做方法引用 实际上等同于一个 Lambda 表达式,可以直接调用指定对象的方法 表示点击找到的按钮
* 整个逻辑的意思是: 找到第一个文本为删除的按钮 并点击它 如果没有找到 这段代码就不会执行任何操作
*/
if (isDeleteButtonPresent && anticipateTitle.equals(actualTitle)) {
buttonElements.stream()
.filter(button -> anticipateButton.equals(button.getText()))
.findFirst()
.ifPresent(WebElement::click);
//.ifPresent(button -> button.click());
break;
}
//不是目标文章就回退
driver.navigate().back();
}
System.out.println("删除功能模块测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("删除功能模块测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下,记录一下最后停留在知识百宝库列表页,这里的测试效果可能有点快
编辑功能模块和删除功能模块联合测试
在上面我们已经分别对编辑功能模块和删除功能模块进行测试了,都是没问题的,现在联合起来进行测试
结合上面我们记录测试用例最后停留在哪个界面,我在删除模块进行了稍微的修改,保证联合测试没有问题。完整代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DetailPageTestCase extends InitAndEnd {
public static String listUrl = "http://106.54.52.89:9999/blog_list.html";
public static String updateUrl = "http://106.54.52.89:9999/blog_update.html";
// public DetailPageTestCase() {
// super(url);
// }
/**
* 优化版
*/
@Test
@Order(1)
public void checkEditBestFunction() throws InterruptedException, IOException, ParseException {
loginSu();
//显示等待 避免强制等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
try {
//预取编辑按钮文本
String anticipateButton = "编辑";
//预取标题
String anticipateTitle = "自动化测试项目";
//定位知识百宝库列表界所有查看全文元素 返回值为集合
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > a"));
for (WebElement element : checkElements) {
// 显示等待 页面加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container > div.right")));
//点击查看全文
element.click();
// 显示等待页面标题和操作按钮加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container > div.right > div > div.operating")));
//获取页面中所有的按钮元素 返回值为集合
List<WebElement> buttonElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > div.operating > button"));
//定位到当前知识百宝库标题 获取到当前实际标题
WebElement titleElement = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
String actualTitle = titleElement.getText();
//流操作 选出符合条件的 返回值为布尔类型 这里使用lambda表达式 这个button即buttonElements
boolean isEditButton = buttonElements.stream().anyMatch(button -> anticipateButton.equals(button.getText()));
//接着进行判断 预取标题和实际标题是否符合条件
if (isEditButton && anticipateTitle.equals(actualTitle)) {
//流操作
buttonElements.stream()
.filter(button -> anticipateButton.equals(button.getText()))
.findFirst()
//符合条件就点击
.ifPresent(button -> button.click());
//验证跳转是否成功 由于跳转后我们的URL中带有知识百宝库ID,我们使用显示等待进行判断是否包含detailUrl即可 可能稍微不够严谨
wait.until(ExpectedConditions.urlContains(updateUrl));
//接下来的操作和发布知识百宝库功能类似就不过多解释了
//显示等待 验证内容输入框是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre > span")));
//定位内容输入框
WebElement contentElement = 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 > span"));
Actions actions = new Actions(driver);
/**
* 1.先利用鼠标双击
* 2.再按快捷键ctrl+a全选内容 注意要分开操作 然后释放掉捷键ctrl
* 3.再根据键盘的DELETE删除内容 最后执行
* 注意按完快捷键后都要释放 顺序释放 否则会这个按键将会一直保持按住状态 跟我们人操作一样
*/
actions.doubleClick(contentElement)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.keyUp(Keys.CONTROL)
.sendKeys(Keys.DELETE)
.sendKeys("自动化测试工具Selenium...")
.perform();
//也可以上述操作结束后 先进行挪动再输入内容操作 但是最好是连贯操作
//actions.moveToElement(element).sendKeys("软件测试").perform();
//先定位到标题元素
WebElement contentTitleElement = driver.findElement(By.cssSelector("body > div.content-edit > div.push > #title"));
//显示等待 测试标题是否符合预期
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.content-edit > div.push > #title")));
//使用鼠标输入标题 和上面操控键盘和鼠标操作类似
actions.doubleClick(contentTitleElement)
.keyDown(Keys.CONTROL)
.sendKeys("a")
.sendKeys(Keys.DELETE)
.sendKeys("自动化测试项目");
//找到发布文章按钮 然后点击
driver.findElement(By.cssSelector("#submit")).click();
/**
* 1.这里注意最好不要用标题进行判断 因为知识百宝库标题可能重复 我们根据发布时间进行验证是否跳转成功
* 2.但是一定会有误差 没事这更新时间差个几百毫秒没什么大事并且测试过有时候低至几十毫秒 不涉及到金钱方面就好说
*/
//提起创建好SimpleDateFormat对象 用于解析页面上的更新时间为Date对象 指定正确的日期格式
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//获取当前时间戳 并计算容差范围(允许误差 比如10000000毫秒内)
long currentTimeMillis = System.currentTimeMillis();
// 10000000毫秒容差范围
long tolerance = 10000000;
//发布文章测试是否跳转回知识百宝库列表页 再测试是否发布成功
wait.until(ExpectedConditions.urlMatches(listUrl));
//String currentUrl = driver.getCurrentUrl();
//Assertions.assertEquals(listUrl, currentUrl);
//获取预期更新时间
//String actualUpdateTime = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText();
//获取预期更新时间 解析页面上的更新时间为Date对象
Date actualUpdateDate = sim.parse(driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText());
System.out.println("时间差 (毫秒):" + Math.abs(currentTimeMillis - actualUpdateDate.getTime()));
//比较时间差 确保实际更新时间在预期时间的容差范围内
Assertions.assertTrue(Math.abs(currentTimeMillis - actualUpdateDate.getTime()) < tolerance, "更新时间不在预期范围内");
System.out.println("编辑功能模块测试成功");
//更新成功 直接退出循环
break;
} else {
//不符合条件 回退接着找
driver.navigate().back();
}
}
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("编辑功能模块测试失败:" + e.getMessage());
throw e;
}
}
/**
* 优化版
*/
@Test
@Order(2)
public void checkDeleteBestFunction() throws IOException, InterruptedException {
// loginSu();
//使用显式等待代替Thread.sleep 避免使用固定的等待时间 这样测试更加稳定
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
try {
driver.navigate().refresh();
//预取删除按钮
String anticipateButton = "删除";
//预取标题
String anticipateTitle = "自动化实战";
// 获取界面中查看全文元素
List<WebElement> checkElements = driver.findElements(By.cssSelector("body > div.container > div.right>div>a"));
for (WebElement element : checkElements) {
element.click();
// 等待页面标题和操作按钮加载完成
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body > div.container > div.right > div > div.operating")));
// 获取页面上的按钮和标题
List<WebElement> buttonElements = driver.findElements(By.cssSelector("body > div.container > div.right > div > div.operating > button"));
WebElement titleElement = driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
String actualTitle = titleElement.getText();
/**
* 流操作
* 1.将List<WebElement>集合转为数据流 帮助我们遍历集合并且处理 省去使用forEach 代码更加优雅简洁
* 2.anyMatch()方法遍历流中的每一个元素 检查任意一个元素是否符合我们给定的条件(也就是预取值) 找到返回true 反之false
* 3.anyMatch()方法使用的是lambda表达式 它定义了检查每个 button(即 buttonElements)的文本内容是否等于 anticipateButton
* 4 简单说 就是它会遍历每个 button 检查它的文本是否是 "删除"
* 整个逻辑的意思是: 检查按钮集合中的任意一个按钮 它的文本是否等于“删除” 如果找到这样的按钮 属性会被设置为 true 反之false
*/
boolean isDeleteButtonPresent = buttonElements.stream().anyMatch(button -> anticipateButton.equals(button.getText()));
/**
* 流操作和上面一样 将List<WebElement>集合转为数据流 帮助我们遍历集合并且处理 省去使用forEach 代码更加优雅简洁
* 1.首先filter()方法先过滤掉不符合条件的元素 留下符合条件的元素 也就是保留文本为删除的按钮 也是使用lambda表达式 和上面差不多
* 2.接着findFirst()方法属于Optional类中方法 从过滤后的流中取出第一个符合条件的元素(如果有多个符合条件的元素 它只会返回第一个 这里我们也只有编辑和删除按钮) 这个方法会返回一个Optional类型
* 3.最后通过Optional类调用ifPresent()方法如果找到了符合条件的元素 (即 findFirst() 成功返回了一个值)那么就执行 ifPresent() 里面的逻辑
* 4.WebElement::click是简写 叫做方法引用 实际上等同于一个 Lambda 表达式,可以直接调用指定对象的方法 表示点击找到的按钮
* 整个逻辑的意思是: 找到第一个文本为删除的按钮 并点击它 如果没有找到 这段代码就不会执行任何操作
*/
if (isDeleteButtonPresent && anticipateTitle.equals(actualTitle)) {
buttonElements.stream()
.filter(button -> anticipateButton.equals(button.getText()))
.findFirst()
.ifPresent(WebElement::click);
//.ifPresent(button -> button.click());
break;
}
//不是目标文章就回退
driver.navigate().back();
}
System.out.println("删除功能模块测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("删除功能模块测试失败:" + e.getMessage());
throw e;
}
}
}
测试效果如下,测试前我把刚才编辑的和删除的知识百宝库都还原了,所以不要认为我之前测试的有问题。并且中间可能有停顿也是我设置的等待,为了方便看一下效果,不然自动化测试唰一下就结束了
退出功能模块测试
在测试包下创建一个java文件用于退出功能模块的测试,具体细节看如下代码,退出功能模块的相关测试都在这个类下
登录成功后跳转到知识百宝库列表页点击退出模块测试
思路:
- 首先调用登录成功方法 因为主页在没有登录情况的时候没有退出按钮
- 登录成功后跳转到知识百宝库列表页 通过定位页面元素验证是否跳转到知识百宝库列表页 点击退出测试是否成功跳转回登录界面
- 然后点击退出验证是否成功退出,如何验证呢?点击退出后会跳转回登录界面通过这个界面的元素进行验证,例如点击主页按钮跳转到知识百宝库列表页
- 走到这说明上面测试都是成功的 再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
- 并且再测试同用户名或密码登录是否能再次登录成功
具体代码实现如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ExitLoginTestCase extends InitAndEnd {
// public ExitLoginTestCase() {
// super(url);
// }
/**
* 1.首先调用登录成功方法 因为主页在没有登录情况的时候没有退出按钮
* 2.登录成功后跳转到知识百宝库列表页 通过定位页面元素验证是否跳转到知识百宝库列表页 点击退出测试是否成功跳转回登录界面
* 3.然后点击退出验证是否成功退出,如何验证呢?点击退出后会跳转回登录界面通过这个界面的元素进行验证,例如点击主页按钮跳转到知识百宝库列表页
* 4.走到这说明上面测试都是成功的 再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
* 5.并且再测试同用户名或密码登录是否能再次登录成功
*/
@Test
@Order(1)
public void ExitByLoginList() throws InterruptedException, IOException {
loginSu();
// driver.get(url);
try {
//强制等待 方便观察
Thread.sleep(1000);
driver.navigate().refresh();
//点击退出 就会跳转会登录界面
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
Thread.sleep(1000);
//测试点击退出验证是否成功跳转回登录界面 通过登录界面 主页按钮跳转到知识百宝库列表页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
//走到这说明上面测试都是成功的
//再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//再测试同用户名或密码登录是否能再次登录成功
driver.findElement(By.cssSelector("#username")).sendKeys("Nan");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//此时点击退出 测试退出模块正式通过
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
//最后停留在登录界面
Thread.sleep(1000);
System.out.println("登录成功后跳转到知识百宝库列表页点击退出测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录成功后跳转到知识百宝库列表页点击退出测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下,记录一下最后停留在登录界面
登录成功后处于知识百宝库详情页点击退出并且再次测试退出是否成功
思路:上面测试了在登录成功后处于知识百宝库详情页点击退出是否成功,为了严谨点再测试一下知识百宝库详情页是否能退出,方法和上面差不多。具体代码实现如下
/**
* 上面测试了在登录成功后处于知识百宝库列表页点击退出是否成功,为了严谨点再测试一下知识百宝库详情页是否能退出,方法和上面差不多
*/
@Test
@Order(2)
public void ExitByLoginDetail() throws InterruptedException, IOException {
loginSu();
// driver.get(url);
try {
//强制等待 方便观察
Thread.sleep(1000);
driver.navigate().refresh();
//点击查看全文 跳转到知识百宝库详情页
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
Thread.sleep(1000);
//点击退出 就会跳转会登录界面
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
Thread.sleep(1000);
//测试点击退出验证是否成功跳转回登录界面 通过登录界面 主页按钮跳转到知识百宝库列表页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
//走到这说明上面测试都是成功的
//再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//再测试同用户名或密码登录是否能再次登录成功
driver.findElement(By.cssSelector("#username")).sendKeys("Nan");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//此时点击退出 测试退出模块正式通过
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
//最后停留在登录界面
Thread.sleep(2000);
System.out.println("登录成功后再去知识百宝库详情页点击退出测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录成功后再去知识百宝库详情页点击退出测试失败:" + e.getMessage());
throw e;
}
}
测试效果如下,记录一下最后也是停留在登录界面了
补充:这里就不接着对知识百宝库发布页/编辑页、知识百宝库更新页的退出功能模块测试了,还是那句话软件测试不可穷举
退出模块联合测试
在上面我们已经分别登录成功情况下跳转知识百宝库列表页和知识百宝库详情页点击退出功能模块进行测试,都是没问题的,现在联合起来进行测试
具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ExitLoginTestCase extends InitAndEnd {
// public ExitLoginTestCase() {
// super(url);
// }
/**
* 1.首先调用登录成功方法 因为主页在没有登录情况的时候没有退出按钮
* 2.登录成功后跳转到知识百宝库列表页 通过定位页面元素验证是否跳转到知识百宝库列表页 点击退出测试是否成功跳转回登录界面
* 3.然后点击退出验证是否成功退出,如何验证呢?点击退出后会跳转回登录界面通过这个界面的元素进行验证,例如点击主页按钮跳转到知识百宝库列表页
* 4.走到这说明上面测试都是成功的 再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
* 5.并且再测试同用户名或密码登录是否能再次登录成功
*/
@Test
@Order(1)
public void ExitByLoginList() throws InterruptedException, IOException {
loginSu();
// driver.get(url);
try {
//强制等待 方便观察
Thread.sleep(1000);
driver.navigate().refresh();
//点击退出 就会跳转会登录界面
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
Thread.sleep(1000);
//测试点击退出验证是否成功跳转回登录界面 通过登录界面 主页按钮跳转到知识百宝库列表页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
//走到这说明上面测试都是成功的
//再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//再测试同用户名或密码登录是否能再次登录成功
driver.findElement(By.cssSelector("#username")).sendKeys("Nan");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//此时点击退出 测试退出模块正式通过
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
//最后停留在登录界面
Thread.sleep(1000);
System.out.println("登录成功后跳转到知识百宝库列表页点击退出测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录成功后跳转到知识百宝库列表页点击退出测试失败:" + e.getMessage());
throw e;
}
}
/**
* 上面测试了在登录成功后处于知识百宝库列表页点击退出是否成功,为了严谨点再测试一下知识百宝库详情页是否能退出,方法和上面差不多
*/
@Test
@Order(2)
public void ExitByLoginDetail() throws InterruptedException, IOException {
loginSu();
// driver.get(url);
try {
//强制等待 方便观察
Thread.sleep(1000);
driver.navigate().refresh();
//点击查看全文 跳转到知识百宝库详情页
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
Thread.sleep(1000);
//点击退出 就会跳转会登录界面
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
Thread.sleep(1000);
//测试点击退出验证是否成功跳转回登录界面 通过登录界面 主页按钮跳转到知识百宝库列表页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
//走到这说明上面测试都是成功的
//再次通过登录界面主页按钮跳转到知识百宝库列表页 是处于未登录状态 我们需要对警告弹窗进行处理 这个测试才算完成
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//再测试同用户名或密码登录是否能再次登录成功
driver.findElement(By.cssSelector("#username")).sendKeys("Nan");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//此时点击退出 测试退出模块正式通过
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
//最后停留在登录界面
Thread.sleep(2000);
System.out.println("登录成功后再去知识百宝库详情页点击退出测试成功");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录成功后再去知识百宝库详情页点击退出测试失败:" + e.getMessage());
throw e;
}
}
}
测试效果如下,上面我们分别记录了两个测试用例最后停留在哪个界面,结合这个我们可能需要稍微修改一下代码,也可能不需要
使用测试套件统一测试所有类的测试用例
在测试包下创建一个java文件用于使用测试套件统一测试所有类的测试用例,具体细节看如下代码
测试所有用例流程:
- 测试未登录模块所有测试用例 一键测试完成 最终停留在登录界面
- 测试注册模块所有测试用例 一键测试完成 最终停留在登录界面
- 测试登录模块所有测试用例 一键测试完成 最终停留在知识百宝库列表页
- 测试登录后知识百宝库列表页所有测试用例 一键测试完成 最终停留在知识百宝库列表页
- 测试发布知识百宝库模块所有测试用例 一键测试完成 最终停留在知识百宝库列表页
- 测试知识百宝库详情页模编辑和删除模块所有测试用例 一键测试完成 最终停留在知识百宝库列表页
- 测试退出功能模块所有用例 一键测试完成 最终停留在登录界面
上述中每个类注意测试顺序 并且注意每个测试类测试完成后所处于的界面是否影响下一个测试类的测试用例!!!
注意:所有测试类联合测试 联合测试顺序结合每个类的具体代码来定 先这样决定
以下是测试套件的代码实现,也就是对整个系统进行测试的代码都将通过整个测试类执行
@Suite
@SelectClasses({PageByNoLoginTestCase.class,
RegistryPageTestCase.class,
LoginPageTestCase.class,
ListPageTestCase.class,
EditPageTestCase.class,
DetailPageTestCase.class,
ExitLoginTestCase.class})
public class AllClassTestCases {
}
问题:在上述代码可能是由于 WebDriver
实例在第一个测试类结束后没有正确复用或释放,导致在第二个测试类中无法正确初始化或者使用已有的 WebDriver
实例
方法 1:确保每个类的测试都能正常初始化WebDriver
这个方法是最常用的,适合在所有测试类中共享同一个
WebDriver
实例。如果你只需要一个WebDriver
实例在多个测试类中复用,那么确保WebDriver
的正确初始化和关闭就足够了
主要就是在关闭驱动对象的时候再重新对驱动对象初始化**,这个方法在其他类中不需要再调用driver.get(url),因为所有测试类中共享同一个 WebDriver
实例,具体代码如下
public class InitAndEnd {
public static WebDriver driver = null;
/**
* 创建驱动管理 创建驱动对象
*/
@BeforeAll
public static void initWebDriver() {
if (driver == null || ((ChromeDriver) driver).getSessionId() == null) {
//使用驱动管理 创建驱动程序对象 打开浏览器
WebDriverManager.chromedriver().setup();
//增加浏览器配置
ChromeOptions options = new ChromeOptions();
//强制运行访问所有链接
options.addArguments("--remote-allow-origins=*");
//设置无头模式 就是不显示浏览器进行测试
// options.addArguments("-headless");
//设置浏览器加载配置 默认值就是这个
options.setPageLoadStrategy(PageLoadStrategy.NORMAL);
driver = new ChromeDriver(options);
// driver.get("http://106.54.52.89:9999/blog_login.html");
//隐式等待 全局等待 只要是关于查找元素的操作的话
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
}
}
/**
* 关闭驱动对象
*/
@AfterAll
public static void closeWebDriver() {
if (driver != null) {
driver.quit();
driver = null; // 清空driver 确保后续测试用例能够重新初始化
}
}
这里就展示一个登录模块测试类的代码,其他测试类的代码都差不多,但是有些地方需要注意,测试的时候才会发现
//通过这个注解 方法执行顺序会根据@Order注解来执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginPageTestCase extends InitAndEnd {
public static String url = "http://106.54.52.89:9999/blog_login.html";
// public LoginPageTestCase() {
// //先帮子类初始化
// //也就调用了子类构造方法中driver.get(url);
// super(url);
// }
/**
* 检查页面元素是否加载成功
*/
@Test
@Order(1)
public void loginPageRight() throws InterruptedException, IOException {
driver.get(url);
//放大好观察
driver.manage().window().maximize();
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("body > div.nav > span"));
driver.findElement(By.cssSelector("body > div.container-login > div"));
System.out.println("页面元素加载测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("页面元素加载测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录失败情况 会有弹窗显示
* 1.先检查用户名或密码为空
* 2.用户名或密码错误
* 3.长度<3(不包括等于) 或 长度>16(不包括等于) 用户名或密码长度不正确
*/
@Test
@Order(2)
public void loginFail() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
//确保没有内容
driver.navigate().refresh();
//上面登录后需要退出清空我们的token
// driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
// //放大好观察
// driver.manage().window().maximize();
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("");
driver.findElement(By.cssSelector("div > #password")).sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
System.out.println("用户名或密码为空测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码为空测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("1234");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
System.out.println("用户名或密码错误测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码错误测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfgh");
driver.findElement(By.cssSelector("div > #password")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait2 = new WebDriverWait(driver, Duration.ofSeconds(5));
wait2.until(ExpectedConditions.alertIsPresent());
Alert alert2 = driver.switchTo().alert();
Thread.sleep(1000);
alert2.accept();
System.out.println("用户名或密码长度不正确测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码长度不正确测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录成功情况
*/
@Test
@Order(3)
public void loginSuccess() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
// //放大好观察
// driver.manage().window().maximize();
// String loginUrl = driver.getCurrentUrl();
//确保没有内容
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//测试当前url是否为知识百宝库列表页
// String currentHandle = driver.getWindowHandle();
// Set<String> windowHandles = driver.getWindowHandles();
// for (String handle : windowHandles) {
// if (handle != currentHandle) {
// //如果不是的话就执行切换为当前句柄
// driver.switchTo().window(handle);
// }
// }
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.titleIs("知识百宝库列表页"));
String title = driver.getTitle();
try {
//获取当前标题通过断言来判断
assert title.equals("知识百宝库列表页");
// Assertions.assertEquals(title, "知识百宝库列表页");
System.out.println("登录功能测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录功能测试失败:" + e.getMessage());
throw e;
}
// Thread.sleep(3000);
// String currentUrl = driver.getCurrentUrl();
// //测试当前url是否为知识百宝库列表页
// if (!Objects.equals(loginUrl, currentUrl)) {
// System.out.println("测试通过");
// System.out.println(currentUrl);
// } else {
// System.out.println("测试失败");
// System.out.println(currentUrl);
// }
}
}
方法 2:为每个测试类创建新的WebDriver实例
如果你希望每个测试类独立运行,且不希望不同测试类之间共享
WebDriver
状态(例如,一个类的测试可能改变浏览器的状态),目标是为每个测试类提供一个独立的WebDriver
实例,避免第一个测试类对WebDriver
的状态影响后续的测试类。你可以使用这个方法,在每个测试类中都创建新的WebDriver
实例,通过在每个测试类启动时重新初始化WebDriver
来实现,而不是每个测试用例共享同一个实例
具体实现就是对InitAndEnd
类中的**initWebDriver()
方法和closeWebDriver()
方法头上的注解分别改为@BeforeEach
和@AfterEach
,这样每个测试用例执行时都会单独创建一个新的WebDriver
实例,还要注意去掉static!!!
public class InitAndEnd {
public WebDriver driver;
/**
* 创建驱动管理 创建驱动对象
*/
@BeforeEach
public void initWebDriver() {
// 每次测试用例运行时都创建一个新的WebDriver实例
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
}
/**
* 关闭驱动对象
*/
@AfterEach
public void closeWebDriver() {
if (driver != null) {
driver.quit();
}
}
/**
* 屏幕截图
*/
public void getScreenShot(String str) throws IOException {
// 截图方法保持不变
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sim2 = new SimpleDateFormat("HH-mm-ss-SS");
String dirTime = sim1.format(System.currentTimeMillis());
String fileTime = sim2.format(System.currentTimeMillis());
String fileName = "src/test/image/" + dirTime + "/" + str + "-" + fileTime + ".png ";
File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File(fileName));
}
}
这里就展示一个登录模块测试类的代码,其他测试类的代码都差不多,但是有些地方需要注意,每个测试用例执行的时候都需要显示调用一下driver.get(url)
可以确保每个测试方法的页面状态正确
//通过这个注解 方法执行顺序会根据@Order注解来执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginPageTestCase extends InitAndEnd {
public static String url = "http://106.54.52.89:9999/blog_login.html";
// public LoginPageTestCase() {
// //先帮子类初始化
// //也就调用了子类构造方法中driver.get(url);
// super(url);
// }
/**
* 检查页面元素是否加载成功
*/
@Test
@Order(1)
public void loginPageRight() throws InterruptedException, IOException {
driver.get(url);
//放大好观察
driver.manage().window().maximize();
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("body > div.nav > span"));
driver.findElement(By.cssSelector("body > div.container-login > div"));
System.out.println("页面元素加载测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("页面元素加载测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录失败情况 会有弹窗显示
* 1.先检查用户名或密码为空
* 2.用户名或密码错误
* 3.长度<3(不包括等于) 或 长度>16(不包括等于) 用户名或密码长度不正确
*/
@Test
@Order(2)
public void loginFail() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
//确保没有内容
driver.navigate().refresh();
//上面登录后需要退出清空我们的token
// driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
// //放大好观察
// driver.manage().window().maximize();
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("");
driver.findElement(By.cssSelector("div > #password")).sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
System.out.println("用户名或密码为空测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码为空测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("1234");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
System.out.println("用户名或密码错误测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码错误测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfgh");
driver.findElement(By.cssSelector("div > #password")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait2 = new WebDriverWait(driver, Duration.ofSeconds(5));
wait2.until(ExpectedConditions.alertIsPresent());
Alert alert2 = driver.switchTo().alert();
Thread.sleep(1000);
alert2.accept();
System.out.println("用户名或密码长度不正确测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码长度不正确测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录成功情况
*/
@Test
@Order(3)
public void loginSuccess() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
// //放大好观察
// driver.manage().window().maximize();
// String loginUrl = driver.getCurrentUrl();
//确保没有内容
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//测试当前url是否为知识百宝库列表页
// String currentHandle = driver.getWindowHandle();
// Set<String> windowHandles = driver.getWindowHandles();
// for (String handle : windowHandles) {
// if (handle != currentHandle) {
// //如果不是的话就执行切换为当前句柄
// driver.switchTo().window(handle);
// }
// }
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.titleIs("知识百宝库列表页"));
String title = driver.getTitle();
try {
//获取当前标题通过断言来判断
assert title.equals("知识百宝库列表页");
// Assertions.assertEquals(title, "知识百宝库列表页");
System.out.println("登录功能测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录功能测试失败:" + e.getMessage());
throw e;
}
// Thread.sleep(3000);
// String currentUrl = driver.getCurrentUrl();
// //测试当前url是否为知识百宝库列表页
// if (!Objects.equals(loginUrl, currentUrl)) {
// System.out.println("测试通过");
// System.out.println(currentUrl);
// } else {
// System.out.println("测试失败");
// System.out.println(currentUrl);
// }
}
}
补充:我使用测试套件根据方法三进行测试的时候,为什么我的loginSuccess()方法不加driver.get(url);方法不会报错,但是loginFail()方法不加driver.get(url);方法会报错呢?**以下是我咨询GTP4o的答案,这个错误比较简单,当时犯傻了
方法2总结:
loginSuccess()
不报错是因为刷新了页面,保持了正确的状态loginFail()
报错是因为页面状态发生了变化,页面已经不在登录页- 显式调用
driver.get(url)
可以确保每个测试方法的页面状态正确,避免类似的报错。
方法 3:通过 @TestInstance
注解控制WebDriver生命周期
使用
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
注解来为每个测试类创建一个新的WebDriver
实例。这个方法是为了解决WebDriver
生命周期问题的另一种选择。它让每个测试类拥有自己的WebDriver
实例,类似于方法 2,但更加自动化,不需要你手动管理WebDriver
的生命周期
只需要在每个测试类上加一个@TestInstance(TestInstance.Lifecycle.PER_CLASS)
注解即可,下面就展示一个类添加这个注解的代码
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
//通过这个注解 方法执行顺序会根据@Order注解来执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginPageTestCase extends InitAndEnd {
这里就展示一个登录模块测试类的代码,其他测试类的代码都差不多,这里需要注意,由于这个注解是为每个测试类创建一个新的WebDriver
实例,可能导致有些测试用例可能会报错,所以测试的时候要注意,具体代码如下
//为每个测试类创建一个新的WebDriver实例
//这样可以确保每个测试类在类级别上有自己的WebDriver实例
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
//通过这个注解 方法执行顺序会根据@Order注解来执行
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginPageTestCase extends InitAndEnd {
public static String url = "http://106.54.52.89:9999/blog_login.html";
// public LoginPageTestCase() {
// //先帮子类初始化
// //也就调用了子类构造方法中driver.get(url);
// super(url);
// }
/**
* 检查页面元素是否加载成功
*/
@Test
@Order(1)
public void loginPageRight() throws InterruptedException, IOException {
driver.get(url);
//放大好观察
driver.manage().window().maximize();
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("body > div.nav > span"));
driver.findElement(By.cssSelector("body > div.container-login > div"));
System.out.println("页面元素加载测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("页面元素加载测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录失败情况 会有弹窗显示
* 1.先检查用户名或密码为空
* 2.用户名或密码错误
* 3.长度<3(不包括等于) 或 长度>16(不包括等于) 用户名或密码长度不正确
*/
@Test
@Order(2)
public void loginFail() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
//确保没有内容
driver.navigate().refresh();
//上面登录后需要退出清空我们的token
// driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
// //放大好观察
// driver.manage().window().maximize();
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("");
driver.findElement(By.cssSelector("div > #password")).sendKeys("");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
System.out.println("用户名或密码为空测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码为空测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("1234");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
System.out.println("用户名或密码错误测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码错误测试失败:" + e.getMessage());
throw e;
}
Thread.sleep(1000);
try {
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.alertIsPresent());
Alert alert = driver.switchTo().alert();
Thread.sleep(1000);
alert.accept();
//刷新界面 将输入框的内容清空
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("qwertyuiopasdfgh");
driver.findElement(By.cssSelector("div > #password")).sendKeys("qwertyuiopasdfghj");
driver.findElement(By.cssSelector("#submit")).click();
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait2 = new WebDriverWait(driver, Duration.ofSeconds(5));
wait2.until(ExpectedConditions.alertIsPresent());
Alert alert2 = driver.switchTo().alert();
Thread.sleep(1000);
alert2.accept();
System.out.println("用户名或密码长度不正确测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
System.out.println("用户名或密码长度不正确测试失败:" + e.getMessage());
throw e;
}
}
/**
* 检查登录功能--登录成功情况
*/
@Test
@Order(3)
public void loginSuccess() throws InterruptedException, IOException {
driver.get(url);
Thread.sleep(1000);
// //放大好观察
// driver.manage().window().maximize();
// String loginUrl = driver.getCurrentUrl();
//确保没有内容
driver.navigate().refresh();
driver.findElement(By.cssSelector("div > #username")).sendKeys("Nan");
driver.findElement(By.cssSelector("div > #password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//测试当前url是否为知识百宝库列表页
// String currentHandle = driver.getWindowHandle();
// Set<String> windowHandles = driver.getWindowHandles();
// for (String handle : windowHandles) {
// if (handle != currentHandle) {
// //如果不是的话就执行切换为当前句柄
// driver.switchTo().window(handle);
// }
// }
//显示等待 否则自动化测试速度过快导致报错
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.titleIs("知识百宝库列表页"));
String title = driver.getTitle();
try {
//获取当前标题通过断言来判断
assert title.equals("知识百宝库列表页");
// Assertions.assertEquals(title, "知识百宝库列表页");
System.out.println("登录功能测试通过");
} catch (Exception e) {
// 获取当前方法名
String methodName = new Object() {
}.getClass().getEnclosingMethod().getName();
// 将方法名传入
getScreenShot(methodName);
e.printStackTrace();
System.out.println("登录功能测试失败:" + e.getMessage());
throw e;
}
// Thread.sleep(3000);
// String currentUrl = driver.getCurrentUrl();
// //测试当前url是否为知识百宝库列表页
// if (!Objects.equals(loginUrl, currentUrl)) {
// System.out.println("测试通过");
// System.out.println(currentUrl);
// } else {
// System.out.println("测试失败");
// System.out.println(currentUrl);
// }
}
}
补充:点进这个注解可以观察出这个注解只作用于类上,以及生命周期是在运行时
总结
- 选择一个方法即可:通常,方法 1 足以解决问题,它能确保在测试类之间正确复用
WebDriver
实例。如果你需要更强的隔离性,可以选择方法 2 或方法 3,这样可以确保每个测试类彼此隔离,避免前一个测试影响后一个测试。 - 不要同时使用三个方法:这些方法是互斥的,选择最符合你需求的即可
知识百宝库系统联合测试
在上面我们对每个测试类中的所有测试用例都测试过了,没有什么问题。也分别对每个测试类单独进行联合测试了,也是没什么问题,接下来我们整合所有测试类所有测试用例进行一次大的测试,看看效果是否符合我们的预期呢?
具体代码就是上面测试套件中的那几行代码,具体如下,接下来我们看看整个项目的测试效果是否符合我们的预期
/**
* 测试所有用例流程:
* 1.测试未登录模块所有测试用例 一键测试完成 开始在登录界面 最终停留在登录界面
* 2.测试注册模块所有测试用例 一键测试完成 开始在登录界面 最终停留在登录界面
* 3.测试登录模块所有测试用例 一键测试完成 开始在登录界面 最终停留在博客列表页
* 4.测试登录后博客列表页所有测试用例 一键测试完成 开始在登录界面 最终停留在博客列表页
* 5.测试发布博客模块所有测试用例 一键测试完成 开始在登录界面 最终停留在博客列表页
* 6.测试博客详情页模编辑和删除模块所有测试用例 一键测试完成 开始在登录界面 最终停留在博客列表页
* 7.测试退出功能模块所有用例 一键测试完成 开始在登录界面 最终停留在登录界面
* 上述中每个类注意测试顺序 并且注意每个测试类测试完成后所处于的界面是否影响下一个测试类的测试用例!!! 待完成
* 所有测试类联合测试 联合测试顺序结合每个类的具体代码来定 先这样决定
*/
@Suite
@SelectClasses({PageByNoLoginTestCase.class,
RegistryPageTestCase.class,
LoginPageTestCase.class,
ListPageTestCase.class,
EditPageTestCase.class,
DetailPageTestCase.class,
ExitLoginTestCase.class})
public class AllClassTestCases {
}
测试效果如下,点击下面的链接看,因为这个时长有点长所以不能使用GIF格式
测试报告
我们的自动化测试项目结束了,作为测试人员我们需要写一份测试报告,来对这次测试的信息做一个记录或者说总结。注意测试报告没有统一的规范和模板,不同的企业的不同的部门对测试报告内容的要求都是不一样的
首先是测试表格,这个测试表格是可有可无的
项目名称 | 知识百宝库 | 版本号 | / |
---|---|---|---|
发布类型 | 分级发布 | 测试负责人 | Nan |
测试完成日期 | 9.20 | 联系方式 | / |
评审人 | Nan | 批准人 | Nan |
评审日期 | 9.20 | 批准日期 | 9.20 |
版本号:注意Web项目是没有版本号的,例如客户端、移动端这样的才有版本号
发布类型:就是讲软件测试的生命周期上线阶段,对上线阶段进行划分的这么一个说法
评审人:前端开发、后端开发、测试开发等评审人员
1.项目背景
项目背景:表示你为啥要做这个项目,意义是什么,目的是什么。首先我们都知道计算机行业的知识点是非常多的,怎么学都学不完,学习了后不及时整理就容易忘记,就算你记忆力好天赋异禀,如果后期不进行复盘依然会忘记,所以我设计这个系统的目的是助于热爱计算机行业的人员对个人知识进行总结,总结后存储至我们的知识百宝库,不仅方便后续个人进行回顾以及补充,也可以将自己获取、理解、感悟、困惑等的知识点,让别人也能参与进来一起思考一起进步,营造一个良好的氛围
1.1.测试目标以及测试任务概况:测试目标就是要把这个系统所有的功能、所有的界面都要测试完成,主页面覆盖达到90%,注意主页面覆盖越多越好,但是不可能到达100%,因为测试只能发现绝大多数的问题,但不可能发现所有问题!!!
1.2.被测的系统、代码包以及文档等信息:被测系统就是Gitee链接,需要提供一个代码链接,就提供关于被测系统的相关信息即可,例如如下就是我的知识百宝库系统的链接
Gitee链接:https://gitee.com/N-0050/test/tree/master/blog-automate-test/src/test/java
测试计划:就是这个测试怎么实施,用到了什么测试工具,你准备什么时候开始介入
1.3产品需求和设计文档等:有则写上,没有则不写。例如《需求文档》《技术文档》《设计文档》
2.测试安排
测试安排就像如下图,可以搞一个表格
模块 | 子模块 | 前端 | 后端 | 提测时间 | 测试 | 工时 | 排期 | 进度 |
---|---|---|---|---|---|---|---|---|
登录 | 登录功能 | Nan | Nan | 9.15 | Nan | 0.5d | 9.16 | 测试完成 |
注册 | 注册功能 | Nan | Nan | 9.15 | Nan | 0.5d | 9.16 | 测试完成 |
主页 | 博主信息、知识百宝库列表页、导航栏 | Nan | Nan | 9.16 | Nan | 1d | 9.17 | 测试完成 |
写博客 | 博客编写与发布 | Nan | Nan | 9.17 | Nan | 1d | 9.18 | 测试完成 |
详情页 | 博客编辑与删除 | Nan | Nan | 9.18 | Nan | 1d | 9.19 | 测试完成 |
退出 | 退出功能 | Nan | Nan | 9.19 | Nan | 0.5d | 9.20 | 测试完成 |
模块和子模块:模块就是我们的功能,子模块就是对我们的功能再进一步划分,然后进行测试
前端和后端:写上去是方便后续测试过程中发现BUG,然后分析出问题出自前端还是后端然后找对应的人来解决,以及可以告诉其他看测试报告的人看到某某人是开发某某功能的,一目了然
提测时间:指的是这个页面开发完成后由我们的测试人员来进行测试,就是开发人员开发完这个界面后也就是我们测试人员可以介入的一个日期时间
工时:指的是测试所耗费的时间,需要耗费多久,例如0.5d就是半天,1d就是1天,以天为单位
排期:指的是什么时候开始测试该功能,例如提测时间能测就直接测试,提测时间测不了但是明天可以就把这个排期时间修改成明天。测试时间根据具体的测试时间来动态变化的,
进度:就字面意思这个功能测没测完成,一般走到写这个测试报告这个步骤肯定是所有的功能都完成测试了,但如果有一个功能测试没完成直接写出测试报告那是不行滴
3.测试分类
对测试类型进行分类,项目用到了哪些测试,就是之前讲过按某某划分的测试类型
1.功能测试
测试用例,就是我们设计的脑图截个图放上去,设计测试用例有需求我们就参考需求来设计,如果没有的话,我们就需要发散性思维的去设计,使命的想,站在用户的角度、以及开发以及能不能实现的角度来想来设计
功能测试结果:测试用例100%通过,但可能还会存在一些个人没有考虑到的测试用例,并且软件测试不可穷举
2.自动化测试结合单元测试
- 自动化测试覆盖模块:首页、登录页面、知识百宝库列表页、知识百宝库详情页等
- 自动化测试代码示例:https://gitee.com/N-0050/test/tree/master/blog-automate-test/src/test/java
- 自动化测试用例数量:56
- 自动化测试结果:pass:56/56 fail:0/56
- 自动化测试问题是否修复:是
3.兼容性测试
- 兼容性测试覆盖模块:首页、登录页面、知识百宝库列表页、知识百宝库详情页等
- 兼容性测试用例数量:7
- 兼容性测试结果:pass:7/7 fail:0/7
- 兼容性测试问题是否修复:是
4.遗留风险
测试时间紧张,先保证知识百宝库系统主功能没有问题,可能存在细节测试不到位等风险,但是覆盖大部分测试用例了
5.测试结果评估
对我们这次自动化测试项目的整个测试结果给出一个评估
-
主功能测试通过,项目可以上线
-
项目上线后,如果有遗留风险的话最高优先级观察线上数据,查看线上用户操作日志,及时跟进用户反馈,如果有bug需要及时修改
以上便是本章内容,以知识百宝库系统为例,基于web项目进行完整的自动动化实战,测试知识点满满,通过这次自动化测试实战对自动化测试工具Selenium和单元测试工具JUnit更加熟悉了,这次我使用的是Markdown的格式所以可能会有点不一样,但是干货满满,我们下一章再见💕