Appium+Java+TestNG启动前出现NullPointerException求助
看起来你遇到的核心问题是Cucumber和TestNG的生命周期不兼容,导致Hooks里的初始化逻辑完全没执行,进而引发driver空指针,甚至Appium服务都没机会启动。咱们一步步拆解解决:
问题根源分析
你的报错指向screens.myAccountOverviewScreen.logoutAccount里的driver.manage().timeouts(),但你说Appium和模拟器都没启动——这说明你的Hooks.startAppiumServer方法根本没被执行!
原因是:你用了AbstractTestNGCucumberTests集成Cucumber和TestNG,但Cucumber有自己的钩子体系(@Before/@After),TestNG的@BeforeTest注解并不会被Cucumber的测试流程触发。所以你的driver和AppiumService都没初始化,第一次调用driver自然就抛出NullPointerException。
另外,你的TestRunner的CucumberOptions里没有指定glue路径,Cucumber可能找不到你的Hooks类,这也会导致钩子不执行。
具体解决步骤
1. 替换TestNG钩子为Cucumber钩子
把Hooks类里的@BeforeTest和@AfterTest换成Cucumber的@Before和@After注解(注意导入cucumber.api.java.Before/After,而非TestNG的同名注解):
package MA.test; import cucumber.api.java.After; import cucumber.api.java.Before; import io.appium.java_client.AppiumDriver; import io.appium.java_client.MobileElement; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.openqa.selenium.remote.DesiredCapabilities; import java.net.MalformedURLException; import java.net.URL; public class Hooks { public AppiumDriver<MobileElement> driver; public AppiumDriverLocalService service; @Before public void startAppiumServer() throws InterruptedException, MalformedURLException { // 这里先暂时用固定值测试,后续可结合TestNG参数优化传递逻辑 String platformVersion = "9.0"; String emulatorNumber = "emulator-5554"; String deviceName = "Android9_Nexus"; String port = "4723"; System.out.println("\n ABC"); service = new AppiumServiceBuilder() .usingPort(Integer.valueOf(port)) .build(); service.start(); DesiredCapabilities caps = new DesiredCapabilities(); caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion); caps.setCapability(MobileCapabilityType.DEVICE_NAME, emulatorNumber); caps.setCapability(AndroidMobileCapabilityType.AVD, deviceName); caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android"); caps.setCapability(MobileCapabilityType.APPLICATION_NAME, "Name"); caps.setCapability(MobileCapabilityType.APPIUM_VERSION, "1.14.0"); caps.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, "activity"); caps.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "package"); caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UiAutomator2"); driver = new AndroidDriver<MobileElement>(new URL("http://0.0.0.0:" + port + "/wd/hub"), caps); System.out.println("\n Appium server: " + service.getUrl()); Thread.sleep(2000); } @After public void teardown() { // 先判断对象不为空再执行,避免二次空指针 if (driver != null) { driver.closeApp(); driver.quit(); } if (service != null && service.isRunning()) { service.stop(); } System.out.println("\n Test quit"); } // 提供获取driver的方法,供screen类调用 public AppiumDriver<MobileElement> getDriver() { return driver; } }
2. 配置TestRunner的glue路径
在TestRunner的CucumberOptions里添加glue,指定Hooks和步骤定义所在的包,让Cucumber能扫描到它们:
package MA.steps; import cucumber.api.CucumberOptions; import cucumber.api.testng.AbstractTestNGCucumberTests; @CucumberOptions( plugin = {"pretty", "html:target/cucumber-reports"}, monochrome = true, features = "src/test/java/feature", tags = "@Login", glue = {"MA.test", "MA.steps"} // 添加这一行,指定钩子和步骤类的包 ) public class TestRunner extends AbstractTestNGCucumberTests { }
3. 让Screen类获取正确的Driver实例
你的myAccountOverviewScreen类需要拿到Hooks里初始化的driver实例,不要使用未初始化的变量。可以通过构造方法注入:
package screens; import io.appium.java_client.AppiumDriver; import io.appium.java_client.MobileElement; import java.util.concurrent.TimeUnit; public class myAccountOverviewScreen { private AppiumDriver<MobileElement> driver; // 构造方法接收driver public myAccountOverviewScreen(AppiumDriver<MobileElement> driver) { this.driver = driver; } public void logoutAccount() { // 现在driver已经初始化,不会抛出空指针 driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); // 后续业务逻辑... } }
然后在你的Cucumber步骤定义类里,通过Hooks获取driver并传入screen类:
package MA.steps; import MA.test.Hooks; import cucumber.api.java.en.Given; import screens.myAccountOverviewScreen; public class LoginSteps { private myAccountOverviewScreen accountScreen; public LoginSteps(Hooks hooks) { this.accountScreen = new myAccountOverviewScreen(hooks.getDriver()); } @Given("^用户执行登出操作$") public void user_logout() { accountScreen.logoutAccount(); } }
4. 优化依赖版本(可选但推荐)
你的TestNG用的是beta版本7.0.0-beta3,建议换成稳定版避免兼容性问题,比如:
<dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.4.0</version> </dependency>
验证效果
修改后运行测试,你应该能看到控制台输出ABC和Appium server的URL,说明Hooks的初始化逻辑已经执行,Appium服务和模拟器会正常启动,screen类里的driver也不会再抛出NullPointerException了。
内容的提问来源于stack exchange,提问作者benni

