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

Selenium+TestNG中工具类继承基类触发NullPointerException问题

Selenium+TestNG中工具类继承基类后变量为Null的问题

我在Selenium+TestNG自动化测试框架里定义了BaseSeleniumTest基类,里面初始化了若干protected变量和通用方法。测试类、页面对象类继承这个基类后,能正常访问基类的protected变量。但当我创建继承该基类的CreationDialogUtils工具类,在其中调用基类的checkInvoicePage()方法时,areaOfBusiness等变量为null,触发了NullPointerException。调试发现,从工具类进入基类时,所有变量都是null。


基类代码

public class BaseSeleniumTest {
private static final Logger logger = LoggerFactory.getLogger(BaseSeleniumTest.class);
protected String browserName;
protected String tenantName;
protected String userRole;
protected String testType;
protected AreaOfBusiness areaOfBusiness;
protected static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
protected static ThreadLocal<WebDriverWait> wait = new ThreadLocal<>();
protected static ThreadLocal<WebDriverWait> longWait = new ThreadLocal<>();
protected BasePage basePage;

@Parameters({"profile", "browserName", "userRole", "testType", "areaOfBusiness", "tenantName"})
@BeforeTest(alwaysRun = true)
public void setup(@Optional("local") String profile, @Optional("chrome") String browserName,
                  @Optional String userRole, @Optional String testType,
                  @Optional("BMW") AreaOfBusiness areaOfBusiness, @Optional("Autohaus Huber") String tenantName)
    throws IOException {
    this.browserName = browserName;
    this.userRole = userRole;
    this.testType = testType;
    this.tenantName = tenantName;
    this.areaOfBusiness = areaOfBusiness;

    ConfigurationManager.loadProperties(profile);
    driver.set(createBrowserSession(browserName));

    wait.set(getWait());
    longWait.set(getLongWait());

    navigateToBackOffice(driver.get(), areaOfBusiness);

    //Login process

    // Instantiate page objects
    }

@Parameters({"browserName", "userRole", "testType", "areaOfBusiness", "tenantName"})
@BeforeClass(alwaysRun = true)
public void beforeClassInitialization(@Optional("chrome") String browserName,
                                      @Optional String userRole, @Optional String testType,
                                      @Optional("BMW") AreaOfBusiness areaOfBusiness,
                                      @Optional("Autohaus Huber") String tenantName) {
    this.browserName = browserName;
    this.userRole = userRole;
    this.testType = testType;
    this.tenantName = tenantName;
    this.areaOfBusiness = areaOfBusiness;
    this.basePage = new BasePage(driver.get());
}

protected WebDriverWait getWait() {
    return new WebDriverWait(driver.get(), Duration.ofSeconds(10));
}

protected WebDriverWait getLongWait() {
    return new WebDriverWait(driver.get(), Duration.ofSeconds(300));
}


public WebDriver createBrowserSession(String browser) {
    some code....
    return webDriver;
}


@AfterTest(alwaysRun = true)
public void tearDown() {
    if (driver.get() != null) {
        logger.info("TearDown Called!");
        logger.info("Browser Closed.");
        driver.get().quit();
    }
}

    public void checkErrorPage() {
    String errorPage = MessageFormat.format(ConfigurationManager.getBackOfficeBaseUrl(),
        areaOfBusiness.getValue()) + ERROR_PAGE;

    if (errorPage.equals(driver.get().getCurrentUrl())) {
        logger.info("Error page occurred, redirecting to base URL");
        String redirectToBaseUrl = MessageFormat.format(ConfigurationManager.getBackOfficeBaseUrl(),
            areaOfBusiness.getValue());
        driver.get().navigate().to(redirectToBaseUrl);
    }
}

}

测试类代码

public class CreateNewInvoiceFieldsValidationTest extends BaseSeleniumTest {
protected InvoiceCreationPage invoiceCreation;
protected ValidationErrorPage validationErrorPage;
protected CreationDialogUtils creationDialogUtils;

@BeforeClass(alwaysRun = true)
public void createNewInvoiceFieldsValidationBeforeClass() {
    invoiceCreation = new InvoiceCreationPage(driver.get());
    validationErrorPage = new ValidationErrorPage(driver.get());
    creationDialogUtils = new CreationDialogUtils();
}

@Test(description = "To test if the user does not check any of the title checks box and verify validation message"
        + "must be displayed")
public void tc019NewCaseFieldTitleValidationTest() throws Exception {
    creationDialogUtils.openInvoiceCreationDialogBox();
    //Invoice creation form
    //Customer data
    var customerData = randomCustomerData();
    customerData.setTitleIndexValue(4);
    creationDialogUtils.fillCustomerData(invoiceCreation, customerData);
    //Invoice data
    var invoiceData = invoiceData();
    creationDialogUtils.fillInvoiceDetails(invoiceCreation, invoiceData, CREATE, INVOICE_CREATION);
    //Click send invoice button
    invoiceCreation.clickSendButton();
    //Verify validation error message
    var errorMessageData = validationErrors(getErrorMessage("requiredError",
            "Title"));
    checkValidationErrors(validationErrorPage, errorMessageData, "title");
    //Close the form
    invoiceCreation.clickCancelButton();
}

工具类代码

public class CreationDialogUtils extends BaseSeleniumTest {
    protected InvoiceCreationPage invoiceCreation;
    protected OfferPage offerCreation;
    protected RepairOrderPage repairOrder;
public CreationDialogUtils() {
    invoiceCreation = new InvoiceCreationPage(driver.get());
    offerCreation = new OfferPage(driver.get());
    repairOrder = new RepairOrderPage(driver.get());
}
   //some methods ....
// 出现问题的方法
public void openInvoiceCreationDialogBox() {
    checkInvoicePage();
    invoiceCreation.clickFormCloseButton();
    invoiceCreation.clickCreateInvoiceButton();
}
}

问题原因与解决办法

原因

TestNG的@BeforeTest、@BeforeClass等注解仅作用于带有@Test注解的测试类,不会触发普通工具类的基类初始化方法。通过new CreationDialogUtils()创建工具类实例时,基类的setup()和beforeClassInitialization()完全没执行,导致areaOfBusiness等非静态成员变量全部为null;虽然driver是static ThreadLocal,但工具类的非静态依赖变量仍未初始化。另外,工具类继承测试基类本身就是不合理的设计——测试基类是为测试生命周期服务的,工具类应该是独立的功能封装,不该耦合测试生命周期逻辑。

解决办法

方案1:重构工具类,通过构造方法传入依赖(推荐)

取消工具类对测试基类的继承,改为通过构造方法传入所需的测试类实例或依赖变量:

public class CreationDialogUtils {
    private final InvoiceCreationPage invoiceCreation;
    private final BaseSeleniumTest testInstance;

    // 传入测试类实例和页面对象
    public CreationDialogUtils(BaseSeleniumTest testInstance, InvoiceCreationPage invoiceCreation) {
        this.testInstance = testInstance;
        this.invoiceCreation = invoiceCreation;
    }

    public void openInvoiceCreationDialogBox() {
        testInstance.checkInvoicePage(); // 调用测试类的基类方法
        invoiceCreation.clickFormCloseButton();
        invoiceCreation.clickCreateInvoiceButton();
    }

    // 其他方法同理,需要基类变量或方法时通过testInstance调用
}

测试类中初始化工具类时传入当前测试实例:

@BeforeClass(alwaysRun = true)
public void createNewInvoiceFieldsValidationBeforeClass() {
    invoiceCreation = new InvoiceCreationPage(driver.get());
    validationErrorPage = new ValidationErrorPage(driver.get());
    creationDialogUtils = new CreationDialogUtils(this, invoiceCreation); // 传入当前测试类实例
}

方案2:抽取通用方法到独立工具类

把checkInvoicePage()这类通用逻辑从测试基类中抽离,放到独立的工具类中,让测试类和CreationDialogUtils直接调用:

public class SeleniumUtils {
    private static final Logger logger = LoggerFactory.getLogger(SeleniumUtils.class);

    public static void checkInvoicePage(AreaOfBusiness areaOfBusiness, WebDriver driver) {
        String errorPage = MessageFormat.format(ConfigurationManager.getBackOfficeBaseUrl(),
            areaOfBusiness.getValue()) + ERROR_PAGE;

        if (errorPage.equals(driver.getCurrentUrl())) {
            logger.info("Error page occurred, redirecting to base URL");
            String redirectToBaseUrl = MessageFormat.format(ConfigurationManager.getBackOfficeBaseUrl(),
                areaOfBusiness.getValue());
            driver.navigate().to(redirectToBaseUrl);
        }
    }
}

工具类中调用该方法时传入所需参数:

public void openInvoiceCreationDialogBox(AreaOfBusiness areaOfBusiness, WebDriver driver) {
    SeleniumUtils.checkInvoicePage(areaOfBusiness, driver);
    invoiceCreation.clickFormCloseButton();
    invoiceCreation.clickCreateInvoiceButton();
}

方案3:手动调用基类初始化方法(不推荐)

如果坚持让工具类继承基类,需要在创建实例时手动调用基类的初始化方法并传入参数:

// 测试类中初始化工具类
creationDialogUtils = new CreationDialogUtils();
// 手动传入测试类中已初始化的参数
creationDialogUtils.beforeClassInitialization(browserName, userRole, testType, areaOfBusiness, tenantName);

这种方法容易导致重复初始化、资源冲突,仅作为临时应急方案。


内容的提问来源于stack exchange,提问作者Raza Sultan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 04:00:54