基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(5)失败用例截图与重试

在UI自动化测试用例执行过程中,经常会有很多不确定的因素导致用例执行失败,比如网络原因、环境问题等,所以我们有必要引入重试机制(失败重跑),来提高测试用例执行稳定性。

准备工作:我们在进行失败截图保存到本地的时候,需要用到FileUtils类,该类是在commons-io包下的,所以我们需要先引入依赖:

复制代码
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

一:失败用例截图:

1、创建一个用例失败截图监听类(TestResultListener,名字自起)实现IHookable接口,实现run方法。 IHookable接口的作用:动态替换每一个被@Test注解标注的方法,即每当运行到@Test注解的方法的时候,就会执行该类的逻辑。

代码如下:

复制代码
@Override
public void run(IHookCallBack iHookCallBack, ITestResult iTestResult) {
    //保证@Test注解标注的测试方法能够正常执行
    iHookCallBack.runTestMethod(iTestResult);
    //判断用例结果是否异常
    if(iTestResult.getThrowable() != null){
        //testResult参数提供了getInstance方法,可以获取当前测试类的实例(对象)
        BaseTest baseTest = (BaseTest) iTestResult.getInstance();
        RemoteWebDriver driver = baseTest.driver;
        //保存到allure报表中
        saveScreenshotToAllure(takeScreenshotAsByte(driver));
        //保存到本地
        takeScreenshot(driver,"test_"+System.currentTimeMillis());
    }
}

2、在testng.xml文件中添加listener标签使监听器生效,代码如下:

复制代码
<!--使监听器生效-->
<listeners>
    <listener class-name="com.howentech.listener.TestResultListener"></listener>
</listeners>

3、在Listener类中添加@Attachment注解方法,将截图保存到allure报表中

复制代码
@Attachment(value = "screenshot",type = "image/png")
public byte[] saveScreenshotToAllure(byte[] data){
    //返回的字节数组的数据 作为附件添加到Allure报表中--》@Attachment注解来实现的
    return data;
}

4、在Listener类中提供生成字节数组的截图数据

复制代码
/**
 * 生成字节数组的截图数据
 * @param driver
 * @return
 */
public byte[] takeScreenshotAsByte(RemoteWebDriver driver){
    byte[] data = driver.getScreenshotAs(OutputType.BYTES);
    return data;
}

5、在Listener类中提供生成普通文件的截图数据,用于在本地也生成截图

复制代码
/**
 * 生成截图以普通文件的形式,并且保存到本地
 * @param driver
 * @param fileName
 */
public void takeScreenshot(RemoteWebDriver driver, String fileName){
    File srcFile = driver.getScreenshotAs(OutputType.FILE);
    File destFile = new File(System.getProperty("user.dir")+"\\screenshot\\"+fileName+".png");
    try {
        FileUtils.copyFile(srcFile,destFile);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

整个失败用例截图类的代码:

复制代码
package com.howentech.listener;

import com.howentech.common.BaseTest;
import io.qameta.allure.Attachment;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;

import java.io.File;
import java.io.IOException;

/**
 * @param
 * @author rebort
 * @create 2025/07/08
 * @return
 * @description
 **/
public class TestResultListener implements IHookable {

    @Override
    public void run(IHookCallBack iHookCallBack, ITestResult iTestResult) {
        //保证@Test注解标注的测试方法能够正常执行
        iHookCallBack.runTestMethod(iTestResult);
        //判断用例结果是否异常
        if(iTestResult.getThrowable() != null){
            //testResult参数提供了getInstance方法,可以获取当前测试类的实例(对象)
            BaseTest baseTest = (BaseTest) iTestResult.getInstance();
            RemoteWebDriver driver = baseTest.driver;
            //保存到allure报表中
            saveScreenshotToAllure(takeScreenshotAsByte(driver));
            //保存到本地
            takeScreenshot(driver,"test_"+System.currentTimeMillis());
        }
    }

    @Attachment(value = "screenshot",type = "image/png")
    public byte[] saveScreenshotToAllure(byte[] data){
        //使用@Attachment注解来实现的返回的字节数组的数据 作为附件添加到Allure报表中
        return data;
    }

    /**
     * 生成字节数组的截图数据
     * @param driver
     * @return
     */
    public byte[] takeScreenshotAsByte(RemoteWebDriver driver){
        byte[] data = driver.getScreenshotAs(OutputType.BYTES);
        return data;
    }

    /**
     * 生成截图以普通文件的形式,并且保存到本地
     * @param driver
     * @param fileName
     */
    public void takeScreenshot(RemoteWebDriver driver, String fileName){
        File srcFile = driver.getScreenshotAs(OutputType.FILE);
        File destFile = new File(System.getProperty("user.dir")+"\\screenshot\\"+fileName+".png");
        try {
            FileUtils.copyFile(srcFile,destFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

整个testng.xml的代码:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" parallel="tests" thread-count="2">
    <!--使监听器生效-->
    <listeners>
        <listener class-name="com.howentech.listener.TestResultListener"></listener>
    </listeners>
    <test name="测试">
        <classes>
            <class name="com.howentech.testcases.TestBaidu3"/>
        </classes>
    </test>
</suite>

下面,我们特意设置用例执行失败,查看用例失败截图是否会生成在allure报表中生成与在本地生成

执行结果:

(1)在本地文件有生成失败用例截图

在allure报表里也有生成失败用例截图:

二:失败用例重试

1、创建用例重试监听类(RetryListener,名字自起)实现testng包下的IRetryAnalyzer类,重写retry方法。

复制代码
package com.howentech.listener;

import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;

/**
 * @param
 * @author rebort
 * @create 2025/7/08
 * @return
 * @description
 **/
public class RetryListener implements IRetryAnalyzer {
    //最大重试次数
    private int maxRetryCount=3;
    //当前的重试次数
    private int currentRetryCount=0;

    @Override
    public boolean retry(ITestResult result) {
        //限制重试的最大次数,否则会进入死循环
        if(currentRetryCount < maxRetryCount) {
            //如果当前的重试次数没有达到限制,就去执行重试机制
            currentRetryCount++;
            return true;
        }else {
            return false;
        }
    }
}

2、添加一个全局注解属性修改的类,用于使@Test注解每次都能拥有retryAnalyzer属性,可以减去每个@Test注解都要配置retryAnalyzer属性操作

复制代码
package com.howentech.listener;

import org.testng.IAnnotationTransformer;
import org.testng.IRetryAnalyzer;
import org.testng.annotations.ITestAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @param
 * @author rebort
 * @create 2025/7/08
 * @return
 * @description
 **/
public class GlobalAnnotationTransformer implements IAnnotationTransformer {
    //通过实现IAnnotationTransformer接口可以动态的修改@Test注解的属性
    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
        // 获取@Test注解的RetryAnalyzer属性对象
        IRetryAnalyzer iRetryAnalyzer = annotation.getRetryAnalyzer();
        if (iRetryAnalyzer == null) {
            annotation.setRetryAnalyzer(RetryListener.class);
        }
    }
}

3、在testng.xml中添加listener标签,使得全局注解修改监听类生效

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" parallel="tests" thread-count="2">
    <!--使监听器生效-->
    <listeners>
        <listener class-name="com.howentech.listener.TestResultListener"></listener>
        <listener class-name="com.howentech.listener.GlobalAnnotationTransformer"></listener>
    </listeners>
    <test name="测试">
        <classes>
            <class name="com.howentech.testcases.TestBaidu3"/>
        </classes>
    </test>
</suite>

下面,我们再来看看失败用例是否会重新运行,最大运行4次,由于上面我特意断言每个用例都失败,所以每个用例都应该运行4次:

每次用户执行失败,都会在本地生成失败截图,如下:

allure报表也会有失败截图,并且监听了当前用例失败重跑了几次:

在allure报表中看到Retries被重复执行了3次,点击每一次的执行结果,都会展示错误截图:

至此,失败用例截图与失败用例重试已经集成完成。

相关推荐
李慕婉学姐6 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆8 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin8 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20058 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉9 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国9 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882489 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈9 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9910 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹10 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理