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

Java Android Appium:USB连手机时TestNG失败截图报错的解决办法

解决Appium Android USB测试截图报错及TestNG失败监听截图方案

问题分析

执行File source = driver.getScreenshotAs(OutputType.FILE);时报“Illegal base64 character a”,通常是Appium与手机系统版本不兼容,导致返回的截图数据无法被正确解析为base64格式,进而无法转成File对象。

替代截图方案

方案1:通过字节数组转存文件

绕过直接转File的逻辑,先获取截图的字节数组,再写入本地文件:

// 从TestNG测试结果中获取AppiumDriver实例
AppiumDriver driver = (AppiumDriver) result.getTestContext().getAttribute("driver");
if (driver != null) {
    try {
        // 获取截图字节数组
        byte[] screenshotBytes = driver.getScreenshotAs(OutputType.BYTES);
        // 指定保存路径和文件名,比如用测试方法名+时间戳
        String fileName = result.getName() + "_" + System.currentTimeMillis() + ".png";
        File screenshotFile = new File("./screenshots/" + fileName);
        // 创建父目录
        if (!screenshotFile.getParentFile().exists()) {
            screenshotFile.getParentFile().mkdirs();
        }
        // 写入字节数组到文件
        FileUtils.writeByteArrayToFile(screenshotFile, screenshotBytes);
        System.out.println("截图已保存:" + screenshotFile.getAbsolutePath());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

注:需要引入Apache Commons IO的FileUtils,如果不想依赖第三方库,也可以用Java原生的FileOutputStream写入字节数组。

方案2:通过ADB命令截图(绕过Appium API)

如果Appium API始终有问题,可以直接调用ADB命令完成截图和拉取:

// 从TestNG测试结果中获取设备UDID(如果有多个设备需要指定)
String udid = (String) result.getTestContext().getAttribute("udid");
String fileName = result.getName() + "_" + System.currentTimeMillis() + ".png";
// 手机端临时截图路径
String devicePath = "/sdcard/" + fileName;
// 本地保存路径
String localPath = "./screenshots/" + fileName;

try {
    // 执行ADB截图命令
    String screencapCmd = udid != null ? "adb -s " + udid + " shell screencap -p " + devicePath : "adb shell screencap -p " + devicePath;
    Runtime.getRuntime().exec(screencapCmd).waitFor();
    // 拉取截图到本地
    String pullCmd = udid != null ? "adb -s " + udid + " pull " + devicePath + " " + localPath : "adb pull " + devicePath + " " + localPath;
    Runtime.getRuntime().exec(pullCmd).waitFor();
    // 删除手机端临时文件
    String deleteCmd = udid != null ? "adb -s " + udid + " shell rm " + devicePath : "adb shell rm " + devicePath;
    Runtime.getRuntime().exec(deleteCmd).waitFor();
    System.out.println("截图已保存:" + localPath);
} catch (Exception e) {
    e.printStackTrace();
}

注:需要确保测试环境中ADB已配置到系统PATH,且手机已授权ADB连接。

集成TestNG失败监听器

把上述截图逻辑整合到TestNG的onTestFailure方法中,完整监听器类示例:

import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import io.appium.java_client.AppiumDriver;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;

public class TestFailureListener extends TestListenerAdapter {

    @Override
    public void onTestFailure(ITestResult result) {
        super.onTestFailure(result);
        // 方案1:字节数组转文件(推荐优先尝试)
        captureScreenshotViaBytes(result);
        // 如果方案1仍有问题,替换为方案2:ADB命令截图
        // captureScreenshotViaADB(result);
    }

    private void captureScreenshotViaBytes(ITestResult result) {
        AppiumDriver driver = (AppiumDriver) result.getTestContext().getAttribute("driver");
        if (driver == null) {
            System.out.println("无法获取AppiumDriver实例,跳过截图");
            return;
        }
        try {
            byte[] screenshotBytes = driver.getScreenshotAs(OutputType.BYTES);
            String fileName = result.getName() + "_failure_" + System.currentTimeMillis() + ".png";
            File screenshotDir = new File("./test-failure-screenshots");
            if (!screenshotDir.exists()) {
                screenshotDir.mkdirs();
            }
            File screenshotFile = new File(screenshotDir, fileName);
            FileUtils.writeByteArrayToFile(screenshotFile, screenshotBytes);
            System.out.println("测试失败截图已保存:" + screenshotFile.getAbsolutePath());
        } catch (IOException e) {
            System.err.println("截图保存失败:" + e.getMessage());
            e.printStackTrace();
        }
    }

    private void captureScreenshotViaADB(ITestResult result) {
        String udid = (String) result.getTestContext().getAttribute("udid");
        String fileName = result.getName() + "_failure_" + System.currentTimeMillis() + ".png";
        String devicePath = "/sdcard/" + fileName;
        String localPath = "./test-failure-screenshots/" + fileName;

        try {
            // 创建本地目录
            File localDir = new File("./test-failure-screenshots");
            if (!localDir.exists()) {
                localDir.mkdirs();
            }
            // 执行ADB命令
            Process screencapProc = Runtime.getRuntime().exec(
                    udid != null ? "adb -s " + udid + " shell screencap -p " + devicePath : "adb shell screencap -p " + devicePath
            );
            if (screencapProc.waitFor() != 0) {
                System.err.println("ADB截图命令执行失败");
                return;
            }

            Process pullProc = Runtime.getRuntime().exec(
                    udid != null ? "adb -s " + udid + " pull " + devicePath + " " + localPath : "adb pull " + devicePath + " " + localPath
            );
            if (pullProc.waitFor() != 0) {
                System.err.println("ADB拉取截图失败");
                return;
            }

            Process deleteProc = Runtime.getRuntime().exec(
                    udid != null ? "adb -s " + udid + " shell rm " + devicePath : "adb shell rm " + devicePath
            );
            deleteProc.waitFor();

            System.out.println("测试失败截图已保存:" + localPath);
        } catch (Exception e) {
            System.err.println("ADB截图流程失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

注意事项

  • 确保在测试类中把AppiumDriver实例存入TestNG的TestContext,比如在@BeforeMethod中执行:result.getTestContext().setAttribute("driver", driver);
  • 如果使用多设备测试,需要把设备UDID也存入TestContext,方便ADB命令指定设备
  • 方案1依赖Appium API的字节流返回正常,若仍报错则切换到方案2

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 15:20:31