1.实现原因
自动化测试执行发现在本地执行较快,在执行机上执行有时因并行其他任务导致执行较慢,所以导致录制不完全部过程,故此期望在点击、输入等方法中去截图,在用例成功和失败时去生成GIF,还是保留上次的通过注解控制的功能。
2.CaseBase部分的代码实现
所有的测试用例中相关的方法都是继承CaseBase的,在执行时CaseBase中的点击、输入等方法时去调用截图方法。
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Listeners;
import service.test.utility.TimelUtility;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.lang.*;
import static io.qameta.allure.Allure.step;
//初始化浏览器
@Listeners({ShotListener.class})
public class CaseBase {
public DriverBase driverBase;
public static final Logger logger = LoggerFactory.getLogger(CaseBase.class);
private static final String rootPath = System.getProperty("user.dir");
private static final String PATH = System.getProperty("user.dir")+File.separator+"screenshot"+File.separator;
@AfterClass(alwaysRun = true)
public void closeBrowser(){
logger.info("=================关闭浏览器=================");
driverBase.closeBrowser();
}
public String isGenerateGif(){
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // 获取调用父类方法A的堆栈元素(即子类方法B的信息)
StackTraceElement caller = stackTrace[3];
String callerClassName = caller.getClassName();// 获取类名
// 获取当前测试类的简单名称(不包含包路径)
String className = callerClassName.substring(callerClassName.lastIndexOf('.') + 1);
String MethodName = caller.getMethodName(); // 获取方法名
try { // 获取子类的Class对象
Class<?> callerClass = Class.forName(callerClassName);
// 获取所有声明的方法
Method[] methods = callerClass.getDeclaredMethods();
// 遍历并找到匹配的方法
for (Method method : methods) {
if (method.getName().equals(MethodName)) {
// 找到匹配的方法 检查方法是否有注解
if (method.isAnnotationPresent(GenerateGif.class)) {
GenerateGif annotation = method.getAnnotation(GenerateGif.class);
if(annotation == null || !annotation.value()){
return "";
}else {
return PATH + className + File.separator + MethodName + File.separator;
}
} else {
return "";
}
}
}
return "";
} catch (Exception e) {
e.printStackTrace(); // 捕捉异常并打印堆栈信息
return "";
}
}
private void screenshot(String gifPath){
if(!gifPath.equals("")) {
try {
File screenshot = ((TakesScreenshot) driverBase.getDriver()).getScreenshotAs(OutputType.FILE); // 捕捉浏览器视口的截图
File destination = new File(gifPath+ System.currentTimeMillis() + ".png"); // 截图保存的目标文件
FileUtils.copyFile(screenshot, destination); // 复制截图到目标文件
}catch (IOException e) {
e.printStackTrace(); // 捕捉并打印异常
}
}
}
/**
* 用selenium自带方法点击元素
* @param stepMsg 步骤名称
* @param xpath 定位元素所需xpath表达式
*/
public void click(String stepMsg,String xpath){
driverBase.click(stepMsg,locateElement(stepMsg,xpath));
screenshot(isGenerateGif());
}
3.ShotListener实现代码
用例成功或是失败时去生成GIF
import com.madgag.gif.fmsware.AnimatedGifEncoder;
import io.qameta.allure.Allure;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
//@Attachment 注解必须放在test文件夹下面
public class ShotListener3 extends TestListenerAdapter {
private static final String PATH = System.getProperty("user.dir")+File.separator+"screenshot"+File.separator;
private static final int MAX_FARAME_INTERVAL_MS = 1000; // 每帧播放间隔 1000毫秒
@Override
public void onTestSuccess(ITestResult tr) {
//判断测试方法是否有gif注解,生成gif
if (isGenerateGif(tr)){
getGenerateGif(getGifPath(tr));// 生成 GIF 动图
}
}
@Override
public void onTestSkipped(ITestResult tr) {
//判断测试方法是否有gif注解,设置了失败重试时删除测试用例之前的截图
if (isGenerateGif(tr)){
rmdir(getGifPath(tr));
}
}
@Override
public void onTestFailure(ITestResult tr) {
//请求失败截图
screenshot(getDriver(tr));
//判断测试方法是否有gif注解,生成gif
if (isGenerateGif(tr)){
String gifPath = getGifPath(tr);
getGenerateGif(gifPath);// 生成 GIF 动图
}
}
@Override
public void onFinish(ITestContext context) {
rmdir(PATH);
}
private boolean isGenerateGif(ITestResult tr){
// 获取测试方法上的 @GenerateGif 注解
GenerateGif GenerateGif = tr.getMethod().getConstructorOrMethod().getMethod().getAnnotation(GenerateGif.class);
return GenerateGif != null && GenerateGif.value(); // 根据注解决定是否捕捉截图
}
private WebDriver getDriver(ITestResult tr){
//获取浏览器driver
CaseBase bt = (CaseBase) tr.getInstance();
return bt.driverBase.getDriver();
}
private String getGifPath(ITestResult tr) {
// 获取当前测试类的完整路径
String fullClassName = tr.getTestClass().getName();
// 获取当前测试类的简单名称(不包含包路径)
String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
// 获取当前执行的测试方法名
String methodName = tr.getMethod().getMethodName();
String gifPath = PATH + className + File.separator + methodName + File.separator;
return gifPath;
}
// 停止截图并生成 GIF 动图
private void getGenerateGif(String gifPath) {
try {
InputStream content;
takeGif(gifPath);//生成GIF
File f= new File(gifPath + "screenshot.gif") ;
content = new FileInputStream(f);
Allure.addAttachment("用例执行过程如下:", content);
content.close();
}catch (IOException e) {
e.printStackTrace(); // 捕捉并打印异常
}
}
// 将截图合成 GIF 动图
private void takeGif(String gifPath){
//合成gif图
try {
String[] pic = new File(gifPath).list();
if (pic.length > 0){
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.setRepeat(0);
e.start(gifPath +"screenshot.gif");
BufferedImage[] src = new BufferedImage[pic.length];
for (int i = 0; i < src.length; i++) {
e.setDelay(MAX_FARAME_INTERVAL_MS); //设置播放的延迟时间
src[i] = ImageIO.read(new File(gifPath+pic[i])); // 读入需要播放的jpg文件
e.addFrame(src[i]); //添加到帧中
}
e.finish();
}else {
System.err.println(gifPath+"没有找到截图文件!");
}
} catch (IOException|Error e) {
System.err.println(gifPath+"合成gif图失败!");
e.printStackTrace();
}
}
private void rmdir (String path) {
// 创建File对象
File file = new File(path);
// 判断目录是否存在
if (file.exists()) {
// 删除目录
try {
FileUtils.forceDelete(file);
} catch (IOException e) {
System.err.println("删除文件目录 " + path + " 失败");
e.printStackTrace();
}
}
}
}
4.自定义注解
在测试方法上使用
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 创建GenerateGif自定义注解
* 控制测试方法中是否需要录制GIF
*/
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时可见
@Target(ElementType.METHOD) // 注解适用于方法
public @interface GenerateGif {
boolean value() default true; // 默认为 true,表示需要截图
}