Skip to content

Commit 8651fa2

Browse files
Fix Ubuntu Opera cache extraction conflict causing NotADirectoryError (#730)
* fix(cache): handle file/dir conflict before zip extraction (Ubuntu Opera CI) * fix(opera): handle file cache path in install and avoid listdir on binary path
1 parent 5554e6d commit 8651fa2

5 files changed

Lines changed: 69 additions & 8 deletions

File tree

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ jobs:
5353
os: [ windows-latest ]
5454
wdm-log: ['']
5555
include:
56-
- python-version: '3.12'
56+
- python-version: '3.13'
5757
selenium-version: '4.10.0'
5858
os: ubuntu-latest
59-
- python-version: '3.12'
59+
- python-version: '3.14'
6060
selenium-version: '4.10.0'
6161
os: macos-latest
6262
wdm-log: '0'

tests/test_file_manager.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import zipfile
2+
3+
from webdriver_manager.core.file_manager import FileManager
4+
from webdriver_manager.core.os_manager import OperationSystemManager
5+
6+
7+
def test_extract_zip_removes_file_dir_conflict(tmp_path):
8+
target_dir = tmp_path / "target"
9+
target_dir.mkdir()
10+
conflict_path = target_dir / "operadriver"
11+
conflict_path.write_text("stale file")
12+
13+
zip_path = tmp_path / "driver.zip"
14+
with zipfile.ZipFile(zip_path, "w") as zf:
15+
zf.writestr("operadriver/operadriver", "binary")
16+
17+
class ArchiveMock:
18+
def __init__(self, file_path):
19+
self.file_path = str(file_path)
20+
21+
file_manager = FileManager(OperationSystemManager())
22+
extracted = file_manager.unpack_archive(ArchiveMock(zip_path), str(target_dir))
23+
24+
assert "operadriver/operadriver" in extracted
25+
assert (target_dir / "operadriver" / "operadriver").exists()

tests/test_opera_manager.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,15 @@ def test_can_get_driver_from_cache(os_type, delete_drivers_dir, opera_release_da
115115
OperaDriverManager(os_system_manager=OperationSystemManager(os_type)).install()
116116
driver_path = OperaDriverManager(os_system_manager=OperationSystemManager(os_type)).install()
117117
assert os.path.exists(driver_path)
118+
119+
120+
def test_opera_install_keeps_file_path_without_listdir(monkeypatch, tmp_path):
121+
binary_path = tmp_path / "operadriver"
122+
binary_path.write_text("bin")
123+
124+
manager = OperaDriverManager()
125+
monkeypatch.setattr(manager, "_get_driver_binary_path", lambda _driver: str(binary_path))
126+
127+
resolved_path = manager.install()
128+
129+
assert resolved_path == str(binary_path)

webdriver_manager/core/file_manager.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def unpack_archive(self, archive_file: Archive, target_dir):
6262
def __extract_zip(self, archive_file, to_directory):
6363
zip_class = (LinuxZipFileWithPermissions if self._os_system_manager.get_os_name() == "linux" else zipfile.ZipFile)
6464
archive = zip_class(archive_file.file_path)
65+
self.__remove_file_dir_conflicts(archive.namelist(), to_directory)
6566
try:
6667
archive.extractall(to_directory)
6768
except Exception as e:
@@ -88,6 +89,19 @@ def __extract_zip(self, archive_file, to_directory):
8889
return sorted(file_names, key=lambda x: x.lower())
8990
return archive.namelist()
9091

92+
def __remove_file_dir_conflicts(self, names, to_directory):
93+
"""Remove stale files that conflict with directory entries in archive.
94+
95+
Example: if cache contains `<to_directory>/operadriver` as a file, and
96+
archive now contains `operadriver/<binary>`, extraction would fail with
97+
NotADirectoryError.
98+
"""
99+
top_level_dirs = {name.split("/", 1)[0] for name in names if "/" in name}
100+
for dir_name in top_level_dirs:
101+
path = os.path.join(to_directory, dir_name)
102+
if os.path.isfile(path):
103+
os.remove(path)
104+
91105
def __extract_tar_file(self, archive_file, to_directory):
92106
try:
93107
tar = tarfile.open(archive_file.file_path, mode="r:gz")

webdriver_manager/opera.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,22 @@ def __init__(
4141

4242
def install(self) -> str:
4343
driver_path = self._get_driver_binary_path(self.driver)
44-
if not os.path.isfile(driver_path):
45-
for name in os.listdir(driver_path):
46-
if "sha512_sum" in name:
47-
os.remove(os.path.join(driver_path, name))
48-
break
49-
driver_path = os.path.join(driver_path, os.listdir(driver_path)[0])
44+
if os.path.isfile(driver_path):
45+
os.chmod(driver_path, 0o755)
46+
return driver_path
47+
48+
for name in os.listdir(driver_path):
49+
if "sha512_sum" in name:
50+
os.remove(os.path.join(driver_path, name))
51+
52+
candidates = [
53+
name for name in os.listdir(driver_path)
54+
if os.path.isfile(os.path.join(driver_path, name))
55+
]
56+
if not candidates:
57+
raise FileNotFoundError(f"No OperaDriver binary found in {driver_path}")
58+
59+
driver_path = os.path.join(driver_path, candidates[0])
5060
os.chmod(driver_path, 0o755)
5161
return driver_path
5262

0 commit comments

Comments
 (0)