前言
- 在運(yùn)行自動(dòng)化測試腳本時(shí),經(jīng)常會(huì)需要增加失敗時(shí)自動(dòng)截圖功能,以及失敗重跑功能,下面我們通過Testng監(jiān)聽器的方式來實(shí)現(xiàn)自動(dòng)截圖和重跑功能。
一、創(chuàng)建一個(gè)ResultTest 接口
// ResultTest 接口主要用于實(shí)例的轉(zhuǎn)換
public interface ResultTest {
DriverBase DriverBase();
}
// 將所有 Test Case 繼承 ResultTest 接口,并 reutrn driverBase 返回
public class CanvasExpressionTest implements ResultTest {
private DriverBase driverBase = new DriverBase("HomeData.BROWSER") ;
@Override
public DriverBase DriverBase() {
return driverBase;
}
}
二、創(chuàng)建一個(gè)截圖工具類:ScreenshotUtils
/** 截圖工具類 */
public class ScreenshotUtils {
private DriverBase driverBase;
public ScreenshotUtils(DriverBase driverBase) {
this.driverBase = driverBase;
}
public void getScreenshot(String methodName) {
// 截圖主體功能
File screen = ((TakesScreenshot) driverBase.getDriver()).getScreenshotAs(OutputType.FILE);
// 拼接路徑和圖片名稱
String xpath = "./image/error/" + methodName + "_" + getCurrentTime() + ".png";
File screenFile = new File(xpath);
try {
FileUtils.copyFile(screen,screenFile);
LogUtils.info(" 截圖保存路徑 ",xpath);
} catch (Exception e) {
LogUtils.error(" 截圖保存失敗 ",xpath);
e.printStackTrace();
}
}
/** 獲取當(dāng)前時(shí)間 */
private String getCurrentTime() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
return sdf.format(date);
}
}
三、創(chuàng)建一個(gè)Java 類并繼承 TestListenerAdapter 類
- 在 TestNG 中 TestListenerAdapter為監(jiān)聽器類,主要使用如下方法:
- onStart(ITestContext testContext):在測試類實(shí)例化之后,調(diào)用任何配置方法之前調(diào)用
- onFinish(ITestContext testContext):在所有測試運(yùn)行并調(diào)用其所有配置方法后調(diào)用
- onTestStart(ITestResult tr):每次在測試被調(diào)用之前調(diào)用
- onTestFailure(ITestResult tr):每次測試失敗時(shí)調(diào)用
- onTestSkipped(ITestResult tr):每次跳過測試時(shí)調(diào)用
- onTestSuccess(ITestResult tr):每次測試成功時(shí)調(diào)用
想實(shí)現(xiàn) case 運(yùn)行失敗時(shí)自動(dòng)截圖,只需要新建一個(gè)類 TestNGListener 繼承 TestListenerAdapter ,然后重寫 onTestFinish、onTestSkipped 等方法,并在這些方法加入截圖操作即可
public class TestNGListener extends TestListenerAdapter {
/** 重寫 onTestFinish 方法 */
@Override
public void onTestFailure(ITestResult tr) {
LogUtils.error("Test Failure", null);
// 獲取示例并轉(zhuǎn)換為 ResultTest
ResultTest result = (ResultTest) tr.getInstance();
// 調(diào)用截圖方法,并傳入方法名稱作為圖片名稱
String methodName = tr.getMethod().getMethodName();
new ScreenshotUtils(result.DriverBase()).getScreenshot(methodName);
}
}
在 TestNG.xml 文件中配置自己編寫的監(jiān)聽類
<listeners>
<listener class-name="testng.TestNGListener"/>
</listeners>
四、失敗時(shí)自動(dòng)重跑
1. 創(chuàng)建一個(gè) TestngRetry 類并繼承 IRetryAnalyzer 接口實(shí)現(xiàn) retry 方法,并實(shí)現(xiàn)重跑邏輯
- 返回 true : 代表重跑
- 返回 false :關(guān)閉重跑
public class TestngRetry implements IRetryAnalyzer {
private static int retryCount = 1; // 定義重跑次數(shù)
private static final int maxRetryCount = 3; // 定義最大重跑次數(shù)
@Override
public boolean retry(ITestResult result) {
if (retryCount < maxRetryCount){
LogUtils.error("retryCount" , retryCount);
retryCount ++;
return true;
}
return false;
}
}
2. 添加用例重跑監(jiān)聽器 RetryListener 繼承 IAnnotationTransformer 接口實(shí)現(xiàn)transform方法,實(shí)現(xiàn)用例失敗時(shí)重跑功能
public class RetryListener implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation annotation,
Class testClass,
Constructor testConstructor,
Method testMethod) {
IRetryAnalyzer retry = annotation.getRetryAnalyzer();
if (retry == null){
annotation.setRetryAnalyzer(TestngRetry.class);
}
}
}
3. 在TestNG.xml 文件中配置編寫的監(jiān)聽器
<listeners>
<listener class-name="testng.TestNGListener"/>
<listener class-name="testng.RetryListener"/>
</listeners>
4. 進(jìn)一步改進(jìn)重跑功能
以上三個(gè)步驟可以搞定 case 失敗時(shí)重跑的功能,但是會(huì)在結(jié)果中會(huì)發(fā)現(xiàn)失敗的 case 會(huì)生成多個(gè),顯然這樣是不好的
// 此時(shí)就需要在之前寫的 TestNGListener監(jiān)聽器類中重寫onFinish 方法
@Override
public void onFinish(ITestContext testContext) {
removeFailed(testContext);
}
private void removeFailed(ITestContext context) {
Iterator<ITestResult> iterator = context.getFailedTests().getAllResults().iterator();
while (iterator.hasNext()) {
ITestResult failedTest = iterator.next();
ITestNGMethod method = failedTest.getMethod();
if (context.getFailedTests().getResults(method).size() > 1) {
iterator.remove();
} else {
if (context.getPassedTests().getResults(method).size() > 0 ){
iterator.remove();
}
}
}