Skip to content

Commit

Permalink
Support for browser version retrieval (#1090)
Browse files Browse the repository at this point in the history
* Support for browser version retrieval

* Fix parameter bug in version retrieval function

* Remove truststore dependency

* Fix version retrieval and webdriver caching for IE

* Release notes

* Fix bug during cache manager initialization

* Update release notes and code notes
  • Loading branch information
cmin764 authored Sep 15, 2023
1 parent 958bff9 commit 26fad7b
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 30 deletions.
6 changes: 6 additions & 0 deletions docs/source/releasenotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Latest versions
`Upcoming release <https://github.com/robocorp/rpaframework/projects/3#column-16713994>`_
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

- Library **RPA.Browser.Selenium** (:issue:`1078`; ``rpaframework-core`` **11.2.0**):

- Display the used webdriver and browser versions in the logs.
- Reminder: You can disable webdriver-manager SSL checks during downloads by setting
``WDM_SSL_VERIFY=false`` in the environment.


`Released <https://pypi.org/project/rpaframework/#history>`_
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Expand Down
28 changes: 8 additions & 20 deletions packages/core/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/core/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "rpaframework-core"
version = "11.1.1"
version = "11.2.0"
description = "Core utilities used by RPA Framework"
authors = ["RPA Framework <[email protected]>"]
license = "Apache-2.0"
Expand Down Expand Up @@ -36,7 +36,6 @@ pywin32 = { version = ">=300,<304", platform = "win32", python = "!=3.8.1" }
uiautomation = { version = "^2.0.15", platform = "win32" }
pillow = "^9.1.1"
packaging = "^23.1"
truststore = { version = "^0.7.0", python = ">=3.10.12" }

[tool.poetry.group.dev.dependencies]
black = "^22.3.0"
Expand Down
133 changes: 126 additions & 7 deletions packages/core/src/RPA/core/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,20 @@
from webdriver_manager.core.driver_cache import (
DriverCacheManager as _DriverCacheManager,
)
from webdriver_manager.core.file_manager import FileManager
from webdriver_manager.core.logger import log
from webdriver_manager.core.manager import DriverManager
from webdriver_manager.core.os_manager import ChromeType, OperationSystemManager
from webdriver_manager.core.os_manager import (
ChromeType,
OSType,
OperationSystemManager as _OperationSystemManager,
PATTERN as _PATTERN,
)
from webdriver_manager.core.utils import (
linux_browser_apps_to_cmd,
read_version_from_cmd,
windows_browser_apps_to_cmd,
)
from webdriver_manager.drivers.chrome import ChromeDriver as _ChromeDriver
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import (
Expand All @@ -31,11 +42,88 @@
from RPA.core.robocorp import robocorp_home


# FIXME(cmin764; 6 Sep 2023): Remove this once the following issue is solved:
# https://github.com/SergeyPirogov/webdriver_manager/issues/618
class BrowserType:
"""Constants for the browser types. (expands the internal one)"""

MSIE = "msie"
FIREFOX = "firefox"


PATTERN = _PATTERN.copy()
PATTERN[BrowserType.MSIE] = r"\d+\.\d+\.\d+\.\d+"


class OperationSystemManager(_OperationSystemManager):
"""Custom manager for browser version retrieval which works with explicit paths."""

@staticmethod
def _get_browser_version(browser_type: str, paths: List[str]) -> Optional[str]:
common_cmds = {
OSType.LINUX: linux_browser_apps_to_cmd(*paths),
OSType.MAC: f"{paths[0]} --version",
OSType.WIN: windows_browser_apps_to_cmd(
*(
f"(Get-Item -Path '{path}').VersionInfo.FileVersion"
for path in paths
)
),
}
cmd_mapping = {
ChromeType.GOOGLE: common_cmds,
ChromeType.CHROMIUM: common_cmds,
ChromeType.MSEDGE: common_cmds,
BrowserType.FIREFOX: common_cmds,
BrowserType.MSIE: common_cmds,
}
try:
cmd_mapping = cmd_mapping[browser_type][
OperationSystemManager.get_os_name()
]
pattern = PATTERN[browser_type]
version = read_version_from_cmd(cmd_mapping, pattern)
return version
# pylint: disable=broad-except
except Exception as exc:
LOGGER.warning(
"Can't read %r browser version due to: %s", browser_type, exc
)
return None

def get_browser_version_from_os(
self, browser_type: Optional[str] = None
) -> Optional[str]:
if browser_type != BrowserType.MSIE:
return super().get_browser_version_from_os(browser_type)

# Add support for IE version retrieval.
# NOTE(cmin764; 15 Sep 2023): This got slightly different due to posted issue:
# https://github.com/SergeyPirogov/webdriver_manager/issues/625
program_files = os.getenv("PROGRAMFILES", r"C:\Program Files")
paths = [
rf"{program_files}\Internet Explorer\iexplore.exe",
rf"{program_files} (x86)\Internet Explorer\iexplore.exe",
]
return self._get_browser_version(BrowserType.MSIE, paths=paths)

def get_browser_version(
self, browser_type: str, path: Optional[str] = None
) -> Optional[str]:
if path:
return self._get_browser_version(browser_type, paths=[path])

return self.get_browser_version_from_os(browser_type)


class DriverCacheManager(_DriverCacheManager):
"""Fixes caching when retrieving an existing already downloaded webdriver."""

def __init__(self, *args, file_manager: Optional[FileManager] = None, **kwargs):
super().__init__(*args, **kwargs)
self._os_system_manager = OperationSystemManager()
self._file_manager = file_manager or FileManager(self._os_system_manager)

# FIXME(cmin764; 6 Sep 2023): Remove this once the following issue is solved:
# https://github.com/SergeyPirogov/webdriver_manager/issues/618
# pylint: disable=unused-private-member
def __get_metadata_key(self, *args, **kwargs) -> str:
# pylint: disable=super-with-arguments
Expand Down Expand Up @@ -351,16 +439,21 @@ def _is_chromium() -> bool:
return not is_browser(ChromeType.GOOGLE) and is_browser(ChromeType.CHROMIUM)


def _to_manager(browser: str, *, root: Path) -> DriverManager:
def _get_browser_lower(browser: str) -> str:
browser = browser.strip()
browser_lower = browser.lower()
manager_factory = AVAILABLE_DRIVERS.get(browser_lower)
if not manager_factory:
if browser_lower not in AVAILABLE_DRIVERS:
raise ValueError(
f"Unsupported browser {browser!r} for webdriver download!"
f" (choose from: {', '.join(SUPPORTED_BROWSERS.values())})"
)

return browser_lower


def _to_manager(browser: str, *, root: Path) -> DriverManager:
browser_lower = _get_browser_lower(browser)
manager_factory = AVAILABLE_DRIVERS[browser_lower]
if manager_factory == ChromeDriverManager and _is_chromium():
manager_factory = functools.partial(
manager_factory, chrome_type=ChromeType.CHROMIUM
Expand All @@ -373,7 +466,9 @@ def _to_manager(browser: str, *, root: Path) -> DriverManager:
LOGGER.warning("Can't set an internal webdriver source for %r!", browser)

cache_manager = DriverCacheManager(root_dir=str(root))
manager = manager_factory(cache_manager=cache_manager)
manager = manager_factory(
cache_manager=cache_manager, os_system_manager=_OPS_MANAGER
)
driver = manager.driver
cache = getattr(functools, "cache", functools.lru_cache(maxsize=None))
driver.get_latest_release_version = cache(driver.get_latest_release_version)
Expand All @@ -397,3 +492,27 @@ def download(browser: str, root: Path = DRIVER_ROOT) -> str:
_set_executable(path)
LOGGER.info("Downloaded webdriver to: %s", path)
return path


def get_browser_version(browser: str, path: Optional[str] = None) -> Optional[str]:
"""Returns the detected browser version from OS in the absence of a given `path`."""
browser_lower = _get_browser_lower(browser)
chrome_type = ChromeType.CHROMIUM if _is_chromium() else ChromeType.GOOGLE
browser_types = {
# NOTE(cmin764; 12 Sep 2023): There's no upstream support on getting the
# automatically detected browser version from the OS for IE, Safari and Opera.
# But we introduce one here for IE only.
"chrome": chrome_type,
"firefox": BrowserType.FIREFOX,
"gecko": BrowserType.FIREFOX,
"mozilla": BrowserType.FIREFOX,
"edge": ChromeType.MSEDGE,
"chromiumedge": ChromeType.MSEDGE,
"ie": BrowserType.MSIE,
}
browser_type = browser_types.get(browser_lower)
if not browser_type:
LOGGER.warning("Can't determine browser version for %r!", browser)
return None

return _OPS_MANAGER.get_browser_version(browser_type, path=path)
37 changes: 36 additions & 1 deletion packages/core/tests/python/test_webdriver.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import platform
import re
import unittest.mock as mock
from pathlib import Path

import pytest
from webdriver_manager.core.os_manager import ChromeType
Expand All @@ -13,7 +16,7 @@ def disable_caching():
with mock.patch(
"webdriver_manager.core.driver_cache.DriverCacheManager.find_driver",
new=mock.Mock(return_value=None),
):
), mock.patch("RPA.core.webdriver.suppress_logging"):
yield


Expand Down Expand Up @@ -48,3 +51,35 @@ def test_edge_download():
def test_ie_download():
path = webdriver.download("Ie", root=RESULTS_DIR)
assert "IEDriverServer.exe" in path


@pytest.mark.parametrize("browser", ["Chrome", "Firefox", "Edge", "Ie"])
def test_get_browser_version(browser):
version = webdriver.get_browser_version(browser)
print(f"{browser}: {version}")


@pytest.mark.skipif(
platform.system() != "Windows", reason="requires Windows with IE installed"
)
@pytest.mark.parametrize(
"path", [None, r"C:\Program Files\Internet Explorer\iexplore.exe"]
)
def test_get_ie_version(path):
version = webdriver.get_browser_version("Ie", path=path)
assert re.match(r"\d+(\.\d+){3}$", version) # 4 atoms in the version


@pytest.mark.skipif(
platform.system() != "Darwin", reason="requires Mac with Chrome installed"
)
def test_get_chrome_version_path_mac():
path = (
Path("/Applications")
/ r"Google\ Chrome.app"
/ "Contents"
/ "MacOS"
/ r"Google\ Chrome"
)
version = webdriver.get_browser_version("Chrome", path=str(path))
assert re.match(r"\d+(\.\d+){2,3}$", version) # 3-4 atoms in the version

0 comments on commit 26fad7b

Please sign in to comment.