身为一个只会后端的测试人员,自动化出来的报告总是testng的默认报告。或者稍微美化一点的reportng的报告。在现在这个只看脸的世界,显然是还不够的。
推荐大家用一个新的报告框架,extentreport。如何跟testng集成,如何使用,大家可以参考[这里写链接内容](http://extentreports.com/),或者直接在csdn里边搜索。各种教程,一抓一大把。这里不再赘述。
这里是参考我用这个报告的时候,遇到的问题,给出来的解决方案。仅供参考。
- 先要自己定义一个reporter,这个只要implent TestNg的IReporter,重写一下generateReport就好。
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
String outputDirectory) {
// true为覆盖已经生成的报告
extent = new ExtentReports(outputDirectory + File.separator
+ "Extent.html", true // true为覆盖已经生成的报告,false
// 在已有的报告上面生成,不会覆盖旧的结果
, DisplayOrder.NEWEST_FIRST // 最新运行的用例结果在第一个
, NetworkMode.OFFLINE //生成离线的,免得样式不对应
);
extent.startReporter(ReporterType.DB, outputDirectory + File.separator
+ "Extent.html"); // 生成本地的DB数据文件
for (ISuite suite : suites) {
Map<String, ISuiteResult> result = suite.getResults();
for (ISuiteResult r : result.values()) {
ITestContext context = r.getTestContext();
setTestNodes(context.getPassedTests(), LogStatus.PASS);
setTestNodes(context.getFailedTests(), LogStatus.FAIL);
setTestNodes(context.getSkippedTests(), LogStatus.SKIP);
}
}
extent.flush();
extent.close();
}
private void setTestNodes(IResultMap tests, LogStatus status) {
ExtentTest test;
if (tests.size() > 0) {
for (ITestResult result : tests.getAllResults()) {
test = extent.startTest(result.getMethod().getMethodName());
test.setStartedTime(getTime(result.getStartMillis()));
test.setEndedTime(getTime(result.getEndMillis()));
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
if (result.getThrowable() != null) {
test.log(status, test.addScreenCapture("../"
+ BaseTest
.getResultInfoByTestname(result.getName())
.getErrorImgList()));
// System.out.println("../"+
// BaseTest.getImgResultInfo().get(result.getMethod().getMethodName()));
test.log(status,
BaseTest.getResultInfoByTestname(result.getName())
.getErrorMessage());
test.log(status, result.getThrowable());
} else {
test.log(status, "Test " + status.toString().toLowerCase()
+ "ed");
}
extent.endTest(test);
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
这里友情提醒一下,最好生成离线的,免得报告的样式在不同的环境下表现有差异。因为那几个css文件都是外国网站下的。
关键的地方在setTestNodes这个函数里边。这里我为每一个测试运行失败的,都添加了截图。完整的报告类,我没贴上来。extent是全局的变量。test.log就是动图里边,右边的成功/失败的测试步骤(方法)了。报告的组织很简单,就是根据testng自己的运行结果,相应的去处理extent的展现就好。
那么问题来了,extent是支持截图的,但是我们的截图要怎么传给这个自定义的report类呢?
这个就因人而异了。看你的测试是怎么组织的。这里我介绍下我的。我的UI自动化是基于page of object的方式来写的。然后每一个页面的元素跟可以操作的方法是一个类,测试的驱动是一个类这样子。每一个测试的类都是继续自一个包装了各种操作的静态基类baseTest,webdriver的启动也是在这里。
错误截图是实现了webdriver的WebDriverEventListener接口来实现错误/异常就自动截图.
这部分不详细说。简单贴一下核心的代码,在WebDriverEventListener这个接口里边可以定义很多操作
@Override
public void onException(Throwable throwable, WebDriver driver) {
// TODO Auto-generated method stub
if (throwable.getClass().equals(NoSuchElementException.class)
|| throwable.getClass()
.equals(ElementNotVisibleException.class)) {
logger.error("element not found:" + lastFindBy);
s.TakeScreenShot(driver,lastFindBy.toString());
} else {
s.TakeScreenShot(driver,lastFindBy.toString());
logger.error("error occour when finding:" + lastFindBy
+ throwable.getCause());
}
}
这样只要是出现了元素找不到,各种各样的selenium有定义的异常,就可以自动截图。
那么如何知道这个截图是哪个方法执行的呢?既然基类是静态的,那么干脆再设置一个截图路径跟当前执行方法的map好了。
/*
* 设置当期运行的状态
*/
public static void setRunning(String testName) {
boolean isContain = false;
if (imgResultInfo.size() == 0) {
resultInfo e = new resultInfo();
e.setFlag(true);
e.setTestName(testName);
e.setTestResult(0);
imgResultInfo.add(e);
}
for (resultInfo resultInfo : imgResultInfo) {
if (resultInfo.getTestName().equals(testName)) {
resultInfo.setFlag(true);
isContain = true;
} else {
resultInfo.setFlag(false);
}
}
if (!isContain) {
resultInfo e = new resultInfo();
e.setFlag(true);
e.setTestName(testName);
e.setTestResult(0);
imgResultInfo.add(e);
}
}
/*
* 获取当前正在运行的测试
*/
public static resultInfo getRunningResultInfo() {
for (resultInfo resultInfo : imgResultInfo) {
if (resultInfo.isFlag()) {
return resultInfo;
}
}
return null;
}
/*
* 按照testname获取
*/
public static resultInfo getResultInfoByTestname(String name) {
for (resultInfo resultInfo : imgResultInfo) {
if (resultInfo.getTestName().equals(name)) {
return resultInfo;
}
}
return null;
}
public static ArrayList<resultInfo> getImgResultInfo() {
return imgResultInfo;
}
public static void setImgResultInfo(ArrayList<resultInfo> imgResultInfo) {
BaseTest.imgResultInfo = imgResultInfo;
}
然后我们实现以下TestListenerAdapter,自定义一下各种测试开始/结束的操作。
在测试开始的时候,将当前执行的方法置入map.
@Override
public void onTestStart(ITestResult tr) {
super.onTestStart(tr);
logger.info("【" + tr.getName() + " Start】");
extent = BaseTest.getextent();
test = extent.startTest(tr.getName());
BaseTest.setRunning(tr.getName());
}
然后我们在失败自动截图的时候,找到当前在运行的方法,将失败截图放进去:
// Convert '\' into '/' for web image browsing.
Map<String,String> resultMap=new HashMap<String, String>();
String path=screenShotPath.replace("\\", "/");
resultMap.put("temp", path);
BaseTest.getRunningResultInfo().setErrorImgList(path);
BaseTest.getRunningResultInfo().setErrorMessage(element);
这里可能有人发现,如果有多个截图怎么办??
事实上,当UI自动化碰到异常,一般来说这个方法就要退出的了,无法继续执行,所以通常,只需要处理一个截图的情况就可以。
通过以上几个步骤就可以实现截图那种直接又美观有用的测试报告了。一下子档次就提上来了。哈哈
当然,上面我们自定义了很多testng的东西,需要在执行的时候指定才会生效
<suite reruntimes="2" thread-count="1" parallel="tests" verbose="10"
name="demo" annotations="JDK" time-out="1800000">
<parameter name="browser"
value="chrome" />
<listeners>
<listener class-name="com.xxxx.utils.testngListener"></listener>
<listener class-name="com.xxx.utils.myReport"></listener>
</listeners>
完整的代码涉及公司商业上的东西,这里就不放出来了。
以上,如果是接口测试或者其他需要放到报告里边的,比如附件/其他错误日志 都可以通过这种思路来实现。extentreports还是很强大的。