目录
初识selenium是建立在已经有一个完整的待测试的项目的基础上的
项目简介
如图,是待测试的项目,此项目是在idea上创建的Spring boot项目,当然项目用到的jar包都在Maven的管理下。
环境搭建
接下来搭建测试环境,先基于Maven导入jar包,没有Maven的,可以直接去网上下一个selenium-java的jar包。
<dependency>
<groupId>org.seleniumhq.seleniumgroupId>
<artifactId>selenium-javaartifactId>
<version>2.53.0version>
dependency>
最下面这行圈圈转完就表示导入完成了
个人比较喜欢用chrome浏览器,需要使用其他浏览器测试的需下载对应的浏览器驱动
驱动版本
chromedriver的版本要与你使用的chrome的版本对应,截至今日,chromedriver.exe与chrome浏览器的对应版本如下
chromedriver.exe的版本 | chrome浏览器的版本 |
---|---|
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 |
v2.31 | v58-60 |
v2.30 | v58-60 |
v2.29 | v56-58 |
v2.28 | v55-57 |
v2.27 | v54-56 |
v2.26 | v53-55 |
v2.25 | v53-55 |
v2.24 | v52-54 |
v2.23 | v51-53 |
v2.22 | v49-52 |
v2.21 | v46-50 |
v2.20 | v43-48 |
v2.19 | v43-47 |
v2.18 | v43-46 |
v2.17 | v42-43 |
v2.13 | v42-45 |
v2.15 | v40-43 |
v2.14 | v39-42 |
v2.13 | v38-41 |
v2.12 | v36-40 |
v2.11 | v36-40 |
v2.10 | v33-36 |
v2.9 | v31-34 |
v2.8 | v30-33 |
v2.7 | v30-33 |
v2.6 | v29-32 |
v2.5 | v29-32 |
v2.4 | v29-32 |
下载地址
点击进入chromedriver.exe的下载地址
项目已经部署到tomcat服务器上了,现在编写打开浏览器访问项目登录页面并进行登录操作。
创建浏览器
上一篇下载了chromedriver.exe,找到这个驱动的位置,在测试类里设置驱动所在的位置,代码如下
//设置驱动所在位置
System.setProperty("webdriver.chrome.driver", "E:\\software\\driver\\chromedriver.exe");
设置好驱动后,引用驱动就可以打开浏览器了
//引用谷歌浏览器驱动
WebDriver driver = new ChromeDriver();
当然也可以通过ChromeOptions设置chrome的属性(不设置的话,默认浏览器一打开只有半屏)
//设置chrome的属性
ChromeOptions options = new ChromeOptions();
List<String> op = new ArrayList<String>();
//实现窗口最大化
op.add("--start-maximized");
//实现全屏
//op.add("start-fullscreen");
//op.add("allow-running-insecure-content");
//op.add("--test-type");
options.addArguments(op);
//引用谷歌浏览器驱动
WebDriver driver = new ChromeDriver(options);
还可以通过manage().window().setSize()自定义浏览器窗口大小
//自定义浏览器窗口大小
driver.manage().window().setSize(new Dimension(375, 812));
按照自己的喜好或者需求弄好后就可以打开浏览器界面了
//打开界面
driver.get("http://localhost/huinongloan2/");
模拟登录操作
通过driver.findElement()获取页面元素,个人比较喜欢使用css格式,即cssSelector
//输入账号
driver.findElement(By.cssSelector(".login-phone input")).sendKeys(loanerLoginVo.getLoanerPhone());
//输入密码
driver.findElement(By.cssSelector(".login-password input")).sendKeys(loanerLoginVo.getLoanerPassword());
填入手机号和密码之后,获取登录按钮并模拟点击操作
//点击登录
driver.findElement(By.cssSelector(".login-submit button")).click();
这时可以运行一下代码进行测试了
上一篇通过chrome进行了登录操作,现在来验证一下是否登录成功。
和第一天一样,在pom.xml文件下写上引入testng的jar包的代码并导入
<dependency>
<groupId>org.testnggroupId>
<artifactId>testngartifactId>
<version>6.14.3version>
<scope>testscope>
dependency>
用例执行前后操作
自定义测试用例执行前后的操作,以下代码在用例执行前后打印和执行后关闭浏览器进程及驱动
@BeforeClass
public void beforeClass() {
System.out.println("-------用例执行前-------");
}
@AfterClass
public void afterClass(){
System.out.println("-------用例执行后-------");
//关闭浏览器进程及驱动
driver.close();
}
判断元素是否存在以判断是否登录成功,参考了以下博文的第一段代码
Selenium2(WebDriver)_如何判断WebElement元素对象是否存在
public boolean doesWebElementExist(WebDriver driver, By selector)
{
try
{
driver.findElement(selector);
return true;
}
catch (NoSuchElementException e)
{
return false;
}
}
判断特定元素是否存在+断言以校验是否登录成功
if(doesWebElementExist(driver,By.cssSelector(".title2"))){
//断言 校验是否登录成功
Assert.assertEquals(driver.findElement(By.cssSelector(".title2")).getText(),loanerLoginVo.getLoanerPhone());
} else {
throw new RuntimeException("登录失败");
}
测试代码
测试代码如下
/**
* @author xian
* @date 2018/8/2 14:32
*/
public class H5LoanerLoginTest {
public boolean doesWebElementExist(WebDriver driver, By selector)
{
try
{
driver.findElement(selector);
return true;
}
catch (NoSuchElementException e)
{
return false;
}
}
WebDriver driver = getDriver();
// 定义数据源
@DataProvider(name = "list")
public Iterator
上一篇通过chrome进行了登录验证操作,也放上了测试代码,分析一下testng的定义数据源操作
第一种方式:需要明确测试数据组数
@DataProvider( name= "list")
public Object[][] createData(){
return new Object[][]{
{ "1", "123456" },
{ "2", "123456" },
{ "1", "111111" }
};
}
第二种方式:不需明确测试数据组数
// 定义数据源
@DataProvider(name = "list")
public Iterator<Object[]> createData() throws FileNotFoundException, ExcelException {
LinkedHashMap<String,String> fieldMap = new LinkedHashMap<String,String>();
fieldMap.put("手机","loanerPhone");
fieldMap.put("密码","loanerPassword");
LoanerLoginVo loanerLoginVo = new LoanerLoginVo();
//从excel中获取数据
return ExcelUtil.excelIn(fieldMap, "loanerLogin", "登录信息", loanerLoginVo.getClass());
}
感受一下从excel表导入测试案例
本项目对excel表的操作主要用了jxl,首先在pom.xml文件下写上引入jxl的jar包的代码并导入
<dependency>
<groupId>net.sourceforge.jexcelapigroupId>
<artifactId>jxlartifactId>
<version>2.6.12version>
dependency>
ExcelUtil工具类代码
public class ExcelUtil {
/**
* @MethodName : excelToList
* @Description : 将Excel转化为List
* @param in :承载着Excel的输入流
* @param entityClass :List中对象的类型(Excel中的每一行都要转化为该类型的对象)
* @param fieldMap :Excel中的中文列头和类的英文属性的对应关系Map
* @param uniqueFields :指定业务主键组合(即复合主键),这些列的组合不能重复
* @return :List
* @throws ExcelException
*/
public static List excelToList(
InputStream in,
String sheetName,
Class entityClass,
LinkedHashMap fieldMap,
String[] uniqueFields
) throws ExcelException {
//定义要返回的list
List resultList=new ArrayList();
try {
//根据Excel数据源创建WorkBook
Workbook wb=Workbook.getWorkbook(in);
//获取工作表
Sheet sheet=wb.getSheet(sheetName);
//获取工作表的有效行数
int realRows=0;
for(int i=0;iint nullCols=0;
for(int j=0;jif(currentCell==null || "".equals(currentCell.getContents().toString())){
nullCols++;
}
}
if(nullCols==sheet.getColumns()){
break;
}else{
realRows++;
}
}
//如果Excel中没有数据则提示错误
if(realRows<=1){
throw new ExcelException("Excel文件中没有任何数据");
}
Cell[] firstRow=sheet.getRow(0);
String[] excelFieldNames=new String[firstRow.length];
//获取Excel中的列名
for(int i=0;i//判断需要的字段在Excel中是否都存在
boolean isExist=true;
List excelFieldList= Arrays.asList(excelFieldNames);
for(String cnName : fieldMap.keySet()){
if(!excelFieldList.contains(cnName)){
isExist=false;
break;
}
}
//如果有列名不存在,则抛出异常,提示错误
if(!isExist){
throw new ExcelException("Excel中缺少必要的字段,或字段名称有误");
}
//将列名和列号放入Map中,这样通过列名就可以拿到列号
LinkedHashMap colMap=new LinkedHashMap();
for(int i=0;i//判断是否有重复行
//1.获取uniqueFields指定的列
Cell[][] uniqueCells=new Cell[uniqueFields.length][];
for(int i=0;iint col=colMap.get(uniqueFields[i]);
uniqueCells[i]=sheet.getColumn(col);
}
//2.从指定列中寻找重复行
for(int i=1;iint nullCols=0;
for(int j=0;j1,
uniqueCells[j][i].getColumn(),
uniqueCells[j][realRows-1].getRow(),
true);
if(sameCell!=null){
nullCols++;
}
}
if(nullCols==uniqueFields.length){
throw new ExcelException("Excel中有重复行,请检查");
}
}
//将sheet转换为list
for(int i=1;i//新建要转换的对象
T entity=entityClass.newInstance();
//给对象中的字段赋值
for(Map.Entry entry : fieldMap.entrySet()){
//获取中文字段名
String cnNormalName=entry.getKey();
//获取英文字段名
String enNormalName=entry.getValue();
//根据中文字段名获取列号
int col=colMap.get(cnNormalName);
//获取当前单元格中的内容
String content=sheet.getCell(col, i).getContents().toString().trim();
//给对象赋值
setFieldValueByName(enNormalName, content, entity);
}
resultList.add(entity);
}
} catch(Exception e){
e.printStackTrace();
//如果是ExcelException,则直接抛出
if(e instanceof ExcelException){
throw (ExcelException)e;
//否则将其它异常包装成ExcelException再抛出
}else{
e.printStackTrace();
throw new ExcelException("导入Excel失败");
}
}
return resultList;
}
/**
* @MethodName : setFieldValueByName
* @Description : 根据字段名给对象的字段赋值
* @param fieldName 字段名
* @param fieldValue 字段值
* @param o 对象
*/
private static void setFieldValueByName(String fieldName,Object fieldValue,Object o) throws Exception{
Field field=getFieldByName(fieldName, o.getClass());
if(field!=null){
field.setAccessible(true);
//获取字段类型
Class> fieldType = field.getType();
//根据字段类型给字段赋值
if (String.class == fieldType) {
field.set(o, String.valueOf(fieldValue));
} else if ((Integer.TYPE == fieldType)
|| (Integer.class == fieldType)) {
field.set(o, Integer.parseInt(fieldValue.toString()));
} else if ((Long.TYPE == fieldType)
|| (Long.class == fieldType)) {
field.set(o, Long.valueOf(fieldValue.toString()));
} else if ((Float.TYPE == fieldType)
|| (Float.class == fieldType)) {
field.set(o, Float.valueOf(fieldValue.toString()));
} else if ((Short.TYPE == fieldType)
|| (Short.class == fieldType)) {
field.set(o, Short.valueOf(fieldValue.toString()));
} else if ((Double.TYPE == fieldType)
|| (Double.class == fieldType)) {
field.set(o, Double.valueOf(fieldValue.toString()));
} else if (Character.TYPE == fieldType) {
if ((fieldValue!= null) && (fieldValue.toString().length() > 0)) {
field.set(o, Character
.valueOf(fieldValue.toString().charAt(0)));
}
}else if(Date.class==fieldType){
field.set(o, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(fieldValue.toString()));
}else{
field.set(o, fieldValue);
}
}else{
throw new ExcelException(o.getClass().getSimpleName() + "类不存在字段名 "+fieldName);
}
}
/**
* @MethodName : getFieldByName
* @Description : 根据字段名获取字段
* @param fieldName 字段名
* @param clazz 包含该字段的类
* @return 字段
*/
private static Field getFieldByName(String fieldName, Class> clazz){
//拿到本类的所有字段
Field[] selfFields=clazz.getDeclaredFields();
//如果本类中存在该字段,则返回
for(Field field : selfFields){
if(field.getName().equals(fieldName)){
return field;
}
}
//否则,查看父类中是否存在此字段,如果有则返回
Class> superClazz=clazz.getSuperclass();
if(superClazz!=null && superClazz !=Object.class){
return getFieldByName(fieldName, superClazz);
}
//如果本类和父类都没有,则返回空
return null;
}
/**
* @author xian
* @date 2018/7/30 16:35
* @param list 实体类列表
* List<实体类>转为List
static public List
ExcelException自定义excel异常类
public class ExcelException extends Exception {
public ExcelException() {
// TODO Auto-generated constructor stub
}
public ExcelException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public ExcelException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
public ExcelException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
}
在testng定义的数据源中导入excel表中的数据
// 定义数据源
@DataProvider(name = "list")
public Iterator<Object[]> createData() throws FileNotFoundException, ExcelException {
LinkedHashMap<String,String> fieldMap = new LinkedHashMap<String,String>();
fieldMap.put("手机","loanerPhone");
fieldMap.put("密码","loanerPassword");
LoanerLoginVo loanerLoginVo = new LoanerLoginVo();
//从excel中获取数据
return ExcelUtil.excelIn(fieldMap, "loanerLogin", "登录信息", loanerLoginVo.getClass());
}
模拟post/get请求进行接口测试跟selenium没有关系,主要用到了OkHttp来对网页进行模拟post/get请求,和利用testng接收测试案例和显示测试结果
本项目对网页请求的操作主要用了okhttp3,首先在pom.xml文件下写上引入okhttp3的jar包的代码并导入
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>3.10.0version>
dependency>
OkhttpUtil工具类代码
public class OkHttpUtil {
private static final Logger logger = LoggerFactory.getLogger(OkHttpUtil.class);
/**
* get
* @param url 请求的url
* @param queries 请求的参数,在浏览器?后面的数据,没有可以传null
* @return
*/
public static String get(String url, Map queries) {
String responseBody = "";
StringBuffer sb = new StringBuffer(url);
if (queries != null && queries.keySet().size() > 0) {
boolean firstFlag = true;
Iterator iterator = queries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
if (firstFlag) {
sb.append("?" + entry.getKey() + "=" + entry.getValue());
firstFlag = false;
} else {
sb.append("&" + entry.getKey() + "=" + entry.getValue());
}
}
}
Request request = new Request.Builder()
.url(sb.toString())
.build();
Response response = null;
try {
OkHttpClient okHttpClient = new OkHttpClient();
response = okHttpClient.newCall(request).execute();
int status = response.code();
if (response.isSuccessful()) {
return response.body().string();
}
} catch (Exception e) {
logger.error("okhttp3 put error >> ex = {}", ExceptionUtils.getStackTrace(e));
} finally {
if (response != null) {
response.close();
}
}
return responseBody;
}
/**
* post
*
* @param url 请求的url
* @param params post form 提交的参数
* @return
*/
public static String post(String url, Map params) {
String responseBody = "";
FormBody.Builder builder = new FormBody.Builder();
//添加参数
if (params != null && params.keySet().size() > 0) {
int count = 1;
for (String key : params.keySet()) {
if(params.get(key) != null)builder.add(key, params.get(key));
count++;
}
}
Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
Response response = null;
try {
OkHttpClient okHttpClient = new OkHttpClient();
response = okHttpClient.newCall(request).execute();
int status = response.code();
if (response.isSuccessful()) {
System.out.println("success:"+url);
return response.body().string();
}
System.out.println("fail:"+url);
} catch (Exception e) {
logger.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e));
} finally {
if (response != null) {
response.close();
}
}
return responseBody;
}
/**
* get
* @param url 请求的url
* @param queries 请求的参数,在浏览器?后面的数据,没有可以传null
* @return
*/
public static String getForHeader(String url, Map queries) {
String responseBody = "";
StringBuffer sb = new StringBuffer(url);
if (queries != null && queries.keySet().size() > 0) {
boolean firstFlag = true;
Iterator iterator = queries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
if (firstFlag) {
sb.append("?" + entry.getKey() + "=" + entry.getValue());
firstFlag = false;
} else {
sb.append("&" + entry.getKey() + "=" + entry.getValue());
}
}
}
Request request = new Request.Builder()
.addHeader("key", "value")
.url(sb.toString())
.build();
Response response = null;
try {
OkHttpClient okHttpClient = new OkHttpClient();
response = okHttpClient.newCall(request).execute();
int status = response.code();
if (response.isSuccessful()) {
return response.body().string();
}
} catch (Exception e) {
logger.error("okhttp3 put error >> ex = {}", e.getMessage());
} finally {
if (response != null) {
response.close();
}
}
return responseBody;
}
/**
* Post请求发送JSON数据....{"name":"zhangsan","pwd":"123456"}
* 参数一:请求Url
* 参数二:请求的JSON
* 参数三:请求回调
*/
public static String postJsonParams(String url, String jsonParams) {
String responseBody = "";
okhttp3.RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Response response = null;
try {
OkHttpClient okHttpClient = new OkHttpClient();
response = okHttpClient.newCall(request).execute();
int status = response.code();
if (response.isSuccessful()) {
return response.body().string();
}
} catch (Exception e) {
logger.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e));
} finally {
if (response != null) {
response.close();
}
}
return responseBody;
}
/**
* Post请求发送xml数据....
* 参数一:请求Url
* 参数二:请求的xmlString
* 参数三:请求回调
*/
public static String postXmlParams(String url, String xml) {
String responseBody = "";
RequestBody requestBody = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), xml);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Response response = null;
try {
OkHttpClient okHttpClient = new OkHttpClient();
response = okHttpClient.newCall(request).execute();
int status = response.code();
if (response.isSuccessful()) {
return response.body().string();
}
} catch (Exception e) {
logger.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e));
} finally {
if (response != null) {
response.close();
}
}
return responseBody;
}
}
调用OkHttpUtil的postJsonParams方法模拟post请求
@Test(dataProvider = "list")
public void action(LoanerLoginVo loanerUpdateBasicInfoVo) {
JSONObject jsonObject = JSONObject.fromObject(loanerUpdateBasicInfoVo);
String url = "http://localhost/huinongloan2/web/loaner/loanerLogin";
String res = OkHttpUtil.postJsonParams(url, String.valueOf(jsonObject));
//System.out.println(res);
JSONObject jsonObjectRes = JSONObject.fromObject(res);
if (jsonObjectRes.get("code")==1) {
throw new RuntimeException(jsonObjectRes.get("errmsg").toString());
} else {
Reporter.log(DateUtils.getCurrentTime() + ": 验证成功");
}
}
先放两张自定义好的测试报告镇一下,大概是人生第一次改完源码还打包,大家随意看就好。
在本来的测试报告总览页面添加了饼图
在本来的测试报告用例页面添加了截图
点击截图可以放大查看
不解释,直接上代码。
pom.xml添加代码
<dependency>
<groupId>org.uncommonsgroupId>
<artifactId>reportngartifactId>
<version>1.1.4version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.google.injectgroupId>
<artifactId>guiceartifactId>
<version>4.0version>
<scope>testscope>
dependency>
引入surefire
<dependency>
<groupId>org.apache.maven.surefiregroupId>
<artifactId>surefireartifactId>
<version>2.22.0version>
<type>pomtype>
dependency>
<dependency>
<groupId>org.apache.maven.surefiregroupId>
<artifactId>surefire-testngartifactId>
<version>2.22.0version>
dependency>
<dependency>
<groupId>org.apache.maven.surefiregroupId>
<artifactId>surefire-providersartifactId>
<version>2.22.0version>
<type>pomtype>
dependency>
指定testng.xml的位置
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-surefire-pluginartifactId>
<version>2.22.0version>
<configuration>
<properties>
<property>
<name>userdefaultlistenersname>
<value>falsevalue>
property>
<property>
<name>listenername>
<value>org.uncommons.reportng.HTMLReporter,
org.uncommons.reportng.JUnitXMLReporter,
value>
property>
properties>
<testFailureIgnore>truetestFailureIgnore>
<suiteXmlFiles>
<file>testng.xmlfile>
suiteXmlFiles>
<workingDirectory>target/workingDirectory>
configuration>
plugin>
改完pom.xml后,import一下,开始配置testng.xml,本项目的testng.xml文件在项目的根目录下,所以上面的配置代码就是testng.xml,注意这个路径代码要根据testng.xml的位置来填
这里在项目的根目录下手动添加了testng.xml
<suite name="All Test Suite">
<test verbose="1" preserve-order="true" name="E:\huinongloan2\src\test\java\selenium">
<classes>
<class name="selenium.H5Test"/>
classes>
test>
suite>
本项目的测试用例暂时这么简单,复杂用例(比如测试组什么的以后再说),配置好testng.xml就可以进行测试了
测试完可以看到reportng生成的测试报告,用浏览器打开target → surefire-reports → html → index.html
就可以看到如下图的测试报告啦
准备改源码……
其实自定义页面,也就是把上次导入的reportng.1.1.4.jar包的源码,改成自己想要样子,再打包放到原来的位置,就是下面pom.xml代码中的这个包
<dependency>
<groupId>org.uncommonsgroupId>
<artifactId>reportngartifactId>
<version>1.1.4version>
<scope>testscope>
dependency>
首先去Reportng的源码地址把源码下载下来,用idea打开,可以看到源码的项目结构如下
先来改pom.xml,本项目用的6.14.3的testng,2.22.0的maven-surefire-plugin和1.7版本的JDK
<dependencies>
<dependency>
<groupId>org.testnggroupId>
<artifactId>testngartifactId>
<version>6.14.3version>
dependency>
<dependency>
<groupId>velocitygroupId>
<artifactId>velocityartifactId>
<version>1.4version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-surefire-pluginartifactId>
<version>2.22.0version>
<configuration>
<systemPropertyVariables>
<org.uncommons.reportng.escape-output>falseorg.uncommons.reportng.escape-output>
systemPropertyVariables>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.7source>
<target>1.7target>
configuration>
plugin>
plugins>
build>
添加饼图
import后,先来添个饼图,打开overview.html.vm,导入ichart组件(开源图形插件)
<script src='http://www.ichartjs.com/ichart.latest.min.js'>script>
接下来添加饼图的div,本项目把饼图放到了总览表格的下面,点击查看图片参考,个人觉得这样比放在表格上方更好看
table>
#end
<div id='ichart-render'>div>
body>
html>
给总数,失败总数和跳过总数添加id,方便后面的js代码获取他们的值
#if ($totalPassed > 0)
"tpn" class="passed number">$totalPassed
#else
"tpn" class="zero number">0
#end
#if ($totalSkipped > 0)
"tsn" class="skipped number">$totalSkipped
#else
"tsn" class="zero number">0
#end
#if ($totalFailed > 0)
"tfn" class="failed number">$totalFailed
#else
"tfn" class="zero number">0
#end
饼图显示的js代码,放在饼图的div下方即可
<script type='text/javascript'>
pcount=document.getElementById("tpn").innerHTML;
fcount=document.getElementById("tfn").innerHTML;
scount=document.getElementById("tsn").innerHTML;
$(function(){
var chart = iChart.create({
render:"ichart-render",
width:800,
height:400,
background_color:"#fefefe",
gradient:false,
color_factor:0.2,
border:{
color:"BCBCBC",
width:0
},
align:"center",
offsetx:0,
offsety:0,
sub_option:{
border:{
color:"#BCBCBC",
width:1
},
label:{
fontweight:500,
fontsize:11,
color:"#4572a7",
sign:"square",
sign_size:12,
border:{
color:"#BCBCBC",
width:1
}
}
},
shadow:true,
shadow_color:"#666666",
shadow_blur:2,
showpercent:false,
column_width:"70%",
bar_height:"70%",
radius:"90%",
subtitle:{
text:"",
color:"#111111",
fontsize:16,
font:"微软雅黑",
textAlign:"center",
height:20,
offsetx:0,
offsety:0
},
footnote:{
text:"",
color:"#111111",
fontsize:12,
font:"微软雅黑",
textAlign:"right",
height:20,
offsetx:0,
offsety:0
},
legend:{
enable:false,
background_color:"#fefefe",
color:"#333333",
fontsize:12,
border:{
color:"#BCBCBC",
width:1
},
column:1,
align:"right",
valign:"center",
offsetx:0,
offsety:0
},
coordinate:{
width:"80%",
height:"84%",
background_color:"#ffffff",
axis:{
color:"#a5acb8",
width:[1,"",1,""]
},
grid_color:"#d9d9d9",
label:{
fontweight:500,
color:"#666666",
fontsize:11
}
},
label:{
fontweight:500,
color:"#666666",
fontsize:11
},
type:"pie2d",
data:[
{
name:"通过",
value:pcount,
color:"#44aa44"
},{
name:"失败",
value:fcount,
color:"#ff4444"
},{
name:"跳过",
value:scount,
color:"#FFD700"
}
]
});
chart.draw();
});
script>
到这里饼图就算添加完成了
添加截图
首先要明确知道,测试用例报错的时候才会截图,打开results.html.vm,找到#if ($failedTests.size() > 0)…#end,改成以下代码
#if ($failedTests.size() > 0)
<table class="resultsTable">
<tr><th colspan="5" class="header failed">$messages.getString("failedTests")</th></tr>
#foreach ($testClass in $failedTests.keySet())
<tr>
<td colspan="1" class="group">$testClass.name</td>
<td colspan="1" class="group">$messages.getString("duration")</td>
<td colspan="1" class="group">$messages.getString("log")</td>
<td colspan="1" class="group">$messages.getString("screenshot")</td>
</tr>
#set ($classResults = $failedTests.get($testClass))
#parse ("org/uncommons/reportng/templates/html/class-results.html.vm")
#end
</table>
#end
对应的,在reportng.properties最底下加上这两行
log=Log Info
screenshot=Screen Shot
打开ReportNGUtils.java,新增两个方法getImageString()和removeImage()
//提取含有img标签的字符串
public String getImageString(String s)
{
String regex = "(
)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);
while (matcher.find()) {
String group = matcher.group(1);
return group;
}
return "";
}
//去除带有img标签的字符串
public String removeImage(String s)
{
return s.replaceAll("
","");
}
打开class-results.html.vm,在倒数第二行加入一列来显示图片,在这里用到了ReportNGUtils.java新增方法的其一
<td class="screenshot">
#set ($output = $utils.getTestOutput($testResult))
#if ($output.size() > 0)
<div class="screenshotimage">
#foreach( $line in $output )
#if ($meta.shouldEscapeOutput())
$utils.getImageString($line)<br/>
#else
$utils.getImageString($line)<br/>
#end
#end
</div>
#end
</td>
打开ReportNGUtils.java……我已经忘记这里有没有改什么了,自己对照一下吧
/**
* Replace any angle brackets, quotes, apostrophes or ampersands with the
* corresponding XML/HTML entities to avoid problems displaying the String in
* an XML document. Assumes that the String does not already contain any
* entities (otherwise the ampersands will be escaped again).
* @param s The String to escape.
* @return The escaped String.
*/
public String escapeString(String s)
{
if (s == null)
{
return null;
}
StringBuilder buffer = new StringBuilder();
for(int i = 0; i < s.length(); i++)
{
buffer.append(escapeChar(s.charAt(i)));
}
return buffer.toString();
}
/**
* Converts a char into a String that can be inserted into an XML document,
* replacing special characters with XML entities as required.
* @param character The character to convert.
* @return An XML entity representing the character (or a String containing
* just the character if it does not need to be escaped).
*/
private String escapeChar(char character)
{
switch (character)
{
case '<': return "<";
case '>': return ">";
case '"': return """;
case '\'': return "'";
case '&': return "&";
default: return String.valueOf(character);
}
}
/**
* Works like {@link #escapeString(String)} but also replaces line breaks with
* <br /> tags and preserves significant whitespace.
* @param s The String to escape.
* @return The escaped String.
*/
public String escapeHTMLString(String s)
{
if (s == null)
{
return null;
}
StringBuilder buffer = new StringBuilder();
for(int i = 0; i < s.length(); i++)
{
char ch = s.charAt(i);
switch (ch)
{
case ' ':
// All spaces in a block of consecutive spaces are converted to
// non-breaking space ( ) except for the last one. This allows
// significant whitespace to be retained without prohibiting wrapping.
char nextCh = i + 1 < s.length() ? s.charAt(i + 1) : 0;
buffer.append(nextCh==' ' ? " " : " ");
break;
case '\n':
buffer.append("
\n");
break;
default:
buffer.append(escapeChar(ch));
}
}
return buffer.toString();
}
添加截图在这里告一段落
调整测试结果显示顺序
测试详情中的结果是按照名称排列的,现在调整为按照测试顺序排列,打开TestResultComparator.java,修改代码如下
public int compare(ITestResult result1, ITestResult result2)
{
//return result1.getName().compareTo(result2.getName());
int longresult2 = 0;
if(result1.getStartMillis() < result2.getStartMillis()){
longresult2 = -1;
} else {
longresult2 = 1;
}
return longresult2;
}
字符转换
在这里填一个我遇到的坑,后期用到jenkins的时候,在jenkins里显示的测试报告会显示中文乱码,需要修改AbstractReporter.java文件的generateFile方法如下,使显示字符转换成utf-8
/**
* Generate the specified output file by merging the specified
* Velocity template with the supplied context.
*/
protected void generateFile(File file,
String templateName,
VelocityContext context) throws Exception
{
//Writer writer = new BufferedWriter(new FileWriter(file));
OutputStream out=new FileOutputStream(file);
Writer writer = new BufferedWriter(new OutputStreamWriter(out,"utf-8"));
try
{
Velocity.mergeTemplate(classpathPrefix + templateName,
ENCODING,
context,
writer);
writer.flush();
}
finally
{
writer.close();
}
}
ant打包
现在自定义了总览饼图,错误截图,展示顺序,差不多该打包reportng项目了,右键build.xml,点击Add as Ant Build File
可以看到右侧出现ant打包步骤,双击release进行打包
点击idea下方的messages查看打包信息,可以看到已经打包完成
可以看到打包好的压缩包,解压后复制jar包到maven的本地仓库
更新一下项目再测试一遍,就可以得到饼图和按执行顺序排序的测试结果了,截图的测试用例还要再写
举一个selenium+testng+reportng测试报告显示测试失败截图的测试用例
失败后操作
//用例结束的时候判断结果是否失败
@AfterMethod(alwaysRun = true)
public void afterMethod(ITestResult result) throws Exception {
if (!result.isSuccess())
catchExceptions(result);
}
//失败将设置的图片写入report
public void catchExceptions(ITestResult result) throws IOException {
if (!result.isSuccess()) {
//在testng的report默认路径test-output下的html文件夹中创建一个截图文件夹以存放截图
//截图放在这里把整个测试报告(test-output文件夹)单独打包发给别人查看也不会报错
File file = new File("test-output/html/snapshot");
Reporter.setCurrentTestResult(result);
Reporter.log(file.getAbsolutePath());
String filePath = file.getAbsolutePath();
String dest = result.getMethod().getRealClass().getSimpleName()+"."+result.getMethod().getMethodName();
String time = DateUtils.getCurrentDateTime2();
String picName=filePath+File.separator+dest+time +".png";
File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(srcFile, new File(picName));
String escapePicName="snapshot"+File.separator+dest+time+".png";
String openPicName=escapeString("snapshot"+File.separator+dest+time+".png");
//window.open()实现点击图片在新窗口显示大图,其中图片路径需要转义
String html="
";
Reporter.log(html);
}
}
/**
* 替换字符串
* @param s 待替换string
* @return 替换之后的string
*/
public String escapeString(String s)
{
if (s == null)
{
return null;
}
StringBuilder buffer = new StringBuilder();
for(int i = 0; i < s.length(); i++)
{
buffer.append(escapeChar(s.charAt(i)));
}
return buffer.toString();
}
/**
* 将\字符替换为\\
* @param character 待替换char
* @return 替换之后的char
*/
private String escapeChar(char character)
{
switch (character)
{
case '\\': return "\\\\";
default: return String.valueOf(character);
}
}
这里我把时间工具类获取时间至毫秒的方法放一下,获取至毫秒就不用担心每分钟多次截图了
public static String getCurrentDateTime2() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
return sdf.format(calendar.getTime());
}
从reportng的源码中找到了修改testng测试报告的输出目录的地方
修改测试报告的输出目录
打开HTMLReporter.java,把generateReport方法的outputDirectoryName参数赋值成你想输出的文件夹,例如
outputDirectoryName = "surefire-reports";
然后打包替换项目中的reportng.jar包
打包参考(9)
测试代码写好后,尝试通过Jenkins搭建自动化测试平台
Jenkins安装
Jenkins下载地址
如图,选择对应的安装包下载
解压后点击安装,根据需要自定义安装路径,其他默认
安装完成后打开localhost:8080访问Jenkins首页,可以看到需要填入登录名和密码
初始登录名为admin,初始登录密码在安装目录Jenkins\secrets\initialAdminPassword文件中
如果页面一直显示Please wait while Jenkins is getting ready to work,尝试重启一下jenkins服务再访问,等待数秒即可进入主页
新手指引会帮助你安装默认插件和添加用户名密码
这些设置好后,在主页点击新建任务,填写任务名称,选择自由风格,点击确定按钮进入项目配置页面
接下来进行项目配置,首先 General 页面需要的话填上项目描述,其他的不选
点击 源码管理 ,选择git,输入项目地址(目前展示的是http形式的url,需要用户名和密码,其他形式的url需采用ssh通信,需要git秘钥)
如果不报红字说明连接成功
点击 构建触发器 ,选择 轮询 SCM ,填入H/3 * * * *,表示每三分钟轮询一遍在源码管理里设置的代码库,只要往代码库的master分支提交代码,jenkins就会构建一次
点击 构建环境 勾上第一行,表示每次构建前删除工作空间(构建时不能打开任何文件,包括文件夹否则就会构建失败)
点击 构建 , 点击 添加构建步骤 ,选择 调用顶层 Maven 目标
在目标处输入clean install -Dtestng.xml findbugs:findbugs,表示重新生成target,打包和运行testng.xml,最后那个,看名字就知道作用了吧
在这里填一下踩过的一个坑,后期想要在jenkins页面显示测试报告,会发现显示了空白页面或者内容不全,具体原因参考以下文章
Jenkins2.2 firefox和chrome不显示测试报告解决办法
在这里,我们在构建时调用命令清除设置解决,还是在 构建 这里,点击 添加构建步骤 ,选择 Execute system Groovy script
输入命令 System.setProperty(“hudson.model.DirectoryBrowserSupport.CSP”,”“) 清除设置
经过上面的设置测试报告就会有内容了,但是可能会出现中文乱码,之前跟着自动化测试系列10修改过reportng.jar的就可以跳过这个步骤了,如果没有,参考以下文章
修改源码进行字符转换
接着点击最后一项 构建后操作 ,点击 添加构建后操作步骤 , 从上往下选好了,首先点击第一个,然后就不用管它了,默认设置就行
点击新增
填写测试报告的位置 target\surefire-reports\html,其他设置默认就好
接下来选择 Publish TestNG Results ,然后也不用管它了,默认设置就行
最后是发邮件,发邮件有点复杂,留到下一篇
ps:在配置时,如果点击增加步骤发现没有这个选项,则需要去系统配置的插件管理里下载插件,比如
等
上一篇的配置保存好后,下一步就是邮件配置
下载插件
下载发送邮件相关插件
系统设置
点击 系统管理
进入 系统设置
找到 Extended E-mail Notification 进行配置
点击高级
上图中SMTP对应的密码需要到QQ邮箱中获取,登录QQ邮箱 → 点击设置 → 进入账户
往下拉,找到POP3/SMTP服务,点击开启后,可以看到图片上的发送短信提示,根据提示发送短信后,点击 我已发送 ,页面会返回SMTP开通密码
同理,找到 邮件通知 进行配置,按照刚刚的步骤把红框中的信息填好即可
构建配置
系统配置好后,就可以配置任务test1了,找到 构建后操作 ,点击 增加构建后操作步骤, 选择倒数第五个
填写需发送的邮箱列表 Recipient List,点击 Advanced settings…
删掉developers,这里的Add Trigger可以添加触发发送邮件动态的构建状态
添加Recipient List,即把developers替换成Recipient List
添加了构建成功后发送
最后记得点击保存
前两篇把构建配置都设置好了,接下来尝试构建
立即构建
找到test1,点击 立即构建 ,可以看到下方的构建历史创建了一个新的构建进度条
等待了数分钟,构建接近尾声的时候,收到了邮件,邮件内容是自定义的html就不看了
结果验收
现在返回jenkins的页面查看,点进最近的一次构建历史,点开 FindBugs Warnings 查看
点开 TestNG Results 查看
查看自定义的测试报告
点击 返回到工程 ,可以看到构建的情况在右方被Jenkins自己做成了图表
点开HTML Report
自定义的测试报告的定制过程请参考
自动化测试系列
的8-12篇
Jenkins项目构建成功后自动部署到远程服务器上
下载插件
系统设置
找到 Publish over SSH, 填入红框内容,点击高级
勾选用户密码登录,填写密码
点击 Test Configuration ,如图,若左边显示Success,表示连接远程服务器成功
修改war包名
回到idea,把以下代码加进pom.xml文件夹,保证生成的包名是项目的根路径名称
<build>
<finalName>yourprojectnamefinalName>
build>
工程配置
点击进入配置,找到 构建 ,点击增加构建步骤, 选择Send files or excute commands over SSH
填写如下图
构建项目
配置好后,执行立即构建test1,构建完成后可到服务器查看,是否重新部署了一遍
跳转文末专用