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

测试失败时用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:47:01