appium自动化报表自定义并且截图

报表信息

appium自动化报表自定义并且截图

失败截图

appium自动化报表自定义并且截图

鼠标放上图片放大

appium自动化报表自定义并且截图

popj代码

ReportTotal


/**
 *  报表信息
 * @author liwen406
 * @date 2019-09-25 11:02
 */
 @Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ReportTotal {
    private Integer id;

    /**
     * 开始时间
     */
    private String testUser;
    /**
     * 结束时间
     */
    private String startTime;
    /**
     * 结束时间
     */
    private String endTime;
    /**
     * 合计运行时间
     */
    private String runTime;
    /**
     * 运行版本
     */
    private String runVersion;
    /**
     * 系统版本
     */
    private String sysVersion;
    /**
     * 设备型号(手机)
     */
    private String typeInfo;
    /**
     * 用例总数
     */
    private String total;
    /**
     * 成功数据
     */
    private String successTotal;
    /**
     * 失败数
     */
    private String failedTotal;
    /**
     * 跳过
     */
    private String skippedTotal;
    /**
     * 错误数
     */
    private String errorTotal;
    /**
     * 执行执行方法
     */
    private String runMethod;
    /**
     * 状态
     */
    private String status;
    /**
     * 详情
     */
    private String descriptionInfo;

    /**
     * 持续时间
     */
    private String duration;

    /**
     * 日志
     */
    private String detail;

ReportUtil


/**
 * @author liwen406
 * @Title: ReportUtil
 * @Description: 报表工具类
 * @date 2019/9/19 / 17:34
 */
 @Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ReportUtil {
    private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000");
    private static final NumberFormat PERCENTAGE_FORMAT = new DecimalFormat("#0.00%");

    public static String formatDate(long date) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return formatter.format(date);
    }

    /**
     * 测试消耗时长
     * return 秒,保留3位小数
     */
    public String getTestDuration(ITestContext context) {
        long duration;
        duration = context.getEndDate().getTime() - context.getStartDate().getTime();
        return formatDuration(duration);
    }

    public static String formatDuration(long elapsed) {
        double seconds = (double) elapsed / 1000;
        return DURATION_FORMAT.format(seconds);
    }

    /**
     * 测试通过率
     * return 2.22%,保留2位小数
     */
    public String formatPercentage(int numerator, int denominator) {
        return PERCENTAGE_FORMAT.format(numerator / (double) denominator);
    }

    /**
     * 获取方法参数,以逗号分隔
     *
     * @param result
     * @return
     */
    public static String getParams(ITestResult result) {
        Object[] params = result.getParameters();
        List<String> list = new ArrayList<String>(params.length);
        for (Object o : params) {
            list.add(renderArgument(o));
        }
        return commaSeparate(list);
    }

    /**
     * 获取依赖的方法
     *
     * @param result
     * @return
     */
    public String getDependMethods(ITestResult result) {
        String[] methods = result.getMethod().getMethodsDependedUpon();
        return commaSeparate(Arrays.asList(methods));
    }

    /**
     * 堆栈轨迹,暂不确定怎么做,放着先
     *
     * @param throwable
     * @return
     */
    public String getCause(Throwable throwable) {
        StackTraceElement[] stackTrace = throwable.getStackTrace(); //堆栈轨迹
        List<String> list = new ArrayList<String>(stackTrace.length);
        for (Object o : stackTrace) {
            list.add(renderArgument(o));
        }
        return commaSeparate(list);
    }

    /**
     * 获取全部日志输出信息
     *
     * @return
     */
    public List<String> getAllOutput() {
        return Reporter.getOutput();
    }

    /**
     * 按testresult获取日志输出信息
     *
     * @param result
     * @return
     */
    public List<String> getTestOutput(ITestResult result) {
        return Reporter.getOutput(result);
    }

    /*将object 转换为String*/
    private static String renderArgument(Object argument) {
        if (argument == null) {
            return "null";
        } else if (argument instanceof String) {
            return "\"" + argument + "\"";
        } else if (argument instanceof Character) {
            return "\‘" + argument + "\‘";
        } else {
            return argument.toString();
        }
    }

    /*将集合转换为以逗号分隔的字符串*/
    private static String commaSeparate(Collection<String> strings) {
        StringBuilder buffer = new StringBuilder();
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()) {
            String string = iterator.next();
            buffer.append(string);
            if (iterator.hasNext()) {
                buffer.append(", ");
            }
        }
        return buffer.toString();
    }

TestResult


/**
 * @author liwen406
 * @Title: TestResult
 * @Description: 用于存储测试结果
 * @date 2019/9/19 / 17:28
 */
 @Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TestResult {
    //测试方法名
    private String testName;
    //测试类名
    private String className;

    private String caseName;
    //测试用参数
    private String params;
    //测试描述
    private String description;
    //Reporter Output
    private  String output;

    private   List<String> twooutparam ;

    //测试异常原因
    private Throwable throwable;

    private String throwableTrace;
    //状态
    private int status;
    private String duration;
    private boolean success;

TestResultCollection


/**
 * @author liwen406
 * @Title: TestResultCollection
 * @Description: testng采用数据驱动,一个测试类可以有多个测试用例集合,每个测试类,应该有个测试结果集
 * @date 2019/9/19 / 17:31
 */
 @Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TestResultCollection {
    private int totalSize = 0;

    private int successSize = 0;

    private int failedSize = 0;

    private int errorSize = 0;

    private int skippedSize = 0;
    private List<TestResult> resultList;

    public void addTestResult(TestResult result) {
        if (resultList == null) {
            resultList = new LinkedList<>();
        }
        resultList.add(result);

        switch (result.getStatus()) {
            case ITestResult.FAILURE:
                failedSize += 1;
                break;
            case ITestResult.SUCCESS:
                successSize += 1;
                break;
            case ITestResult.SKIP:
                skippedSize += 1;
                break;
        }

        totalSize += 1;
    }

监听器代码

ReporterListener.class


import cases.startdemo.utils.SendMailUtils;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONObject;
import com.jd.dhf.api.test.util.LogUtil;
import com.jd.fastjson.JSON;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;
import jdth.getsku.dao.imp.ReportUiDaoImpl;
import jdth.getsku.pojo.ReportUi;
import jdth.getsku.util.Log;
import jdth.getsku.util.OperationalCmd;
import jdth.getsku.util.WaitUtil;
import jdth.global.pagatest.BestRuner;
import org.apache.commons.io.FileUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.openqa.selenium.OutputType;
import org.testng.*;
import org.testng.annotations.Test;
import org.testng.xml.XmlSuite;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author liwen
 * @Title: ReporterListener
 * @Description: 监听类  UI自动化测试报告
 * @date 2019/9/19 / 17:33
 */
public class ReporterListener implements IReporter, ITestListener {

    DateFormat dateFormat = new SimpleDateFormat("yyyy_MMdd_hhmmss");

    /**
     * 保存数据
     */
    ReportUiDaoImpl reportUiDao = new ReportUiDaoImpl();

    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        List<ITestResult> list = new LinkedList<>();
        Date startDate = new Date();
        Date endDate = new Date();

        int TOTAL = 0;
        int SUCCESS = 1;
        int FAILED = 0;
        int ERROR = 0;
        int SKIPPED = 0;
        for (ISuite suite : suites) {
            Map<String, ISuiteResult> suiteResults = suite.getResults();
            for (ISuiteResult suiteResult : suiteResults.values()) {
                ITestContext testContext = suiteResult.getTestContext();

                startDate = startDate.getTime() > testContext.getStartDate().getTime() ? testContext.getStartDate() : startDate;

                if (endDate == null) {
                    endDate = testContext.getEndDate();
                } else {
                    endDate = endDate.getTime() < testContext.getEndDate().getTime() ? testContext.getEndDate() : endDate;
                }

                IResultMap passedTests = testContext.getPassedTests();
                IResultMap failedTests = testContext.getFailedTests();
                IResultMap skippedTests = testContext.getSkippedTests();
                IResultMap failedConfig = testContext.getFailedConfigurations();

                SUCCESS += passedTests.size();
                FAILED += failedTests.size();
                SKIPPED += skippedTests.size();
                ERROR += failedConfig.size();

                list.addAll(this.listTestResult(passedTests));
                list.addAll(this.listTestResult(failedTests));
                list.addAll(this.listTestResult(skippedTests));
                list.addAll(this.listTestResult(failedConfig));

            }
        }
        /* 计算总数 */
        TOTAL = SUCCESS + FAILED + SKIPPED + ERROR;

        this.sort(list);
        Map<String, TestResultCollection> collections = this.parse(list);
        VelocityContext context = new VelocityContext();

        context.put("TOTAL", TOTAL);
        context.put("mobileModel", OperationalCmd.getMobileModel());
        context.put("versionName", OperationalCmd.getVersionNameInfo());
        context.put("SUCCESS", SUCCESS);
        context.put("FAILED", FAILED);
        context.put("ERROR", ERROR);
        context.put("SKIPPED", SKIPPED);
        context.put("startTime", ReportUtil.formatDate(startDate.getTime()) + "<--->" + ReportUtil.formatDate(endDate.getTime()));
        context.put("DURATION", ReportUtil.formatDuration(endDate.getTime() - startDate.getTime()));
        context.put("results", collections);
        write(context, outputDirectory);

        //通过post请求
        ReportTotal reportTotal = new ReportTotal();
        reportTotal.setTestUser(System.getProperty("user.name"));
        reportTotal.setStartTime(startDate + "");
        reportTotal.setEndTime(endDate + "");
        reportTotal.setRunTime(ReportUtil.formatDuration(endDate.getTime() - startDate.getTime()));
        reportTotal.setRunVersion(OperationalCmd.getVersionNameInfo());
        reportTotal.setSysVersion(OperationalCmd.getVersionNameInfo());
        reportTotal.setTypeInfo(OperationalCmd.getMobileModel());
        reportTotal.setTotal(TOTAL + "");
        reportTotal.setSuccessTotal(SUCCESS + "");
        reportTotal.setFailedTotal(FAILED + "");
        reportTotal.setSkippedTotal(SKIPPED + "");
//        String s = JSONObject.toJSONString(reportTotal);
//        HttpRequest.post("http://127.0.0.1:8081/report/inserttotal").body(s).execute().body();

    }

    /**
     * 输出模板
     *
     * @param context
     * @param outputDirectory
     */
    private void write(VelocityContext context, String outputDirectory) {
        String fileDir = ReporterListener.class.getResource("/Template").getPath();

        String reslutpath = outputDirectory + "/html/report" + dateFormat.format(new Date()) + ".html";
        try {
            //写文件
            VelocityEngine ve = new VelocityEngine();
            Properties p = new Properties();
            p.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, fileDir);
            p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8");
            p.setProperty(Velocity.INPUT_ENCODING, "utf-8");
            ve.init(p);

            Template t = ve.getTemplate("report.vm");
            //输出结果
            OutputStream out = new FileOutputStream(new File(reslutpath));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
            // 转换输出
            t.merge(context, writer);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        /**
         * 发送邮件
         */
//        SendMailUtils.sendemali(reslutpath);
    }

    private void sort(List<ITestResult> list) {
        Collections.sort(list, new Comparator<ITestResult>() {
            @Override
            public int compare(ITestResult r1, ITestResult r2) {
                if (r1.getStatus() < r2.getStatus()) {
                    return 1;
                } else {
                    return -1;
                }
            }
        });
    }

    private LinkedList<ITestResult> listTestResult(IResultMap resultMap) {
        Set<ITestResult> results = resultMap.getAllResults();
        return new LinkedList<>(results);
    }

    private Map<String, TestResultCollection> parse(List<ITestResult> list) {

        Map<String, TestResultCollection> collectionMap = new HashMap<>();

        for (ITestResult t : list) {
            String className = t.getTestClass().getName();
            if (collectionMap.containsKey(className)) {
                TestResultCollection collection = collectionMap.get(className);
                collection.addTestResult(toTestResult(t));

            } else {
                TestResultCollection collection = new TestResultCollection();
                collection.addTestResult(toTestResult(t));
                collectionMap.put(className, collection);
            }
        }

        return collectionMap;
    }

    private TestResult toTestResult(ITestResult t) {
        TestResult testResult = new TestResult();
//        Object[] params = t.getParameters();
//        testResult.setParams(params + "");

//        if (params != null && params.length >= 1) {
//            String caseId = (String) params[0];
//            LogUtil.info("caseid" + caseId);
//            testResult.setCaseName(caseId);
//        } else {
//            testResult.setCaseName("null");
//        }

        testResult.setTestName(t.getMethod().getMethodName());
        testResult.setParams(System.getProperty("user.name"));
        testResult.setClassName(t.getTestClass().getName());
//        testResult.setParams(ReportUtil.getParams(t));
        //获取注解上面的 testName
        testResult.setCaseName(t.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
        testResult.setDescription(t.getMethod().getDescription());
        testResult.setStatus(t.getStatus());
        testResult.setThrowableTrace("class: " + t.getTestClass().getName() + " <br/> method: " + t.getName() + " <br/> error: " + t.getThrowable());
        testResult.setThrowable(t.getThrowable());
        long duration = t.getEndMillis() - t.getStartMillis();
        testResult.setDuration(ReportUtil.formatDuration(duration));
        testResult.setTwooutparam(Reporter.getOutput(t));
        List<String> output = Reporter.getOutput(t);
        StringBuffer stringBuffer = new StringBuffer();
        for (String s : output) {
            stringBuffer.append(s + ",");
        }

        testResult.setOutput(stringBuffer.toString());
//        String s = JSONObject.toJSONString(testResult);
//        HttpRequest.post("http://127.0.0.1:8081/report/insertreport").body(s).execute().body();

        return testResult;
    }

    /**
     * 每次调用测试@Test之前调用
     *
     * @param result
     */
    @Override
    public void onTestStart(ITestResult result) {
        logTestStart(result);
    }

    /**
     * 用例执行结束后,用例执行成功时调用
     *
     * @param result
     */
    @Override
    public void onTestSuccess(ITestResult result) {
        ReportUi reportUi = new ReportUi();

        //开始时间
        reportUi.setStartTime(ReportUtil.formatDate(result.getStartMillis()));

        reportUi.setStatus("1");
        //结束时间
        reportUi.setEndtime(ReportUtil.formatDate(result.getEndMillis()));

        //成功数据
        reportUi.setSuccess("成功");

        //运行用例类名字
        reportUi.setRunMethodName(result.getName());
        //描述
        reportUi.setDescriptionInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).description());
        reportUi.setTypeInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());

        LogUtil.info("查看是否获取数据:" + result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
        //持续时间
        reportUi.setDuration(String.valueOf(result.getEndMillis() - result.getStartMillis()) + "毫秒");

        //日志
        List<String> output = Reporter.getOutput(result);
        StringBuffer stringBuffer = new StringBuffer();
        for (String s : output) {
            stringBuffer.append(s + "<br/>");
        }
        reportUi.setDetail(stringBuffer.toString());
        reportUiDao.savereport(reportUi);

        logTestEnd(result, "Success");
    }

    /**
     * 用例执行结束后,用例执行失败时调用
     * 跑fail则截图 获取屏幕截图
     *
     * @param result
     */

    @Override
    public void onTestFailure(ITestResult result) {
        WaitUtil.sleep(2000);
        AndroidDriver<AndroidElement> driver = BestRuner.getDriver();
        File srcFile = driver.getScreenshotAs(OutputType.FILE);

        File location = new File("./test-output/html/result/screenshots");
        if (!location.exists()) {
            location.mkdirs();
        }
        String dest = result.getMethod().getRealClass().getSimpleName() + "." + result.getMethod().getMethodName();
        String s = dest + "_" + dateFormat.format(new Date()) + ".png";
        File targetFile =
                new File(location + "/" + s);
        LogUtil.info("截图位置:");
        Reporter.log("<font color=\"#FF0000\">截图位置</font><br /> " + targetFile.getPath());
        LogUtil.info("------file is ---- " + targetFile.getPath());
        try {
            FileUtils.copyFile(srcFile, targetFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
        logTestEnd(result, "Failed");
        //报告截图后面显示
        Reporter.log("<img  src=\"./result/screenshots/" + s + "\" width=\"64\" height=\"64\" alt=\"***\"  onMouseover=\"this.width=353; this.height=613\" onMouseout=\"this.width=64;this.height=64\" />");

        ReportUi reportUi = new ReportUi();

        //开始时间
        reportUi.setStartTime(ReportUtil.formatDate(result.getStartMillis()));

        //结束时间
        reportUi.setEndtime(ReportUtil.formatDate(result.getEndMillis()));
        reportUi.setStatus("0");
        //成功数据
        reportUi.setFailed("失败");

        //运行用例类名字
        reportUi.setRunMethodName(result.getName());
        //描述
        reportUi.setDescriptionInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).description());
        reportUi.setTypeInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
        //持续时间
        reportUi.setDuration(String.valueOf(result.getEndMillis() - result.getStartMillis()) + "毫秒");

        //日志
        List<String> output = Reporter.getOutput(result);
        StringBuffer stringBuffer = new StringBuffer();
        for (String s1 : output) {
            stringBuffer.append(s1 + "<br/>");
        }

        stringBuffer.append("+截图路径:" + targetFile.getPath());
        reportUi.setDetail(stringBuffer.toString());

        reportUiDao.savereport(reportUi);
    }

    /**
     * 用例执行结束后,用例执行skip时调用
     *
     * @param result
     */
    @Override
    public void onTestSkipped(ITestResult result) {
        ReportUi reportUi = new ReportUi();

        //开始时间
        reportUi.setStartTime(ReportUtil.formatDate(result.getStartMillis()));

        //结束时间
        reportUi.setEndtime(ReportUtil.formatDate(result.getEndMillis()));
        reportUi.setStatus("2");
        //成功数据
        reportUi.setSkipped("跳过");

        //运行用例类名字
        reportUi.setRunMethodName(result.getName());
        //描述
        reportUi.setDescriptionInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).description());
        reportUi.setTypeInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
        //持续时间
        reportUi.setDuration(String.valueOf(result.getEndMillis() - result.getStartMillis()) + "毫秒");

        //日志
        List<String> output = Reporter.getOutput(result);
        StringBuffer stringBuffer = new StringBuffer();
        for (String s : output) {
            stringBuffer.append(s + "<br/>");
        }
        reportUi.setDetail(stringBuffer.toString());

        reportUiDao.savereport(reportUi);

        logTestEnd(result, "Skipped");
    }

    /**
     * 每次方法失败但是已经使用successPercentage进行注释时调用,并且此失败仍保留在请求的成功百分比之内。
     *
     * @param result
     */
    @Override
    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
        logTestEnd(result, "FailedButWithinSuccessPercentage");
    }

    /**
     * 在测试类被实例化之后调用,并在调用任何配置方法之前调用。
     *
     * @param context
     */
    @Override
    public void onStart(ITestContext context) {
        return;
    }

    /**
     * 在所有测试运行之后调用,并且所有的配置方法都被调用
     *
     * @param context
     */
    @Override
    public void onFinish(ITestContext context) {
        return;
    }

    /**
     * 在用例执行结束时,打印用例的执行结果信息
     */
    protected void logTestEnd(ITestResult tr, String result) {
        Reporter.log(String.format("=============Result: %s=============", result), true);

    }

    /**
     * 在用例开始时,打印用例的一些信息,比如@Test对应的方法名,用例的描述等等
     */
    protected void logTestStart(ITestResult tr) {
        Reporter.log(String.format("=============Run: %s===============", tr.getMethod().getMethodName()), true);
        Reporter.log(String.format("用例描述: %s, 优先级: %s", tr.getMethod().getDescription(), tr.getMethod().getPriority()),
                true);
        return;
    }

}

报告模板

注意存放路径


report.vm

<head>
    <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>UI自动</title>
    <style>
        body {
            background-color: #f2f2f2;
            color: #333;
            margin: 0 auto;
            width: 960px;
        }

        #summary {
            width: 960px;
            margin-bottom: 20px;
        }

        #summary th {
            background-color: skyblue;
            padding: 5px 12px;
        }

        #summary td {
            background-color: lightblue;
            text-align: center;
            padding: 4px 8px;
        }

        .details {
            width: 960px;
            margin-bottom: 20px;
        }

        .details th {
            background-color: skyblue;
            padding: 5px 12px;
        }

        .details tr .passed {
            background-color: lightgreen;
        }

        .details tr .failed {
            background-color: red;
        }

        .details tr .unchecked {
            background-color: gray;
        }

        .details td {
            background-color: lightblue;
            padding: 5px 12px;
        }

        .details .detail {
            background-color: lightgrey;
            font-size: smaller;
            padding: 5px 10px;
            text-align: center;
        }

        .details .success {
            background-color: greenyellow;
        }

        .details .error {
            background-color: red;
        }

        .details .failure {
            background-color: salmon;
        }

        .details .skipped {
            background-color: gray;
        }

        .button {
            font-size: 1em;
            padding: 6px;
            width: 4em;
            text-align: center;
            background-color: #06d85f;
            border-radius: 20px/50px;
            cursor: pointer;
            transition: all 0.3s ease-out;
        }

        a.button {
            color: gray;
            text-decoration: none;
        }

        .button:hover {
            background: #2cffbd;
        }

        .overlay {
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            background: rgba(0, 0, 0, 0.7);
            transition: opacity 500ms;
            visibility: hidden;
            opacity: 0;
        }

        .overlay:target {
            visibility: visible;
            opacity: 1;
        }

        .popup {
            margin: 70px auto;
            padding: 20px;
            background: #fff;
            border-radius: 10px;
            width: 50%;
            position: relative;
            transition: all 3s ease-in-out;
        }

        .popup h2 {
            margin-top: 0;
            color: #333;
            font-family: Tahoma, Arial, sans-serif;
        }

        .popup .close {
            position: absolute;
            top: 20px;
            right: 30px;
            transition: all 200ms;
            font-size: 30px;
            font-weight: bold;
            text-decoration: none;
            color: #333;
        }

        .popup .close:hover {
            color: #06d85f;
        }

        .popup .content {
            max-height: 80%;
            overflow: auto;
            text-align: left;
        }

        @media screen and (max-width: 700px) {
            .box {
                width: 70%;
            }

            .popup {
                width: 70%;
            }
        }

    </style>
</head>

<body>
<br>
<h1 align="center">泰国站UI自动化回归报告</h1>

<h2>汇总信息</h2>
<table id="summary">

    <tr>
        <th>开始与结束时间</th>
        <td colspan="2">${startTime}</td>
        <th>执行时间</th>
        <td colspan="2">$DURATION seconds</td>
    </tr>
    <tr>
        <th>运行版本与系统版本</th>
        <td colspan="2">${versionName}</td>
        <th>设备型号</th>
        <td colspan="2">${mobileModel}</td>
    </tr>
    <tr>
        <th>TOTAL</th>
        <th>SUCCESS</th>
        <th>FAILED</th>
        <th>ERROR</th>
        <th>SKIPPED</th>
    </tr>
    <tr>
        <td>$TOTAL</td>
        <td>$SUCCESS</td>
        <td>$FAILED</td>
        <td>$ERROR</td>
        <td>$SKIPPED</td>
    </tr>
</table>

<h2>详情</h2>

    #foreach($result in $results.entrySet())
        #set($item = $result.value)
    <table id="$result.key" class="details">
        <tr>
            <th>测试类</th>
            <td colspan="4">$result.key</td>
        </tr>
        <tr>
            <td>TOTAL: $item.totalSize</td>
            <td>SUCCESS: $item.successSize</td>
            <td>FAILED: $item.failedSize</td>
            <td>ERROR: $item.errorSize</td>
            <td>SKIPPED: $item.skippedSize</td>
        </tr>
        <tr>
            <th>Status</th>
            <th>Method</th>
            <th>Description</th>
            <th>Duration</th>
            <th>Detail</th>
        </tr>
        #foreach($testResult in $item.resultList)
            <tr>
                #if($testResult.status==1)
                    <th class="success" style="width:5em;">success
                    </td>
                #elseif($testResult.status==2)
                    <th class="failure" style="width:5em;">failure
                    </td>
                #elseif($testResult.status==3)
                    <th class="skipped" style="width:5em;">skipped
                    </td>
                #end
                <td>$testResult.testName</td>
                <td>${testResult.description}</td>
                <td>${testResult.duration} seconds</td>
                <td class="detail">
                    <a class="button" href="#popup_log_${testResult.caseName}_${testResult.testName}">log</a>
                    <div id="popup_log_${testResult.caseName}_${testResult.testName}" class="overlay">
                        <div class="popup">
                            <h2>Request and Response data</h2>
                            <a class="close" href="">×</a>
                            <div class="content">
                                <h3>Response:</h3>
                                <div style="overflow: auto">
                                    <table>
                                        <tr>
                                            <th>日志</th>
                                            <td>
                                                #foreach($msg in $testResult.twooutparam)
                                                    <pre>$msg</pre>
                                                #end
                                            </td>
                                        </tr>
                                        #if($testResult.status==2)
                                            <tr>
                                                <th>异常</th>
                                                <td>
                                                    <pre>$testResult.throwableTrace</pre>
                                                </td>
                                            </tr>
                                        #end
                                    </table>
                                </div>
                            </div>
                        </div>
                    </div>
                </td>
            </tr>
        #end
    </table>
    #end
<a href="#top">Android前端UI自动化</a>
</body>

appium自动化报表自定义并且截图

上一篇:Android自动化测试------monkey(三)


下一篇:CSS的文本格式化样式总汇