测试失败时用ITestListener截图并集成ExtentReport遇Driver空指针问题
问题描述
作为自动化测试新手,我希望实现测试用例失败时自动截取屏幕截图,并集成到ExtentReport中。当前无失败用例时程序运行正常,但存在失败用例时程序异常终止,报错Driver为空,出现NoSuchFieldException和NullPointerException。
相关代码
TestBase类
包含driver初始化、Before/After方法及截图方法:
public abstract class TestBase { public WebDriver driver; public static Properties prop; @BeforeSuite public void loadPropertiesFile() { prop = new Properties(); File file = new File("C:\\Users\\anoop\\IdeaProjects\\QAGURU\\TutorialsNinjaProject\\src\\main\\java\\com\\tutorialsninja\\config\\config.properties"); FileInputStream fis = null; try { fis = new FileInputStream(file); } catch (FileNotFoundException e) { System.out.println("File Not Found"); e.printStackTrace(); throw new RuntimeException(e); } try { prop.load(fis); } catch (IOException e) { System.out.println("file not loaded"); e.printStackTrace(); throw new RuntimeException(e); } } public WebDriver initializeBrowser() { String browser = prop.getProperty("browser"); if(browser.equalsIgnoreCase("chrome")) { driver = new ChromeDriver(); } else if (browser.equalsIgnoreCase("firefox")) { driver = new FirefoxDriver(); } else if (browser.equalsIgnoreCase("edge")) { driver = new EdgeDriver(); } driver.manage().window().maximize(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5)); return driver; } @BeforeMethod(alwaysRun = true) public void openBrowserWithUrl() { driver = initializeBrowser(); driver.get(prop.getProperty("url")); } @AfterMethod(alwaysRun = true) public void teardown() { driver.quit(); } public String getScreenshot(String testName, WebDriver driver) { TakesScreenshot takesScreenshot = (TakesScreenshot) driver; File screenshot = takesScreenshot.getScreenshotAs(OutputType.FILE); String screenshotPath = "C:\\Users\\anoop\\IdeaProjects\\QAGURU\\TutorialsNinjaProject\\screenshot" + testName +".png"; try { FileUtils.copyFile(screenshot,new File(screenshotPath)); } catch (Exception e) { System.out.println("Takes Screenshot is null"); e.printStackTrace(); } return screenshotPath; } }
测试用例类(LoginTests)
刻意让verifyloginWithInvalidCredentials用例失败以测试截图功能:
public class LoginTests extends TestBase { @Test(priority = 1, dataProvider = "Valid LogIn Data", dataProviderClass = DataProviders.class) public void verifyloginWithValidCredentials(String[] login) { HomePageBeforeLogin homePage = new HomePageBeforeLogin(driver); homePage.clickLoginOption(); LoginPage loginPage = new LoginPage(driver); loginPage.login(login[0], login[1]); Assert.assertTrue(loginPage.loginValidation()); } @Test(priority = 2) public void verifyloginWithInvalidCredentials() { HomePageBeforeLogin homePage = new HomePageBeforeLogin(driver); homePage.clickLoginOption(); LoginPage loginPage = new LoginPage(driver); loginPage.login(Utilities.generateEmailWithTimeStamp(), "vispedafsgardi@gufum.com"); String actualWarningMessage = "Warning: No match .for E-Mail Address and/or Password."; Assert.assertEquals(actualWarningMessage, loginPage.getWarningMessage()); } @Test(priority = 3) public void verifyloginWithInvalidEmailAndValidPassword() { HomePageBeforeLogin homePage = new HomePageBeforeLogin(driver); homePage.clickLoginOption(); LoginPage loginPage = new LoginPage(driver); loginPage.login(Utilities.generateEmailWithTimeStamp(), "vispegardi@gufum.com"); String actualWarningMessage = "Warning: No match for E-Mail Address and/or Password."; Assert.assertEquals(actualWarningMessage, loginPage.getWarningMessage()); } @Test(priority = 4) public void verifyloginWithValidEmailAndInvalidPassword() { HomePageBeforeLogin homePage = new HomePageBeforeLogin(driver); homePage.clickLoginOption(); LoginPage loginPage = new LoginPage(driver); loginPage.login("vispegardi@gufum.com", "123vispegardi@gufum.com"); String actualWarningMessage = "Warning: No match for E-Mail Address and/or Password."; Assert.assertEquals(actualWarningMessage, loginPage.getWarningMessage()); } @Test(priority = 5) public void verifyLoginWithoutProvidingCredentials() { HomePageBeforeLogin homePage = new HomePageBeforeLogin(driver); homePage.clickLoginOption(); LoginPage loginPage = new LoginPage(driver); loginPage.clickLogin(); String actualWarningMessage = "Warning: No match for E-Mail Address and/or Password."; Assert.assertEquals(actualWarningMessage, loginPage.getWarningMessage()); } }
MyListeners类(实现ITestListener)
public class MyListeners extends TestBase implements ITestListener { ExtentReports extentReport; ExtentTest extentTest; @Override public void onStart(ITestContext context) { extentReport = ExtentReporter.generateExtentReport(); } @Override public void onTestStart(ITestResult result) { String testName = result.getMethod().getMethodName(); extentTest = extentReport.createTest(testName); extentTest.log(Status.INFO,testName + " Started Executing"); } @Override public void onTestSuccess(ITestResult result) { String testName = result.getMethod().getMethodName(); extentTest.log(Status.PASS,testName + " got successfully executed"); } @Override public void onTestFailure(ITestResult result) { String testName = result.getMethod().getMethodName(); try { driver = (WebDriver) result.getTestClass().getRealClass().getDeclaredField("driver").get(result.getInstance()); } catch (Exception e) { System.out.println("driver not found"); e.printStackTrace(); } String screenshotPath = getScreenshot(testName,driver); extentTest.addScreenCaptureFromPath(screenshotPath); extentTest.log(Status.INFO,result.getThrowable()); extentTest.log(Status.FAIL,testName + " got failed"); } @Override public void onTestSkipped(ITestResult result) { System.out.println(result.getName() + " skipped"); System.out.println(result.getThrowable()); } @Override public void onFinish(ITestContext context) { extentReport.flush(); } @Override public void onTestFailedButWithinSuccessPercentage(ITestResult result) { } @Override public void onTestFailedWithTimeout(ITestResult result) { } }
testng.xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Parallel Test Suite" thread-count="5" > <listeners> <listener class-name="com.tutorialsninja.listeners.MyListeners"></listener> </listeners> <test verbose="2" preserve-order="true" name="C:/Users/anoop/IdeaProjects/QAGURU/TutorialsNinjaProject"> <classes> <!--<class name="com.tutorialsninja.testcases.RegisterTests"></class>--> <class name="com.tutorialsninja.testcases.LoginTests"></class> <!-- <class name="com.tutorialsninja.testcases.SearchTests"></class>--> </classes> </test> </suite>
错误日志
java.lang.NoSuchFieldException: driver at com.tutorialsninja.listeners.MyListeners.onTestFailure(MyListeners.java:50) java.lang.NoSuchFieldException: driver at com.tutorialsninja.listeners.MyListeners.onTestFailure(MyListeners.java:50) java.lang.NullPointerException: Cannot invoke "org.openqa.selenium.TakesScreenshot.getScreenshotAs(org.openqa.selenium.OutputType)" because "takesScreenshot" is null at com.tutorialsninja.base.TestBase.getScreenshot(TestBase.java:82) at com.tutorialsninja.listeners.MyListeners.onTestFailure(MyListeners.java:56)
解决方案
1. 修复Driver获取逻辑(解决NoSuchFieldException)
原代码用反射的getDeclaredField只能获取当前类声明的字段,而driver是父类TestBase中的字段,导致找不到。替换为直接类型转换获取测试实例的driver:
修改MyListeners类的onTestFailure方法:
@Override public void onTestFailure(ITestResult result) { String testName = result.getMethod().getMethodName(); try { // 直接将测试实例转换为TestBase,避免反射的字段查找问题 TestBase testInstance = (TestBase) result.getInstance(); driver = testInstance.driver; } catch (Exception e) { System.out.println("Failed to get driver instance"); e.printStackTrace(); } if (driver != null) { String screenshotPath = getScreenshot(testName, driver); extentTest.addScreenCaptureFromPath(screenshotPath); } else { extentTest.log(Status.WARNING, "Driver is null, cannot capture screenshot"); } extentTest.log(Status.INFO, result.getThrowable()); extentTest.log(Status.FAIL, testName + " got failed"); }
2. 优化截图方法(避免NullPointerException)
在TestBase的getScreenshot方法中添加Driver空检查,并修复截图路径的分隔符问题:
public String getScreenshot(String testName, WebDriver driver) { if (driver == null) { System.out.println("Driver is null, cannot take screenshot"); return ""; } // 创建截图目录(如果不存在) File screenshotDir = new File("C:\\Users\\anoop\\IdeaProjects\\QAGURU\\TutorialsNinjaProject\\screenshot"); if (!screenshotDir.exists()) { screenshotDir.mkdirs(); } TakesScreenshot takesScreenshot = (TakesScreenshot) driver; File screenshot = takesScreenshot.getScreenshotAs(OutputType.FILE); // 修复路径分隔符,确保文件存入screenshot文件夹 String screenshotPath = screenshotDir.getAbsolutePath() + File.separator + testName + ".png"; try { FileUtils.copyFile(screenshot, new File(screenshotPath)); } catch (Exception e) { System.out.println("Failed to save screenshot"); e.printStackTrace(); } return screenshotPath; }
3. 确认执行顺序(确保Driver未被提前关闭)
TestNG默认执行顺序为:@BeforeMethod → 测试方法 → onTestFailure → @AfterMethod,因此onTestFailure执行时Driver尚未被teardown方法quit。若发现顺序异常,可在@AfterMethod和onTestFailure中添加打印语句验证,或调整TestNG配置确保Listener回调优先执行。
内容的提问来源于stack exchange,提问作者anoopezhap
相关产品推荐
相关产品推荐

