Skip to content

Commit dd89d33

Browse files
authored
Merge pull request #1902 from emanlove/add-service-class
Add service class
2 parents 3ea3041 + 4768e7b commit dd89d33

17 files changed

+662
-183
lines changed

atest/acceptance/1-plugin/OpenBrowserExample.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def open_browser(
2424
service_log_path=None,
2525
extra_dictionary=None,
2626
executable_path=None,
27+
service=None,
2728
):
2829
self._new_creator.extra_dictionary = extra_dictionary
2930
browser_manager = BrowserManagementKeywords(self.ctx)
@@ -37,7 +38,8 @@ def open_browser(
3738
ff_profile_dir=ff_profile_dir,
3839
options=options,
3940
service_log_path=service_log_path,
40-
executable_path=None,
41+
executable_path=executable_path,
42+
service=service,
4143
)
4244

4345
def _make_driver(
@@ -49,6 +51,7 @@ def _make_driver(
4951
options=None,
5052
service_log_path=None,
5153
executable_path=None,
54+
service=None,
5255
):
5356
driver = self._new_creator.create_driver(
5457
browser=browser,
@@ -58,6 +61,7 @@ def _make_driver(
5861
options=options,
5962
service_log_path=service_log_path,
6063
executable_path=executable_path,
64+
service=None,
6165
)
6266
driver.set_script_timeout(self.ctx.timeout)
6367
driver.implicitly_wait(self.ctx.implicit_wait)
@@ -76,13 +80,15 @@ def create_driver(
7680
options=None,
7781
service_log_path=None,
7882
executable_path=None,
83+
service=None,
7984
):
8085
self.browser_names["seleniumwire"] = "seleniumwire"
8186
browser = self._normalise_browser_name(browser)
8287
creation_method = self._get_creator_method(browser)
8388
desired_capabilities = self._parse_capabilities(desired_capabilities, browser)
8489
service_log_path = self._get_log_path(service_log_path)
8590
options = self.selenium_options.create(self.browser_names.get(browser), options)
91+
service = self.selenium_service.create(self.browser_names.get(browser), service)
8692
if service_log_path:
8793
logger.info("Browser driver log file created to: %s" % service_log_path)
8894
self._create_directory(service_log_path)
@@ -96,23 +102,26 @@ def create_driver(
96102
profile_dir,
97103
options=options,
98104
service_log_path=service_log_path,
105+
service=service,
99106
)
100107
if creation_method == self.create_seleniumwire:
101108
return creation_method(
102109
desired_capabilities,
103110
remote_url,
104111
options=options,
105112
service_log_path=service_log_path,
113+
service=service,
106114
)
107115
return creation_method(
108116
desired_capabilities,
109117
remote_url,
110118
options=options,
111119
service_log_path=service_log_path,
120+
service=service,
112121
)
113122

114123
def create_seleniumwire(
115-
self, desired_capabilities, remote_url, options=None, service_log_path=None
124+
self, desired_capabilities, remote_url, options=None, service_log_path=None, service=None,
116125
):
117126
logger.info(self.extra_dictionary)
118127
return webdriver.Chrome()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
*** Settings ***
2+
Suite Teardown Close All Browsers
3+
Library ../resources/testlibs/get_driver_path.py
4+
Resource resource.robot
5+
# Force Tags Known Issue Firefox Known Issue Safari Known Issue Internet Explorer
6+
Documentation Creating test which would work on all browser is not possible.
7+
... These tests are for Chrome only.
8+
9+
*** Test Cases ***
10+
Chrome Browser With Chrome Service As String
11+
[Documentation]
12+
... LOG 2:2 DEBUG STARTS: Started executable:
13+
... LOG 2:3 DEBUG GLOB: POST*/session*
14+
${driver_path}= Get Driver Path Chrome
15+
Open Browser ${FRONT PAGE} Chrome remote_url=${REMOTE_URL}
16+
... service=executable_path='${driver_path}'
17+
18+
Chrome Browser With Chrome Service As String With service_args As List
19+
Open Browser ${FRONT PAGE} Chrome remote_url=${REMOTE_URL}
20+
... service=service_args=['--append-log', '--readable-timestamp']; log_output='${OUTPUT_DIR}/chromedriverlog.txt'
21+
File Should Exist ${OUTPUT_DIR}/chromedriverlog.txt
22+
# ... service=service_args=['--append-log', '--readable-timestamp']; log_output='./'
23+
# ... service=service_args=['--append-log', '--readable-timestamp']
24+
25+
Firefox Browser With Firefox Service As String
26+
[Documentation]
27+
... LOG 2:2 DEBUG STARTS: Started executable:
28+
... LOG 2:3 DEBUG GLOB: POST*/session*
29+
${driver_path}= Get Driver Path Firefox
30+
Open Browser ${FRONT PAGE} Firefox remote_url=${REMOTE_URL}
31+
... service=executable_path='${driver_path}'
32+
33+
#Chrome Browser With Selenium Options Invalid Method
34+
# Run Keyword And Expect Error AttributeError: 'Options' object has no attribute 'not_here_method'
35+
# ... Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
36+
# ... desired_capabilities=${DESIRED_CAPABILITIES} options=not_here_method("arg1")
37+
#
38+
#
39+
#Chrome Browser With Selenium Options Argument With Semicolon
40+
# [Documentation]
41+
# ... LOG 1:14 DEBUG GLOB: *"goog:chromeOptions"*
42+
# ... LOG 1:14 DEBUG GLOB: *["has;semicolon"*
43+
# Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
44+
# ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("has;semicolon")

atest/acceptance/multiple_browsers_service_log_path.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Resource resource.robot
55
*** Test Cases ***
66
First Browser With Service Log Path
77
[Documentation]
8-
... LOG 1:2 INFO STARTS: Browser driver log file created to:
8+
... LOG 1:3 INFO STARTS: Browser driver log file created to:
99
[Setup] OperatingSystem.Remove Files ${OUTPUT DIR}/${BROWSER}.log
1010
Open Browser ${FRONT PAGE} ${BROWSER} service_log_path=${OUTPUT DIR}/${BROWSER}.log
1111
OperatingSystem.List Directories In Directory ${OUTPUT DIR}/
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
>>> from selenium.webdriver.common import driver_finder
3+
>>> drfind = driver_finder.DriverFinder()
4+
>>> from selenium.webdriver.chrome.service import Service
5+
>>> from selenium.webdriver.chrome.options import Options
6+
>>> drfind.get_path(Service(),Options())
7+
8+
9+
def _import_service(self, browser):
10+
browser = browser.replace("headless_", "", 1)
11+
# Throw error is used with remote .. "They cannot be used with a Remote WebDriver session." [ref doc]
12+
service = importlib.import_module(f"selenium.webdriver.{browser}.service")
13+
return service.Service
14+
15+
def _import_options(self, browser):
16+
browser = browser.replace("headless_", "", 1)
17+
options = importlib.import_module(f"selenium.webdriver.{browser}.options")
18+
return options.Options
19+
20+
"""
21+
from selenium import webdriver
22+
from selenium.webdriver.common import driver_finder
23+
import importlib
24+
25+
26+
def get_driver_path(browser):
27+
browser = browser.lower().replace("headless_", "", 1)
28+
service = importlib.import_module(f"selenium.webdriver.{browser}.service")
29+
options = importlib.import_module(f"selenium.webdriver.{browser}.options")
30+
# finder = driver_finder.DriverFinder()
31+
32+
# Selenium v4.19.0 and prior
33+
try:
34+
finder = driver_finder.DriverFinder()
35+
func = getattr(finder, 'get_path')
36+
return finder.get_path(service.Service(), options.Options())
37+
except (AttributeError, TypeError):
38+
pass
39+
40+
# Selenium V4.20.0
41+
try:
42+
finder = driver_finder.DriverFinder(service.Service(), options.Options())
43+
return finder.get_driver_drivepath()
44+
except:
45+
pass
46+
47+
raise Exception('Unable to determine driver path')

src/SeleniumLibrary/__init__.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,115 @@ class SeleniumLibrary(DynamicCore):
322322
https://robocon.io/, https://github.com/robotframework/'
323323
and 'https://github.com/.
324324
325+
= Browser and Driver options and service class =
326+
327+
This section talks about how to configure either the browser or
328+
the driver using the options and service arguments of the `Open
329+
Browser` keyword.
330+
331+
== Configuring the browser using the Selenium Options ==
332+
333+
As noted within the keyword documentation for `Open Browser`, its
334+
``options`` argument accepts Selenium options in two different
335+
formats: as a string and as Python object which is an instance of
336+
the Selenium options class.
337+
338+
=== Options string format ===
339+
340+
The string format allows defining Selenium options methods
341+
or attributes and their arguments in Robot Framework test data.
342+
The method and attributes names are case and space sensitive and
343+
must match to the Selenium options methods and attributes names.
344+
When defining a method, it must be defined in a similar way as in
345+
python: method name, opening parenthesis, zero to many arguments
346+
and closing parenthesis. If there is a need to define multiple
347+
arguments for a single method, arguments must be separated with
348+
comma, just like in Python. Example: `add_argument("--headless")`
349+
or `add_experimental_option("key", "value")`. Attributes are
350+
defined in a similar way as in Python: attribute name, equal sign,
351+
and attribute value. Example, `headless=True`. Multiple methods
352+
and attributes must be separated by a semicolon. Example:
353+
`add_argument("--headless");add_argument("--start-maximized")`.
354+
355+
Arguments allow defining Python data types and arguments are
356+
evaluated by using Python
357+
[https://docs.python.org/3/library/ast.html#ast.literal_eval|ast.literal_eval].
358+
Strings must be quoted with single or double quotes, example "value"
359+
or 'value'. It is also possible to define other Python builtin
360+
data types, example `True` or `None`, by not using quotes
361+
around the arguments.
362+
363+
The string format is space friendly. Usually, spaces do not alter
364+
the defining methods or attributes. There are two exceptions.
365+
In some Robot Framework test data formats, two or more spaces are
366+
considered as cell separator and instead of defining a single
367+
argument, two or more arguments may be defined. Spaces in string
368+
arguments are not removed and are left as is. Example
369+
`add_argument ( "--headless" )` is same as
370+
`add_argument("--headless")`. But `add_argument(" --headless ")` is
371+
not same same as `add_argument ( "--headless" )`, because
372+
spaces inside of quotes are not removed. Please note that if
373+
options string contains backslash, example a Windows OS path,
374+
the backslash needs escaping both in Robot Framework data and
375+
in Python side. This means single backslash must be writen using
376+
four backslash characters. Example, Windows path:
377+
"C:\\path\\to\\profile" must be written as
378+
"C:\\\\\\\\path\\\\\\to\\\\\\\\profile". Another way to write
379+
backslash is use Python
380+
[https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals|raw strings]
381+
and example write: r"C:\\\\path\\\\to\\\\profile".
382+
383+
=== Selenium Options as Python class ===
384+
385+
As last format, ``options`` argument also supports receiving
386+
the Selenium options as Python class instance. In this case, the
387+
instance is used as-is and the SeleniumLibrary will not convert
388+
the instance to other formats.
389+
For example, if the following code return value is saved to
390+
`${options}` variable in the Robot Framework data:
391+
| options = webdriver.ChromeOptions()
392+
| options.add_argument('--disable-dev-shm-usage')
393+
| return options
394+
395+
Then the `${options}` variable can be used as an argument to
396+
``options``.
397+
398+
Example the ``options`` argument can be used to launch Chomium-based
399+
applications which utilize the
400+
[https://bitbucket.org/chromiumembedded/cef/wiki/UsingChromeDriver|Chromium Embedded Framework]
401+
. To launch Chromium-based application, use ``options`` to define
402+
`binary_location` attribute and use `add_argument` method to define
403+
`remote-debugging-port` port for the application. Once the browser
404+
is opened, the test can interact with the embedded web-content of
405+
the system under test.
406+
407+
== Configuring the driver using the Service class ==
408+
409+
With the ``service`` argument, one can setup and configure the driver. For example
410+
one can set the driver location and/port or specify the command line arguments. There
411+
are several browser specific attributes related to logging as well. For the various
412+
Service Class attributes refer to
413+
[https://www.selenium.dev/documentation/webdriver/drivers/service/|the Selenium documentation]
414+
. Currently the ``service`` argument only accepts Selenium service in the string format.
415+
416+
=== Service string format ===
417+
418+
The string format allows for defining Selenium service attributes
419+
and their values in the `Open Browser` keyword. The attributes names
420+
are case and space sensitive and must match to the Selenium attributes
421+
names. Attributes are defined in a similar way as in Python: attribute
422+
name, equal sign, and attribute value. Example, `port=1234`. Multiple
423+
attributes must be separated by a semicolon. Example:
424+
`executable_path='/path/to/driver';port=1234`. Don't have duplicate
425+
attributes, like `service_args=['--append-log', '--readable-timestamp'];
426+
service_args=['--log-level=DEBUG']` as the second will override the first.
427+
Instead combine them as in
428+
`service_args=['--append-log', '--readable-timestamp', '--log-level=DEBUG']`
429+
430+
Arguments allow defining Python data types and arguments are
431+
evaluated by using Python. Strings must be quoted with single
432+
or double quotes, example "value" or 'value'
433+
325434
= Timeouts, waits, and delays =
326435
327436
This section discusses different ways how to wait for elements to

0 commit comments

Comments
 (0)