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.");
    }
}

本文标签: