Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service class #1902

Merged
merged 18 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6f9954b
Initial retry of creating a Service class
emanlove Feb 17, 2024
da2004f
Starting to bring service class down into driver creation for various…
emanlove Feb 18, 2024
5ca677d
Completed initial construct of service class
emanlove Feb 19, 2024
680ce1d
Updated work on adding service class
emanlove Apr 30, 2024
623be5a
Almost complete buit still an error with instatiating the service class
emanlove May 1, 2024
5d6ad83
Have working code for adding Service as string
emanlove May 5, 2024
64c5283
Added service argument to many of the browser creation functions
emanlove May 5, 2024
9a60357
Merge branch 'master' into add-service-class
emanlove May 5, 2024
f00df07
Fixed unit tests and call signature on _make_driver
emanlove May 5, 2024
b20d895
Merge remote-tracking branch 'tatu/fizes_for_mac' into add-service-class
emanlove May 5, 2024
b6170c1
Removed initial browser_service.robot as now have tests within multip…
emanlove May 5, 2024
8d8fec0
Updated log check correcting for deprecated warning
emanlove May 5, 2024
1415f12
Added service argument to create browser methods
emanlove May 5, 2024
e34282e
Added more unit tests for Service string
emanlove May 8, 2024
81f7d0c
Initial reworking of the `Open Browser` keyword documentation
emanlove May 9, 2024
421de23
More modifications to the Open Browser keyword documentation
emanlove May 10, 2024
138e533
Removed unit test which checked spaces on service argument
emanlove May 10, 2024
4768e7b
Updated documentation explaining the new service class
emanlove May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading