提交 9df8ed0e authored 作者: 黄承天's avatar 黄承天

修复Chrome驱动无法运行的问题。判断预期结果前和每个步骤执行后会等待2秒以保证结果更精准。

上级 807f10f3
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</parent> </parent>
<groupId>com.zjty</groupId> <groupId>com.zjty</groupId>
<artifactId>automated-testing</artifactId> <artifactId>automated-testing</artifactId>
<version>0.0.4-SNAPSHOT</version> <version>0.0.5-SNAPSHOT</version>
<name>automated-testing</name> <name>automated-testing</name>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
...@@ -44,12 +44,7 @@ ...@@ -44,12 +44,7 @@
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>21.0</version> <version>28.2-jre</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency> </dependency>
<!--fastJson--> <!--fastJson-->
<dependency> <dependency>
......
...@@ -42,6 +42,7 @@ public class Report { ...@@ -42,6 +42,7 @@ public class Report {
/** /**
* 网站地址 * 网站地址
*/ */
@Column(columnDefinition = "text")
private String url; private String url;
/** /**
......
...@@ -22,26 +22,27 @@ public class Case { ...@@ -22,26 +22,27 @@ public class Case {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id; private Integer id;
/** /**
* 标题 * 标题
*/ */
String title; private String title;
/** /**
* 浏览器 * 浏览器
*/ */
String browser; private String browser;
/** /**
* 起始网站 * 起始网站
*/ */
String url; @Column(columnDefinition = "text")
private String url;
/** /**
* 步骤详情 * 步骤详情
*/ */
@Column(columnDefinition = "text") @Column(columnDefinition = "text")
String steps; private String steps;
} }
...@@ -16,12 +16,14 @@ import org.openqa.selenium.WebDriver; ...@@ -16,12 +16,14 @@ import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.zjty.automatedtesting.common.action.Action.*; import static com.zjty.automatedtesting.common.action.Action.*;
...@@ -64,7 +66,7 @@ public class SeleniumServiceImpl implements SeleniumService { ...@@ -64,7 +66,7 @@ public class SeleniumServiceImpl implements SeleniumService {
return new ChromeDriver(); return new ChromeDriver();
} else if (Objects.equals(browser, Browser.IE)) { } else if (Objects.equals(browser, Browser.IE)) {
System.setProperty("webdriver.ie.driver", CommonUtils.IE_EXE); System.setProperty("webdriver.ie.driver", CommonUtils.IE_EXE);
return new ChromeDriver(); return new InternetExplorerDriver();
} else { } else {
throw new RuntimeException("该浏览器不存在:" + browser); throw new RuntimeException("该浏览器不存在:" + browser);
} }
...@@ -94,12 +96,12 @@ public class SeleniumServiceImpl implements SeleniumService { ...@@ -94,12 +96,12 @@ public class SeleniumServiceImpl implements SeleniumService {
} else { } else {
throw new RuntimeException("不匹配的操作类型:" + step.getAction()); throw new RuntimeException("不匹配的操作类型:" + step.getAction());
} }
} }
if (isNull(step.getAssertion())){ if (isNull(step.getAssertion()) || Objects.equals(step.getAssertion(), "")) {
success = true; success = true;
message = "成功"; message = "成功";
}else { } else {
waitTime(2000L);
if (Objects.equals(step.getAssertion(), Assertion.VALUE)) { if (Objects.equals(step.getAssertion(), Assertion.VALUE)) {
if (nonNull(webElement)) { if (nonNull(webElement)) {
practice = webElement.getAttribute("value"); practice = webElement.getAttribute("value");
...@@ -109,16 +111,17 @@ public class SeleniumServiceImpl implements SeleniumService { ...@@ -109,16 +111,17 @@ public class SeleniumServiceImpl implements SeleniumService {
} else { } else {
throw new RuntimeException("不匹配的判断类型:" + step.getAssertion()); throw new RuntimeException("不匹配的判断类型:" + step.getAssertion());
} }
if (Objects.equals(practice, step.getExpected())) { if (Objects.equals(practice, step.getExpected())) {
success = true; success = true;
message = "成功"; message = "成功";
}else { } else {
success = false; success = false;
message = String.format("失败 实际与预期不符 预期:[%s] 实际:[%s] ", step.getExpected(), practice); message = String.format("失败 实际与预期不符 预期:[%s] 实际:[%s] ", step.getExpected(), practice);
} }
} }
waitTime(2000L);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
success = false; success = false;
message = String.format("出现错误:[%s]", e.getMessage()); message = String.format("出现错误:[%s]", e.getMessage());
log.error("出现错误:", e); log.error("出现错误:", e);
...@@ -176,4 +179,12 @@ public class SeleniumServiceImpl implements SeleniumService { ...@@ -176,4 +179,12 @@ public class SeleniumServiceImpl implements SeleniumService {
} }
private void waitTime(Long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
log.error("暂停等待时出现异常:" + e);
}
}
} }
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=favicon.ico><title>adapter</title><link href=static/css/serviceConfig.008ef5a3.css rel=prefetch><link href=static/js/serviceConfig.15284ede.js rel=prefetch><link href=static/css/app.f596fcc9.css rel=preload as=style><link href=static/css/chunk-vendors.717c90ab.css rel=preload as=style><link href=static/js/app.b64cb21f.js rel=preload as=script><link href=static/js/chunk-vendors.5f648e7a.js rel=preload as=script><link href=static/css/chunk-vendors.717c90ab.css rel=stylesheet><link href=static/css/app.f596fcc9.css rel=stylesheet></head><body><noscript><strong>We're sorry but adapter doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=static/js/chunk-vendors.5f648e7a.js></script><script src=static/js/app.b64cb21f.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=favicon.ico><title>adapter</title><link href=static/css/serviceConfig.f14ab9ce.css rel=prefetch><link href=static/js/serviceConfig.767d4038.js rel=prefetch><link href=static/css/app.f596fcc9.css rel=preload as=style><link href=static/css/chunk-vendors.717c90ab.css rel=preload as=style><link href=static/js/app.80934e38.js rel=preload as=script><link href=static/js/chunk-vendors.5f648e7a.js rel=preload as=script><link href=static/css/chunk-vendors.717c90ab.css rel=stylesheet><link href=static/css/app.f596fcc9.css rel=stylesheet></head><body><noscript><strong>We're sorry but adapter doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=static/js/chunk-vendors.5f648e7a.js></script><script src=static/js/app.80934e38.js></script></body></html>
\ No newline at end of file \ No newline at end of file
...@@ -8,4 +8,4 @@ spring.jpa.hibernate.ddl-auto=update ...@@ -8,4 +8,4 @@ spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=false spring.jpa.show-sql=false
spring.resources.static-locations=classpath:/adapter/ spring.resources.static-locations=classpath:/adapter/
\ No newline at end of file
## 自动测试-数据库设计
### 引言
本文档旨在描述自动测试系统中的数据库结构与设计。面向参与开发、测试、维护之人。
### 数据库
#### 概述
- 数据库选取MySQL。
- 数据库名:automated_testing
- 字符集:utf8
- 本数据库中的所有表均以“AUTO_TEST_”为前缀,如测试用例表名称为AUTO_TEST_CASE。
- 所有表以int自增类型为主键。
- 在表字段中集合形式的数据以json格式字符串存储。在调用时数据会在程序中进行转换后提供,或进行转换后保存。
#### 表结构设计
测试用例
表名:AUTO_TEST_CASE
| 字段名 | 类型 | 长度 | 是否可null | 备注 |
| ------- | ------- | ---- | ---------- | ------------------------------------------------------------ |
| id | int | | 否 | 主键 |
| title | varchar | 255 | 是 | 标题 |
| browser | varchar | 255 | 是 | 浏览器 Chrome、Firefox、IE |
| url | text | | 是 | 起始网站地址 |
| steps | text | | 是 | 各步骤详情,以json格式字符串形式存储。在程序中可以转换为集合类型。 |
测试报告
表名:AUTO_TEST_REPORT
| 字段名 | 类型 | 长度 | 是否可null | 备注 |
| -------- | ------- | ---- | ---------- | ------------------------------------------------------------ |
| id | int | | 否 | 主键 |
| case_id | int | | 是 | 所属测试用例的id |
| title | varchar | 255 | 是 | 标题 |
| browser | varchar | 255 | 是 | 浏览器 Chrome、Firefox、IE |
| url | text | | 是 | 起始网站地址 |
| measures | text | | 是 | 各步骤结果详情,以json格式字符串形式存储。在程序中可以转换为集合类型。 |
## 自动测试模块-设计文档
### 概述
自动测试模块是一个独立的微服务。
它的作用是对于特定的系统,在特定的运行环境下(包括操作系统、浏览器版本)是否可以正常运作
它的根本功能需求,可以概括为3步:
- 事先写好测试用例
- 由程序来执行测试用例
- 输出测试报告
### 总体设计
使用Spring Boot框架。
使用mysql作为数据库,存储测试用例数据与测试报告数据。
以restful接口形式对外提供其功能,包括:测试用例的增删改查、测试用例的执行、测试报告的查询。
数据格式采用json格式。
使用selenium组件实现测试用例的执行,该组件依赖对应的驱动程序。
#### 关于测试用例
- 测试用例就是类似于脚本,用于解释整个测试过程的数据。
- 测试用例是事先写好的,针对性很高,需要提前对网站系统做充分的了解,包括各种元素的:
- 定位方式(id、class、xpath、css)
- 操作类型(输入、点击、切换、初始)
- 预期结果(根据某个元素的值来判断是否输入成功、根据点击后的页面标题来判断是否跳转成功)。
- 测试用例的编写并非由客户来做,而是由专门的人来写。
- 程序根据测试用例来实现自动测试。
- 根据实际结果与期望结果是否一致来判断当前步骤是否成功,最终输出测试报告。
#### 关于Selenium
这是本项目所使用的组件,用它实现的网页自动化测试功能。
在运行时需要与浏览器版本对应的驱动程序(否则会出错),放在项目同目录下即可。
##### 驱动程序下载地址
**Chrome**
chromedriver:[点此打开](http://chromedriver.storage.googleapis.com/index.html)
**FireFox**
geckodriver:[点此打开](https://github.com/mozilla/geckodriver/releases)
**IE**
iedriverserver:[点此打开](http://selenium-release.storage.googleapis.com/index.html)
##### 驱动程序与浏览器版本关系
**Chrome**
| chromedriver版本 | 支持的Chrome版本 |
| ---------------- | ---------------- |
| v2.46 | v71-73 |
| v2.45 | v70-72 |
| v2.44 | v69-71 |
| v2.43 | v69-71 |
| v2.42 | v68-70 |
| v2.41 | v67-69 |
| v2.40 | v66-68 |
| v2.39 | v66-68 |
| v2.38 | v65-67 |
| v2.37 | v64-66 |
| v2.36 | v63-65 |
| v2.35 | v62-64 |
| v2.34 | v61-63 |
| v2.33 | v60-62 |
| v2.32 | v59-61 |
**FireFox***
具体在其下载页面查看。在各个版本的说明中会提及其对应的浏览器版本。
例如:0.26.0版本在其第一行说明中提及:
```
Note that with this release the minimum recommended Firefox version
has changed to Firefox ≥60.
```
要求FireFox版本大于60。
地址:[点此打开](https://github.com/mozilla/geckodriver/releases)
**IE**
IE浏览器的版本对应这个并没有找到具体所对应的版本,**一般用2.5版本**比较好一些(ie11)。
### 相关接口
#### 概览
测试用例:
| url | 类型 | 描述 |
| --------------------- | ------ | ------------------------------------------- |
| /testcase/execute | POST | 执行测试用例 |
| /testcase/save | POST | 保存测试用例(不附带id为新增 附带id为修改) |
| /testcase/get | GET | 获取所有测试用例 |
| /testcase/get/{id} | GET | 根据id获取单个测试用例 |
| /testcase/delete/{id} | DELETE | 根据id删除单个测试用例 |
测试报告:
| url | 类型 | 描述 |
| ------------------------- | ------ | ------------------------------------------------------------ |
| /report/get | GET | 获取所有测试报告 |
| /report/get/case/{caseId} | GET | 根据所属测试用例的id查询测试报告.返回由该测试用例执行后生成的所有报告. |
| /report/get/{id} | GET | 根据id获取单个测试报告 |
| /report/delete/{id} | DELETE | 根据id删除单个测试报告 |
| /report/download/{id} | GET | 根据id下载测试报告 |
#### 详情
##### 执行测试用例
URL:/testcase/execute
类型:POST
请求数据:
```json
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox", //浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com",//网站地址
"steps": [ //测试步骤集合
{
"order": 1, //步骤序号 从小到大按顺序执行步骤
"title": "输入关键字",//步骤标题
"type": "id", //元素的定位类型:xpath css id name
"key": "kw",//元素的定位关键值
"action": "input", //元素的操作类型 目前有2:input-输入 click-点击
"value": "ty",//输入值 操作为input时需要
"assertion":"value", //判断类型 目前有2:value-当前元素的值 title-页面标题
"expected": "ty" //期望结果
}
{
"order": 2,
"title": "点击搜索按钮",
"type": "id",
"key": "su",
"action": "click",
"assertion":"value",
"expected": "ty"
}
]
}
```
返回数据:
```json
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox",//浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com"//网站地址
"measures": [ //步骤详情
{
"order": 1,//步骤序号
"title": "输入关键字",//步骤标题
"assertion":"value", //判断类型 同测试用例
"expected": "ty", //期望值
"practice": "ty", //实际值
"success": true, //是否成功
"message": "成功." //相关信息
}
{
"order": 2,
"title": "点击搜索按钮",
"assertion":"title",
"expected": "ty",
"practice": "not found",
"success": false,
"message": "失败. 期望:ty. 实际:not found."
}
],
}
```
##### 保存测试用例(不附带id为新增 附带id为修改)
URL:/testcase/save
类型:POST
请求数据:
```json
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox", //浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com",//网站地址
"steps": [ //测试步骤集合
{
"order": 1, //步骤序号 从小到大按顺序执行步骤
"title": "输入关键字",//步骤标题
"type": "id", //元素的定位类型:xpath css id name
"key": "kw",//元素的定位关键值
"action": "input", //元素的操作类型 目前有2:input-输入 click-点击
"value": "ty",//输入值 操作为input时需要
"assertion":"value", //判断类型 目前有2:value-当前元素的值 title-页面标题
"expected": "ty" //期望结果
}
{
"order": 2,
"title": "点击搜索按钮",
"type": "id",
"key": "su",
"action": "click",
"assertion":"value",
"expected": "ty"
}
]
}
```
返回数据:
```json
{
"message","success"
}
```
##### 获取所有测试用例
URL:/testcase/get
类型:GET
请求数据:无
返回数据:
```json
[
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox", //浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com",//网站地址
"steps": [ //测试步骤集合
{
"order": 1, //步骤序号 从小到大按顺序执行步骤
"title": "输入关键字",//步骤标题
"type": "id", //元素的定位类型:xpath css id name
"key": "kw",//元素的定位关键值
"action": "input", //元素的操作类型 目前有2:input-输入 click-点击
"value": "ty",//输入值 操作为input时需要
"assertion":"value", //判断类型 目前有2:value-当前元素的值 title-页面标题
"expected": "ty" //期望结果
}
{
"order": 2,
"title": "点击搜索按钮",
"type": "id",
"key": "su",
"action": "click",
"assertion":"value",
"expected": "ty"
}
]
}
]
```
##### 根据id获取单个测试用例
URL:/testcase/get/{id}
类型:GET
请求数据:附带在url中
返回数据:
```json
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox", //浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com",//网站地址
"steps": [ //测试步骤集合
{
"order": 1, //步骤序号 从小到大按顺序执行步骤
"title": "输入关键字",//步骤标题
"type": "id", //元素的定位类型:xpath css id name
"key": "kw",//元素的定位关键值
"action": "input", //元素的操作类型 目前有2:input-输入 click-点击
"value": "ty",//输入值 操作为input时需要
"assertion":"value", //判断类型 目前有2:value-当前元素的值 title-页面标题
"expected": "ty" //期望结果
}
{
"order": 2,
"title": "点击搜索按钮",
"type": "id",
"key": "su",
"action": "click",
"assertion":"value",
"expected": "ty"
}
]
}
```
##### 根据id删除单个测试用例
URL:/testcase/delete/{id}
类型:DELETE
请求数据:附带在url中
返回数据:
```json
{
"message","success"
}
```
##### 获取所有测试报告
URL:/report/get
类型:GET
请求数据:无
返回数据:
```json
[
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox",//浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com"//网站地址
"measures": [ //步骤详情
{
"order": 1,//步骤序号
"title": "输入关键字",//步骤标题
"assertion":"value", //判断类型 同测试用例
"expected": "ty", //期望值
"practice": "ty", //实际值
"success": true, //是否成功
"message": "成功." //相关信息
}
{
"order": 2,
"title": "点击搜索按钮",
"assertion":"title",
"expected": "ty",
"practice": "not found",
"success": false,
"message": "失败. 期望:ty. 实际:not found."
}
],
}
]
```
##### 根据测试用例id获取所属测试报告
URL:/report/get/case/{caseId}
类型:GET
请求数据:附带在url中
返回数据:
```json
[
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox",//浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com"//网站地址
"measures": [ //步骤详情
{
"order": 1,//步骤序号
"title": "输入关键字",//步骤标题
"assertion":"value", //判断类型 同测试用例
"expected": "ty", //期望值
"practice": "ty", //实际值
"success": true, //是否成功
"message": "成功." //相关信息
}
{
"order": 2,
"title": "点击搜索按钮",
"assertion":"title",
"expected": "ty",
"practice": "not found",
"success": false,
"message": "失败. 期望:ty. 实际:not found."
}
],
}
]
```
#####
##### 根据id获取单个测试报告
URL:/report/get/{id}
类型:GET
请求数据:附带在url中
```json
{
"id":1,//主键
"title":"baidu",//标题
"browser": "firefox",//浏览器:谷歌-chrome 火狐-firefox IE-ie
"url": "http://www.baidu.com"//网站地址
"measures": [ //步骤详情
{
"order": 1,//步骤序号
"title": "输入关键字",//步骤标题
"assertion":"value", //判断类型 同测试用例
"expected": "ty", //期望值
"practice": "ty", //实际值
"success": true, //是否成功
"message": "成功." //相关信息
}
{
"order": 2,
"title": "点击搜索按钮",
"assertion":"title",
"expected": "ty",
"practice": "not found",
"success": false,
"message": "失败. 期望:ty. 实际:not found."
}
],
}
```
##### 根据id删除单个测试报告
URL:/report/delete/{id}
类型:DELETE
请求数据:附带在url中
返回数据:
```json
{
"message","success"
}
```
##### 根据id下载测试报告
URL:/report/download/{id}
类型:GET
请求数据:附带在url中
返回数据:文件输出流
### 更新记录
- 2020.2.10:最初的用selenium组件简单实现了自动化测试,并定义好了最初的测试用例和测试报告数据结构。此时只有一个执行测试用例接口。
- 2020.2.12:考虑到需要对测试用例的存储以及管理,使用spring-data-jpa接入MySQL数据库,并增加测试用例数据的增、改、删、查的接口。
- 2020.2.14:测试用例增加标题字段。测试步骤的操作类型增加switch(切换至指定的元素)和home(回到起始页面),由2种增加至4种。
- 2020.2.17:增加测试报告的数据库存储功能。在测试用例执行后自动将测试报告存至数据库。增加测试报告的查、删接口。
- 2020.2.20:增加下载测试报告的功能。以html文件的形式提供下载。
- 2020.2.24:测试用例执行结束后自动关闭浏览器。增加接口根据测试用例的id查询其所属的测试报告。预期属性改变为可选项,若没有预期属性则会低限度的以是否出现异常来判断步骤是否成功。
- 2020.2.25:修复Chrome驱动无法运行的问题。判断预期结果前和每个步骤执行后会等待2秒以保证结果更精准。
### 遗留问题
- 该项目本身所依赖的运行环境并非国产化环境,例如数据库是使用非国产数据库的MySQL。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论