You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Appium+Serenity+POM的Android框架步骤失败截图实现咨询

自动截图实现方案及部署说明

1. 优先使用Serenity原生截图能力(无代码侵入)

Serenity框架本身已内置失败自动截图能力,无需额外编写业务代码,仅需要调整配置即可启用:

  • 在项目的serenity.properties或serenity.conf中添加如下配置:
# 仅在步骤失败时截图,可选值:ALL/EACH_STEP/BEFORE_AND_AFTER_STEP/FOR_FAILURES/NONE
serenity.take.screenshots = FOR_FAILURES
# 同时保存页面源码,方便排查问题
serenity.store.html.sources = true

生效前提:你的Page类已继承Serenity的PageObject类、Steps类使用@Steps注解托管,Serenity会自动接管驱动实例完成截图,截图会自动关联到Serenity测试报告中。

2. 自定义截图逻辑的实现方案

如果原生截图无法满足你的自定义需求(比如指定存储路径、自定义命名规则、额外日志关联等),可以按优先级从以下三种方案中选择:

2.1 最优方案:全局TestLifecycleListener监听器(推荐)

完全解耦现有业务代码,全局监听步骤失败事件触发截图,不需要修改现有Pages/Steps/StepDefinition代码。

  • 部署位置:新建listener包,存放自定义监听器类
  • 实现代码:
import net.serenitybdd.core.Serenity;
import net.serenitybdd.core.di.SerenityInjection;
import net.thucydides.core.steps.TestLifecycleListener;
import net.thucydides.core.steps.events.StepFailedEvent;
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import com.google.common.eventbus.Subscribe;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class AutoScreenshotListener implements TestLifecycleListener {
    private final AppiumDriver driver;
    // 注入Serenity托管的Appium驱动实例
    public AutoScreenshotListener() {
        this.driver = SerenityInjection.getInjector().getInstance(AppiumDriver.class);
    }
    @Subscribe
    public void onStepFailed(StepFailedEvent event) {
        if (driver == null) return;
        // 生成带时间戳和失败步骤名的文件名
        String timeStamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
        String stepName = event.getStepDescription().replaceAll("[^a-zA-Z0-9]", "_");
        String fileName = String.format("fail_%s_%s.png", timeStamp, stepName);
        // 自定义存储路径,默认存在Serenity报告目录下,方便报告关联
        Path savePath = Paths.get(System.getProperty("user.dir"), "target", "site", "serenity", "screenshots", fileName);
        try {
            File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            Files.createDirectories(savePath.getParent());
            Files.copy(screenshot.toPath(), savePath);
            // 把截图绑定到Serenity报告,失败步骤下直接可见
            Serenity.recordReportData().withTitle("失败截图").downloadable().fromFile(savePath.toFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 注册方式:在src/main/resources/META-INF/services/net.thucydides.core.steps.TestLifecycleListener文件中添加监听器的全限定类名(比如com.yourproject.listener.AutoScreenshotListener),Serenity启动时会自动加载。

2.2 次选方案:Cucumber全局Hook(仅适用Cucumber BDD项目)

如果你的项目用Cucumber作为BDD框架,可以直接在StepDefinition层加全局步骤钩子。

  • 部署位置:放在公共Hook类或所有StepDefinition的父类中
  • 实现代码:
import io.cucumber.java.AfterStep;
import io.cucumber.java.Scenario;
import net.serenitybdd.core.Serenity;
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;

public class CommonStepHooks {
    private final AppiumDriver driver = Serenity.getCurrentSession().getDriver();
    @AfterStep
    public void afterEachStep(Scenario scenario) {
        if (scenario.isFailed()) {
            byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
            // 直接绑定到Cucumber报告中
            scenario.attach(screenshot, "image/png", "失败截图_" + scenario.getName());
        }
    }
}

2.3 兜底方案:BasePage基类封装

如果以上两种方案都不适用,可以封装Page层基类,所有业务Page继承该基类,操作异常时自动触发截图。

  • 部署位置:新建BasePage作为所有Page类的父类
  • 实现代码:
import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementFacade;
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class BasePage extends PageObject {
    public BasePage(AppiumDriver driver) {
        super(driver);
    }
    // 封装所有元素操作方法,异常时自动截图
    public void safeClick(WebElementFacade element) {
        try {
            element.click();
        } catch (Exception e) {
            takeScreenshot("click_failed_" + element.getTextOrNull());
            throw e; // 抛出异常保证测试标记为失败
        }
    }
    public void safeSendKeys(WebElementFacade element, String content) {
        try {
            element.sendKeys(content);
        } catch (Exception e) {
            takeScreenshot("input_failed_" + element.getTextOrNull());
            throw e;
        }
    }
    private void takeScreenshot(String tag) {
        String timeStamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
        File screenshot = ((TakesScreenshot) getDriver()).getScreenshotAs(OutputType.FILE);
        File savePath = new File(String.format("target/custom-screenshots/%s_%s.png", tag, timeStamp));
        try {
            Files.createDirectories(savePath.getParentFile().toPath());
            Files.copy(screenshot.toPath(), savePath.toPath());
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

所有业务Page类继承BasePage后,元素操作调用封装的safeXXX方法即可自动触发失败截图。


内容的提问来源于stack exchange,提问作者Indira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.19 09:57:03