Skip to content

Commit bfbed91

Browse files
titusfortnerdiemol
andauthored
[py] update SOC for driver finder and selenium manager classes (#13387)
* [py] update SOC for driver finder and selenium manager classes * [py] change error and log handling and add tests * [py] adjustments * [py] change names and validations and make tweaks * [py] make the driver finder class non-static * [py] missed renaming a bunch of things * [py] memoize and fix tests * [py] bad logic * [py] tidy things from merges --------- Co-authored-by: Diego Molina <[email protected]>
1 parent 77df95b commit bfbed91

File tree

11 files changed

+312
-172
lines changed

11 files changed

+312
-172
lines changed

Rakefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,23 @@ namespace :py do
693693
Bazel.execute('test', [],"//py:test-remote")
694694
end
695695
end
696+
697+
namespace :test do
698+
desc 'Python unit tests'
699+
task :unit do
700+
Rake::Task['py:clean'].invoke
701+
Bazel.execute('test', ['--test_size_filters=small'], '//py/...')
702+
end
703+
704+
%i[chrome edge firefox safari].each do |browser|
705+
desc "Python #{browser} tests"
706+
task browser do
707+
Rake::Task['py:clean'].invoke
708+
Bazel.execute('test', %w[--test_output all],"//py:common-#{browser}")
709+
Bazel.execute('test', %w[--test_output all],"//py:test-#{browser}")
710+
end
711+
end
712+
end
696713
end
697714

698715
def ruby_version

py/selenium/webdriver/chromium/webdriver.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ def __init__(
4646
"""
4747
self.service = service
4848

49-
self.service.path = DriverFinder.get_path(self.service, options)
49+
finder = DriverFinder(self.service, options)
50+
if finder.get_browser_path():
51+
options.binary_location = finder.get_browser_path()
52+
options.browser_version = None
53+
54+
self.service.path = finder.get_driver_path()
5055
self.service.start()
5156

5257
executor = ChromiumRemoteConnection(

py/selenium/webdriver/common/driver_finder.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,74 @@
2626

2727

2828
class DriverFinder:
29+
"""A Driver finding class responsible for obtaining the correct driver and
30+
associated browser.
31+
32+
:param service: instance of the driver service class.
33+
:param options: instance of the browser options class.
34+
"""
35+
36+
def __init__(self, service: Service, options: BaseOptions) -> None:
37+
self._service = service
38+
self._options = options
39+
self._paths = {"driver_path": "", "browser_path": ""}
40+
2941
"""Utility to find if a given file is present and executable.
3042
3143
This implementation is still in beta, and may change.
3244
"""
3345

34-
@staticmethod
35-
def get_path(service: Service, options: BaseOptions) -> str:
36-
path = service.path
46+
def get_browser_path(self) -> str:
47+
return self._binary_paths()["browser_path"]
48+
49+
def get_driver_path(self) -> str:
50+
return self._binary_paths()["driver_path"]
51+
52+
def _binary_paths(self) -> dict:
53+
if self._paths["driver_path"]:
54+
return self._paths
55+
56+
browser = self._options.capabilities["browserName"]
3757
try:
38-
path = SeleniumManager().driver_location(options) if path is None else path
58+
path = self._service.path
59+
if path:
60+
logger.debug(
61+
"Skipping Selenium Manager; path to %s driver specified in Service class: %s", browser, path
62+
)
63+
if not Path(path).is_file():
64+
raise ValueError(f"The path is not a valid file: {path}")
65+
self._paths["driver_path"] = path
66+
else:
67+
output = SeleniumManager().binary_paths(self._to_args())
68+
if Path(output["driver_path"]).is_file():
69+
self._paths["driver_path"] = output["driver_path"]
70+
else:
71+
raise ValueError(f'The driver path is not a valid file: {output["driver_path"]}')
72+
if Path(output["browser_path"]).is_file():
73+
self._paths["browser_path"] = output["browser_path"]
74+
else:
75+
raise ValueError(f'The browser path is not a valid file: {output["browser_path"]}')
3976
except Exception as err:
40-
msg = f"Unable to obtain driver for {options.capabilities['browserName']} using Selenium Manager."
77+
msg = f"Unable to obtain driver for {browser}"
4178
raise NoSuchDriverException(msg) from err
79+
return self._paths
80+
81+
def _to_args(self) -> list:
82+
args = ["--browser", self._options.capabilities["browserName"]]
83+
84+
if self._options.browser_version:
85+
args.append("--browser-version")
86+
args.append(str(self._options.browser_version))
87+
88+
binary_location = getattr(self._options, "binary_location", None)
89+
if binary_location:
90+
args.append("--browser-path")
91+
args.append(str(binary_location))
4292

43-
if path is None or not Path(path).is_file():
44-
raise NoSuchDriverException(f"Unable to locate or obtain driver for {options.capabilities['browserName']}")
93+
proxy = self._options.proxy
94+
if proxy and (proxy.http_proxy or proxy.ssl_proxy):
95+
args.append("--proxy")
96+
value = proxy.ssl_proxy if proxy.ssl_proxy else proxy.http_proxy
97+
args.append(value)
4598

46-
return path
99+
return args

py/selenium/webdriver/common/selenium_manager.py

Lines changed: 54 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from typing import List
2525

2626
from selenium.common import WebDriverException
27-
from selenium.webdriver.common.options import BaseOptions
2827

2928
logger = logging.getLogger(__name__)
3029

@@ -35,8 +34,26 @@ class SeleniumManager:
3534
This implementation is still in beta, and may change.
3635
"""
3736

37+
def binary_paths(self, args: List) -> dict:
38+
"""Determines the locations of the requested assets.
39+
40+
:Args:
41+
- args: the commands to send to the selenium manager binary.
42+
:Returns: dictionary of assets and their path
43+
"""
44+
45+
args = [str(self._get_binary())] + args
46+
if logger.getEffectiveLevel() == logging.DEBUG:
47+
args.append("--debug")
48+
args.append("--language-binding")
49+
args.append("python")
50+
args.append("--output")
51+
args.append("json")
52+
53+
return self._run(args)
54+
3855
@staticmethod
39-
def get_binary() -> Path:
56+
def _get_binary() -> Path:
4057
"""Determines the path of the correct Selenium Manager binary.
4158
4259
:Returns: The Selenium Manager executable location
@@ -45,29 +62,27 @@ def get_binary() -> Path:
4562
"""
4663

4764
if (path := os.getenv("SE_MANAGER_PATH")) is not None:
48-
return Path(path)
49-
50-
dirs = {
51-
("darwin", "any"): "macos",
52-
("win32", "any"): "windows",
53-
("cygwin", "any"): "windows",
54-
("linux", "x86_64"): "linux",
55-
("freebsd", "x86_64"): "linux",
56-
("openbsd", "x86_64"): "linux",
57-
}
58-
59-
arch = platform.machine() if sys.platform in ("linux", "freebsd", "openbsd") else "any"
60-
61-
directory = dirs.get((sys.platform, arch))
62-
if directory is None:
63-
raise WebDriverException(f"Unsupported platform/architecture combination: {sys.platform}/{arch}")
64-
65-
if sys.platform in ["freebsd", "openbsd"]:
66-
logger.warning("Selenium Manager binary may not be compatible with %s; verify settings", sys.platform)
67-
68-
file = "selenium-manager.exe" if directory == "windows" else "selenium-manager"
69-
70-
path = Path(__file__).parent.joinpath(directory, file)
65+
logger.debug("Selenium Manager set by env SE_MANAGER_PATH to: %s", path)
66+
path = Path(path)
67+
else:
68+
allowed = {
69+
("darwin", "any"): "macos/selenium-manager",
70+
("win32", "any"): "windows/selenium-manager.exe",
71+
("cygwin", "any"): "windows/selenium-manager.exe",
72+
("linux", "x86_64"): "linux/selenium-manager",
73+
("freebsd", "x86_64"): "linux/selenium-manager",
74+
("openbsd", "x86_64"): "linux/selenium-manager",
75+
}
76+
77+
arch = platform.machine() if sys.platform in ("linux", "freebsd", "openbsd") else "any"
78+
if sys.platform in ["freebsd", "openbsd"]:
79+
logger.warning("Selenium Manager binary may not be compatible with %s; verify settings", sys.platform)
80+
81+
location = allowed.get((sys.platform, arch))
82+
if location is None:
83+
raise WebDriverException(f"Unsupported platform/architecture combination: {sys.platform}/{arch}")
84+
85+
path = Path(__file__).parent.joinpath(location)
7186

7287
if not path.is_file():
7388
raise WebDriverException(f"Unable to obtain working Selenium Manager binary; {path}")
@@ -76,60 +91,14 @@ def get_binary() -> Path:
7691

7792
return path
7893

79-
def driver_location(self, options: BaseOptions) -> str:
80-
"""Determines the path of the correct driver.
81-
82-
:Args:
83-
- browser: which browser to get the driver path for.
84-
:Returns: The driver path to use
85-
"""
86-
87-
browser = options.capabilities["browserName"]
88-
89-
args = [str(self.get_binary()), "--browser", browser]
90-
91-
if options.browser_version:
92-
args.append("--browser-version")
93-
args.append(str(options.browser_version))
94-
95-
binary_location = getattr(options, "binary_location", None)
96-
if binary_location:
97-
args.append("--browser-path")
98-
args.append(str(binary_location))
99-
100-
proxy = options.proxy
101-
if proxy and (proxy.http_proxy or proxy.ssl_proxy):
102-
args.append("--proxy")
103-
value = proxy.ssl_proxy if proxy.ssl_proxy else proxy.http_proxy
104-
args.append(value)
105-
106-
output = self.run(args)
107-
108-
browser_path = output["browser_path"]
109-
driver_path = output["driver_path"]
110-
logger.debug("Using driver at: %s", driver_path)
111-
112-
if hasattr(options.__class__, "binary_location") and browser_path:
113-
options.binary_location = browser_path
114-
options.browser_version = None # if we have the binary location we no longer need the version
115-
116-
return driver_path
117-
11894
@staticmethod
119-
def run(args: List[str]) -> dict:
95+
def _run(args: List[str]) -> dict:
12096
"""Executes the Selenium Manager Binary.
12197
12298
:Args:
12399
- args: the components of the command being executed.
124100
:Returns: The log string containing the driver location.
125101
"""
126-
if logger.getEffectiveLevel() == logging.DEBUG:
127-
args.append("--debug")
128-
args.append("--language-binding")
129-
args.append("python")
130-
args.append("--output")
131-
args.append("json")
132-
133102
command = " ".join(args)
134103
logger.debug("Executing process: %s", command)
135104
try:
@@ -139,17 +108,22 @@ def run(args: List[str]) -> dict:
139108
completed_proc = subprocess.run(args, capture_output=True)
140109
stdout = completed_proc.stdout.decode("utf-8").rstrip("\n")
141110
stderr = completed_proc.stderr.decode("utf-8").rstrip("\n")
142-
output = json.loads(stdout)
143-
result = output["result"]
111+
output = json.loads(stdout) if stdout != "" else {"logs": [], "result": {}}
144112
except Exception as err:
145113
raise WebDriverException(f"Unsuccessful command executed: {command}") from err
146114

147-
for item in output["logs"]:
115+
SeleniumManager._process_logs(output["logs"])
116+
result = output["result"]
117+
if completed_proc.returncode:
118+
raise WebDriverException(
119+
f"Unsuccessful command executed: {command}; code: {completed_proc.returncode}\n{result}\n{stderr}"
120+
)
121+
return result
122+
123+
@staticmethod
124+
def _process_logs(log_items: List[dict]):
125+
for item in log_items:
148126
if item["level"] == "WARN":
149127
logger.warning(item["message"])
150-
if item["level"] == "DEBUG" or item["level"] == "INFO":
128+
elif item["level"] in ["DEBUG", "INFO"]:
151129
logger.debug(item["message"])
152-
153-
if completed_proc.returncode:
154-
raise WebDriverException(f"Unsuccessful command executed: {command}.\n{result}{stderr}")
155-
return result

py/selenium/webdriver/firefox/webdriver.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ def __init__(
5656
self.service = service if service else Service()
5757
options = options if options else Options()
5858

59-
self.service.path = DriverFinder.get_path(self.service, options)
59+
finder = DriverFinder(self.service, options)
60+
if finder.get_browser_path():
61+
options.binary_location = finder.get_browser_path()
62+
options.browser_version = None
63+
64+
self.service.path = finder.get_driver_path()
6065
self.service.start()
6166

6267
executor = FirefoxRemoteConnection(

py/selenium/webdriver/ie/webdriver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(
4646
self.service = service if service else Service()
4747
options = options if options else Options()
4848

49-
self.service.path = DriverFinder.get_path(self.service, options)
49+
self.service.path = DriverFinder(self.service, options).get_driver_path()
5050
self.service.start()
5151

5252
executor = RemoteConnection(

py/selenium/webdriver/safari/webdriver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __init__(
4545
self.service = service if service else Service()
4646
options = options if options else Options()
4747

48-
self.service.path = DriverFinder.get_path(self.service, options)
48+
self.service.path = DriverFinder(self.service, options).get_driver_path()
4949

5050
if not self.service.reuse_service:
5151
self.service.start()

py/selenium/webdriver/webkitgtk/webdriver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def __init__(
6060
desired_capabilities = capabilities
6161

6262
self.service = Service(executable_path, port=port, log_path=service_log_path)
63-
self.service.path = DriverFinder.get_path(self.service, options)
63+
self.service.path = DriverFinder(self.service, options).get_driver_path()
6464
self.service.start()
6565

6666
super().__init__(

py/selenium/webdriver/wpewebkit/webdriver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def __init__(
4444
options = Options()
4545

4646
self.service = service if service else Service()
47-
self.service.path = DriverFinder.get_path(self.service, options)
47+
self.service.path = DriverFinder(self.service, options).get_driver_path()
4848
self.service.start()
4949

5050
super().__init__(command_executor=self.service.service_url, options=options)

0 commit comments

Comments
 (0)