Skip to content

Commit

Permalink
Merge pull request #1902 from emanlove/add-service-class
Browse files Browse the repository at this point in the history
Add service class
  • Loading branch information
emanlove committed May 18, 2024
2 parents 3ea3041 + 4768e7b commit dd89d33
Show file tree
Hide file tree
Showing 17 changed files with 662 additions and 183 deletions.
13 changes: 11 additions & 2 deletions atest/acceptance/1-plugin/OpenBrowserExample.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def open_browser(
service_log_path=None,
extra_dictionary=None,
executable_path=None,
service=None,
):
self._new_creator.extra_dictionary = extra_dictionary
browser_manager = BrowserManagementKeywords(self.ctx)
Expand All @@ -37,7 +38,8 @@ def open_browser(
ff_profile_dir=ff_profile_dir,
options=options,
service_log_path=service_log_path,
executable_path=None,
executable_path=executable_path,
service=service,
)

def _make_driver(
Expand All @@ -49,6 +51,7 @@ def _make_driver(
options=None,
service_log_path=None,
executable_path=None,
service=None,
):
driver = self._new_creator.create_driver(
browser=browser,
Expand All @@ -58,6 +61,7 @@ def _make_driver(
options=options,
service_log_path=service_log_path,
executable_path=executable_path,
service=None,
)
driver.set_script_timeout(self.ctx.timeout)
driver.implicitly_wait(self.ctx.implicit_wait)
Expand All @@ -76,13 +80,15 @@ def create_driver(
options=None,
service_log_path=None,
executable_path=None,
service=None,
):
self.browser_names["seleniumwire"] = "seleniumwire"
browser = self._normalise_browser_name(browser)
creation_method = self._get_creator_method(browser)
desired_capabilities = self._parse_capabilities(desired_capabilities, browser)
service_log_path = self._get_log_path(service_log_path)
options = self.selenium_options.create(self.browser_names.get(browser), options)
service = self.selenium_service.create(self.browser_names.get(browser), service)
if service_log_path:
logger.info("Browser driver log file created to: %s" % service_log_path)
self._create_directory(service_log_path)
Expand All @@ -96,23 +102,26 @@ def create_driver(
profile_dir,
options=options,
service_log_path=service_log_path,
service=service,
)
if creation_method == self.create_seleniumwire:
return creation_method(
desired_capabilities,
remote_url,
options=options,
service_log_path=service_log_path,
service=service,
)
return creation_method(
desired_capabilities,
remote_url,
options=options,
service_log_path=service_log_path,
service=service,
)

def create_seleniumwire(
self, desired_capabilities, remote_url, options=None, service_log_path=None
self, desired_capabilities, remote_url, options=None, service_log_path=None, service=None,
):
logger.info(self.extra_dictionary)
return webdriver.Chrome()
44 changes: 44 additions & 0 deletions atest/acceptance/multiple_browsers_service.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
*** Settings ***
Suite Teardown Close All Browsers
Library ../resources/testlibs/get_driver_path.py
Resource resource.robot
# Force Tags Known Issue Firefox Known Issue Safari Known Issue Internet Explorer
Documentation Creating test which would work on all browser is not possible.
... These tests are for Chrome only.
*** Test Cases ***
Chrome Browser With Chrome Service As String
[Documentation]
... LOG 2:2 DEBUG STARTS: Started executable:
... LOG 2:3 DEBUG GLOB: POST*/session*
${driver_path}= Get Driver Path Chrome
Open Browser ${FRONT PAGE} Chrome remote_url=${REMOTE_URL}
... service=executable_path='${driver_path}'

Chrome Browser With Chrome Service As String With service_args As List
Open Browser ${FRONT PAGE} Chrome remote_url=${REMOTE_URL}
... service=service_args=['--append-log', '--readable-timestamp']; log_output='${OUTPUT_DIR}/chromedriverlog.txt'
File Should Exist ${OUTPUT_DIR}/chromedriverlog.txt
# ... service=service_args=['--append-log', '--readable-timestamp']; log_output='./'
# ... service=service_args=['--append-log', '--readable-timestamp']

Firefox Browser With Firefox Service As String
[Documentation]
... LOG 2:2 DEBUG STARTS: Started executable:
... LOG 2:3 DEBUG GLOB: POST*/session*
${driver_path}= Get Driver Path Firefox
Open Browser ${FRONT PAGE} Firefox remote_url=${REMOTE_URL}
... service=executable_path='${driver_path}'

#Chrome Browser With Selenium Options Invalid Method
# Run Keyword And Expect Error AttributeError: 'Options' object has no attribute 'not_here_method'
# ... Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
# ... desired_capabilities=${DESIRED_CAPABILITIES} options=not_here_method("arg1")
#
#
#Chrome Browser With Selenium Options Argument With Semicolon
# [Documentation]
# ... LOG 1:14 DEBUG GLOB: *"goog:chromeOptions"*
# ... LOG 1:14 DEBUG GLOB: *["has;semicolon"*
# Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
# ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("has;semicolon")
2 changes: 1 addition & 1 deletion atest/acceptance/multiple_browsers_service_log_path.robot
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Resource resource.robot
*** Test Cases ***
First Browser With Service Log Path
[Documentation]
... LOG 1:2 INFO STARTS: Browser driver log file created to:
... LOG 1:3 INFO STARTS: Browser driver log file created to:
[Setup] OperatingSystem.Remove Files ${OUTPUT DIR}/${BROWSER}.log
Open Browser ${FRONT PAGE} ${BROWSER} service_log_path=${OUTPUT DIR}/${BROWSER}.log
OperatingSystem.List Directories In Directory ${OUTPUT DIR}/
Expand Down
47 changes: 47 additions & 0 deletions atest/resources/testlibs/get_driver_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
>>> from selenium.webdriver.common import driver_finder
>>> drfind = driver_finder.DriverFinder()
>>> from selenium.webdriver.chrome.service import Service
>>> from selenium.webdriver.chrome.options import Options
>>> drfind.get_path(Service(),Options())
def _import_service(self, browser):
browser = browser.replace("headless_", "", 1)
# Throw error is used with remote .. "They cannot be used with a Remote WebDriver session." [ref doc]
service = importlib.import_module(f"selenium.webdriver.{browser}.service")
return service.Service
def _import_options(self, browser):
browser = browser.replace("headless_", "", 1)
options = importlib.import_module(f"selenium.webdriver.{browser}.options")
return options.Options
"""
from selenium import webdriver
from selenium.webdriver.common import driver_finder
import importlib


def get_driver_path(browser):
browser = browser.lower().replace("headless_", "", 1)
service = importlib.import_module(f"selenium.webdriver.{browser}.service")
options = importlib.import_module(f"selenium.webdriver.{browser}.options")
# finder = driver_finder.DriverFinder()

# Selenium v4.19.0 and prior
try:
finder = driver_finder.DriverFinder()
func = getattr(finder, 'get_path')
return finder.get_path(service.Service(), options.Options())
except (AttributeError, TypeError):
pass

# Selenium V4.20.0
try:
finder = driver_finder.DriverFinder(service.Service(), options.Options())
return finder.get_driver_drivepath()
except:
pass

raise Exception('Unable to determine driver path')
109 changes: 109 additions & 0 deletions src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,115 @@ class SeleniumLibrary(DynamicCore):
https://robocon.io/, https://github.com/robotframework/'
and 'https://github.com/.
= Browser and Driver options and service class =
This section talks about how to configure either the browser or
the driver using the options and service arguments of the `Open
Browser` keyword.
== Configuring the browser using the Selenium Options ==
As noted within the keyword documentation for `Open Browser`, its
``options`` argument accepts Selenium options in two different
formats: as a string and as Python object which is an instance of
the Selenium options class.
=== Options string format ===
The string format allows defining Selenium options methods
or attributes and their arguments in Robot Framework test data.
The method and attributes names are case and space sensitive and
must match to the Selenium options methods and attributes names.
When defining a method, it must be defined in a similar way as in
python: method name, opening parenthesis, zero to many arguments
and closing parenthesis. If there is a need to define multiple
arguments for a single method, arguments must be separated with
comma, just like in Python. Example: `add_argument("--headless")`
or `add_experimental_option("key", "value")`. Attributes are
defined in a similar way as in Python: attribute name, equal sign,
and attribute value. Example, `headless=True`. Multiple methods
and attributes must be separated by a semicolon. Example:
`add_argument("--headless");add_argument("--start-maximized")`.
Arguments allow defining Python data types and arguments are
evaluated by using Python
[https://docs.python.org/3/library/ast.html#ast.literal_eval|ast.literal_eval].
Strings must be quoted with single or double quotes, example "value"
or 'value'. It is also possible to define other Python builtin
data types, example `True` or `None`, by not using quotes
around the arguments.
The string format is space friendly. Usually, spaces do not alter
the defining methods or attributes. There are two exceptions.
In some Robot Framework test data formats, two or more spaces are
considered as cell separator and instead of defining a single
argument, two or more arguments may be defined. Spaces in string
arguments are not removed and are left as is. Example
`add_argument ( "--headless" )` is same as
`add_argument("--headless")`. But `add_argument(" --headless ")` is
not same same as `add_argument ( "--headless" )`, because
spaces inside of quotes are not removed. Please note that if
options string contains backslash, example a Windows OS path,
the backslash needs escaping both in Robot Framework data and
in Python side. This means single backslash must be writen using
four backslash characters. Example, Windows path:
"C:\\path\\to\\profile" must be written as
"C:\\\\\\\\path\\\\\\to\\\\\\\\profile". Another way to write
backslash is use Python
[https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals|raw strings]
and example write: r"C:\\\\path\\\\to\\\\profile".
=== Selenium Options as Python class ===
As last format, ``options`` argument also supports receiving
the Selenium options as Python class instance. In this case, the
instance is used as-is and the SeleniumLibrary will not convert
the instance to other formats.
For example, if the following code return value is saved to
`${options}` variable in the Robot Framework data:
| options = webdriver.ChromeOptions()
| options.add_argument('--disable-dev-shm-usage')
| return options
Then the `${options}` variable can be used as an argument to
``options``.
Example the ``options`` argument can be used to launch Chomium-based
applications which utilize the
[https://bitbucket.org/chromiumembedded/cef/wiki/UsingChromeDriver|Chromium Embedded Framework]
. To launch Chromium-based application, use ``options`` to define
`binary_location` attribute and use `add_argument` method to define
`remote-debugging-port` port for the application. Once the browser
is opened, the test can interact with the embedded web-content of
the system under test.
== Configuring the driver using the Service class ==
With the ``service`` argument, one can setup and configure the driver. For example
one can set the driver location and/port or specify the command line arguments. There
are several browser specific attributes related to logging as well. For the various
Service Class attributes refer to
[https://www.selenium.dev/documentation/webdriver/drivers/service/|the Selenium documentation]
. Currently the ``service`` argument only accepts Selenium service in the string format.
=== Service string format ===
The string format allows for defining Selenium service attributes
and their values in the `Open Browser` keyword. The attributes names
are case and space sensitive and must match to the Selenium attributes
names. Attributes are defined in a similar way as in Python: attribute
name, equal sign, and attribute value. Example, `port=1234`. Multiple
attributes must be separated by a semicolon. Example:
`executable_path='/path/to/driver';port=1234`. Don't have duplicate
attributes, like `service_args=['--append-log', '--readable-timestamp'];
service_args=['--log-level=DEBUG']` as the second will override the first.
Instead combine them as in
`service_args=['--append-log', '--readable-timestamp', '--log-level=DEBUG']`
Arguments allow defining Python data types and arguments are
evaluated by using Python. Strings must be quoted with single
or double quotes, example "value" or 'value'
= Timeouts, waits, and delays =
This section discusses different ways how to wait for elements to
Expand Down

0 comments on commit dd89d33

Please sign in to comment.