admin管理员组文章数量:1389777
I'm integrating Cucumber with Spring Boot to manage an Appium server for my tests. I a custom listener that implements ConcurrentEventListener and is attached via @CucumberOptions in my test runner. The listener attempts to retrieve a Spring-managed bean (AppiumServerServices) from a static SpringContextHolder. However, I'm encountering a NullPointerException because the Spring context is not available at the time the listener is invoked.
I am using cucumber version 7.21.1 and spring-boot version 3.4.3
package .example.core.listeners;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestRunStarted;
import .example.core.services.AppiumServerServices;
import .example.core.util.SpringContextHolder;
public class AppiumServerListener implements ConcurrentEventListener {
@Override
public void setEventPublisher(EventPublisher eventPublisher) {
eventPublisher.registerHandlerFor(TestRunStarted.class, this::beforeTestStarted);
eventPublisher.registerHandlerFor(TestRunFinished.class, this::afterTestFinished);
}
private void beforeTestStarted(TestRunStarted event) {
System.out.println("Before Test");
// Causes NPE because SpringContextHolder.getContext() returns null at this point
SpringContextHolder.getContext().getBean(AppiumServerServices.class).initAppiumServer();
}
private void afterTestFinished(TestRunFinished event) {
System.out.println("After Test");
SpringContextHolder.getContext().getBean(AppiumServerServices.class).tearAppiumServer();
}
}
package .example.core;
import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
import .springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@CucumberOptions(
plugin = {"pretty", ".example.core.listeners.AppiumServerListener"},
glue = ".example.stepdefinitions"
)
public abstract class BaseRunnerTest extends AbstractTestNGCucumberTests {
}
package .example.core.services;
import io.appium.java_client.service.local.AppiumDriverLocalService;
import io.appium.java_client.service.local.AppiumServiceBuilder;
import io.appium.java_client.service.local.flags.GeneralServerFlag;
import .example.config.GlobalConfig;
import .slf4j.Logger;
import .slf4j.LoggerFactory;
import .springframework.beans.factory.DisposableBean;
import .springframework.stereotype.Service;
import java.io.File;
import java.Socket;
import java.URL;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service
public class AppiumServerServices implements ServerManager, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(AppiumServerServices.class);
private static AppiumDriverLocalService appiumService;
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
private static final String IP_ADDRESS = "127.0.0.1";
private final GlobalConfig globalConfig;
private final int SERVER_PORT;
public AppiumServerServices(GlobalConfig globalConfig) {
this.globalConfig = globalConfig;
this.SERVER_PORT = globalConfig.getDefaultPort();
// Register a shutdown hook to stop the Appium server on JVM termination
Runtime.getRuntime().addShutdownHook(new Thread(this::tearAppiumServer));
}
private AppiumDriverLocalService getAppiumDriverService() {
String strDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String strTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH_mm_ss"));
var strReportDir = Paths.get(strDate, "appiumServer");
String logFileName = String.format("Appium_Server_%s.log", strTime);
File logFile = new File(Paths.get("../Logs", strReportDir.toString(), logFileName).toString());
AppiumServiceBuilder appiumServiceBuilder = new AppiumServiceBuilder();
appiumServiceBuilder
.withIPAddress(IP_ADDRESS)
.usingPort(SERVER_PORT);
File appiumBinaryPath = globalConfig.getAppiumBinaryPath();
if (appiumBinaryPath != null) {
appiumServiceBuilder.withAppiumJS(appiumBinaryPath);
}
appiumServiceBuilder.withArgument(() -> "--long-stacktrace")
.withArgument(GeneralServerFlag.LOCAL_TIMEZONE)
.withArgument(GeneralServerFlag.LOG_TIMESTAMP)
.withArgument(GeneralServerFlag.DEBUG_LOG_SPACING)
.withArgument(GeneralServerFlag.USE_DRIVERS, globalConfig.getAutomationDriverList().toLowerCase())
.withArgument(GeneralServerFlag.RELAXED_SECURITY)
.withArgument(GeneralServerFlag.NO_PERMS_CHECKS)
.withArgument(GeneralServerFlag.USE_PLUGINS, globalConfig.getPluginList())
.withArgument(GeneralServerFlag.SESSION_OVERRIDE)
.withArgument(GeneralServerFlag.LOCAL_TIMEZONE)
.withArgument(() -> "--config", new File(".." + File.separator + "config" + File.separator + "server-config.json").toString())
.withTimeout(Duration.ofSeconds(240))
.withArgument(() -> "-ka", "800")
.withLogOutput(System.err)
.withLogFile(logFile);
return AppiumDriverLocalService.buildService(appiumServiceBuilder);
}
@Override
public void initAppiumServer() {
if (globalConfig.getAppiumServerStartMode() == null) {
return;
}
// Combined check: verify both the internal state and external socket connection.
if (isAppiumServerRunning(SERVER_PORT)) {
logger.info("Appium is already running on port {}. Skipping startup.", SERVER_PORT);
return;
}
if (appiumService == null) {
logger.info("Starting Appium server...");
appiumService = getAppiumDriverService();
appiumService.start();
logger.info("Appium server started on port {}", SERVER_PORT);
startMonitoringAppiumServer();
}
}
@Override
public void tearAppiumServer() {
if ("DEVICE_FARM".equalsIgnoreCase(globalConfig.getAppiumServerStartMode()) || "WEB".equalsIgnoreCase(globalConfig.getAppiumServerStartMode())) {
logger.info("Running on Appium Device Farm. No local Appium server to stop.");
return;
}
if (appiumService != null) {
logger.info("Stopping Appium server...");
appiumService.stop();
appiumService = null;
}
}
@Override
public URL getAppiumServerURL() {
return appiumService != null ? appiumService.getUrl() : null;
}
/**
* Combines an internal check and an external socket check.
*
* @param port the port to verify
* @return true only if both checks confirm the server is running.
*/
private boolean isAppiumServerRunning(int port) {
boolean internalCheck = appiumService != null && appiumService.isRunning();
if (!internalCheck) {
logger.info("Internal check: Appium service is not running.");
return false;
}
try (Socket socket = new Socket(IP_ADDRESS, port)) {
logger.info("Socket check: Appium server detected running on port {}.", port);
return true;
} catch (Exception e) {
logger.info("Socket check failed: No server detected on port {}.", port);
return false;
}
}
private void startMonitoringAppiumServer() {
executorService.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(5000);
if (!isAppiumServerRunning(globalConfig.getDefaultPort())) {
logger.error("Appium server crashed! Restarting...");
initAppiumServer();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Appium monitoring service interrupted!");
break;
}
}
});
}
@Override
public void destroy() {
tearAppiumServer();
executorService.shutdownNow();
logger.info("Appium server and monitoring service have been shut down.");
}
}
本文标签:
版权声明:本文标题:Cucumber Listener Invoked Before Spring Boot Context Initialization Causing NullPointerException - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744593684a2614648.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论