不管是 selenium 还是 appium 都可以使用的截图方式,都是通过 driver
File file = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(file, path);
getScreenshotAs 主要是驱动来调用做截图操作的,然后通过 FileUtils 的 copyFile 方法进行 file 的拷贝,拷贝到 path 路径下
进一步思考对于这种截图方式,我们不用非要自己写在代码里头,我们可以封装成一个工具类,然后那里需要截图操作,我们使用工具类中的静态方法即可
再进一步思考,如果我们已经写好了一个截图的工具类了,然后我们还需要能够断言异常时候能自动监听该怎么办呢?请往下看
断言监听自动截图有两种方式比较常用:
使用 IHookable 接口
本人使用的较少,此接口可以动态监听用例情况,可以重写 run 方法,run 方法调用自己写的截图工具类 ScreenshotUtil.takeScreenshot(driver)
即可,具体使用如下:
public class TestResultListener implements IHookable {
@Override
public void run(IHookCallBack callBack, ITestResult testResult) {
callBack.runTestMethod(testResult);
if (testResult.getThrowable() != null) {
// 这个地方要想办法把 driver 传进去,如果是多线程的话可以利用 ThreadLocal
ScreenshotUtil.takeScreenshot(driver);
}
}
}
使用 TestListenerAdapter 接口
这种我用的比较多,因为这个接口可以通过调用各个阶段的方法,从而做各个阶段时期的工作,比如在 beforeSuite 阶段做一些 log 日志输出等
public class TestLogListener extends TestListenerAdapter {
@Override
public void onStart(ITestContext iTestContext) {
super.onStart(iTestContext);
log.info(String.format("====================%s开始====================", iTestContext.getName()));
}
@Override
publicvoid onTestStart(ITestResult iTestResult) {
super.onTestStart(iTestResult);
log.info(String.format("========%s.%s测试开始========", iTestResult.getInstanceName(), iTestResult.getName()));
}
@Override
public void onTestSuccess(ITestResult iTestResult) {
super.onTestSuccess(iTestResult);
log.info(String.format("========%s.%s测试通过========", iTestResult.getInstanceName(), iTestResult.getName()));
}
@Override
public void onTestFailure(ITestResult iTestResult) {
super.onTestFailure(iTestResult);
log.error(String.format("========%s.%s测试失败,失败原因如下:\n%s========", iTestResult.getInstanceName(), iTestResult.getName(), iTestResult.getThrowable()));
// 失败时候抛出异常进行截图操作
ScreenshotUtil.takeScreenshot(iTestResult);
}
@Override
public void onTestSkipped(ITestResult iTestResult) {
super.onTestSkipped(iTestResult);
log.info(String.format("========%s.%s跳过测试========", iTestResult.getInstanceName(), iTestResult.getName()));
}
@Override
public void onFinish(ITestContext iTestContext) {
super.onFinish(iTestContext);
log.info(String.format("====================%s结束====================", iTestContext.getName()));
}
}
值得注意的是我没有把保存在 ThreadLocal 中的驱动传进 takeScreenshot 而是把 iTestResult 传了进去,如果使用 ThreadLocal 保存 driver 避免多线程串扰是完全可以的,如果使用 iTestResult 其实也行,这是因为 iTestResult 可以强转成测试基类 BaseTest,如果 driver 在 BaseTest 中声明,那就可以拿到 driver,进而去做driver.getScreenshotAs
的操作了,具体截图工具类的实现如下:
public class ScreenshotUtil {
/**
* 截图存储路径
*/
private static String SCREENSHOT_PATH = System.getProperty("user.dir") + File.separator + "target" + File.separator + "test-output" + File.separator + "screenshot";
/**
* 截图
*
* @param iTestResult i测试结果
*/
public static void capture(ITestResult iTestResult) {
log.info("开始截图");
// 拿到需要截图的驱动
WebDriver driver = ((BaseTest) iTestResult.getInstance()).driver;
// 截图目录
File screenshotFile = new File(SCREENSHOT_PATH);
// 若文件夹不存在就创建该文件夹
if (!screenshotFile.exists() && !screenshotFile.isDirectory()) {
screenshotFile.mkdirs();
}
// 截图格式
String screenshotFormat = PropertiesReader.getKey("output.screenshot.format");
// 哪个类导致的截图
String className = iTestResult.getInstance().getClass().getSimpleName();
// 时间格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年M月d日H时m分s秒");
String timeStr = simpleDateFormat.format(new Date());
// 截图名称
String screenshotName = className + "-" + timeStr;
try {
// 截图操作
File sourcefile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
// 截图存储
FileUtils.copyFile(sourcefile, new File(SCREENSHOT_PATH + File.separator + screenshotName + screenshotFormat));
} catch (IOException e) {
e.printStackTrace();
log.error("截图操作异常!");
}
}
}
注意上面我有使用PropertiesReader.getKey("output.screenshot.format");
这样的方法,这个类实际上是自己写的读取一个配置文件的内容,实际产生的还是一个 String,这里没做此类的具体展示,请酌情拷贝代码!
另外常常会配合 allure 2 的报告使用,我们就要用上 @Attachment 注解配合上 String 或 byte[] 的返回值,可以方便 allure 2 报告中附上这些截图,具体使用方式请参考 allure 官网中怎么搭配 testng 使用