基于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
相关产品推荐
相关产品推荐

