diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 585a7ed4..3159f1cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,10 +53,10 @@ jobs: os: [ windows-latest ] wdm-log: [''] include: - - python-version: '3.12' + - python-version: '3.13' selenium-version: '4.10.0' os: ubuntu-latest - - python-version: '3.12' + - python-version: '3.14' selenium-version: '4.10.0' os: macos-latest wdm-log: '0' diff --git a/tests/test_file_manager.py b/tests/test_file_manager.py new file mode 100644 index 00000000..f8253b44 --- /dev/null +++ b/tests/test_file_manager.py @@ -0,0 +1,25 @@ +import zipfile + +from webdriver_manager.core.file_manager import FileManager +from webdriver_manager.core.os_manager import OperationSystemManager + + +def test_extract_zip_removes_file_dir_conflict(tmp_path): + target_dir = tmp_path / "target" + target_dir.mkdir() + conflict_path = target_dir / "operadriver" + conflict_path.write_text("stale file") + + zip_path = tmp_path / "driver.zip" + with zipfile.ZipFile(zip_path, "w") as zf: + zf.writestr("operadriver/operadriver", "binary") + + class ArchiveMock: + def __init__(self, file_path): + self.file_path = str(file_path) + + file_manager = FileManager(OperationSystemManager()) + extracted = file_manager.unpack_archive(ArchiveMock(zip_path), str(target_dir)) + + assert "operadriver/operadriver" in extracted + assert (target_dir / "operadriver" / "operadriver").exists() diff --git a/tests/test_opera_manager.py b/tests/test_opera_manager.py index 72f3cdfe..1516b6b7 100755 --- a/tests/test_opera_manager.py +++ b/tests/test_opera_manager.py @@ -115,3 +115,15 @@ def test_can_get_driver_from_cache(os_type, delete_drivers_dir, opera_release_da OperaDriverManager(os_system_manager=OperationSystemManager(os_type)).install() driver_path = OperaDriverManager(os_system_manager=OperationSystemManager(os_type)).install() assert os.path.exists(driver_path) + + +def test_opera_install_keeps_file_path_without_listdir(monkeypatch, tmp_path): + binary_path = tmp_path / "operadriver" + binary_path.write_text("bin") + + manager = OperaDriverManager() + monkeypatch.setattr(manager, "_get_driver_binary_path", lambda _driver: str(binary_path)) + + resolved_path = manager.install() + + assert resolved_path == str(binary_path) diff --git a/webdriver_manager/core/file_manager.py b/webdriver_manager/core/file_manager.py index 8c7abd91..27fbc38b 100644 --- a/webdriver_manager/core/file_manager.py +++ b/webdriver_manager/core/file_manager.py @@ -62,6 +62,7 @@ def unpack_archive(self, archive_file: Archive, target_dir): def __extract_zip(self, archive_file, to_directory): zip_class = (LinuxZipFileWithPermissions if self._os_system_manager.get_os_name() == "linux" else zipfile.ZipFile) archive = zip_class(archive_file.file_path) + self.__remove_file_dir_conflicts(archive.namelist(), to_directory) try: archive.extractall(to_directory) except Exception as e: @@ -88,6 +89,19 @@ def __extract_zip(self, archive_file, to_directory): return sorted(file_names, key=lambda x: x.lower()) return archive.namelist() + def __remove_file_dir_conflicts(self, names, to_directory): + """Remove stale files that conflict with directory entries in archive. + + Example: if cache contains `/operadriver` as a file, and + archive now contains `operadriver/`, extraction would fail with + NotADirectoryError. + """ + top_level_dirs = {name.split("/", 1)[0] for name in names if "/" in name} + for dir_name in top_level_dirs: + path = os.path.join(to_directory, dir_name) + if os.path.isfile(path): + os.remove(path) + def __extract_tar_file(self, archive_file, to_directory): try: tar = tarfile.open(archive_file.file_path, mode="r:gz") diff --git a/webdriver_manager/opera.py b/webdriver_manager/opera.py index 893c11a6..dc8bb08d 100755 --- a/webdriver_manager/opera.py +++ b/webdriver_manager/opera.py @@ -41,12 +41,22 @@ def __init__( def install(self) -> str: driver_path = self._get_driver_binary_path(self.driver) - if not os.path.isfile(driver_path): - for name in os.listdir(driver_path): - if "sha512_sum" in name: - os.remove(os.path.join(driver_path, name)) - break - driver_path = os.path.join(driver_path, os.listdir(driver_path)[0]) + if os.path.isfile(driver_path): + os.chmod(driver_path, 0o755) + return driver_path + + for name in os.listdir(driver_path): + if "sha512_sum" in name: + os.remove(os.path.join(driver_path, name)) + + candidates = [ + name for name in os.listdir(driver_path) + if os.path.isfile(os.path.join(driver_path, name)) + ] + if not candidates: + raise FileNotFoundError(f"No OperaDriver binary found in {driver_path}") + + driver_path = os.path.join(driver_path, candidates[0]) os.chmod(driver_path, 0o755) return driver_path