是的我蛋疼,因为最近要用 ExtentReports 生成测试报告,但是我 E 文不好,所以边翻译别学习,有些翻译的我觉得没有表达原文含义的或者可能会有二义性的地方,我都把原文贴出来了,反正将就着看吧,啊哈哈哈哈。不说废话上正文。
为了成功生成测试信息,需要启动并建立 Reporter(测试报告相关信息)和 ExtentReports 的关联。如果启动 Reporter 失败或未关联至 ExtentReports 类,在创建测试用例或将测试执行结果上传时,将提示 IllegalStateException 错误。
初始化 Reporter 示例:
// 初始化HtmlReporter
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter("extent.html");
// 初始化ExtentXReporter
ExtentXReporter extentxReporter = new ExtentXReporter("mongodb-host", mongodb-port);
// 初始化EmailReporter (社区版不支持)
ExtentEmailReporter emailReporter = new ExtentEmailReporter("email.html");
// 初始化realtime logger (社区版不支持)
ExtentLogger logger = new ExtentLogger();
// 创建ExtentReports对象
ExtentReports extent = new ExtentReports();
// 将HtmlReporter关联ExtentReports对象
extent.attachReporter(htmlReporter);
// 将所有Reporter都关联ExtentReports对象
extent.attachReporter(htmlReporter, extentxReporter, emailReporter, logger);
当测试执行完成并准备将测试执行结果生成测试报告时,只需要简单调用 flush() 方法。
以下是几种报告模式在调用 flush() 方法生成/更新执行结果时执行的操作:
HtmlReporter 允许创建离线报告(将 css 和 js 文件本地保存),使用以下方法:
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter("extent.html");
htmlReporter.config().setCreateOfflineReport(true);
ExtentHtmlReporter 和 ExtentXReporter 允许在已有报告中追加新的测试结果,设置以下属性:
htmlReporter.setAppendExisting(true);
如果希望在同一 reportId 的已有测试报告中追加新的测试结果,则:
extentXReporter.setAppendExisting(true, reportId);
// or:
extentxReporter.config().setReportObjectId(ObjectId id);
extentxReporter.config().setReportObjectId(String id);
如果希望在同一 reportName 的已有测试报告中追加新的测试结果,则:
extentxReporter.setAppendExisting(true);
注意:如果 reportName 非唯一,将在最后生成的测试报告中追加测试结果。
使用 createTest 或 createNode 方法来建立测试用例:
注意:只可能在至少一个 reporter 关联至 ExtentReports 时,才能创建 tests 或 nodes
代码示例:
// creating tests
ExtentTest test = extent.createTest("MyFirstTest");
test.pass("details");
// short-hand for above
extent
.createTest("MyFirstTest")
.pass("details");
// test with description
extent
.createTest("MyFirstTest", "Test Description")
.pass("details");
使用 createNode 方法将测试 node 作为其他 test 的子案例。
代码示例:
// creating nodes
ExtentTest parentTest = extent.createTest("MyFirstTest");
ExtentTest childTest = parentTest.createNode("MyFirstChildTest");
childTest.pass("details");
// short-hand for above
extent
.createTest("MyFirstTest")
.createNode("MyFirstChildTest")
.pass("details");
// node description
ExtentTest childTest = parentTest.createNode("MyFirstChildTest", "Node Description");
用 gherkin 模型或模型名来创建 cucumber / gherkin 模式的测试用例:
注意:当创建 BDD 模式的测试用例时,所有的测试用例必须用相同的样式。不要在 BDD 中混用常规测试案例样式。一个测试报告中只能包含一种测试用例样式。
代码示例:
// gherkin classes
// feature
ExtentTest feature = extent.createTest("Refund item");
// scenario
ExtentTest scenario = feature.createNode(Scenario.class, "Jeff returns a faulty microwave");
scenario.createNode(Given.class, "Jeff has bought a microwave for $100").pass("pass");
scenario.createNode(And.class, "he has a receipt").pass("pass");
scenario.createNode(When.class, "he returns the microwave").pass("pass");
scenario.createNode(Then.class, "Jeff should be refunded $100").fail("fail");
// using keyword names
// feature
ExtentTest feature = extent.createTest("Refund item");
// scenario
ExtentTest scenario = feature.createNode(new GherkinKeyword("Scenario") , "Jeff returns a faulty microwave");
scenario.createNode(new GherkinKeyword("Given"), "Jeff has bought a microwave for $100").pass("pass");
scenario.createNode(new GherkinKeyword("And"), "he has a receipt").pass("pass");
scenario.createNode(new GherkinKeyword("When"), "he returns the microwave").pass("pass");
scenario.createNode(new GherkinKeyword("Then"), "Jeff should be refunded $100").fail("fail");
如果希望移除任何已经开始执行的测试用例,调用 removeTest(3.0.4+)方法:
ExtentTest test = extent.createTest("Test");
extent.removeTest(test);
如果希望设置测试案例在测试报告中的显示位置,则:
// The 1st executed test appears at the top of the report
extent.setTestDisplayOrder(TestDisplayOrder.OLDEST_FIRST);
// The last executed test appears at the top of the report
extent.setTestDisplayOrder(TestDisplayOrder.NEWEST_FIRST);
ExtentTest test = extent.createTest("TestName");
test.log(Status.PASS, "pass");
// or:
test.pass("pass");
test.log(Status.FAIL, "fail");
// or:
test.fail("fail");
To log exceptions, simply pass the exception.
Note: doing this will also enable the defect/bug tab in the report.
Exception e;
test.fail(e);
可以使用 assignCategory 方法来建立测试案例分类:
test.assignCategory("Regression");
test.assignCategory("Regression", "ExtentAPI");
test.assignCategory("Regression", "ExtentAPI", "category-3", "cagegory-4", ..);
// while creating test
extent
.createTest("MyFirstTest")
.assignCategory("Regression")
.pass("details");
可以使用 assignAuthor 方法来创建测试案例作者信息
test.assignAuthor("aventstack");
test.assignAuthor("name1", "name2");
// while creating test
extent
.createTest("MyFirstTest")
.assignAuthor("aventstack")
.pass("details");
可以在测试案例和日志中关联截屏:
// adding screenshots to log
test.fail("details", MediaEntityBuilder.createScreenCaptureFromPath("screenshot.png").build());
// or:
MediaModelProvider mediaModel = MediaEntityBuilder.createScreenCaptureFromPath("screenshot.png").build();
test.fail("details", mediaModel);
// adding screenshots to test
test.fail("details").addScreenCaptureFromPath("screenshot.png");
使用 createScreenCaptureFromBase64String 或 addScreenCaptureFromBase64String 方法在测试案例和日志中关联 Base64 截屏。
// adding base64 strings to log:
test.warning("details", MediaEntityBuilder.createScreenCaptureFromBase64String("base64String").build());
test.log(Status.WARNING, "details", MediaEntityBuilder.createScreenCaptureFromBase64String("base64String").build());
// adding base64 strings to tests:
test.addScreenCaptureFromBase64String("base64String");
使用 HTML 标签进行 HTML 报告样式自定义:
extent.log(LogStatus.INFO, "HTML", "Usage: BOLD TEXT");
通过使用 setSystemInfo 方法可以在测试报告中增加系统或环境信息。
注意:方法将自动增加信息到所有启动的 reporter 中
extent.setSystemInfo("os", "win7");
ExtentHtmlReporter 支持一些快捷键,用来快速在视图和导航中切换。
t - test-view
c - category-view
x - exception-view
d - dashboard
p - show passed tests
e - show error tests
f - show failed tests
s - show skipped tests
w - show warning tests
esc - clear filters
down-arrow - scroll down
up-arrow - scroll up
<?xml version="1.0" encoding="UTF-8"?>
<extentreports>
<configuration>
<!-- report theme -->
<!-- standard, dark -->
<theme>standard</theme>
<!-- document encoding -->
<!-- defaults to UTF-8 -->
<encoding>UTF-8</encoding>
<!-- protocol for script and stylesheets -->
<!-- defaults to https -->
<protocol>https</protocol>
<!-- title of the document -->
<documentTitle>Extent</documentTitle>
<!-- report name - displayed at top-nav -->
<reportName>Automation Report</reportName>
<!-- location of charts in the test view -->
<!-- top, bottom -->
<testViewChartLocation>bottom</testViewChartLocation>
<!-- settings to enable/disable views -->
<!-- professional version only -->
<enableCategoryView>true</enableCategoryView>
<enableAuthorView>false</enableAuthorView>
<enableExceptionView>true</enableExceptionView>
<enableTestRunnerLogsView>true</enableTestRunnerLogsView>
<!-- custom javascript -->
<scripts>
<![CDATA[
$(document).ready(function() {
});
]]>
</scripts>
<!-- custom styles -->
<styles>
<![CDATA[
]]>
</styles>
</configuration>
</extentreports>
<?xml version="1.0" encoding="UTF-8"?>
<extentreports>
<configuration>
<!-- report theme -->
<!-- standard, dark -->
<theme>standard</theme>
<!-- document encoding -->
<!-- defaults to UTF-8 -->
<encoding>UTF-8</encoding>
<!-- protocol for script and stylesheets -->
<!-- defaults to https -->
<protocol>https</protocol>
<!-- title of the document -->
<documentTitle>Extent</documentTitle>
<!-- report name - displayed at top-nav -->
<reportName>Automation Report</reportName>
<!-- location of charts in the test view -->
<!-- top, bottom -->
<testViewChartLocation>bottom</testViewChartLocation>
<!-- custom javascript -->
<scripts>
<![CDATA[
$(document).ready(function() {
});
]]>
</scripts>
<!-- custom styles -->
<styles>
<![CDATA[
]]>
</styles>
</configuration>
</extentreports>
使用如下设置来自动创建截屏相对地址与测试报告的关联:
htmlReporter.config().setAutoCreateRelativePathMedia(true);
这个设置能将文件拷贝到对应相对路径的目录下,而不需要手动设置:
代码示例:
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter("extent.html");
htmlReporter.config().setAutoCreateRelativePathMedia(true);
test1.fail("details", MediaEntityBuilder.createScreenCaptureFromPath("1.png").build());
test2.fail("details", MediaEntityBuilder.createScreenCaptureFromPath("2.png").build());
上面两个截屏将自动保存并关联至测试报告。你可以移动报告和截屏而不会有任何副作用,比如破坏测试报告或者加载截屏失败错误。
htmlReporter.config().setCreateOfflineReport(true);
ExtentHtmlReporter htmlreporter = new ExtentHtmlReporter("Extent.html");
htmlreporter.setAppendExisting(true);
可以在测试报告中配置分析策略(只适用于 HTMLReporter)。在下面链接中查看不同的地方:
可以使用 config.xml 或者代码来配置视图可见性:
代码示例:
// enable category view
htmlReporter.config().setCategoryViewVisibility(true);
// disable Author view
htmlReporter.config().setAuthorViewVisibility(false);
// disable TestRunnerLogs view
htmlReporter.config().setTestRunnerLogsViewVisibility(false);
// enable Exception view
htmlReporter.config().setExceptionViewVisibility(true);
config.xml 配置示例:
<!-- settings to enable/disable views -->
<enableCategoryView>true</enableCategoryView>
<enableAuthorView>false</enableAuthorView>
<enableExceptionView>true</enableExceptionView>
<enableTestRunnerLogsView>true</enableTestRunnerLogsView>
htmlReporter.loadXMLConfig("extent-config.xml");
使用 htmlReporter.views().dashboard().setSection(...) 在报告的 Dashboard 中增加自定义部件:
代码示例:
// setSection(String name, SectionSize size, String[] header, List data)
String[] header = new String[] { "HeaderA", "HeaderB", "HeaderC" };
List list = new ArrayList();
list.add(new String[] { "cell1", "cell2", "cell3" });
list.add(new String[] { "cell4", "cell5", "cell6" });
htmlReporter.views().dashboard().setSection("Sample", SectionSize.S4, header, list);
extentXReporter.getReportId();
extentXReporter.getProjectId();
extentXReporter.setAppendExisting(true, reportId);
// or:
extentXReporter.config().setReportObjectId(id);
(pro version only, 3.0.2+) 可以在 ExtentX(mongodb)中,将当前的执行结果追加到上一次执行结果中。
代码示例:
extentXReporter.appendToLastRunReport();
// or:
ObjectId reportId = extentXReporter.getLastRunReportId();
extentXReporter.setAppendExisting(true, reportId);
当有多个项目时小心使用该设置。假如测试代码中指定项目失败或在该设置之后才指定项目,ExtentXReporter 将默认将测试结果追加到可能属于其他项目的测试报告中。切记在使用该设置时设置项目(当你有多个项目时)
//设置项目名称
extentXReporter.config().setProjectName(projectName);
测试案例中允许使用一些标记协助定位:
String code = "\n\t\n\t\tText\n\t\n";
Markup m = MarkupHelper.createCodeBlock(code);
test.pass(m);
// or
test.log(Status.PASS, m);
String text = "extent";
Markup m = MarkupHelper.createLabel(text, ExtentColor.BLUE);
test.pass(m);
// or
test.log(Status.PASS, m);
String text = "This text will become part of a material card.";
Markup m = MarkupHelper.createCard(text, ExtentColor.CYAN);
test.pass(m);
// or
test.log(Status.PASS, m);
String url = "http://extentreports.com";
String name = "extent";
Markup m = MarkupHelper.createExternalLink(url, name);
test.pass(m);
// or
test.log(Status.PASS, m);
test.info(MarkupHelper.createModal("Modal text"));
通过 ExtentReports config() 可以自定义测试结果类型:
List statusHierarchy = Arrays.asList(
Status.FATAL,
Status.FAIL,
Status.ERROR,
Status.WARNING,
Status.SKIP,
Status.PASS,
Status.DEBUG,
Status.INFO
);
extent.config().statusConfigurator().setStatusHierarchy(statusHierarchy);
每个 reporter 都支持许多设置项,用来改变外观,增加给荣,管理测试等。
// make the charts visible on report open
htmlReporter.config().setChartVisibilityOnOpen(true);
// create offline report (pro-only)
htmlReporter.config().setCreateOfflineReport(true);
// automatic screenshot management (pro-only)
htmlReporter.config().setAutoCreateRelativePathMedia(true);
// report title
htmlReporter.config().setDocumentTitle("aventstack - ExtentReports");
// encoding, default = UTF-8
htmlReporter.config().setEncoding("UTF-8");
// protocol (http, https)
htmlReporter.config().setProtocol(Protocol.HTTPS);
// report or build name
htmlReporter.config().setReportName("Build-1224");
// chart location - top, bottom
htmlReporter.config().setTestViewChartLocation(ChartLocation.BOTTOM);
// theme - standard, dark
htmlReporter.config().setTheme(Theme.STANDARD);
// set timeStamp format
htmlReporter.config().setTimeStampFormat("mm/dd/yyyy hh:mm:ss a");
// add custom css
htmlreporter.config().setCSS("css-string");
// add custom javascript
htmlreporter.config().setJS("js-string");
// project name
extentx.config().setProjectName("ProjectName");
// report or build name
extentx.config().setReportName("Build-1224");
// server URL
// ! must provide this to be able to upload snapshots
// Note: this is the address to the ExtentX server, not the Mongo database
extentx.config().setServerUrl("http://localhost:1337");
public class Main {
public static void main(String[] args) {
// start reporters
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter("extent.html");
// create ExtentReports and attach reporter(s)
ExtentReports extent = new ExtentReports();
extent.attachReporter(htmlReporter);
// creates a toggle for the given test, adds all log events under it
ExtentTest test = extent.createTest("MyFirstTest", "Sample description");
// log(Status, details)
test.log(Status.INFO, "This step shows usage of log(status, details)");
// info(details)
test.info("This step shows usage of info(details)");
// log with snapshot
test.fail("details", MediaEntityBuilder.createScreenCaptureFromPath("screenshot.png").build());
// test with snapshot
test.addScreenCaptureFromPath("screenshot.png");
// calling flush writes everything to the log file
extent.flush();
}
}
public class ExtentTestNGReportBuilder {
private static ExtentReports extent;
private static ThreadLocal parentTest = new ThreadLocal();
private static ThreadLocal test = new ThreadLocal();
@BeforeSuite
public void beforeSuite() {
extent = ExtentManager.createInstance("extent.html");
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter("extent.html");
extent.attachReporter(htmlReporter);
}
@BeforeClass
public synchronized void beforeClass() {
ExtentTest parent = extent.createTest(getClass().getName());
parentTest.set(parent);
}
@BeforeMethod
public synchronized void beforeMethod(Method method) {
ExtentTest child = parentTest.get().createNode(method.getName());
test.set(child);
}
@AfterMethod
public synchronized void afterMethod(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE)
test.get().fail(result.getThrowable());
else if (result.getStatus() == ITestResult.SKIP)
test.get().skip(result.getThrowable());
else
test.get().pass("Test passed");
extent.flush();
}
}
public class ExtentManager {
private static ExtentReports extent;
public static ExtentReports getInstance() {
if (extent == null)
createInstance("test-output/extent.html");
return extent;
}
public static ExtentReports createInstance(String fileName) {
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(fileName);
htmlReporter.config().setTestViewChartLocation(ChartLocation.BOTTOM);
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setDocumentTitle(fileName);
htmlReporter.config().setEncoding("utf-8");
htmlReporter.config().setReportName(fileName);
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
return extent;
}
}
public class ExtentTestNGIReporterListener implements IReporter {
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "Extent.html";
private ExtentReports extent;
@Override
public void generateReport(List xmlSuites, List suites, String outputDirectory) {
init();
for (ISuite suite : suites) {
Map result = suite.getResults();
for (ISuiteResult r : result.values()) {
ITestContext context = r.getTestContext();
buildTestNodes(context.getFailedTests(), Status.FAIL);
buildTestNodes(context.getSkippedTests(), Status.SKIP);
buildTestNodes(context.getPassedTests(), Status.PASS);
}
}
for (String s : Reporter.getOutput()) {
extent.setTestRunnerOutput(s);
}
extent.flush();
}
private void init() {
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
htmlReporter.config().setDocumentTitle("ExtentReports - Created by TestNG Listener");
htmlReporter.config().setReportName("ExtentReports - Created by TestNG Listener");
htmlReporter.config().setTestViewChartLocation(ChartLocation.BOTTOM);
htmlReporter.config().setTheme(Theme.STANDARD);
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
private void buildTestNodes(IResultMap tests, Status status) {
ExtentTest test;
if (tests.size() > 0) {
for (ITestResult result : tests.getAllResults()) {
test = extent.createTest(result.getMethod().getMethodName());
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
if (result.getThrowable() != null) {
test.log(status, result.getThrowable());
}
else {
test.log(status, "Test " + status.toString().toLowerCase() + "ed");
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
}
public class ExtentTestNGITestListener implements ITestListener {
private static ExtentReports extent = ExtentManager.createInstance("extent.html");
//private static ThreadLocal parentTest = new ThreadLocal();
//private static ThreadLocal test = new ThreadLocal();
//不造为嘛,代码苦手表示用上面的代码就是编译报错,所以就随便改了改,如下,就通过了。。
private static ThreadLocal<ExtentTest> parentTest = new ThreadLocal();
private static ThreadLocal<ExtentTest> test = new ThreadLocal();
@Override
public synchronized void onStart(ITestContext context) {
ExtentTest parent = extent.createTest(getClass().getName());
parentTest.set(parent);
}
@Override
public synchronized void onFinish(ITestContext context) {
extent.flush();
}
@Override
public synchronized void onTestStart(ITestResult result) {
ExtentTest child = parentTest.get().createNode(result.getMethod().getMethodName());
test.set(child);
}
@Override
public synchronized void onTestSuccess(ITestResult result) {
test.get().pass("Test passed");
}
@Override
public synchronized void onTestFailure(ITestResult result) {
test.get().fail(result.getThrowable());
}
@Override
public synchronized void onTestSkipped(ITestResult result) {
test.get().skip(result.getThrowable());
}
@Override
public synchronized void onTestFailedButWithinSuccessPercentage(ITestResult result) {
}
}
public class ExtentManager {
private static ExtentReports extent;
public static ExtentReports getInstance() {
if (extent == null)
createInstance("test-output/extent.html");
return extent;
}
public static ExtentReports createInstance(String fileName) {
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(fileName);
htmlReporter.config().setTestViewChartLocation(ChartLocation.BOTTOM);
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setDocumentTitle(fileName);
htmlReporter.config().setEncoding("utf-8");
htmlReporter.config().setReportName(fileName);
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
return extent;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class Email {
// supply your SMTP host name
private static String host = "host";
// supply a static sendTo list
private static String to = "recipient@host.com";
// supply the sender email
private static String from = "sender@host.com";
/ default cc list
private static String cc = "";
// default bcc list
private static String bcc = "";
public static void send(String from, String to, String subject, String contents) throws MessagingException {
Properties prop = System.getProperties();
prop.setProperty("mail.smtp.host", host);
Session session = Session.getDefaultInstance(prop);
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setSubject(subject);
message.setContent(contents, "text/html");
List toList = getAddress(to);
for (String address : toList) {
message.addRecipient(Message.RecipientType.TO, new InternetAddress(address));
}
List ccList = getAddress(cc);
for (String address : ccList) {
message.addRecipient(Message.RecipientType.CC, new InternetAddress(address));
}
List bccList = getAddress(bcc);
for (String address : bccList) {
message.addRecipient(Message.RecipientType.BCC, new InternetAddress(address));
}
Transport.send(message);
}
public static void send(String to, String subject, String contents) throws MessagingException {
send(from, to, subject, contents);
}
public static void send(String subject, String contents) throws MessagingException {
send(from, to, subject, contents);
}
private static List getAddress(String address) {
List addressList = new ArrayList();
if (address.isEmpty())
return addressList;
if (address.indexOf(";") > 0) {
String[] addresses = address.split(";");
for (String a : addresses) {
addressList.add(a);
}
} else {
addressList.add(address);
}
return addressList;
}
}
// usage
Email.send("me@host.com", "recip1@host.com;recip2@host.com", "New Subject", "<h1>header</h2>")