文章目录
一、自动化测试介绍
1. 概念
自动化测试是指软件测试的自动化,在预设状态下运行应用程序或者系统,预设条件包括正常和异常,最后评估运行结果,将人为驱动的测试行为转化为机器执行的过程
自动化测试包括UI自动化、接口自动化、单元测试自动化。按照这个金字塔模型来进行自动化测试规划,可以产生最佳的自动化测试产出投入比,可以用较少的投入获得很好的收益
(1)单元测试
最大的投入应该在单元测试上,单元测试运行的频率也更高。Java的单元测试框架就是Junit
(2)接口测试
接口测试就是API测试,相对于UI自动化API自动化更加容易实现,执行起来也更加稳定
接口自动化有以下几个特点:
- 可在产品前期,接口完成后介入
- 用例维护量小
- 适合接口变动较小,界面变动频繁的项目
常见的接口自动化测试工具:Postman
(3)UI测试
UI层的自动化测试更加贴近用户的需求和软件系统的实际业务,并且有时候不得不进行UI层的测试
UI自动化测试的特点:
- 用例维护量大
- 页面相关性强,必须后期项目页面开发完成后介入
- UI测试适合于界面变动较小的项目
UI层自动化测试框架:selenium
2. 为什么要进行自动化测试
自动化测试能够代替一部分手工测试。自动化测试能够提高测试效率。比如随着功能的增加,版本越来越多,回归测试的压力也越来越大,所以仅仅通过人工测试来回归所有的版本不现实,所以我们需要借助自动化来进行回归
二、驱动
1. 概念
首先驱动是指驱动计算机里软件的程序,是添加到操作系统中的特殊程序,其中包含有关硬件设备的信息。对于自动化测试来说,代码可以借助驱动协助打开浏览器,不需要人为的帮助
2. 本质
浏览器驱动就是一个服务器,我们通过代码向驱动发送操作浏览器的请求,驱动进行解析并执行,最后将结果返回给我们。说它是一个服务器最直接的证据就是它占用了9515端口号
三、selenium
1. 为什么选择selenium作为我们的web自动化工具
- 开源免费
- 支持多浏览器
- 支持多系统
- 支持多语言
- selenium包底层有很多可使用的API
2. 环境部署
- selenium工具包
- Chrome浏览器
- Chromedriver谷歌驱动
- jdk8
四、 webdriver API
1. 元素定位
一个简单的自动化示例
/**
* 创建驱动对象并返回
* @param url 驱动里的地址
* @return
*/
private static ChromeDriver getDriver(String url) {
ChromeDriver driver = new ChromeDriver();
driver.get(url);
return driver;
}
/**
* 退出浏览器
* @param driver
*/
private static void closeDriver(ChromeDriver driver) {
driver.quit();
}
public static void test() throws InterruptedException {
Thread.sleep(2000);
// 输入百度网址
ChromeDriver driver = getDriver("https://www.baidu.com");
Thread.sleep(2000);
// 找到百度输入框并输入“胡歌”
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
Thread.sleep(2000);
// 找到百度一下按钮,并点击
driver.findElement(By.xpath("//*[@id=\"su\"]")).click();
Thread.sleep(2000);
// 释放驱动对象/关闭浏览器
closeDriver(driver);
}
(1)id
id是页面元素的属性,我们最常用元素定位方式,但是不是所有的元素都有id的。id只能使用一次,如果某一个元素有id属性,那么我们就能通过id来唯一的定位这个元素。例如百度首页输入框的id就是"kw"
(2)name
如果这个元素有name,并且元素的name命名在整个页面是唯一的,那么我们可以用name来定位这个元素。例如百度首页输入框的name就是"wd",并且是唯一的
(3)class name 和 tag name
tag name(标签名)
和 class name
也是定位元素的两种方式,如果那个标签或者类在页面中只使用了一次,那么我们就可以使用这两种方式来唯一的定位元素,但是并不常用,因为还需要我们对唯一性进行判断。例如百度首页输入框的class=“s_ipt”,标签名是"input"(但是百度首页有多个input标签)
(4)CSS
CSS(Cascading Style Sheets)是一种语言,它被用来描述 HTML 和 XML 文档的表现
CSS比较灵活,可以选择控件的任意属性,比如id属性,在百度首页要选中输入框也可以这样写:driver.findElement(By.cssSelector("#kw"))
CSS的获取可以用chrome的F12开发者模式中Element-右键-copy-copy selector来获取
(5)XPath
XPath 是一种在XML文档中定位元素的语言。因为HTML 可以看做XML的一种实现,所以selenium用户可以使用这种语言在web应用中定位元素
Xpath的获取可以用chrome的F12开发者模式中Element-右键-copy-copy xpath来获取
需要注意的是,Xpath在一个页面也会有重复的情况,并不是一个Xpath在一个页面就是唯一存在的
(4)link text
有时候不是一个输入框也不是一个按钮,而是一个文字链接,我们可以通过链接内容,也就是 link text 来定位。需要注意的是链接内容必须这个页面唯一,否则会报错。比如百度首页左上角的"新闻"等文字链接,就可以通过link text来定位
(5)partial link text
通过部分链接定位,这个有时候也会用到
2. 操作测试对象
上述的API都是定位元素的方法,但是定位元素只是操作的第一步,之后我们要对这个元素进行操作
(1)鼠标点击和键盘输入
click
,用于点击,但是不局限于按钮,一个页面所有位置都能点击sendKeys
,键盘输入内容clear
,清除元素中输入的文本内容
代码示例:
public static void test5() throws InterruptedException {
Thread.sleep(2000);
ChromeDriver driver = getDriver("https://www.baidu.com");
Thread.sleep(2000);
// 在输入框中输入内容
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
Thread.sleep(2000);
// 点击按钮
driver.findElement(By.xpath("//*[@id=\"su\"]")).click();
Thread.sleep(2000);
// 清除内容
driver.findElement(By.cssSelector("#kw")).clear();
Thread.sleep(2000);
closeDriver(driver);
}
(2)submit提交表单
一般来说,submit
和click
效果几乎相同,但是不同的是,submit
只能点击按钮用于提交表单,无法点击其他地方。因此我们说,提交有的功能点击都有,所以建议直接用click()
(3)获取元素文本和属性
getText
是用来获得标签之间的文本内容,而由于单标签只有一个标签,因此也就无法获取文本内容;getAttribute
用来获得元素的属性
代码示例:
public static void test6() throws InterruptedException {
Thread.sleep(2000);
ChromeDriver driver = getDriver("https://www.baidu.com");
Thread.sleep(2000);
// 获取输入框元素属性
String textAttribute = driver.findElement(By.cssSelector("#kw")).getAttribute("class");
System.out.println(textAttribute);
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
Thread.sleep(2000);
// 获取胡歌人物介绍:"中国内地影视男演员流行乐歌手"
String text = driver.findElement(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > div:nth-child(2)")).getText();
System.out.println(text);
Thread.sleep(2000);
closeDriver(driver);
}
3. 添加等待
等待的意义:我们在做web网站自动化测试的过程中,代码的执行速度比较快,而前端页面渲染的速度相对较慢,那么有可能我们进行元素定位时页面的元素还没加载完成,此时对元素进行定位就会出现异常,降低自动化脚本的稳定性和测试效率
(1)sleep休眠
sleep休眠就是通过Thread.sleep(毫秒)来进行定时休眠
- 优点:语法简单,适合调试的时候用
- 缺点:需要等待固定的时间,造成测试时间的大量消耗,大大降低了自动化测试的效率
(2)隐式等待
隐式等待不同于sleep休眠,隐式等待是在页面加载完毕或者等待时间超过设置的最长等待时间的情况下结束等待,如果超出等待时间,就会抛出异常
- 特点:隐式等待是作用于webDriver的整个生命周期,它并不需要知道用户要查询哪个元素
- 优点:节省大量的等待时间,元素展现后可以直接执行下一步,执行效率高
- 缺点:需要等待页面所有元素都展现才会执行下一步,仍然会有额外的时间浪费
代码示例:
public static void test7() {
ChromeDriver driver = getDriver("https://www.baidu.com");
// 设置隐式等待的最长等待时间为2s
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
driver.findElement(By.cssSelector("#su")).click();
// 获取胡歌人物介绍:"中国内地影视男演员流行乐歌手"
String text = driver.findElement(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > div:nth-child(2)")).getText();
System.out.println(text);
closeDriver(driver);
}
(3)显式等待
显式等待和隐式等待类似,但是它是针对某一个元素进行等待,在固定的时间范围内频繁的轮询元素是否存在
- 优点:可以针对某一个元素来进行等待,不需要等待整个页面渲染完成,极大降低了自动化整体的等待时间
- 缺点:写法更加复杂
代码示例:
public static void test8() {
ChromeDriver driver = getDriver("https://www.baidu.com");
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
driver.findElement(By.cssSelector("#su")).click();
// 第一个参数是WebDriver对象
// 第二个参数是Duration类方法,用于设置强制等待的最长时间
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
// ExpectedConditions类提供了很多方法可以用来测试
// presenceOfElementLocated返回当前页面的web元素,即查看我们指定的元素是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > div:nth-child(2)")));
// 获取胡歌人物介绍:"中国内地影视男演员流行乐歌手"
String text = driver.findElement(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > div:nth-child(2)")).getText();
System.out.println(text);
// 检查页面元素对应的文本信息是否正确
wait.until(ExpectedConditions.textToBe(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > a > div > p > span > span"), "胡歌"));
closeDriver(driver);
}
补充:ExpectedConditions类提供了很多方法可以用来测试,例如presenceOfElementLocated,检查页面是否存在对应的元素;textToBe,检查页面元素对应的文本信息是否正确
注意
- 隐式等待和显式等待都检测不到弹窗的加载,因此如果对弹窗的信息进行操作,必须加入强制等待
- 隐式等待和显式等待不建议一起使用,会产生意想不到的效果,每次等待的时间也是不确定的
4. 信息打印
(1)title打印
将webDriver控制的当前标签页的页面的title打印出来。言外之意,就是如果新打开了一个标签页,webDriver仍然打印的是原来标签页的title,这里注意浏览器标签页和页面的区别
(2)url打印
打印当前webDriver驱动对象控制的标签页的url。这里需要注意的和title打印需要注意的一样
代码示例:
public static void test9() {
ChromeDriver driver = getDriver("https://www.baidu.com");
System.out.println(driver.getTitle());
System.out.println(driver.getCurrentUrl());
closeDriver(driver);
}
5. 浏览器操作
上述webDriver以一个标签页为单位进行操作,实际是selenium为每一个标签页设置了唯一标识,称之为句柄,如果我们想操作新打开的标签页,就必须明确修改webDriver的句柄(注意,句柄是实时生成的,每次测试时句柄都不一样)
(1)获取当前webDriver操作的标签页的句柄
driver.getWindowHandle() // 返回值是字符串类型
(2)获取浏览器所有标签页的句柄
driver.getWindowHandles() // 返回值是Set<String>类型
(3)修改webDriver操作的句柄
跳转到最新的标签页,最简单粗暴的做法就是维护两个Set,一个保存旧的所有句柄,一个保存新的所有句柄,通过遍历新的Set,在旧的Set里找是否存在,如果不存在,那么该句柄所对应的标签页就是新打开的标签页。个人建议,善用driver的close操作,保证测试的整个过程中浏览器最多只有两个标签页,然后我们就不需要维护两个Set,就可以实现句柄的快速切换,因为我们进行测试的时候,最多就是点击原标签页的某个链接打开了另一个标签页,验证两个页面之间的关系,不会说一下打开两个新页面的情况,因此完全可以只保存当前标签页和新标签页的句柄
如果真的打开标签页很多又想要跳转到指定的标签页,建议是在刚打开那个标签页的时候就对它的句柄进行保存
代码示例:
public static void test10() throws InterruptedException {
ChromeDriver driver = getDriver("https://www.baidu.com");
// 设置隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
// 点击百度首页的新闻链接
driver.findElement(By.cssSelector("#s-top-left > a:nth-child(1)")).click();
Thread.sleep(3000);
// 获取当前标签页的句柄
String handle = driver.getWindowHandle();
System.out.println("当前句柄:" + handle);
// 关闭当前driver操作的标签页
driver.close();
Thread.sleep(3000);
System.out.println("==================");
// 获取所有标签页的句柄
Set<String> handles = driver.getWindowHandles();
for (String str: handles) {
// 切换 WebDriver 控制的句柄
if(!str.equals(handle)) {
driver.switchTo().window(str);
}
System.out.println(str);
}
System.out.println("当前句柄:" + driver.getWindowHandle());
closeDriver(driver);
}
(4)浏览器窗口大小的控制
- 窗口最大化
driver.manage.window().maxmize();
- 窗口最小化
driver.manage.window().minmize();
- 设置窗口尺寸
driver.manage.window().setSize(new Dimension(宽度,高度));(单位是像素)
代码示例:
public static void test11() throws InterruptedException {
ChromeDriver driver = getDriver("https://www.baidu.com");
Thread.sleep(2000);
// 窗口最大化
driver.manage().window().maximize();
Thread.sleep(2000);
// 窗口最小化
driver.manage().window().minimize();
Thread.sleep(2000);
// 设置窗口尺寸,参数是一个Dimension对象,这个对象的构造方法的参数分别是宽度,高度
driver.manage().window().setSize(new Dimension(1200, 600));
Thread.sleep(2000);
closeDriver(driver);
}
(5)浏览器页面滑动
WebDriver本身是无法控制页面滑动的,但是它可以通过方法执行js语句来实现页面滑动
- 滑动到底部
String js1 = "window.scrollTo(0, document.body.scrollHeight)"; // WebDriver通过关于执行js语言的字符串来实现窗口滑动 driver.executeScript(js1);
- 滑动到顶部
String js2 = "window.scrollTo(0, 0)"; driver.executeScript(js2);
代码示例:
public static void test12() throws InterruptedException {
ChromeDriver driver = getDriver("https://www.baidu.com");
// 点击新闻链接并将driver的句柄切换到新闻标签页
driver.findElement(By.cssSelector("#s-top-left > a:nth-child(1)")).click();
String handle = driver.getWindowHandle();
Set<String> handles = driver.getWindowHandles();
for (String str: handles) {
if(!str.equals(handle)) {
driver.switchTo().window(str);
break;
}
}
Thread.sleep(2000);
// 将窗口滑动到底部
// 但是如果页面不完全是由html写成,那么可能就无法滑动到最下面
String js1 = "window.scrollTo(0, document.body.scrollHeight)";
// WebDriver通过关于执行js语言的字符串来实现窗口滑动
driver.executeScript(js1);
Thread.sleep(4000);
// 再写一遍是为了测试是否能够通过多次滑动将页面滑到底部
// 实验发现百度新闻页并不是一次性加载完成,而是随着滑动而加载
// 因此我们才无法一次将它滑动到底部
driver.executeScript(js1);
Thread.sleep(4000);
driver.executeScript(js1);
Thread.sleep(4000);
driver.executeScript(js1);
Thread.sleep(4000);
// 将窗口滑动到顶部
String js2 = "window.scrollTo(0, 0)";
driver.executeScript(js2);
Thread.sleep(2000);
closeDriver(driver);
}
(6)控制一个标签页页面的前进和后退
注意,前进和后退操作都是针对当前标签页的,如果打开了新的标签页,除非通过修改WebDriver保存的句柄,否则新标签页将无法操作
- 前进
driver.navigate().forward()
- 后退
driver.navigate().back()
代码示例:
public static void test13() throws InterruptedException {
ChromeDriver driver = getDriver("https://www.baidu.com");
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
driver.findElement(By.xpath("//*[@id=\"su\"]")).click();
Thread.sleep(2000);
// 后退
driver.navigate().back();
Thread.sleep(2000);
// 前进
driver.navigate().forward();
Thread.sleep(2000);
closeDriver(driver);
}
(7)弹窗操作(警告弹窗)
我们在前端代码里能定位到的普通弹窗都可以使用driver.findElement()方法来定位元素,但是有的弹窗我们无法定位到,就需要借助WebDriver的特殊接口,比如alert的警告弹窗,我们需要通过使用Selenium中提供的alert接口来处理
Alert alert = driver.switchTo().alert();
// 点击确认按钮
alert.accept();
// 点击取消按钮
alert.dismiss();
// 往弹窗的输入框输入信息
alert.sendKeys("hello");
代码示例:
public static void test14() throws InterruptedException {
ChromeDriver driver = getDriver("file:///D:/study/%E6%AF%94%E7%89%B9/gitee%E4%B8%8A%E4%BC%A0%E4%BB%A3%E7%A0%81/javaweb_study/H-20230106/test2.html");
Thread.sleep(2000);
// 点击按钮,触发警告框
driver.findElement(By.cssSelector("body > form > input[type=button]:nth-child(14)")).click();
Thread.sleep(2000);
// 点击警告框
Alert alert = driver.switchTo().alert();
// 点击确认按钮
alert.accept();
// 点击取消按钮
alert.dismiss();
// 往弹窗的输入框输入信息
alert.sendKeys("hello");
Thread.sleep(2000);
closeDriver(driver);
}
6. 模拟鼠标操作
点击操作我们虽然可以通过click()实现,但是我们无法直观的看到点击操作,只能通过数据改变或者页面跳转来看到效果。selenium提供了Actions接口,我们就可以通过设置,直观的看到某个元素被选中
WebElement ele = driver.findElement(元素标识);
Actions actions = new Actions(driver);
// clickAndHold()参数必不可少,该方法表示鼠标移动到指定元素并保持
// perform表示演示
actions.clickAndHold(ele).perform();
// 该方法表示鼠标移动到元素并点击
actions.click(ele).perform();
代码示例:
public static void test15() throws InterruptedException {
ChromeDriver driver = getDriver("https://www.baidu.com");
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
driver.findElement(By.cssSelector("#su")).click();
WebElement ele = driver.findElement(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > a > div > p > span > span"));
Actions actions = new Actions(driver);
actions.clickAndHold(ele).perform();
Thread.sleep(2000);
actions.click(ele).perform();
Thread.sleep(2000);
closeDriver(driver);
}
7. 选中下拉框的选项
关于下拉框,我们是可以通过click进行选择的,但是可能有些麻烦,因此Selenium提供了接口Select,通过Select来选择下拉框的选项
WebElement ele = driver.findElement(By.cssSelector("body > form > select"));
Select select = new Select(ele);
// 索引从0开始递增
select.selectByIndex(0);
// 通过元素的value属性选择
select.selectByValue("计算机网络");
// 通过可见到的文本进行选择
select.selectByVisibleText("计算机网络");
//
代码示例:
public static void test16() throws InterruptedException {
ChromeDriver driver = getDriver("file:///D:/study/%E6%AF%94%E7%89%B9/gitee%E4%B8%8A%E4%BC%A0%E4%BB%A3%E7%A0%81/javaweb_study/H-20230106/test2.html");
Thread.sleep(2000);
WebElement ele = driver.findElement(By.cssSelector("body > form > select"));
Select select = new Select(ele);
select.selectByIndex(0);
Thread.sleep(2000);
closeDriver(driver);
}
8. 文件上传
由于驱动只能控制浏览器的操作,但是文件在本地,因此我们就需要想个其他的方法,通过sendKeys(文件路径)来实现上传本地文件
代码示例:
public static void test17() throws InterruptedException {
ChromeDriver driver = getDriver("file:///D:/study/%E6%AF%94%E7%89%B9/gitee%E4%B8%8A%E4%BC%A0%E4%BB%A3%E7%A0%81/javaweb_study/H-20230106/test2.html");
// 点击上传文件按钮,并输入要输入文件的文件路径+文件名
driver.findElement(By.cssSelector("body > form > input[type=file]:nth-child(16)")).sendKeys("C:\\Users\\DELL\\Desktop\\面试复习计划.png");
Thread.sleep(2000);
closeDriver(driver);
}
9. 屏幕截图
由于代码执行速度比页面渲染的速度要快,因此我们有时报错不是因为代码问题,而是因为速度问题,但是这种问题如果不是提前知道,就很难想到,也很难定位问题,因此我们就可以通过截图来留证,通过查看对每个页面进行测试时的屏幕截图,就能更容易的定位问题
语法:
// 截图后产生的文件,创建截图文件对象
File srcFile = driver.getScreenshotAs(OutputType.FILE);
// 将截图文件保存到我们指定路径下
File fileName = new File("./src/test/java/screenshot/test1.png");
// 将截图文件保存到指定路径下
FileUtils.copyFile(srcFile, fileName);
代码示例:
public static void test18() throws IOException {
ChromeDriver driver = getDriver("https://www.baidu.com");
// 设置隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
driver.findElement(By.cssSelector("#kw")).sendKeys("胡歌");
driver.findElement(By.cssSelector("#su")).click();
// 截图后产生的文件,创建截图文件对象
File srcFile = driver.getScreenshotAs(OutputType.FILE);
// 将截图文件保存到我们指定路径下
File fileName = new File("./src/test/java/screenshot/test1.png");
// 将截图文件保存到指定路径下
FileUtils.copyFile(srcFile, fileName);
driver.findElement(By.cssSelector("#\\31 > div > div > div > div > div.cos-row.row-text_Johh7.row_5y9Az > div > div:nth-child(2) > span:nth-child(1)"));
File srcFile2 = driver.getScreenshotAs(OutputType.FILE);
File fileName2 = new File("./src/test/java/screenshot/test2.png");
FileUtils.copyFile(srcFile2, fileName2);
closeDriver(driver);
}