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

JavaFX打包exe后Selenium ChromeDriver启动挂起问题排查

JavaFX+Selenium应用jpackage打包exe后ChromeDriver挂起问题

问题描述

开发的JavaFX应用集成Selenium实现网页爬取,在IntelliJ本地运行一切正常,但通过jpackage打包为Windows exe后,除ChromeDriver相关功能外其余模块均正常。代码总会卡在new ChromeDriver()处,无任何报错信息,最后一条调试日志显示已找到驱动路径:

SeleniumUtil - next line is new ChromeDriver(). Driver system location: C:\Users\cdavi\.cache\selenium\chromedriver\win64\144.0.7559.133\chromedriver.exe

环境与已尝试方案

当前环境

使用Selenium 4.40.0版本,Gradle依赖配置:

implementation("org.seleniumhq.selenium:selenium-java:4.40.0")

已尝试的解决方案

  • 显式设置chromedriver.exe路径:
System.setProperty("webdriver.chrome.driver", "C:\\dev\\chromedriver.exe");
  • 切换至Selenium 4.6.0版本(该版本宣称内置Driver Manager,无需手动指定驱动路径),但4.6版本运行时会报错"The path to the driver executable must be set by the webdriver.chrome.driver system property;",4.40版本无此报错但仍存在挂起问题
  • 引入bonigarcia的webdrivermanager依赖:
implementation("io.github.bonigarcia:webdrivermanager:6.3.3")

上述方案均无效,代码始终卡在new ChromeDriver()处。

对应的工具类代码:

public final class SeleniumUtil {

    private static final Logger logger = LoggerFactory.getLogger(SeleniumUtil.class);

    public static ScrapeResult getDocument(final String url) {
        WebDriverManager.chromedriver().setup();
        WebDriver driver = null;
        ScrapeResult scrapeResult = new ScrapeResult(url);
        try {
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--headless=new"); // Modern headless flag
            options.addArguments("--disable-gpu"); // Recommended for headless
            options.addArguments("--disable-extensions"); // Recommended for headless
            options.addArguments("--disable-background-networking"); // Recommended for headless
            options.addArguments("--window-size=1280,800"); // Define a consistent screen size
            options.addArguments("--remote-debugging-pipe");
            options.addArguments("--disable-blink-features=AutomationControlled");
            options.setExperimentalOption("excludeSwitches", Collections.singleton("enable-automation"));
            logger.debug("next line is new ChromeDriver(). Driver system location: "
                    + System.getProperty("webdriver.chrome.driver"));
            driver = new ChromeDriver(options);
            logger.debug("finished creating chromedriver. Driver system location: "
                    + System.getProperty("webdriver.chrome.driver"));

            //driver.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
            logger.debug("Retrieve from " + url);
            driver.get(url);
            logger.debug("Done!");
            scrapeResult.parse(driver.getPageSource());

        } catch (Exception e) {
            scrapeResult.setException(e);
            logger.error("Selenium request failed: " + e.getMessage(), e);
        } finally {
            if (driver != null) {
                driver.quit(); // CRITICAL: Always quit the driver
            }
        }
        return scrapeResult;
    }

    private SeleniumUtil() {}
}

评论建议后的更新

移除bonigarcia:webdrivermanager依赖并注释相关代码后,IDE本地运行仍正常,但打包为exe后依旧卡在new ChromeDriver()处,日志显示Driver system location: null。已关闭微软防火墙排除网络问题,设置webdriver.chrome.logfile属性但打包后未生成任何日志。

更新#2:最小复现示例

该示例可复现问题,推测问题与在JavaFX的Task后台线程中运行ChromeDriver代码有关:

  • 第一个按钮直接在GUI线程执行ChromeDriver代码(出现日志类找不到错误,非核心问题)
  • 第二个按钮在Task后台线程中调用ChromeDriver代码(JavaFX处理长任务的标准方式),出现与原程序完全一致的问题——卡在new ChromeDriver()处,无任何日志输出。

相关文件代码

build.gradle

plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.13'
    id 'org.beryx.runtime' version '2.0.1'
}

group 'my.selenium'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}
	ext {
    junitVersion = '5.10.2'
}

java {
    sourceCompatibility = JavaVersion.VERSION_22
    targetCompatibility = JavaVersion.VERSION_22
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

application {
    applicationName = 'MySeleniumFX'
    mainClass = 'my.selenium.myseleniumfx.HelloApplication'
}

javafx {
    version = '22'
    modules = ['javafx.controls', 'javafx.fxml']
}

dependencies {

    implementation("org.seleniumhq.selenium:selenium-java:4.40.0")

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}

test {
    useJUnitPlatform()
}

runtime {
    options = ['--strip-debug', '--compress', 'zip-6', '--no-header-files', '--no-man-pages']

    jpackage {
        appVersion = '0.0.5'
        imageName = 'MySeleniumFX'
        imageOptions = ["--win-console"]
        installerOptions += ['--resource-dir', "src/main/resources"]
        installerOptions += ['--vendor', 'Me']
        installerOptions += ['--name', 'MySeleniumFX']
        installerOptions += ['--win-per-user-install', '--win-dir-chooser', '--win-menu']
    }
}

SeleniumLoader.java

package my.selenium.myseleniumfx;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.util.Collections;

public class SeleniumLoader {

    protected String process( final String url ) {
        WebDriver driver = null;
        String pageTitle = null;
        try {
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--headless=new"); // Modern headless flag
            options.addArguments("--disable-gpu"); // Recommended for headless
            options.addArguments("--disable-extensions"); // Recommended for headless
            //options.addArguments("--disable-background-networking"); // Recommended for headless
            options.addArguments("--window-size=1280,800"); // Define a consistent screen size
            //options.addArguments("--remote-debugging-pipe");
            // 添加参数禁用AutomationControlled标识
            options.addArguments("--disable-blink-features=AutomationControlled");
            options.setExperimentalOption("excludeSwitches", Collections.singleton("enable-automation"));
            //options.setExperimentalOption("useAutomationExtension", false);
            System.out.println("About to create new ChromeDriver()");
            System.out.println("System.getProperty('webdriver.chrome.driver') = "
                    + System.getProperty("webdriver.chrome.driver"));
            driver = new ChromeDriver(options);
            System.out.println("new ChromeDriver() success!");
            // 验证new ChromeDriver()是否设置了系统属性,结果未设置
            System.out.println("System.getProperty('webdriver.chrome.driver') = "
                    + System.getProperty("webdriver.chrome.driver"));
            System.out.println("Getting from " + url);
            driver.get(url);
            pageTitle = driver.getTitle();
            System.out.println("Done! Page title = " + pageTitle);
        } catch (Exception ex) {
            ex.printStackTrace(System.out);
        } finally {
            if (driver != null) {
                driver.quit();
                System.out.println("Driver has been quit");
            }
        }
        return pageTitle;
    }
}

HelloController.java

package my.selenium.myseleniumfx;

import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class HelloController {

    @FXML
    private Label buttonMessages;

    @FXML
    protected void onClickInsideThread() {
        buttonMessages.setText("Clicked on action to be run inside the gui thread");
        String pageTitle = new SeleniumLoader().process("https://www.selenium.dev/documentation/");
        buttonMessages.setText(pageTitle);
    }

    @FXML
    protected void onClickOutsideThread() {
        buttonMessages.setText("Clicked on action to be run outside the gui thread");
        startBackgroundTask();
    }

    private void startBackgroundTask() {
        Task<String> task = new Task<String>() {
            @Override
            protected String call() throws Exception {
                // 后台线程执行长任务
                updateMessage("Task started...");
                String pageTitle = new SeleniumLoader().process("https://www.selenium.dev/documentation/");
                updateMessage("Task finished.");
                return pageTitle;
            }
        };

        task.setOnSucceeded(event -> {
            // UI线程更新结果
            buttonMessages.setText("Succeeded: " + task.getValue());
        });

        task.setOnFailed(event -> {
            // UI线程显示错误
            buttonMessages.setText("Failed: " + task.getException().getMessage());
        });

        // 启动后台线程执行任务
        new Thread(task).start();
    }
}

HelloApplication.java

package my.selenium.myseleniumfx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

hello-view.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" prefHeight="174.0" prefWidth="262.0" spacing="20.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/25" fx:controller="my.selenium.myseleniumfx.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>

    <Label fx:id="buttonMessages" />
    <Button onAction="#onClickInsideThread" text="Run on the thread" />
   <Button mnemonicParsing="false" onAction="#onClickOutsideThread" text="Run outside the thread" />
</VBox>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 15:05:54