一、总述
还记得这个SpringBoot实战项目吗?SpringBoot实战——个人博客项目_是小鱼儿哈的博客-CSDN博客
今天我们就对这个web项目,用selenium进行自动化测试,看看这个项目有什么问题?是否达到了我们的预期效果。
博客网站如下:登陆页面
首先要对这个博客各个页面设计测试页面。
下面我们就一个页面一个页面的写代码,进行测试。
二、登录页面测试
一些准备工作
首先我们新建一个Maven项目。
在test包下面写我们的测试代码。
因为我们在自动化测试的时候要频繁获取页面中的元素,但很多时候我们页面元素的加载速度赶不上我们自动化代码的执行速度,所以就会导致找不到元素这种情况。
可以看到,报了错误——》找不到我们页面对应的元素。
那么我们加上隐式等待试试
因此,我们不如在整个项目中,创建一个公共类(进行隐式等待,让我们的程序能够等一下我们的页面加载)
【另外, 隐式等待 作用于 WebDriver 整个生命周期】
【只要没有走到 driver.quit,即没有退出浏览器,隐式等待都是一直存在的】
所以我们之后要写的登录界面只要继承的隐式等待,自然也能够使得测试登录界面的代码能够稍微停顿一下,等页面渲染完成。
下面我们进行登录页面的自动化测试代码编写
我们要编写3个测试用例
- 验证页面显示是否正确
- 验证正常登录的情况
- 验证登录失败的情况
首先因为我们每个测试用例都要 创建驱动实例,进入到用户登录页面、所以我们不妨这样做:
这样再将我们的测试用例按一定的顺序来执行,就会使得我们的整个测试过程很流程、自然。
验证页面显示是否正确
/** * 检查登录页面是否正常显示 * @throws InterruptedException */ @Test @Order(1) void loginPageTest() throws InterruptedException { // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); // 利用断言判断登录的文本内容显示是否正确 String expect = "登录"; String actual = driver.findElement(By.cssSelector("body > div.login-container > div > h3")).getText(); // 检查登录页面的登录文本是否存在 Assertions.assertEquals(expect, actual); driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")); // 检查博客登录页的主页超链接是否存在 // 检查提交按钮是否存在 driver.findElement(By.cssSelector("#submit")); }
上面中我们用到了junit和隐式等待
junit 中提供和了非常强大的注解功能
@Test说明方法 是测试方法,执行当前这个类时,会自动的执行该类下的所有带@Test注解的用例
下面我们进行测试
验证正常登录的情况
/** * 检查正常登录的情况,每写一个测试用例就测试一下 */ @ParameterizedTest // 写了该注解就不用在写@Test注解了(多参数) @Order(2) @CsvSource({"admin, admin", "小鱼儿, 123"}) void loginRightTest(String username, String password) throws InterruptedException, IOException { // 隐式等待是作用不了非HTML页面的元素的,所以弹窗无法等待,看下是否在切换到弹窗之前弹窗还没有出现,终端报的错误是不是noalert // 多个账号登录,在重新输入账号时,需要把之前的输入的内容清空 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); // 隐式等待无法处理弹窗 && 显示等待和隐式等待无法共存(父类AutotestUtils中用了隐式等待) Thread.sleep(100); // 显示等待,等待弹窗出现 Alert alert = driver.switchTo().alert(); alert.accept(); // 选择确认 // 上述步骤只是说明输入了账号和密码,但还不知道点击提交后是否会跳转到博客列表页 String expect = "http://49.235.66.46:9000/blog_list.html"; String actual = driver.getCurrentUrl(); Assertions.assertEquals(expect, actual); // 查看当前的url是否在博客详情页面 // 进行截图,看当前是否跳转到了登录界面 // 程序执行的速度和页面渲染的速度 File srcFile = driver.getScreenshotAs(OutputType.FILE); String fileName = "loginRightTest.png"; FileUtils.copyFile(srcFile, new File(fileName)); //因为我们要测试多个账号,所有在一个账号检测完了后,还需要回退到登录界面 driver.navigate().back(); }
该过程中出现的问题
在验证用户正常登录的过程中,我一开始没有用强制等待或者显示等待(我只是用了隐式等待)。结果——在处理弹窗的过程就出现了问题。
咦!不对呀,我不是用了隐式等待了吗?
难道不应该是等弹窗加载完了,程序才会继续往下执行——获取弹窗的吗?
原来:
隐式等待是作用不了非HTML页面的元素的,所以弹窗无法等待(弹窗还没有出现,页面还没加载完成,我们的程序就在尝试着获取弹窗了——》这怎么获取?自然就报错了!!!
那么我们既然用不了隐式等待,我们用显示等待好了。但你别忘了,你这个对登录界面测试的类是继承了AutoTestUtils的(里面实现了隐式等待)
并且——显示等待和隐式等待尽量不要共存(会出现一些意想不到的错误)
所以呢?这种情况下,我们只好用强制等待了。
但是——强制等待是比较消耗时间的
我们需要考虑在整个项目中,类似这样的强制等待多不多,如果太多的话——我们就要考虑重写换一种策略了。
验证登录失败的情况
/** * 检查登录失败的情况 */ @Order(3) @ParameterizedTest // 多个参数 @CsvSource({"admin, 123"}) void loginFailTest(String username, String password) throws IOException, InterruptedException { // 把之前默认填充内容清空 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(100); Alert alert = driver.switchTo().alert(); System.out.println(alert.getText()); }
关于登录界面的总代码
package webAutoTest.tests; import com.sun.xml.internal.stream.StaxErrorReporter; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import webAutoTest.common.AutotestUtils; import org.openqa.selenium.OutputType; import java.io.File; import java.io.IOException; import java.time.Duration; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) // 说明当前该类下面的测试方法要按一定的顺序执行 public class loginTest extends AutotestUtils { public static ChromeDriver driver = createDriver(); @Test @BeforeAll // 被@BeforeAll修饰的方法要是静态的 static void init() { // 跳转到博客登录页面 driver.get("http://49.235.66.46:9000/login.html"); } /** * 检查登录页面是否正常显示 * @throws InterruptedException */ @Test @Order(1) void loginPageTest() throws InterruptedException { // 隐式等待--// 隐式等待,更加丝滑——》作用于下面的整个作用领域,这个方法中的所有元素,在这3秒内不断轮询 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); // 利用断言判断登录的文本内容显示是否正确 String expect = "登录"; String actual = driver.findElement(By.cssSelector("body > div.login-container > div > h3")).getText(); // 检查登录页面的登录文本是否存在 Assertions.assertEquals(expect, actual); driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")); // 检查博客登录页的主页超链接是否存在 // 检查提交按钮是否存在 driver.findElement(By.cssSelector("#submit")); } /** * 检查正常登录的情况,每写一个测试用例就测试一下 */ @ParameterizedTest // 写了该注解就不用在写@Test注解了(多参数) @Order(2) @CsvSource({"admin, admin", "小鱼儿, 123"}) void loginRightTest(String username, String password) throws InterruptedException, IOException { // 多个账号登录,在重新输入账号时,需要把之前的输入的内容清空 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(100); Alert alert = driver.switchTo().alert(); alert.accept(); // 选择确认 // 上述步骤只是说明输入了账号和密码,但还不知道点击提交后是否会跳转到博客列表页 String expect = "http://49.235.66.46:9000/blog_list.html"; String actual = driver.getCurrentUrl(); Assertions.assertEquals(expect, actual); // 查看当前的url是否在博客详情页面 // 进行截图,看当前是否跳转到了登录界面 // 程序执行的速度和页面渲染的速度 File srcFile = driver.getScreenshotAs(OutputType.FILE); String fileName = "loginRightTest.png"; FileUtils.copyFile(srcFile, new File(fileName)); //因为我们要测试多个账号,所有在一个账号检测完了后,还需要回退到登录界面 driver.navigate().back(); } /** * 检查登录失败的情况 */ @Order(3) @ParameterizedTest // 多个参数 @CsvSource({"admin, 123"}) void loginFailTest(String username, String password) throws IOException, InterruptedException { // 把之前默认填充内容清空 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password")).sendKeys(password); driver.findElement(By.cssSelector("#submit")).click(); Thread.sleep(100); Alert alert = driver.switchTo().alert(); System.out.println(alert.getText()); } @AfterAll @Test static void quit() { driver.quit(); } }
三、注册界面的自动化测试
测试代码
package webAutoTest.tests; import org.apache.commons.io.FileUtils; import org.checkerframework.checker.units.qual.A; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.OutputType; import org.openqa.selenium.chrome.ChromeDriver; import webAutoTest.common.AutotestUtils; import java.io.File; import java.io.IOException; /** * 注册界面的自动化测试 */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) // 说明当前该类下面的测试方法要按一定的顺序执行 public class regTest extends AutotestUtils { // 继承用于隐式等待的公共方法 public static ChromeDriver driver = new ChromeDriver(); @Test // @Test说明方法 是测试方法,执行当前这个类时,会自动的执行该类下的所有带@Test注解的用例 @BeforeAll // 带有BeforeAll注解的方法会在当前类下的所有测试用例之前(方法)执行一次,注意只是执行一次 public static void init() { // 既然是对注册界面的测试,自然要先跳转到该界面 driver.get("http://49.235.66.46:9000/reg.html"); } /** * 对页面内容的完整性进行测试 */ @Test @Order(1) public void regPageTest() { // 利用断言验证页面显示的文本是否正确 String expect = "注册"; String actual = driver.findElement(By.cssSelector("body > div.login-container > div > h3")).getText(); Assertions.assertEquals(expect, actual); // 如果不正确 driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")); // 检查博客登录页的主页超链接是否存在 // 检查提交按钮是否存在 driver.findElement(By.cssSelector("#submit")); } /** * 正常注册 */ @ParameterizedTest // 多参数——加了该注解就不用@Test了 @Order(2) @CsvSource({"皮皮, 123456, 123456"}) // 多参数 public void regRightTest(String username, String password1, String password2) throws InterruptedException, IOException { // 每次都要提前把之前输入框的内容给清除(不管有没有内容) driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password1")).clear(); driver.findElement(By.cssSelector("#password2")).clear(); // 将信息填入输入框 driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password1")).sendKeys(password1); driver.findElement(By.cssSelector("#password2")).sendKeys(password2); // 找到提交按钮,并点击提交 driver.findElement(By.cssSelector("#submit")).click(); // 强制等待,让弹窗显示出来(避免我们页面还没加载完成,我们下面的代码就尝试获取弹窗 Thread.sleep(500); // 注册成功后,会出现弹窗,获取弹窗并且关闭 Alert alert = driver.switchTo().alert(); alert.accept(); // 点击弹窗中的确定,以便让程序继续执行下去 // 注册成功后,应该会跳转到登录页面 Thread.sleep(100); String expectURL = "http://49.235.66.46:9000/login.html"; String actualURL = driver.getCurrentUrl(); // 获取当前页面的URL Assertions.assertEquals(expectURL, actualURL); // 获取此时的屏幕截图,此时应该以及跳转到了登录页面 File srcFile = driver.getScreenshotAs(OutputType.FILE); String fileName = "regRightTest.png"; FileUtils.copyFile(srcFile, new File(fileName)); // 因为注册成功会跳转到登录界面,所以但接下来我们还有在注册界面测试,所以要回退到注册界面 driver.navigate().back(); } /** * 测试注册失败的情况 * (小鱼儿这个用户名我以及注册过了再次注册,由于用户名的唯一约束,会导致注册失败) * (前后两次输入的密码不一致) */ @ParameterizedTest @Order(3) @CsvSource({"小鱼儿, 1234, 1234", "阿良, 123, 123456"}) public void regFailTest(String username, String password1, String password2) throws InterruptedException { // 每次输入信息前, 先要清除输入框的原有内容 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password1")).clear(); driver.findElement(By.cssSelector("#password2")).clear(); // 往输入框中输入数据 driver.findElement(By.cssSelector("#username")).sendKeys(username); driver.findElement(By.cssSelector("#password1")).sendKeys(password1); driver.findElement(By.cssSelector("#password2")).sendKeys(password2); driver.findElement(By.cssSelector("#submit")).click(); // 等待弹窗加载完成 Thread.sleep(100); Alert alert = driver.switchTo().alert(); // 获取弹窗 // 利用断言判断是否注册失败 if (password1.equals(password2)) { String expect = "注册失败,请检查你的输入!"; // 前后密码一致的情况下 String actual = alert.getText(); alert.accept(); // 获取到弹窗内容后在关闭弹窗 Assertions.assertEquals(expect, actual); // 看浏览器的实际弹窗内容是否和我们预期的一样 } else { String expect = "两次密码输入不一致,请先检查!"; String acutal = alert.getText(); alert.accept(); Assertions.assertEquals(expect, acutal); } } /** * 关闭注册弹窗 */ @Test @AfterAll // 带有AfterAll注解的方法会在当前类下的所有测试用例(方法)执行之后 执行一次,注意只是执行一次 public static void close() { driver.quit(); } }
过程中出现的bug
2、在注册页面的自动化测试的过程中,我通过多对象传入同一个测试方法来对多种注册失败的情况进行测试(该用户以及注册、前后密码输入不一致)
结果在通过断言——发现我预期的弹窗内容和实际的弹窗内容不一致,导致测试的时候出现问题
通过查看下面的报错信息,结合程序一起查看,我才发现——我的判断条件有问题
应该用password1.equals(password2)
接着我改好了,但程序又出现了问题🤔
No ParameterResolver registered for parameter [java.lang.String arg1] in method [public void webAutoTest.tests.regTest.regFailTest(java.lang.String,java.lang.String,java.lang.String) throws java.lang.InterruptedException].
中间的逗点我写成了全角的中文——》当然有问题啊
改成半角的逗点后——》又又有新的问题出现了😂
通过这行报错信息可以看出
Command: [f0ef8a1466c85d3fc87f96ebd8e83a28, findElement {using=css selector, value=#username}]
在第二个错误登录的测试用例的执行的时候——》他找不到页面中的元素#username。
不对呀!!!我明明这个cssSelector写的没有问题啊,页面中也确实存在这个元素啊,为什么会找不到呢?
后来我把 ——driverfindElement(By.cssSelector("#username")).clear(); 这行代码给注释掉了
结果
看来不是页面元素selector的问题,难道是页面加载还没完成???
不会的——我们整个类继承了AutotestUtil(里面实现了隐式等待了啊,这里又不是弹窗,隐式等待应该能够发挥作用的呀!!!)
弹窗——于是我检查了代码中有关弹窗的部分。
结果:
没错,我是获取弹窗了
但是我没关闭弹窗啊!!!
这就导致在执行第二个测试用例的时候,上一个测试用例的弹窗还没有关闭——当然获取不到第二个测试用例的输入了呀!
终于可以了,不容易啊!