mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/WECENG/ticket-purchase.git
synced 2026-09-20 08:04:02 +08:00
Merge pull request #93 from chris22333-dot/fix/windows-chrome-driver
fix: support ChromeDriver setup on Windows
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -162,6 +162,7 @@ chromedriver.log
|
|||||||
config.json
|
config.json
|
||||||
credentials.json
|
credentials.json
|
||||||
*.cookie
|
*.cookie
|
||||||
|
*.pkl
|
||||||
*.session
|
*.session
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
@@ -177,4 +178,4 @@ credentials.json
|
|||||||
|
|
||||||
# Lock files (except poetry.lock)
|
# Lock files (except poetry.lock)
|
||||||
*.lock
|
*.lock
|
||||||
!poetry.lock
|
!poetry.lock
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -23,16 +24,147 @@ def _run_command_get_version(command):
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
command,
|
command,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
|
errors="replace",
|
||||||
text=True,
|
text=True,
|
||||||
timeout=5
|
timeout=5
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
return result.stdout.strip()
|
return (result.stdout or result.stderr).strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _unique_paths(paths):
|
||||||
|
"""保持顺序去重,并忽略空路径。"""
|
||||||
|
unique = []
|
||||||
|
seen = set()
|
||||||
|
for path in paths:
|
||||||
|
if not path or path in seen:
|
||||||
|
continue
|
||||||
|
seen.add(path)
|
||||||
|
unique.append(path)
|
||||||
|
return unique
|
||||||
|
|
||||||
|
|
||||||
|
def _get_chrome_paths():
|
||||||
|
"""返回常见平台上的 Chrome 可执行文件候选路径。"""
|
||||||
|
program_files = os.environ.get("PROGRAMFILES", r"C:\Program Files")
|
||||||
|
program_files_x86 = os.environ.get("PROGRAMFILES(X86)", r"C:\Program Files (x86)")
|
||||||
|
local_appdata = os.environ.get("LOCALAPPDATA")
|
||||||
|
|
||||||
|
paths = [
|
||||||
|
os.environ.get("CHROME_PATH"),
|
||||||
|
os.environ.get("GOOGLE_CHROME_BIN"),
|
||||||
|
os.path.join(program_files, "Google", "Chrome", "Application", "chrome.exe"),
|
||||||
|
os.path.join(program_files_x86, "Google", "Chrome", "Application", "chrome.exe"),
|
||||||
|
os.path.join(local_appdata, "Google", "Chrome", "Application", "chrome.exe") if local_appdata else None,
|
||||||
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
|
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
||||||
|
"/usr/bin/google-chrome",
|
||||||
|
"/usr/bin/google-chrome-stable",
|
||||||
|
"/usr/bin/chromium",
|
||||||
|
"/usr/bin/chromium-browser",
|
||||||
|
]
|
||||||
|
|
||||||
|
for command in [
|
||||||
|
"chrome.exe",
|
||||||
|
"chrome",
|
||||||
|
"google-chrome",
|
||||||
|
"google-chrome-stable",
|
||||||
|
"chromium",
|
||||||
|
"chromium-browser",
|
||||||
|
]:
|
||||||
|
paths.append(shutil.which(command))
|
||||||
|
|
||||||
|
return _unique_paths(paths)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_chromedriver_paths():
|
||||||
|
"""返回常见平台上的 ChromeDriver 候选路径。"""
|
||||||
|
paths = [
|
||||||
|
os.environ.get("CHROMEDRIVER_PATH"),
|
||||||
|
os.environ.get("SELENIUM_DRIVER_PATH"),
|
||||||
|
shutil.which("chromedriver.exe"),
|
||||||
|
shutil.which("chromedriver"),
|
||||||
|
os.path.join(os.getcwd(), "chromedriver.exe"),
|
||||||
|
os.path.join(os.getcwd(), "chromedriver"),
|
||||||
|
"/opt/homebrew/bin/chromedriver",
|
||||||
|
"/usr/local/bin/chromedriver",
|
||||||
|
"/opt/homebrew/Caskroom/chromedriver",
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
import chromedriver_autoinstaller
|
||||||
|
package_dir = os.path.dirname(chromedriver_autoinstaller.__file__)
|
||||||
|
for name in os.listdir(package_dir):
|
||||||
|
driver_dir = os.path.join(package_dir, name)
|
||||||
|
if os.path.isdir(driver_dir):
|
||||||
|
paths.append(os.path.join(driver_dir, "chromedriver.exe"))
|
||||||
|
paths.append(os.path.join(driver_dir, "chromedriver"))
|
||||||
|
except (AttributeError, ImportError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return _unique_paths(paths)
|
||||||
|
|
||||||
|
|
||||||
|
def _version_sort_key(version):
|
||||||
|
return tuple(int(part) for part in version.split("."))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_windows_chrome_version(chrome_path):
|
||||||
|
"""Windows 上 chrome.exe --version 可能被已有会话吞掉,改从安装目录读取版本。"""
|
||||||
|
app_dir = os.path.dirname(chrome_path)
|
||||||
|
try:
|
||||||
|
version_dirs = [
|
||||||
|
name for name in os.listdir(app_dir)
|
||||||
|
if re.fullmatch(r"\d+(?:\.\d+){1,3}", name)
|
||||||
|
and os.path.isdir(os.path.join(app_dir, name))
|
||||||
|
]
|
||||||
|
except OSError:
|
||||||
|
version_dirs = []
|
||||||
|
|
||||||
|
if version_dirs:
|
||||||
|
return sorted(version_dirs, key=_version_sort_key)[-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
import winreg
|
||||||
|
except ImportError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
registry_locations = [
|
||||||
|
(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon"),
|
||||||
|
(winreg.HKEY_LOCAL_MACHINE, r"Software\Google\Chrome\BLBeacon"),
|
||||||
|
(winreg.HKEY_LOCAL_MACHINE, r"Software\WOW6432Node\Google\Chrome\BLBeacon"),
|
||||||
|
]
|
||||||
|
for hive, key_path in registry_locations:
|
||||||
|
try:
|
||||||
|
with winreg.OpenKey(hive, key_path) as key:
|
||||||
|
version, _ = winreg.QueryValueEx(key, "version")
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_chrome():
|
||||||
|
"""查找 Chrome,返回 (path, version_str, major_version)。"""
|
||||||
|
for chrome_path in _get_chrome_paths():
|
||||||
|
if os.path.exists(chrome_path):
|
||||||
|
version_str = _run_command_get_version([chrome_path, "--version"])
|
||||||
|
if version_str:
|
||||||
|
chrome_version = _get_version_from_output(version_str)
|
||||||
|
if chrome_version:
|
||||||
|
return chrome_path, version_str, chrome_version
|
||||||
|
|
||||||
|
windows_version = _get_windows_chrome_version(chrome_path)
|
||||||
|
if windows_version:
|
||||||
|
return chrome_path, f"Google Chrome {windows_version}", _get_version_from_output(windows_version)
|
||||||
|
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
def check_python_version():
|
def check_python_version():
|
||||||
"""检查 Python 版本"""
|
"""检查 Python 版本"""
|
||||||
print("Python 版本检查...")
|
print("Python 版本检查...")
|
||||||
@@ -73,23 +205,13 @@ def check_dependencies():
|
|||||||
def check_chrome():
|
def check_chrome():
|
||||||
"""检查 Chrome 浏览器"""
|
"""检查 Chrome 浏览器"""
|
||||||
print("Chrome 浏览器检查...")
|
print("Chrome 浏览器检查...")
|
||||||
chrome_paths = [
|
chrome_path, version_str, chrome_version = _find_chrome()
|
||||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
if chrome_path:
|
||||||
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
print(f" ✓ Chrome 浏览器: {version_str}")
|
||||||
"/usr/bin/google-chrome",
|
print(f" ✓ 主版本号: {chrome_version}")
|
||||||
"/usr/bin/chromium-browser",
|
print(f" ✓ 路径: {chrome_path}")
|
||||||
]
|
print()
|
||||||
|
return True
|
||||||
for chrome_path in chrome_paths:
|
|
||||||
if os.path.exists(chrome_path):
|
|
||||||
version_str = _run_command_get_version([chrome_path, "--version"])
|
|
||||||
if version_str:
|
|
||||||
chrome_version = _get_version_from_output(version_str)
|
|
||||||
if chrome_version:
|
|
||||||
print(f" ✓ Chrome 浏览器: {version_str}")
|
|
||||||
print(f" ✓ 主版本号: {chrome_version}")
|
|
||||||
print()
|
|
||||||
return True
|
|
||||||
|
|
||||||
print(" ✗ 未找到 Chrome 浏览器")
|
print(" ✗ 未找到 Chrome 浏览器")
|
||||||
print(" 请安装 Chrome: https://www.google.com/chrome/")
|
print(" 请安装 Chrome: https://www.google.com/chrome/")
|
||||||
@@ -100,13 +222,7 @@ def check_chrome():
|
|||||||
def check_chromedriver():
|
def check_chromedriver():
|
||||||
"""检查 ChromeDriver"""
|
"""检查 ChromeDriver"""
|
||||||
print("ChromeDriver 检查...")
|
print("ChromeDriver 检查...")
|
||||||
chromedriver_paths = [
|
for driver_path in _get_chromedriver_paths():
|
||||||
"/opt/homebrew/bin/chromedriver",
|
|
||||||
"/usr/local/bin/chromedriver",
|
|
||||||
"/opt/homebrew/Caskroom/chromedriver",
|
|
||||||
]
|
|
||||||
|
|
||||||
for driver_path in chromedriver_paths:
|
|
||||||
if os.path.exists(driver_path) or os.path.islink(driver_path):
|
if os.path.exists(driver_path) or os.path.islink(driver_path):
|
||||||
version_str = _run_command_get_version([driver_path, "--version"])
|
version_str = _run_command_get_version([driver_path, "--version"])
|
||||||
if version_str:
|
if version_str:
|
||||||
@@ -121,7 +237,8 @@ def check_chromedriver():
|
|||||||
print(" ⚠ 未找到 ChromeDriver")
|
print(" ⚠ 未找到 ChromeDriver")
|
||||||
print(" 安装方法:")
|
print(" 安装方法:")
|
||||||
print(" - macOS: brew install --cask chromedriver")
|
print(" - macOS: brew install --cask chromedriver")
|
||||||
print(" - 或使用脚本自动安装: chromedriver-autoinstaller")
|
print(" - Windows/macOS/Linux: 使用脚本自动安装 chromedriver-autoinstaller")
|
||||||
|
print(" - 或手动下载: https://googlechromelabs.github.io/chrome-for-testing/")
|
||||||
print()
|
print()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -130,19 +247,15 @@ def check_version_match():
|
|||||||
"""检查 Chrome 和 ChromeDriver 版本是否匹配"""
|
"""检查 Chrome 和 ChromeDriver 版本是否匹配"""
|
||||||
print("版本匹配检查...")
|
print("版本匹配检查...")
|
||||||
|
|
||||||
chrome_version = None
|
_, _, chrome_version = _find_chrome()
|
||||||
chrome_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
||||||
if os.path.exists(chrome_path):
|
|
||||||
version_str = _run_command_get_version([chrome_path, "--version"])
|
|
||||||
if version_str:
|
|
||||||
chrome_version = _get_version_from_output(version_str)
|
|
||||||
|
|
||||||
driver_version = None
|
driver_version = None
|
||||||
driver_path = "/opt/homebrew/bin/chromedriver"
|
for driver_path in _get_chromedriver_paths():
|
||||||
if os.path.exists(driver_path) or os.path.islink(driver_path):
|
if os.path.exists(driver_path) or os.path.islink(driver_path):
|
||||||
version_str = _run_command_get_version([driver_path, "--version"])
|
version_str = _run_command_get_version([driver_path, "--version"])
|
||||||
if version_str:
|
if version_str:
|
||||||
driver_version = _get_version_from_output(version_str)
|
driver_version = _get_version_from_output(version_str)
|
||||||
|
break
|
||||||
|
|
||||||
if not chrome_version or not driver_version:
|
if not chrome_version or not driver_version:
|
||||||
print(" ⚠ 无法获取版本信息")
|
print(" ⚠ 无法获取版本信息")
|
||||||
@@ -171,45 +284,30 @@ def check_version_match():
|
|||||||
|
|
||||||
def get_chromedriver_path():
|
def get_chromedriver_path():
|
||||||
"""
|
"""
|
||||||
获取 ChromeDriver 路径,如果不存在则自动安装
|
获取 ChromeDriver 路径,如果不存在则自动安装。
|
||||||
供其他脚本导入使用
|
供其他脚本导入使用。
|
||||||
:return: ChromeDriver 可执行文件路径
|
:return: ChromeDriver 可执行文件路径
|
||||||
"""
|
"""
|
||||||
chrome_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
_, _, chrome_version = _find_chrome()
|
||||||
|
|
||||||
if not os.path.exists(chrome_path):
|
if chrome_version:
|
||||||
raise RuntimeError("未找到 Chrome 浏览器,请先安装 Chrome")
|
for driver_path in _get_chromedriver_paths():
|
||||||
|
if os.path.exists(driver_path) or os.path.islink(driver_path):
|
||||||
|
driver_version_str = _run_command_get_version([driver_path, "--version"])
|
||||||
|
if driver_version_str:
|
||||||
|
driver_version = _get_version_from_output(driver_version_str)
|
||||||
|
if driver_version == chrome_version:
|
||||||
|
return driver_path
|
||||||
|
|
||||||
# 获取 Chrome 版本
|
print(f" Chrome 版本: {chrome_version}")
|
||||||
chrome_version_str = _run_command_get_version([chrome_path, "--version"])
|
else:
|
||||||
chrome_version = _get_version_from_output(chrome_version_str) if chrome_version_str else None
|
print(" ⚠ 未通过常见路径检测到 Chrome,尝试使用 chromedriver-autoinstaller 自动检测...")
|
||||||
|
|
||||||
if not chrome_version:
|
|
||||||
raise RuntimeError("无法获取 Chrome 版本")
|
|
||||||
|
|
||||||
# 检查已安装的 ChromeDriver 是否匹配
|
|
||||||
chromedriver_paths = [
|
|
||||||
"/opt/homebrew/bin/chromedriver",
|
|
||||||
"/usr/local/bin/chromedriver",
|
|
||||||
]
|
|
||||||
|
|
||||||
for driver_path in chromedriver_paths:
|
|
||||||
if os.path.exists(driver_path) or os.path.islink(driver_path):
|
|
||||||
driver_version_str = _run_command_get_version([driver_path, "--version"])
|
|
||||||
if driver_version_str:
|
|
||||||
driver_version = _get_version_from_output(driver_version_str)
|
|
||||||
if driver_version == chrome_version:
|
|
||||||
# 版本匹配,直接使用
|
|
||||||
return driver_path
|
|
||||||
|
|
||||||
# 版本不匹配或不存在,使用自动安装器
|
|
||||||
print(f" Chrome 版本: {chrome_version}")
|
|
||||||
print(" 正在自动安装匹配的 ChromeDriver...")
|
print(" 正在自动安装匹配的 ChromeDriver...")
|
||||||
try:
|
try:
|
||||||
import chromedriver_autoinstaller
|
import chromedriver_autoinstaller
|
||||||
chromedriver_path = chromedriver_autoinstaller.install()
|
chromedriver_path = chromedriver_autoinstaller.install()
|
||||||
|
|
||||||
# 验证安装的版本
|
|
||||||
result = _run_command_get_version([chromedriver_path, "--version"])
|
result = _run_command_get_version([chromedriver_path, "--version"])
|
||||||
if not result:
|
if not result:
|
||||||
raise RuntimeError("ChromeDriver 无法执行")
|
raise RuntimeError("ChromeDriver 无法执行")
|
||||||
|
|||||||
@@ -64,9 +64,7 @@ def mock_appium_driver():
|
|||||||
mock_driver.swipe = Mock()
|
mock_driver.swipe = Mock()
|
||||||
mock_driver.quit = Mock()
|
mock_driver.quit = Mock()
|
||||||
|
|
||||||
# Mock the Remote class
|
yield mock_driver
|
||||||
with patch.object(mock_driver, "__class__.__name__", "Remote"):
|
|
||||||
yield mock_driver
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -153,4 +151,4 @@ def pytest_collection_modifyitems(config, items):
|
|||||||
if "unit" in str(item.fspath):
|
if "unit" in str(item.fspath):
|
||||||
item.add_marker(pytest.mark.unit)
|
item.add_marker(pytest.mark.unit)
|
||||||
elif "integration" in str(item.fspath):
|
elif "integration" in str(item.fspath):
|
||||||
item.add_marker(pytest.mark.integration)
|
item.add_marker(pytest.mark.integration)
|
||||||
|
|||||||
96
tests/unit/test_check_environment_windows.py
Normal file
96
tests/unit/test_check_environment_windows.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from damai import check_environment
|
||||||
|
|
||||||
|
|
||||||
|
WINDOWS_CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
||||||
|
WINDOWS_CHROMEDRIVER = r"C:\Tools\chromedriver.exe"
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_chrome_detects_windows_program_files_install(monkeypatch):
|
||||||
|
"""Chrome installed in Program Files should be detected on Windows."""
|
||||||
|
|
||||||
|
def fake_exists(path):
|
||||||
|
return path == WINDOWS_CHROME
|
||||||
|
|
||||||
|
def fake_version(command):
|
||||||
|
if command[0] == WINDOWS_CHROME:
|
||||||
|
return "Google Chrome 136.0.7103.114"
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setattr(check_environment.os.path, "exists", fake_exists)
|
||||||
|
monkeypatch.setattr(check_environment, "_run_command_get_version", fake_version)
|
||||||
|
|
||||||
|
assert check_environment.check_chrome() is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_version_match_uses_windows_chrome_and_driver_paths(monkeypatch):
|
||||||
|
"""Version matching should work for Windows Chrome and chromedriver.exe."""
|
||||||
|
|
||||||
|
def fake_exists(path):
|
||||||
|
return path in {WINDOWS_CHROME, WINDOWS_CHROMEDRIVER}
|
||||||
|
|
||||||
|
def fake_islink(path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def fake_version(command):
|
||||||
|
if command[0] == WINDOWS_CHROME:
|
||||||
|
return "Google Chrome 136.0.7103.114"
|
||||||
|
if command[0] == WINDOWS_CHROMEDRIVER:
|
||||||
|
return "ChromeDriver 136.0.7103.114"
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setenv("CHROMEDRIVER_PATH", WINDOWS_CHROMEDRIVER)
|
||||||
|
monkeypatch.setattr(check_environment.os.path, "exists", fake_exists)
|
||||||
|
monkeypatch.setattr(check_environment.os.path, "islink", fake_islink)
|
||||||
|
monkeypatch.setattr(check_environment, "_run_command_get_version", fake_version)
|
||||||
|
|
||||||
|
assert check_environment.check_version_match() is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_chrome_reads_windows_install_version_directory(monkeypatch, tmp_path):
|
||||||
|
"""
|
||||||
|
On Windows, chrome.exe --version can open an existing browser session instead
|
||||||
|
of printing a version, so fall back to the versioned install directory.
|
||||||
|
"""
|
||||||
|
chrome_path = str(tmp_path / "Google" / "Chrome" / "Application" / "chrome.exe")
|
||||||
|
version_dir = Path(chrome_path).parent / "136.0.7103.114"
|
||||||
|
|
||||||
|
def fake_exists(path):
|
||||||
|
return path == chrome_path
|
||||||
|
|
||||||
|
def fake_listdir(path):
|
||||||
|
if path == str(Path(chrome_path).parent):
|
||||||
|
return [version_dir.name]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def fake_isdir(path):
|
||||||
|
return path == str(version_dir)
|
||||||
|
|
||||||
|
monkeypatch.setattr(check_environment, "_get_chrome_paths", lambda: [chrome_path], raising=False)
|
||||||
|
monkeypatch.setattr(check_environment.os.path, "exists", fake_exists)
|
||||||
|
monkeypatch.setattr(check_environment.os.path, "isdir", fake_isdir)
|
||||||
|
monkeypatch.setattr(check_environment.os, "listdir", fake_listdir)
|
||||||
|
monkeypatch.setattr(check_environment, "_run_command_get_version", lambda command: "正在现有的浏览器会话中打开。")
|
||||||
|
|
||||||
|
assert check_environment._find_chrome() == (
|
||||||
|
chrome_path,
|
||||||
|
"Google Chrome 136.0.7103.114",
|
||||||
|
"136",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_chromedriver_paths_includes_autoinstaller_cache(monkeypatch, tmp_path):
|
||||||
|
"""Environment checks should find drivers downloaded by chromedriver-autoinstaller."""
|
||||||
|
package_dir = tmp_path / "chromedriver_autoinstaller"
|
||||||
|
driver_dir = package_dir / "136"
|
||||||
|
driver_dir.mkdir(parents=True)
|
||||||
|
driver_path = driver_dir / "chromedriver.exe"
|
||||||
|
driver_path.write_text("")
|
||||||
|
|
||||||
|
fake_autoinstaller = SimpleNamespace(__file__=str(package_dir / "__init__.py"))
|
||||||
|
monkeypatch.setitem(sys.modules, "chromedriver_autoinstaller", fake_autoinstaller)
|
||||||
|
|
||||||
|
assert str(driver_path) in check_environment._get_chromedriver_paths()
|
||||||
Reference in New Issue
Block a user