调试了几次,解决了第二次执行用例无法打开浏览器或者打开浏览器但是异常的情况:
deepseek的解释应该是配置项问题,改来改去,这一版没啥问题了
<selenium.version>4.27.0</selenium.version>
package com.automation.core.impl;
import com.automation.core.assertion.AssertionHelper;
import com.automation.core.config.ConfigLoader;
import com.automation.core.executor.ExecutionContext;
import com.automation.core.executor.TestExecutor;
import com.automation.core.model.TestCaseInfo;
import com.automation.core.model.TestCaseResult;
import com.automation.core.monitor.TestExecutionMonitor;
import com.automation.core.report.ExtentReportManager;
import com.automation.test.context.ArtifactStorage;
import com.automation.test.uibasetest.BaseTest;
import com.aventstack.extentreports.ExtentReports;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
/**
* Selenium UI 测试执行器
* 所有配置从 application.yml 读取
*/
@Component
public class SeleniumTestExecutor implements TestExecutor {
private static final Logger log = LoggerFactory.getLogger(SeleniumTestExecutor.class);
private static final List<String> SUPPORTED_FRAMEWORKS = Arrays.asList("SELENIUM", "WEBDRIVER");
private static final ThreadLocal<WebDriver> driverThreadLocal = new ThreadLocal<>();
// 记录自己创建的进程(用于超时清理)
private static final Map<String, Long> managedDriverPids = new ConcurrentHashMap<>();
private static final long MAX_DRIVER_AGE = 300000; // 5分钟
@Autowired
private ConfigLoader configLoader;
@Autowired
private ApplicationContext applicationContext;
// 使用 ConcurrentHashMap 管理不同 executionId 对应的 WebDriver
private static final Map<String, WebDriver> driverMap = new ConcurrentHashMap<>();
private static final Map<String, String> driverStatus = new ConcurrentHashMap<>();
// 从 YAML 读取的配置项
private static String driverPath;
private static String browserPath;
private static boolean headless;
private static int timeout;
private static int implicitWait;
@Autowired
private ExtentReportManager extentReportManager;
@Autowired
private TestExecutionMonitor monitor;
@PostConstruct
public void init() {
// ✅ 从配置加载到静态变量
SeleniumTestExecutor.driverPath = configLoader.getString("webdriver.chrome.driver-path");
SeleniumTestExecutor.browserPath = configLoader.getString("webdriver.chrome.browser-path", null);
SeleniumTestExecutor.headless = configLoader.getBoolean("webdriver.browser.headless", false);
SeleniumTestExecutor.timeout = configLoader.getInt("webdriver.browser.timeout", 60);
SeleniumTestExecutor.implicitWait = configLoader.getInt("webdriver.browser.implicit-wait", 10);
// ✅ 设置系统属性(确保 Selenium Manager 被禁用)
System.setProperty("seleniummanager.enabled", "false");
System.setProperty("selenium.manager.enabled", "false");
System.setProperty("webdriver.chrome.driver", driverPath);
log.info(" SeleniumTestExecutor configuration loaded from YAML:");
log.info(" driver-path: {}", driverPath.isEmpty() ? "(not set, will auto-find)" : driverPath);
log.info(" browser-path: {}", browserPath.isEmpty() ? "(default)" : browserPath);
log.info(" headless: {}", headless);
log.info(" timeout: {}s", timeout);
log.info(" implicit-wait: {}s", implicitWait);
}
// ==================== 进程清理方法 ====================
/**
* 清理僵尸 ChromeDriver 进程
* 只清理没有关联 Chrome 浏览器窗口的 Chromedriver 进程
* 不会影响正在运行的测试
*/
private static void killZombieChromeDriverProcesses() {
String os = System.getProperty("os.name").toLowerCase();
if (!os.contains("win")) {
return;
}
try {
// 1. 获取所有 chromedriver.exe 进程
Process tasklist = Runtime.getRuntime().exec(
"tasklist /FI \"IMAGENAME eq chromedriver.exe\" /FO CSV /NH"
);
BufferedReader reader = new BufferedReader(new InputStreamReader(tasklist.getInputStream()));
List<String> driverPids = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
// CSV 格式: "chromedriver.exe","12345","Console","1","12,345 K"
String\[\] parts = line.split(",");
if (parts.length >= 2) {
String pid = parts1.replace("\"", "").trim();
if (pid.matches("\\d+")) {
driverPids.add(pid);
}
}
}
reader.close();
if (driverPids.isEmpty()) {
log.debug("没有发现 ChromeDriver 进程");
return;
}
log.debug("发现 {} 个 ChromeDriver 进程", driverPids.size());
// 2. 检查每个 ChromeDriver 是否是僵尸进程
int killedCount = 0;
for (String driverPid : driverPids) {
if (isZombieDriver(driverPid)) {
Process kill = Runtime.getRuntime().exec("taskkill /F /PID " + driverPid);
kill.waitFor();
killedCount++;
managedDriverPids.remove(driverPid);
log.info(" 已清理僵尸 ChromeDriver 进程, PID: {}", driverPid);
}
}
if (killedCount > 0) {
log.info(" 共清理 {} 个僵尸 ChromeDriver 进程", killedCount);
}
} catch (Exception e) {
log.debug("清理僵尸进程失败: {}", e.getMessage());
}
}
/**
* 判断 ChromeDriver 是否是僵尸进程
*/
private static boolean isZombieDriver(String driverPid) throws Exception {
// 1. 获取 ChromeDriver 的父进程 PID
Process wmic = Runtime.getRuntime().exec(
"wmic process where \"ProcessId=" + driverPid + "\" get ParentProcessId /VALUE"
);
BufferedReader reader = new BufferedReader(new InputStreamReader(wmic.getInputStream()));
String parentPid = null;
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("ParentProcessId=")) {
parentPid = line.substring("ParentProcessId=".length()).trim();
break;
}
}
reader.close();
if (parentPid == null || parentPid.isEmpty() || "0".equals(parentPid)) {
return true; // 没有父进程,是僵尸
}
// 2. 检查父进程是否还在运行
Process check = Runtime.getRuntime().exec(
"tasklist /FI \"PID eq " + parentPid + "\" /FO CSV /NH"
);
BufferedReader checkReader = new BufferedReader(new InputStreamReader(check.getInputStream()));
String checkLine = checkReader.readLine();
checkReader.close();
if (checkLine == null || checkLine.isEmpty()) {
log.debug("发现孤立 ChromeDriver, 父进程已不存在, PID: {}", driverPid);
return true;
}
// 3. 检查父进程是否是 Java 或 Chrome
if (checkLine.toLowerCase().contains("java.exe") ||
checkLine.toLowerCase().contains("javaw.exe") ||
checkLine.toLowerCase().contains("chrome.exe")) {
log.debug("ChromeDriver PID: {} 正常 (父进程: {})", driverPid, parentPid);
return false;
}
log.debug("发现孤立 ChromeDriver, 父进程不是 Java/Chrome, PID: {}, 父进程: {}", driverPid, parentPid);
return true;
}
/**
* 清理超时的 ChromeDriver 进程
* 只清理由当前服务创建且超过 5 分钟的进程
*/
private static void cleanTimeoutDrivers() {
String os = System.getProperty("os.name").toLowerCase();
log.info("os: {} " , os);
if (!os.contains("win")) {
return;
}
try {
long now = System.currentTimeMillis();
List<String> pidsToRemove = new ArrayList<>();
for (Map.Entry<String, Long> entry : managedDriverPids.entrySet()) {
String pid = entry.getKey();
long startTime = entry.getValue();
long age = now - startTime;
// 如果超过 5 分钟,检查并清理
if (age > MAX_DRIVER_AGE) {
try {
if (isProcessExists(pid)) {
if (isZombieDriver(pid)) {
Process kill = Runtime.getRuntime().exec("taskkill /F /PID " + pid);
kill.waitFor();
pidsToRemove.add(pid);
log.info(" 清理超时 ChromeDriver, PID: {}, 存活: {}ms", pid, age);
} else {
log.debug("ChromeDriver 超时但仍有活动, PID: {}, 存活: {}ms", pid, age);
}
} else {
pidsToRemove.add(pid);
log.debug("ChromeDriver 进程已消失, PID: {}", pid);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
for (String pid : pidsToRemove) {
managedDriverPids.remove(pid);
}
if (!pidsToRemove.isEmpty()) {
log.info("⏱ 共清理 {} 个超时 ChromeDriver 进程", pidsToRemove.size());
}
} catch (Exception e) {
log.debug("清理超时进程失败: {}", e.getMessage());
}
}
/**
* 检查进程是否存在
*/
private static boolean isProcessExists(String pid) throws Exception {
Process check = Runtime.getRuntime().exec(
"tasklist /FI \"PID eq " + pid + "\" /FO CSV /NH"
);
BufferedReader reader = new BufferedReader(new InputStreamReader(check.getInputStream()));
String line = reader.readLine();
reader.close();
return line != null && !line.isEmpty() && !line.contains("没有运行");
}
/**
* 获取 ChromeDriver 进程 PID
*/
private String getChromeDriverPid() {
String os = System.getProperty("os.name").toLowerCase();
if (!os.contains("win")) {
return null;
}
try {
String javaPid = getCurrentJavaPid();
if (javaPid == null) {
return null;
}
Process wmic = Runtime.getRuntime().exec(
"wmic process where \"name='chromedriver.exe'\" get ProcessId,ParentProcessId /FORMAT:CSV"
);
BufferedReader reader = new BufferedReader(new InputStreamReader(wmic.getInputStream()));
String line;
boolean first = true;
while ((line = reader.readLine()) != null) {
if (first) { first = false; continue; }
String\[\] parts = line.split(",");
if (parts.length >= 3) {
String pid = parts1.trim();
String parentPid = parts2.trim();
if (parentPid.equals(javaPid)) {
return pid;
}
}
}
reader.close();
} catch (Exception e) {
log.debug("获取 ChromeDriver PID 失败: {}", e.getMessage());
}
return null;
}
/**
* 获取当前 Java 进程 PID
*/
private String getCurrentJavaPid() {
try {
String name = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
if (name != null && name.contains("@")) {
return name.split("@")0;
}
} catch (Exception e) {
log.debug("获取 Java PID 失败: {}", e.getMessage());
}
return null;
}
/**
* 注册自己创建的 ChromeDriver 进程
*/
private void registerDriverProcess(String pid) {
if (pid != null && pid.matches("\\d+")) {
managedDriverPids.put(pid, System.currentTimeMillis());
log.debug("注册 ChromeDriver 进程, PID: {}", pid);
}
}
// ==================== 获取配置辅助方法 ====================
private String getStringValue(Map<String, Object> config, String key, String defaultValue) {
if (config == null) {
return defaultValue;
}
Object value = config.get(key);
if (value != null && !value.toString().isEmpty()) {
return value.toString();
}
return defaultValue;
}
private boolean getBooleanValue(Map<String, Object> config, String key, boolean defaultValue) {
if (config == null) {
return defaultValue;
}
Object value = config.get(key);
if (value != null) {
return Boolean.parseBoolean(value.toString());
}
return defaultValue;
}
private int getIntValue(Map<String, Object> config, String key, int defaultValue) {
if (config == null) {
return defaultValue;
}
Object value = config.get(key);
if (value != null) {
try {
return Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
log.warn(" Failed to parse int value for {}: {}, using default: {}", key, value, defaultValue);
}
}
return defaultValue;
}
// ==================== TestExecutor 接口实现 ====================
@Override
public TestCaseResult execute(TestCaseInfo testCase, ExecutionContext context) {
log.info("执行 Selenium 测试: {}", testCase.getName());
TestCaseResult result = new TestCaseResult();
result.setTestCaseId(testCase.getId());
result.setName(testCase.getName());
result.setStatus("RUNNING");
long startTime = System.currentTimeMillis();
String executionId = context.getExecutionId();
log.info("execute method executionId 获取{} " ,executionId);
try {
WebDriver driver = getDriver(executionId);
log.info("driver 获取{} " ,driver);
executeByReflection(testCase, driver, context);
result.setStatus("PASSED");
result.setDurationMs(System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("测试执行失败: {}", e.getMessage(), e);
result.setStatus("FAILED");
result.setErrorMessage(e.getMessage());
result.setStackTrace(getStackTraceString(e));
result.setDurationMs(System.currentTimeMillis() - startTime);
} finally {
// closeDriver(executionId);
// log.info("浏览器已关闭,测试执行结束");
}
return result;
}
@Override
public String getType() {
return "UI";
}
@Override
public boolean supports(TestCaseInfo testCase) {
if (testCase == null || testCase.getFramework() == null) {
return false;
}
return SUPPORTED_FRAMEWORKS.contains(testCase.getFramework().toUpperCase());
}
/**
* 同一个 executionId:多个用例串行执行,共享同一个浏览器会话(复用 WebDriver)
* 不同的 executionId:新的执行批次,重新初始化浏览器
* 这样既能提高执行效率(避免重复启动浏览器),又能正确隔离不同的执行批次
* @param executionId
* @return
*/
// ==================== WebDriver 初始化 ====================
/**
private WebDriver getDriver(String executionId) {
WebDriver driver = driverThreadLocal.get();
if (driver == null) {
log.info(" 初始化 WebDriver for thread: {}", Thread.currentThread().getName());
// ✅ 创建前清理进程
cleanTimeoutDrivers(); // 方案三:清理超时进程
killZombieChromeDriverProcesses(); // 方案二:清理僵尸进程
setupDriverPath();
ChromeOptions options = createChromeOptions();
try {
driver = new ChromeDriver(options);
} catch (Exception e) {
log.error(" 创建 ChromeDriver 失败: {}", e.getMessage(), e);
// 重试一次,先清理再创建
killZombieChromeDriverProcesses();
cleanTimeoutDrivers();
try {
driver = new ChromeDriver(options);
log.info(" 重试创建 ChromeDriver 成功");
} catch (Exception e2) {
throw new RuntimeException("ChromeDriver 初始化失败,请检查:\n" +
"1. ChromeDriver 路径是否正确: " + System.getProperty("webdriver.chrome.driver") + "\n" +
"2. Chrome 浏览器是否已安装\n" +
"3. ChromeDriver 版本是否与 Chrome 浏览器版本匹配", e2);
}
}
// 设置窗口大小
driver.manage().window().setSize(new Dimension(windowWidth, windowHigh));
// 设置超时
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(timeout));
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(implicitWait));
driverThreadLocal.set(driver);
// ✅ 注册进程(用于超时管理)
try {
String pid = getChromeDriverPid();
if (pid != null) {
registerDriverProcess(pid);
}
} catch (Exception e) {
log.debug("注册进程失败: {}", e.getMessage());
}
log.info(" WebDriver 初始化成功");
}
return driver;
}
**/
/**
* 获取指定 executionId 的 WebDriver
* 如果不存在则创建新的,如果存在则复用
*/
public static WebDriver getDriver(String executionId) {
log.info("getDriver 入参 executionId {}" ,executionId);
// 检查是否已有该 executionId 的 WebDriver
WebDriver existingDriver = driverMap.get(executionId);
log.info("driverMap 第一次查询 driver 入参 executionId {}, driver {} " ,executionId, existingDriver);
if (existingDriver != null) {
try {
// 验证 driver 是否仍然有效
existingDriver.getTitle();
log.info(" 复用 WebDriver: executionId={}", executionId);
driverThreadLocal.set(existingDriver);
driverStatus.put(executionId, "ACTIVE");
return existingDriver;
} catch (Exception e) {
// driver 已失效,移除并重新创建
log.warn("WebDriver 已失效,重新创建: executionId={}", executionId);
driverMap.remove(executionId);
driverStatus.remove(executionId);
}
}
// 创建新的 WebDriver
log.info(" 创建新的 WebDriver: executionId={}", executionId);
WebDriver driver = null;
try {
driver = createNewDriver(executionId);
driverMap.put(executionId, driver);
driverStatus.put(executionId, "ACTIVE");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return driver;
}
/**
* 创建新的 WebDriver
*/
private static WebDriver createNewDriver(String executionId) {
/*
* String driverPath1 = System.getProperty("webdriver.chrome.driver"); log.
* info("create new driver SeleniumTestExecutor.System.getProperty(\"webdriver.chrome.driver\") : {} "
* ,driverPath1 ); File driverFile = new File(driverPath1);
*
* if (!driverFile.exists()) { log.info("SeleniumTestExecutor.driverPath : {} "
* ,SeleniumTestExecutor.driverPath );
* System.setProperty("webdriver.chrome.driver",
* SeleniumTestExecutor.driverPath); }
*/
ChromeOptions options = createChromeOptions(executionId);
setupDriverPath();
// 设置浏览器路径(如果默认路径不对)
// options.setBinary("C:/Program Files/Google/Chrome/Application/chrome.exe");
// 增加超时时间
System.setProperty("webdriver.chrome.driver.timeout", "120");
// 设置日志级别,便于调试
System.setProperty("webdriver.chrome.logfile", "chromedriver.log");
System.setProperty("webdriver.chrome.verboseLogging", "true");
String binaryPath = browserPath;
if (binaryPath == null || binaryPath.isEmpty()) {
log.warn(" browser-path 未配置,尝试自动查找 Chrome");
String foundPath = null;
try {
foundPath = findChromeBinaryAuto();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (foundPath != null) {
binaryPath = foundPath;
log.info(" 自动找到 Chrome: {}", binaryPath);
}
}
if (binaryPath != null && !binaryPath.isEmpty()) {
options.setBinary(binaryPath);
log.info(" 使用浏览器: {}", binaryPath);
} else {
log.warn(" 未找到 Chrome 浏览器路径,将使用 ChromeDriver 默认查找机制");
}
// 打印当前配置信息
log.info("ChromeOptions 配置:");
log.info(" driver-path: {}", System.getProperty("webdriver.chrome.driver"));
log.info(" browser-path: {}", binaryPath != null ? binaryPath : "default");
log.info(" headless: {}", headless);
// 重试机制
int maxRetries = 3;
Exception lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
log.info("尝试创建 ChromeDriver, 第 {}/{} 次", attempt, maxRetries);
// 清理旧的用户目录
String userDataDir = "C:/temp/chrome-profile-" + executionId;
deleteDirectory(new File(userDataDir));
WebDriver driver = new ChromeDriver(options);
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);
driver.manage().timeouts().setScriptTimeout(60, TimeUnit.SECONDS);
driverThreadLocal.set(driver);
log.info(" ChromeDriver 创建成功: executionId={}", executionId);
return driver;
} catch (Exception e) {
killZombieChromeDriverProcesses();
cleanTimeoutDrivers();
lastException = e;
log.warn("第 {} 次创建失败: {}", attempt, e.getMessage());
// 等待后重试
if (attempt < maxRetries) {
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) {}
}
}finally{
killZombieChromeDriverProcesses();
cleanTimeoutDrivers();
}
}
throw new RuntimeException("ChromeDriver 初始化失败,请检查:\n" +
"1. ChromeDriver 路径是否正确: " + System.getProperty("webdriver.chrome.driver") + "\n" +
"2. Chrome 浏览器是否已安装\n" +
"3. ChromeDriver 版本是否与 Chrome 浏览器版本匹配", lastException);
}
private static void deleteDirectory(File dir) {
if (dir.exists()) {
File\[\] files = dir.listFiles();
if (files != null) {
for (File file : files) {
deleteDirectory(file);
}
}
dir.delete();
}
}
/**
* 关闭指定 executionId 的 WebDriver
*/
public static void closeDriver(String executionId) {
if (executionId == null || executionId.isEmpty()) {
log.warn("executionId 为空,跳过关闭");
return;
}
WebDriver driver = driverMap.get(executionId);
driverStatus.remove(executionId);
if (driver != null) {
driverMap.remove(executionId);
driverThreadLocal.remove();
try {
driver.quit();
// 创建前清理进程
cleanTimeoutDrivers(); // 方案三:清理超时进程
killZombieChromeDriverProcesses(); // 方案二:清理僵尸进程
log.info(" WebDriver 已关闭: executionId={}", executionId);
} catch (Exception e) {
log.warn("关闭 WebDriver 异常: executionId={}, error={}", executionId, e.getMessage());
}
} else {
log.debug("未找到要关闭的 WebDriver: executionId={}", executionId);
}
}
/**
* 关闭所有 WebDriver(应用关闭时调用)
*/
public static void closeAllDrivers() {
log.info("关闭所有 WebDriver,共 {} 个", driverMap.size());
for (String executionId : driverMap.keySet()) {
closeDriver(executionId);
}
driverMap.clear();
driverStatus.clear();
driverThreadLocal.remove();
}
/**
* 检查指定 executionId 是否有活跃的 WebDriver
*/
public static boolean hasActiveDriver(String executionId) {
if (executionId == null || executionId.isEmpty()) {
return false;
}
WebDriver driver = driverMap.get(executionId);
if (driver == null) {
return false;
}
try {
driver.getTitle();
return true;
} catch (Exception e) {
driverMap.remove(executionId);
driverStatus.remove(executionId);
return false;
}
}
/**
* 获取当前所有活跃的 executionId
*/
public static Map<String, String> getActiveDrivers() {
return new ConcurrentHashMap<>(driverStatus);
}
// ==================== ChromeDriver 路径设置 ====================
private static void setupDriverPath() {
String driverPath = SeleniumTestExecutor.driverPath;
log.info("default driverpath: {}" ,driverPath);
if (driverPath != null && !driverPath.isEmpty()) {
setDriverPath(driverPath);
return;
}
String foundPath = null;
try {
foundPath = findDriverAuto();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (foundPath != null) {
setDriverPath(foundPath);
return;
}
throw new RuntimeException("无法找到 ChromeDriver,请在 application.yml 中配置 webdriver.chrome.driver-path");
}
private static String findDriverAuto() {
String os = System.getProperty("os.name").toLowerCase();
String driverName = os.contains("win") ? "chromedriver.exe" : "chromedriver";
String\[\] possiblePaths = {
System.getProperty("user.dir") + "/drivers/" + driverName,
System.getProperty("user.dir") + "/src/test/resources/drivers/" + driverName,
System.getProperty("user.dir") + "/target/classes/drivers/" + driverName,
System.getProperty("user.home") + "/.drivers/" + driverName,
System.getProperty("user.home") + "/Downloads/chromedriver-win64/" + driverName,
System.getProperty("user.home") + "/Downloads/" + driverName
};
for (String path : possiblePaths) {
File file = new File(path);
if (file.exists() && file.isFile()) {
if (os.contains("win") || file.canExecute()) {
log.info(" 找到默认 ChromeDriver: {}", path);
return path;
}
}
}
log.warn(" 未找到默认 ChromeDriver,请配置 webdriver.chrome.driver-path");
return null;
}
private static void setDriverPath(String driverPath) {
File driverFile = new File(driverPath);
if (!driverFile.isAbsolute()) {
String basePath = System.getProperty("user.dir");
driverFile = new File(basePath, driverPath);
}
String absolutePath = driverFile.getAbsolutePath();
System.setProperty("webdriver.chrome.driver", absolutePath);
log.info(" 设置 ChromeDriver 路径: {}", absolutePath);
if (!driverFile.exists()) {
log.error(" ChromeDriver 文件不存在: {}", absolutePath);
throw new RuntimeException("ChromeDriver 文件不存在: " + absolutePath);
}
String os = System.getProperty("os.name").toLowerCase();
if (!os.contains("win") && !driverFile.canExecute()) {
log.warn(" ChromeDriver 文件不可执行,尝试设置执行权限");
try {
driverFile.setExecutable(true);
} catch (Exception e) {
log.warn("设置执行权限失败: {}", e.getMessage());
}
}
}
private static String findChromeBinaryAuto() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
String\[\] possiblePaths = {
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
System.getProperty("user.home") + "\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe"
};
for (String path : possiblePaths) {
File file = new File(path);
if (file.exists() && file.isFile()) {
return path;
}
}
}
return null;
}
private static ChromeOptions createChromeOptions(String executionId) {
ChromeOptions options = new ChromeOptions();
// 核心参数
options.addArguments("--remote-allow-origins=*");
options.addArguments("--start-maximized");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--no-sandbox");
options.addArguments("--disable-gpu");
options.addArguments("--remote-debugging-port=0");
options.addArguments("--disable-blink-features=AutomationControlled");
options.addArguments("--no-first-run");
options.addArguments("--disable-default-apps");
options.addArguments("--disable-sync");
options.addArguments("--disable-features=ChromeWhatsNewUI,PrivacySandboxPrompt,SigninPromo");
// 为每次执行创建一个新的临时用户目录
String userDataDir = "C:/temp/chrome-profile-" + executionId;
log.info("为每次执行创建一个新的临时用户目录 : {} " ,userDataDir );
options.addArguments("--user-data-dir=" + userDataDir);
if (headless) {
options.addArguments("--headless");
log.info(" 启用无头模式 (from YAML)");
}
log.info("ChromeOptions 配置:");
return options;
}
// ==================== 反射执行 ====================
private void executeByReflection(TestCaseInfo testCase, WebDriver driver, ExecutionContext context) throws Exception {
ExtentReports extentReports = extentReportManager.initReport(context.getExecutionId());
Class<?> clazz = Class.forName(testCase.getClassName());
Object instance;
try {
instance = applicationContext.getBean(clazz);
log.info("获取 Spring Bean: {}", testCase.getClassName());
} catch (Exception e) {
instance = clazz.getDeclaredConstructor().newInstance();
log.info("反射创建实例: {}", testCase.getClassName());
}
if (instance instanceof BaseTest) {
BaseTest baseTest = (BaseTest) instance;
baseTest.setDriver(driver);
// 2. 验证 driver 是否真的被设置了
if (baseTest.getDriver() == null) {
throw new RuntimeException("Driver 注入失败,请检查 BaseTest.setDriver() 方法");
}
log.info("Driver 已注入到: {}", testCase.getClassName());
baseTest.setContext(context);
log.info("Context 已注入到: {}", testCase.getClassName());
// 注入 monitor
setField(baseTest, "monitor", monitor);
log.info("Monitor 已注入到: {}", testCase.getClassName());
// 创建并注入 AssertionHelper
AssertionHelper assertionHelper = new AssertionHelper();
assertionHelper.setMonitor(monitor);
baseTest.setAssertion(assertionHelper);
log.info("AssertionHelper 已注入到: {}", testCase.getClassName());
// 注入 configLoader
baseTest.setConfigLoader(configLoader);
log.info("ConfigLoader 已注入到: {}", testCase.getClassName());
// 设置产物存储
String artifactBasePath = "test-output/executions/" + context.getExecutionId();
ArtifactStorage storage = new ArtifactStorage(artifactBasePath);
baseTest.setArtifactStorage(storage);
if (monitor != null) {
monitor.setArtifactStorage(storage);
}
log.info("ArtifactStorage 已注入到: {}", testCase.getClassName());
// 设置 ExtentReports
baseTest.setExtentReports(extentReports);
log.info("ExtentReports 已注入到: {}", testCase.getClassName());
}
// 执行测试
Method method = clazz.getMethod(testCase.getMethodName());
method.invoke(instance);
// 获取测试结果
TestCaseResult result = monitor.getResult();
if (result != null) {
context.setSharedData("testResult", result);
log.info("测试结果: {}", result.getStatus());
}
// 生成报告
extentReportManager.generateReport();
}
/**
* 通过反射设置字段值
*/
private void setField(Object target, String fieldName, Object value) {
try {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
} catch (Exception e) {
log.warn("设置字段失败: {}.{} - {}", target.getClass().getSimpleName(), fieldName, e.getMessage());
}
}
private String getStackTraceString(Throwable e) {
StringBuilder sb = new StringBuilder();
sb.append(e.toString()).append("\n");
for (StackTraceElement element : e.getStackTrace()) {
sb.append(" at ").append(element.toString()).append("\n");
}
return sb.toString();
}
public static void closeDriver() {
WebDriver driver = driverThreadLocal.get();
if (driver != null) {
try {
driver.quit();
log.info("WebDriver 已关闭");
} catch (Exception e) {
log.warn("关闭 WebDriver 异常: {}", e.getMessage());
} finally {
driverThreadLocal.remove();
}
}
}
}