报表信息

appium自动化报表自定义并且截图_第1张图片

失败截图

appium自动化报表自定义并且截图_第2张图片

鼠标放上图片放大

appium自动化报表自定义并且截图_第3张图片

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 list = new ArrayList(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 list = new ArrayList(stackTrace.length);
        for (Object o : stackTrace) {
            list.add(renderArgument(o));
        }
        return commaSeparate(list);
    }

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

    /**
     * 按testresult获取日志输出信息
     *
     * @param result
     * @return
     */
    public List 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 strings) {
        StringBuilder buffer = new StringBuilder();
        Iterator 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 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 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 xmlSuites, List suites, String outputDirectory) {
        List 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 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 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 list) {
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(ITestResult r1, ITestResult r2) {
                if (r1.getStatus() < r2.getStatus()) {
                    return 1;
                } else {
                    return -1;
                }
            }
        });
    }

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

    private Map parse(List list) {

        Map 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() + " 
method: " + t.getName() + "
error: " + t.getThrowable()); testResult.setThrowable(t.getThrowable()); long duration = t.getEndMillis() - t.getStartMillis(); testResult.setDuration(ReportUtil.formatDuration(duration)); testResult.setTwooutparam(Reporter.getOutput(t)); List 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 output = Reporter.getOutput(result); StringBuffer stringBuffer = new StringBuffer(); for (String s : output) { stringBuffer.append(s + "
"); } reportUi.setDetail(stringBuffer.toString()); reportUiDao.savereport(reportUi); logTestEnd(result, "Success"); } /** * 用例执行结束后,用例执行失败时调用 * 跑fail则截图 获取屏幕截图 * * @param result */ @Override public void onTestFailure(ITestResult result) { WaitUtil.sleep(2000); AndroidDriver 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("截图位置
" + targetFile.getPath()); LogUtil.info("------file is ---- " + targetFile.getPath()); try { FileUtils.copyFile(srcFile, targetFile); } catch (IOException e) { e.printStackTrace(); } logTestEnd(result, "Failed"); //报告截图后面显示 Reporter.log("\"***\""); 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 output = Reporter.getOutput(result); StringBuffer stringBuffer = new StringBuffer(); for (String s1 : output) { stringBuffer.append(s1 + "
"); } 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 output = Reporter.getOutput(result); StringBuffer stringBuffer = new StringBuffer(); for (String s : output) { stringBuffer.append(s + "
"); } 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


    
    
    UI自动
    




泰国站UI自动化回归报告

汇总信息

开始与结束时间 ${startTime} 执行时间 $DURATION seconds
运行版本与系统版本 ${versionName} 设备型号 ${mobileModel}
TOTAL SUCCESS FAILED ERROR SKIPPED
$TOTAL $SUCCESS $FAILED $ERROR $SKIPPED

详情

#foreach($result in $results.entrySet()) #set($item = $result.value) #foreach($testResult in $item.resultList) #if($testResult.status==1) #end
测试类 $result.key
TOTAL: $item.totalSize SUCCESS: $item.successSize FAILED: $item.failedSize ERROR: $item.errorSize SKIPPED: $item.skippedSize
Status Method Description Duration Detail
success #elseif($testResult.status==2) failure #elseif($testResult.status==3) skipped #end $testResult.testName ${testResult.description} ${testResult.duration} seconds log
#end Android前端UI自动化