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

