一、自动化测试概念
1.自动化
1.1 自动化概念
自动化的主要目的就是用来进行回归测试。
1.1.1 回归测试
软件有多个版本需要进行功能的整体回归。
为了避免新增功能影响到历史的功能需要进行功能回归。
【常见面试题】

测试工作:需求分析---测试计划---测试设计---执行测试(手工测试/自动化)---测试评估---上线
- 所以只有在执行测试这一环节才会使用自动化测试。
1.2 自动化分类
1.2.1 接口自动化
围绕接口设计测试用例
比如请求接口,可以构造不同的请求方法、请求参数、请求头来校验接口返回值是否满足预期
1.2.2 UI自动化

也称为界面测试,常见的UI自动化测试包含web自动化测试、移动端自动化测试等
- 移动端自动化测试
就是部署在手机上的应用程序。移动端自动化测试主要测试程序部署在手机上能否按照预期的结果进行操作。
移动端测试通常不是将程序部署在移动端上进行测试,而是需要安装模拟器,在电脑上编写自动化测试脚本代码对模拟器上的软件进行操作。
移动端测试难度相对较大,移动端测试的稳定性要比接口自动化测试和web自动化测试的稳定性要差很多,原因主要是移动端测试收到的环境影响比较多,诸如以下:
- web自动化测试
模拟人在浏览器上的操作行为,自动的打开浏览器,访问百度首页,并进行一系列的搜索和验证等行为。
1.3 自动化测试金字塔

2.web自动化测试
2.1 驱动
程序想要打开web浏览器就需要安装web驱动(即WebDriver),WebDriver以本地化方式驱动浏览器。

手动下载驱动连接:

2.1.1 安装驱动管理
驱动管理
WebDriverManager是一个开源Java库 ,以完全自动化的方式对SeleniumWebDriver所需的驱动程序(如chromedriver、geckodriver、msedgedriver等)进行管理(即下载、设置和维护),自版本5起,WebDriverManager还提供了其他相关功能,如发现本地系统中安装的浏览器的能力,构建WebDriver对象 (如ChromeDriver、FirefoxDriver、EdgeDriver等)
3.Selenium
3.1 编写第一个自动化测试脚本
1)安装selenium库
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
2)使用selenium编写代码
public class firstTest {
// public void aaa(){
// //下载驱动------驱动在哪里~~~
// WebDriverManager.chromedriver().setup();
//
// }
//百度搜索关键词
public void search() throws InterruptedException {
// 1、打开浏览器(驱动)
WebDriverManager.chromedriver().setup();
// 2)添加浏览器配置
ChromeOptions options = new ChromeOptions();
//允许访问所有链接
options.addArguments("--remote-allow-origins=*");
WebDriver driver = new ChromeDriver(options);
// 2、输入网址:https://www.baidu.com/
driver.get("https://www.baidu.com/");
// 3、找到输入框,输入关键词:"迪丽热巴"
driver.findElement(By.cssSelector("#chat-textarea")).sendKeys("迪丽热巴");
// 4、找到百度一下按钮,并点击
driver.findElement(By.cssSelector("#chat-submit-button")).click();
// 5、关闭浏览器
driver.quit();
}
}
3.2 selenium+驱动+浏览器的工作原理
- 通过selenium编写的自动化脚本代码中在ChromeDriverService中创建一个服务
- 通过创建好的服务打开webdriver,安装在本地的驱动服务IP为localhost,PORT为ChromeDriverService中创建的端口号,该服务地址为selenium向webdriver发送请求的服务地址。
- 向浏览器驱动程序发送HTTP请求,浏览器驱动程序解析请求,打开浏览器,并获得seSsionid,如果再次对浏览器操作需携带此id
- 打开浏览器后,所有的selenium的操作(访问地址,查找元素等)均通过创建好的服务链接到webdriver,然后使用execute发送请求
- 驱动收到请求并对请求进行解析,转成浏览器能够解析的脚本并发送给浏览器,浏览器通过请求的内容执行对应动作
- 浏览器再把执行的动作结果通过浏览器驱动程序返回给测试脚本
驱动需要接收、解析请求,发送请求给浏览器,那么驱动到底是什么样的角色呢?验证方式:
1)执行selenium编写的自动化脚本代码中,可以在终端看到创建的驱动服务地址。

二、自动化测试常用函数
1.元素的定位
web自动化测试的操作核心是能够找到页面对应的元素,然后才能对元素进行具体的操作。常见的元素定位方式非常多,如id,classname,tagname,xpath,cssSelector常用的主要由cssSelector和xpath
启动:
start();
driver.get("https://www.baidu.com");
查找元素一
WebElement findElement(By by); //查找一个元素
查找 定位

常用的为cssSelector和xpath,因为其是唯一的,其他的元素可能导致查找结果不唯一
【例子】
WebElement element = driver.findElement(By.xpath("//*[@id='wp']")); // 接收
- 括号里的内容需要打开前端开发者工具来定位页面元素
- 鼠标右键点击"检查"
- 快捷键
F12

点击打开,灰色默认关闭
- 定位需要的元素后根据相应的函数放到其括号内

函数括号里的内容是字符串,要加双引号
查找元素二
List<WebElement> findElements(By by); //查找多个元素【相同元素】

【例子】
打印新闻列表(10个 li 里的标题内容)

打印结果:

会发现打印结果有问题,是因为li标签下还有许多元素,要想只打印标题,优化路径如下:
路径:

因列表新闻标题在span标签下,会发现span标签有两个,标题在第二个里,加上[2]即可
最终打印结果:

定位元素
1.1 cssSelector

WebElement element = driver.findElement(By.cssSelector("#chat-submit-button"));
选择器的功能:选中页面中指定的标签元素
选择器的种类分为基础选择器和复合选择器,常见的元素定位方式可以通过id选择器和子类选择器来进行定位。
定位百度首页的"百度热搜"元素,可以使用通过id选择器和子类选择器进行定位:#s-hotsearch-wrapper> div
【例如】
"搜索输入框元素":#kw
"百度一下按钮":#Su
1.2 xpath
XML路径语言,不仅可以在XML文件中查找信息,还可以在HTML中选取节点。
xpath使用路径表达式来选择xml文档中的节点,xpath语法中:
1.2.1 获取HTML页面所有的节点
//*
1.2.2 获取HTML页面指定的节点
// [指定节点]
例如:
//ul : 获取HTML页面所有的ul节点
//input:获取HTML页面所有的input节点
1.2.3 获取一个节点中的直接子节点
/
例如:
//span/input:获取span下的input标签
1.2.4 获取一个节点的父节点
..
例如:
//input/.. 获取input节点的父节点【动态节点有时会用】
1.2.5 实现节点属性的匹配
@...
例如:
//*[@id='kw'] 匹配HTML页面中id属性为kw的节点
@:表示HTML页面中某个属性的结点
1.2.6 使用指定索引的方式获取对应的节点内容
注意: xpath的索引是从1开始的。
百度首页通过://div/ul//i[3]定位到第三个百度热搜标签
更便捷的生成selector /xpath的方式:右键选择复制"Copy selector /xpath"
注意:元素的定位方法必须唯一。
案例:如果想要匹配到百度首页指定的新闻文本或者节点集,直接使用 #hotsearch-content-wrapper > li 能够满足吗?
问题:既然可以手动复制 selector /xpath的方式,为什么还有了解语法?
手动复制的selector/xpath表达式并不一定满足上面的唯一性的要求,有时候也需要手动的进行修改表达式
案例:百度首页(需要登陆百度账号)右侧的热搜,复制li标签下的a标签,复制好的的selector为:#title-content,xpath为://*[@id="title-content"],动操作手动复制的表达式是否唯一?
1.3 总结
xpath子节点写法:

,使用的是 /
选择器子节点写法:

使用的是 >
【示例】

如果程序出现错误可能出现的情况有以下几点:

2.操作测试对象
获取到了页面的元素之后,接下来就是要对元素进行操作了。常见的操作有点击、提交、输入、清除、获取文本。【基本上是WebElement接口里提供的方法】
2.1 点击/提交对象
click()
//找到百度⼀下按钮并点击
driver.findElement(By.cssSelector("#su")).click();
分开写:
//先接受查找元素的返回值
WebElement ele = driver.findElement(By.cssSelector("#su")).click();
ele.click(); //调用
页面上大多元素都可点击,只有特殊元素不可点击(被隐藏的,不可见的元素),如果将其写入程序,会出现报错:元素不可交互
2.2 模拟按键输入
sendKeys("")
-
在输入框输入,所以要找到输入框对应的元素(如果不是输入框对应的元素,不会报错但没有反应;在不可交互的元素中,会报错)
driver.findElement(By.cssSelector("#kw")).sendKeys("输⼊⽂字");
2.3 清除文本内容
输入文本后又想换一个新的关键词,这里就需要用到clear()
如果不清除,两次连续sendKeys("")输入关键词,效果为追加,所以需先清除clear(),再输入
driver.findElement(By.cssSelector("#kw")).sendKeys("我爱游戏");
driver.findElement(By.cssSelector("#kw")).clear();
driver.findElement(By.cssSelector("#kw")).sendKeys("我爱学习");
2.4 获取文本信息
获取文本信息:getText()【获取的是字符串,所以下面为String字符串类型来接收】
String bdtext = driver.findElement(
By.xpath("//*[@id="title-content"]/span[1]")).getText();
System.out.println("打印的内容是:"+bdtext);
问题:是否可以通过 getText()获取到"百度一下按钮" 上的文字"百度一下"呢?
注意:文本和属性值不要混淆了。
获取属性值需要使用方法 getAttribute("属性名称");
String getAttribute(String:属性值)


2.5 获取当前页面标题
getTitle()返回值是String
eg:
String title = driver.getTitle(); //接收返回值
2.6获取当前页面URL
getCurrentUrl()返回值是String
String url = driver.getCurrentUrl();
3.窗口
打开一个新的页面之后获取到的title和URL仍然还是前一个页面的?
当我们手工测试的时候,我们可以通过眼睛来判断当前的窗口是什么,但对于程序来说它是不知道当前最新的窗口应该是哪一个。对于程序来说它怎么来识别每一个窗口呢?每个浏览器窗口都有一个唯一的属性句柄(handle)来表示,我们就可以通过句柄来切换
3.1 切换窗口
1)获取当前页面句柄:
driver.getWindowHandle() ;
2)获取所有页面句柄:
driver.getWindowHandles()
3)切换当前句柄为最新页面:
String curWindow = driver.getWindowHandle();
Set<String> allWindow = driver.getWindowHandles();
for( String w : allWindow){
if(!win.equals(curWindow)){
driver.switchTo().window(w);
}
}
结果:

注意:执行了 driver.close()之前需要切换到未被关闭的窗口

3.2 窗口设置大小
1)窗口的大小设置
//窗⼝最⼤化
driver.manage().window().maximize();
//窗⼝最⼩化
driver.manage().window().minimize();
//全屏窗⼝
driver.manage().window().fullscreen();
//⼿动设置窗⼝⼤⼩
driver.manage().window().setSize(new Dimension(1024, 768));
3.3 窗口切换
去掉等待后,获取跳转后的页面元素失败
//获取所有句柄
//获取当前停留⻚⾯句柄
String curWindow = driver.getWindowHandle();
Set<String> allWindow = driver.getWindowHandles();
for( String w : allWindow){
if(w!=curWindow){
driver.switchTo().window(w);
}
}
3.4 屏幕截图
我们的自动化脚本一般部署在机器上自动的去运行,如果出现了报错,我们是不知道的,可以通过抓拍来记录当时的错误场景
屏幕截图方法需要额外导入包:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
File file = ((TakesScreenshot)webDriver).getScreenshotAs(OutputType.FILE);
//截图要以文件形式存储,存储成.png形式,用下面方法,使用前导入上面的包
//括号里面分别是(原文件,保存的文件以及文件名[所以要new一个文件,new的文件括号里是文件名])
FileUtils.copyFile(file, new File(filename));
代码演示
// 简单版本
File srcfile = driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(srcfile, new File("my.png"));
// 高阶版本
List<String> times = getTime();
// 生成的文件夹路径: /src/test/autotest-2022-08-01/goodsbrosrer-20220801-214130.png
String filename = "./src/test/autotest-" + times.get(0) + "/" + str + "-" + times.get(1) + ".png";
File srcfile = driver.getScreenshotAs(OutputType.FILE);
// 把截图放到指定的路径下
FileUtils.copyFile(srcfile, new File(filename));
3.5 关闭窗口
关闭浏览器: driver.quit();
关闭标签页(窗口): driver.close();
driver.close();
注意:窗⼝关闭后driver要重新定义
4.等待
通常代码执行的速度比页面渲染的速度要快,如果避免因为渲染过慢出现的自动化误报的问题呢?可以使用selenium中提供的三种等待方法:
4.1 强制等待
Thread.sleep ()【阻塞式等待】
优点:使用简单,调试的时候比较有效
缺点:影响运行效率,浪费大量的时间
4.2 隐式等待
隐式等待是一种智能等待,他可以规定在查找元素时,在指定时间内不断查找元素。如果找到则代码继续执行,直到超时没找到元素才会报错。
**implicitlyWait ()**参数:Duration类中提供的毫秒、秒、分钟等方法
示例:
//隐式等待1000毫秒
driver.manage().timeouts().implicitlyWait(Duration.ofMillis(1000));
//隐式等待5秒
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
隐式等待作用域是整个脚本的所有元素 。即只要driver对象没有被释放掉(driver.quit()),隐式等待就一直生效。 【所以在写代码时,隐式等待只需要写一次,后面的查找元素都会遵循隐式等待】
优点:智能等待,作用于全局
局限性:仅仅作用在页面元素上(比如页面弹窗无法处理)
4.3 显示等待
显示等待也是一种智能等待,在指定超时时间范围内只要满足操作的条件就会继续执行后续代码
new WebDriverWait(driver, Duration.ofSeconds(3)).until(express)
【注释】
new WebDriverWait**需要接收,**写法如下:
WebDriverWait wait = new WebDriverWait(driver,Duration.ofSeconds(3));
-
wait.until($Spress)- 顾名思义,就是等待到满足什么条件为止。所以括号里的$Spress是一个表达式,表达式结果要么为真,要么为假,也就是true或者fails(是布尔类型)。为真的话,满足条件,等待立即结束。如果为假,显示等待就会报错。
- driver,Duration.ofSeconds(3):规定的超时时间
$Spress:涉及到selenium.support.ui.ExpectedConditions包下的ExpectedConditions类
返回值:boolean
示例:
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#id")));
ExpectedConditions预定义方法的一些示例:
-
elementToBeClickable(By locator)-- 用于检查元素的期望是可见的并已启用,以便可以单击它。 -
textToBe(ByLocator, String str)- 检查元素。 -
presenceOfElementLocated(ByLocator)-- 检查页面的 DOM 上是否存在元素。 -
urlToBe(java.lang.String url)-- 检查当前页面的 URL 是一个特定的 URL。WebDriverWait wait = new WebDriverWait(driver,Duration.ofSeconds(10));
boolean ispass = wait.until(ExpectedConditions.textToBe(By.cssSelector("#s-top-left > a:nth-child(1)"), "新闻"));
if(ispass){
System.out.println("测试通过");
}else {
System.out.println("测试失败");
}
优点:显示等待是智能等待,可以自定义显示等待的条件,操作灵活
缺点:写法复杂
隐式等待和显示等待一起使用效果如何呢?
测试一下
//隐式等待设置为5s,显示等待设置为10s,那么结果会是5+10=15s吗?
SimpleDateFormat sim =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sim.format(System.currentTimeMillis()));
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
driver.findElement(By.cssSelector("#hotsearch-content-wrapper > li:nth-child(1) > a > span.title-content"));
WebDriverWait wait = new WebDriverWait(driver,Duration.ofSeconds(10));
try{
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#hotsearch-content-wrapper > li:nth-child(1) > a > span.title-content")));
catch (Exception e) {
System.out.println("nosuelement!");
}
System.out.println(sim.format(System.currentTimeMillis()));
结果:重复多次,最终打印的等待时间有10s、11s...
结论:不要混合隐式和显示等待(元素),可能会导致不可预期的等待时间。

5. 浏览器导航
常见操作:
-
打开网站
// 更长的方法
driver.navigate().to("https://selenium.dev");
// 简洁的方法
driver.get("https://selenium.dev"); -
浏览器的前进、后退、刷新
driver.navigate().back();
driver.navigate().forward();
driver.navigate().refresh();
案例:百度首页测试在线工具 - 你的工具箱标签入口
6.弹窗
弹窗是在页面是找不到任何元素的【无法对弹窗进行元素定位】
这种情况怎么处理?使用selenium提供的Alert接口
6.1 警告弹窗+确认弹窗

Alert alert = driver.switchTo().alert();
//确认
alert.accept();
//取消
alert.dismiss();
//获取弹窗上文本信息
alert.getText();
- 警告弹窗只有一个按钮,确定和取消都能关闭弹窗
6.2 提示弹窗

Alert alert = driver.switchTo().alert(); //切换弹窗
alert.sendKeys("hello"); //输入文本消息
alert.accept(); //确认
alert.dismiss(); //取消
【代码书写】
警告弹窗****代码书写步骤:
- 获取网址
- 触发弹窗(点击)
- 切换到弹窗
- 获取弹窗上的文本
- 点击确认/取消按钮


确认弹窗****代码书写:

提示弹窗****代码书写:

7. 文件上传
点击文件上传的场景下会弹窗系统窗口,进行文件的选择。
selenium无法识别非web的控件,上传文件窗口为系统自带,无法识别到窗口元素
但是可以使用sendkeys来上传指定路径的文件,达到的效果是一样的

-
不是有所有场景都是这样上传文件,具体场景具体对待
WebElement ele = driver.findElement(By.cssSelector("body > div > div > input[type=file]"));
ele.sendKeys("D:\selenium2html\selenium2html\upload.html");
8. 浏览器参数设置
1) 设置无头模式【后台执行】
ChromeOptions options = new ChromeOptions(); //添加xxx浏览器配置:xxxOptions
options.addArguments("--headless"); //允许访问所有链接
ChromeDriver driver = new ChromeDriver(options);
driver.get("https://www.baidu.com");
driver.quit();
【代码书写】

2) 设置浏览器加载策略
【在跳转页面时,有时候会很慢,是因为有很多资源还未加载出来】

options.setPageLoadStrategy(PageLoadStrategy.NONE);


三、自动化测试实战篇
- 在使用标签无法获取时,使用原生JS

- Selenium 里的 Actions 接口的相关操作,比如下图所示:
- 键盘操作键盘操作 | Selenium

以博客系统为例,基于web项目进行完整的自动化实战。
一个个测试脚本就是一个个的测试用例,所以第一步就是要编写测试用例
1.自动化实施步骤
1.1 编写测试用例
基于 功能+页面 进行编写

1.2 自动化测试脚本开发

需导入的包
、<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
common/AutotestUtils.java


//创建driver对象

public class AutotestUtils {
public static EdgeDriver driver;
// 创建驱动对象
public static EdgeDriver createDriver() {
// 驱动对象已经创建好了/没有创建
if (driver == null) {
driver = new EdgeDriver();
// 创建隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return driver;
}
public AutotestUtils(String url) {
EdgeDriver driver = createDriver();
driver.get(url);
}
public List<String> getTime() {
// 文件能不能按照天的维度按文件夹进行保存
// 文件格式 20230212-123030毫秒
SimpleDateFormat sim1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
SimpleDateFormat sim2 = new SimpleDateFormat("yyyyMMdd");
String filename = sim1.format(System.currentTimeMillis());
String dirname = sim2.format(System.currentTimeMillis());
List<String> list = new ArrayList<>();
list.add(dirname);
list.add(filename);
return list;
}
/**
* 获取屏幕截图,把所有的用例执行的结果保存下来
*/
public void getShot(String str) throws IOException {
List<String> list = getTime();
// ./指的是当前的项目路径下,也就是BlogAutoTest下
// ./src/test/java/com/blogwebAutoTest/dirname/filename
// ./src/test/java/com/blogwebAutoTest/20230212/logintest_20230212-123030毫秒.png
String filename = "./src/test/java/com/blogwebAutoTest/" + list.get(0) + "/" + str + "_" + list.get(1) + ".png";
File srcfile = driver.getScreenshotAs(OutputType.FILE);
// 把屏幕截图生成的文件放到指定的路径
FileUtils.copyFile(srcfile, new File(filename));
}
}
tests/BlogLoginTest.java
public class BlogLoginTest extends AutotestUtils {
public BlogLoginTest(String url) {
super(url);
}
void loginPageLoadRight() throws IOException, IOException {
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
driver.findElement(By.xpath("/html/body/div[1]/a[2]"));
getScreenShot(getClass().getName());
}
/**
* 检查正常登录情况
*/
void loginSuc() throws InterruptedException, IOException {
// 这三步是登录的步骤结束了,能不能保证登录是成功的呢??
driver.findElement(By.cssSelector("username")).clear();
driver.findElement(By.cssSelector("epassword")).clear();
driver.findElement(By.cssSelector("username")).sendKeys("admin");
driver.findElement(By.cssSelector("epassword")).sendKeys("123");
driver.findElement(By.cssSelector("submit")).click();
// 对登录结果进行检测,如果跳转到了博客列表页才算是登录成功了
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a"));
getScreenShot(getClass().getName());
driver.navigate().back();
}
void loginFail() throws IOException {
driver.findElement(By.cssSelector("username")).clear();
driver.findElement(By.cssSelector("epassword")).clear();
driver.findElement(By.cssSelector("username")).sendKeys("admin");
driver.findElement(By.cssSelector("epassword")).sendKeys("1111");
driver.findElement(By.cssSelector("submit")).click();
// 等登录失败的结果进行检测,不能仅通过页面来校验结果,因为登录成功的结果页也有 body元素
String expect = "婴儿奶瓶喂养漫唠叨开开心心E1E4";
String actual = driver.findElement(By.cssSelector("body")).getText();
getScreenShot(getClass().getName());
System.out.println("expect:" + expect);
System.out.println("actual:" + actual);
assert expect.equals(actual);
driver.navigate().back();
driver.quit();
}
}
tests/BlogListTest.java
public class BlogListTest extends AutotestUtils {
public BlogListTest(String url) {
super(url);
}
/**
* 博客列表页可以正常显示
*/
void listPageLoadRight() throws IOException {
driver.findElement(By.cssSelector("body > div.container > div.left > div > div:nth-child(4) > span:nth-child(1)"));
driver.findElement(By.cssSelector("body > div.container > div.left > div > div:nth-child(4) > span:nth-child(2)"));
getScreenShot(getClass().getName());
}
}
开启idea中的断言功能

1.3 将自动化测试补充至测试报告
测试报告模板:
