From 46ceebca72715fedc4553954dc3e76ecaa1ddf00 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Fri, 31 May 2019 23:00:11 +0300 Subject: [PATCH 01/52] Selenium options string to object --- .../keywords/webdrivertools.py | 25 ++++++++ ...sPessKeys.test_options_escape.approved.txt | 5 ++ ...dsPessKeys.test_parse_options.approved.txt | 10 ++++ .../keywords/test_selenium_options_parser.py | 60 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_options_escape.approved.txt create mode 100644 utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_parse_options.approved.txt create mode 100644 utest/test/keywords/test_selenium_options_parser.py diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 68c3e5844..af0bcb69a 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -309,3 +309,28 @@ def _get_index(self, alias_or_index): return self._resolve_alias_or_index(alias_or_index) except ValueError: return None + + +class SeleniumOptions(object): + + def parse(self, options): + result = [] + for single_option in options.split(','): + options_split = single_option.split(':') + options_split = self._options_escape(options_split) + argument = {options_split[0]: options_split[1:]} + result.append(argument) + return result + + def _options_escape(self, options_split): + escape_detected = False + result = [] + for opt in options_split: + if opt.endswith('\\'): + escape_detected = opt[:-1] + elif escape_detected: + result.append('%s:%s' % (escape_detected, opt)) + escape_detected = False + else: + result.append(opt) + return result diff --git a/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_options_escape.approved.txt b/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_options_escape.approved.txt new file mode 100644 index 000000000..6e55e644c --- /dev/null +++ b/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_options_escape.approved.txt @@ -0,0 +1,5 @@ +Selenium options escape string to dict + +0) ['--proxy-server=66.97.38.58:80'] +1) ['arg1', 'arg2'] +2) ['arg1'] diff --git a/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_parse_options.approved.txt b/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_parse_options.approved.txt new file mode 100644 index 000000000..b78fddf88 --- /dev/null +++ b/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_parse_options.approved.txt @@ -0,0 +1,10 @@ +Selenium options string to dict + +0) [{'method': ['arg1']}] +1) [{'method': ['arg1', 'arg2']}] +2) [{'method': ['arg1']}, {'method': ['arg2']}] +3) [{'method': []}] +4) [{'method1': []}, {'method2': []}] +5) [{'method': []}, {'method': []}] +6) [{'add_argument': ['--disable-dev-shm-usage']}] +7) [{'add_argument': ['--proxy-server=66.97.38.58:80']}] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py new file mode 100644 index 000000000..e9e505283 --- /dev/null +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -0,0 +1,60 @@ +import unittest +import os + +from robot.utils import JYTHON + +try: + from approvaltests.approvals import verify_all + from approvaltests.reporters.generic_diff_reporter_factory import GenericDiffReporterFactory +except ImportError: + if JYTHON: + verify = None + GenericDiffReporterFactory = None + else: + raise + +from SeleniumLibrary.keywords.webdrivertools import SeleniumOptions +from SeleniumLibrary.utils import PY3 + + +class ElementKeywordsPessKeys(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.options = SeleniumOptions() + + def setUp(self): + path = os.path.dirname(__file__) + reporter_json = os.path.abspath(os.path.join(path, '..', 'approvals_reporters.json')) + factory = GenericDiffReporterFactory() + factory.load(reporter_json) + self.reporter = factory.get_first_working() + self.results = [] + + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_parse_options(self): + self.results.append(self.options.parse('method:arg1')) + self.results.append(self.options.parse('method:arg1:arg2')) + self.results.append(self.options.parse('method:arg1,method:arg2')) + self.results.append(self.options.parse('method')) + self.results.append(self.options.parse('method1,method2')) + self.results.append(self.options.parse('method,method')) + self.results.append(self.options.parse('add_argument:--disable-dev-shm-usage')) + self.results.append(self.options.parse('add_argument:--proxy-server=66.97.38.58\:80')) + self.result_formatter() + verify_all('Selenium options string to dict', self.results, reporter=self.reporter) + + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_options_escape(self): + self.results.append(self.options._options_escape('--proxy-server=66.97.38.58\:80'.split(':'))) + self.results.append(self.options._options_escape('arg1:arg2'.split(':'))) + self.results.append(self.options._options_escape('arg1'.split(':'))) + self.result_formatter() + verify_all('Selenium options escape string to dict', self.results, reporter=self.reporter) + + def result_formatter(self): + if PY3: + pass + for index, result in enumerate(self.results): + result = str(result) + self.results[index] = result.replace("=u'", "='") From 9a0ba84e7ffde46ad656207132666e94b55ff623 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 1 Jun 2019 23:15:27 +0300 Subject: [PATCH 02/52] Renamed test class and test name --- ...leniumOptionsParserTests.test_options_escape.approved.txt} | 0 ...OptionsParserTests.test_parse_options_string.approved.txt} | 0 utest/test/keywords/test_selenium_options_parser.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename utest/test/keywords/approved_files/{ElementKeywordsPessKeys.test_options_escape.approved.txt => SeleniumOptionsParserTests.test_options_escape.approved.txt} (100%) rename utest/test/keywords/approved_files/{ElementKeywordsPessKeys.test_parse_options.approved.txt => SeleniumOptionsParserTests.test_parse_options_string.approved.txt} (100%) diff --git a/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_options_escape.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_escape.approved.txt similarity index 100% rename from utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_options_escape.approved.txt rename to utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_escape.approved.txt diff --git a/utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_parse_options.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt similarity index 100% rename from utest/test/keywords/approved_files/ElementKeywordsPessKeys.test_parse_options.approved.txt rename to utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index e9e505283..f7109fe5e 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -17,7 +17,7 @@ from SeleniumLibrary.utils import PY3 -class ElementKeywordsPessKeys(unittest.TestCase): +class SeleniumOptionsParserTests(unittest.TestCase): @classmethod def setUpClass(cls): @@ -32,7 +32,7 @@ def setUp(self): self.results = [] @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') - def test_parse_options(self): + def test_parse_options_string(self): self.results.append(self.options.parse('method:arg1')) self.results.append(self.options.parse('method:arg1:arg2')) self.results.append(self.options.parse('method:arg1,method:arg2')) From 4c60ba485852e1eeefcbbb71a5487a3e34fac0fa Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 1 Jun 2019 23:30:24 +0300 Subject: [PATCH 03/52] Selenium options support for other types --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 ++++ ...ests.test_parse_options_other_types.approved.txt | 8 ++++++++ utest/test/keywords/test_selenium_options_parser.py | 13 +++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index af0bcb69a..466bfa4b4 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -314,6 +314,10 @@ def _get_index(self, alias_or_index): class SeleniumOptions(object): def parse(self, options): + if is_falsy(options): + return [] + if isinstance(options, list): + return options result = [] for single_option in options.split(','): options_split = single_option.split(':') diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt new file mode 100644 index 000000000..9977c13ca --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt @@ -0,0 +1,8 @@ +Selenium options other types to dict + +0) [] +1) [] +2) [] +3) [] +4) [{'add_argument': ['--disable-dev-shm-usage']}] +5) [] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index f7109fe5e..22a543f21 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -1,6 +1,7 @@ import unittest import os +from robot.libraries.BuiltIn import BuiltIn from robot.utils import JYTHON try: @@ -44,6 +45,18 @@ def test_parse_options_string(self): self.result_formatter() verify_all('Selenium options string to dict', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_parse_options_other_types(self): + self.results.append(self.options.parse('None')) + self.results.append(self.options.parse(None)) + self.results.append(self.options.parse(False)) + self.results.append(self.options.parse('False')) + options = [{'add_argument': ['--disable-dev-shm-usage']}] + self.results.append(self.options.parse(options)) + self.results.append(self.options.parse([])) + self.result_formatter() + verify_all('Selenium options other types to dict', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_options_escape(self): self.results.append(self.options._options_escape('--proxy-server=66.97.38.58\:80'.split(':'))) From c29f2ad27c3e1b65cb3e01ad5868eada7365b790 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 2 Jun 2019 01:01:08 +0300 Subject: [PATCH 04/52] Selenium options for methods --- .../keywords/webdrivertools.py | 15 +++++++ ...ionsParserTests.test_importer.approved.txt | 15 +++++++ ...rserTests.test_options_create.approved.txt | 5 +++ .../keywords/test_selenium_options_parser.py | 40 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 466bfa4b4..e1645bfa6 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -13,6 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import importlib import inspect import os import warnings @@ -338,3 +339,17 @@ def _options_escape(self, options_split): else: result.append(opt) return result + + def create(self, browser, options): + selenium_options = self._import_options(browser) + selenium_options = selenium_options() + for option in options: + for key in option: + attr = getattr(selenium_options, key) + attr(*option[key]) + return selenium_options + + def _import_options(self, browser): + browser = browser.replace('headless_', '', 1) + options = importlib.import_module('selenium.webdriver.%s.options' % browser) + return options.Options diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt new file mode 100644 index 000000000..00305cc6f --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt @@ -0,0 +1,15 @@ +Selenium options import + +0) +1) +2) +3) +4) +5) +6) +7) phantomjs No module named +8) safari No module named +9) htmlunit No module named +10) htmlunit_with_js No module named +11) android No module named +12) iphone No module named diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt new file mode 100644 index 000000000..21a92c8e4 --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt @@ -0,0 +1,5 @@ +Selenium options + +0) ['--disable-dev-shm-usage'] +1) ['--disable-dev-shm-usage', '--headless'] +2) ['--disable-dev-shm-usage', '--headless', '--proxy-server=66.97.38.58:80'] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 22a543f21..19ed10894 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -65,9 +65,49 @@ def test_options_escape(self): self.result_formatter() verify_all('Selenium options escape string to dict', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_options_create(self): + options = [{'add_argument': ['--disable-dev-shm-usage']}] + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.arguments) + + options.append({'add_argument': ['--headless']}) + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.arguments) + + options.append({'add_argument': ['--proxy-server=66.97.38.58:80']}) + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.arguments) + + self.result_formatter() + verify_all('Selenium options', self.results, reporter=self.reporter) + + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_importer(self): + self.results.append(self.options._import_options('firefox')) + self.results.append(self.options._import_options('headless_firefox')) + self.results.append(self.options._import_options('chrome')) + self.results.append(self.options._import_options('headless_chrome')) + self.results.append(self.options._import_options('ie')) + self.results.append(self.options._import_options('opera')) + self.results.append(self.options._import_options('edge')) + self.results.append(self.error_formatter(self.options._import_options, 'phantomjs')) + self.results.append(self.error_formatter(self.options._import_options, 'safari')) + self.results.append(self.error_formatter(self.options._import_options, 'htmlunit')) + self.results.append(self.error_formatter(self.options._import_options, 'htmlunit_with_js')) + self.results.append(self.error_formatter(self.options._import_options, 'android')) + self.results.append(self.error_formatter(self.options._import_options, 'iphone')) + verify_all('Selenium options import', self.results, reporter=self.reporter) + def result_formatter(self): if PY3: pass for index, result in enumerate(self.results): result = str(result) self.results[index] = result.replace("=u'", "='") + + def error_formatter(self, method, arg): + try: + method(arg) + except Exception as error: + return '%s %s' % (arg, error.__str__()[:15]) From d99ab2d85bbe1504eb921544a41eb44de553c87c Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 2 Jun 2019 01:08:55 +0300 Subject: [PATCH 05/52] Test for Chrome add_experimental_option --- ...Tests.test_options_create_many_args.approved.txt | 4 ++++ utest/test/keywords/test_selenium_options_parser.py | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt new file mode 100644 index 000000000..6178d1a8e --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt @@ -0,0 +1,4 @@ +Selenium options + +0) {'profile.default_content_settings.popups': 0} +1) {'profile.default_content_settings.popups': 0, 'foo': 'bar'} diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 19ed10894..712bd32c9 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -82,6 +82,19 @@ def test_options_create(self): self.result_formatter() verify_all('Selenium options', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_options_create_many_args(self): + options = [{'add_experimental_option': ['profile.default_content_settings.popups', 0]}] + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.experimental_options) + + options.append({'add_experimental_option': ['foo', 'bar']}) + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.experimental_options) + + self.result_formatter() + verify_all('Selenium options', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_importer(self): self.results.append(self.options._import_options('firefox')) From 44de3bdcac96ec46c3920d1fa96e5a367d979d46 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 2 Jun 2019 01:28:45 +0300 Subject: [PATCH 06/52] Test for options attributes --- .../keywords/webdrivertools.py | 5 ++++- ...test_options_create_attribute.approved.txt | 6 +++++ .../keywords/test_selenium_options_parser.py | 22 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index e1645bfa6..147290df4 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -346,7 +346,10 @@ def create(self, browser, options): for option in options: for key in option: attr = getattr(selenium_options, key) - attr(*option[key]) + if callable(attr): + attr(*option[key]) + else: + setattr(selenium_options, key, option[key][0]) return selenium_options def _import_options(self, browser): diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt new file mode 100644 index 000000000..9b14ed28b --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt @@ -0,0 +1,6 @@ +Selenium options attribute + +0) ['--headless'] +1) ['--headless'] +2) chromedriver +3) 'Options' object has no attribute 'not_here' diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 712bd32c9..c695ad6ee 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -95,6 +95,28 @@ def test_options_create_many_args(self): self.result_formatter() verify_all('Selenium options', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_options_create_attribute(self): + options = [{'headless': [True]}] + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.arguments) + + sel_options = self.options.create('headless_chrome', options) + self.results.append(sel_options.arguments) + + options.append({'binary_location': ['chromedriver']}) + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.binary_location) + + options.append({'not_here': ['tidii']}) + try: + self.options.create('chrome', options) + except AttributeError as error: + self.results.append(error) + + self.result_formatter() + verify_all('Selenium options attribute', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_importer(self): self.results.append(self.options._import_options('firefox')) From 332cb8801be05d21b623b6901a013b14eced3131 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 2 Jun 2019 23:06:27 +0300 Subject: [PATCH 07/52] Changed SeleniumOptions logic --- .../keywords/webdrivertools.py | 30 ++++++++------- ...sParserTests.test_get_options.approved.txt | 3 ++ .../keywords/test_selenium_options_parser.py | 38 +++++++++++-------- 3 files changed, 42 insertions(+), 29 deletions(-) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_get_options.approved.txt diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 147290df4..ad246a8ac 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -50,9 +50,10 @@ class WebDriverCreator(object): def __init__(self, log_dir): self.log_dir = log_dir + self.selenium_options = SeleniumOptions() def create_driver(self, browser, desired_capabilities, remote_url, - profile_dir=None, service_log_path=None): + profile_dir=None, options=None, service_log_path=None): 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) @@ -314,7 +315,20 @@ def _get_index(self, alias_or_index): class SeleniumOptions(object): - def parse(self, options): + def create(self, browser, options): + options = self._parse(options) + selenium_options = self._import_options(browser) + selenium_options = selenium_options() + for option in options: + for key in option: + attr = getattr(selenium_options, key) + if callable(attr): + attr(*option[key]) + else: + setattr(selenium_options, key, option[key][0]) + return selenium_options + + def _parse(self, options): if is_falsy(options): return [] if isinstance(options, list): @@ -340,18 +354,6 @@ def _options_escape(self, options_split): result.append(opt) return result - def create(self, browser, options): - selenium_options = self._import_options(browser) - selenium_options = selenium_options() - for option in options: - for key in option: - attr = getattr(selenium_options, key) - if callable(attr): - attr(*option[key]) - else: - setattr(selenium_options, key, option[key][0]) - return selenium_options - def _import_options(self, browser): browser = browser.replace('headless_', '', 1) options = importlib.import_module('selenium.webdriver.%s.options' % browser) diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_get_options.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_get_options.approved.txt new file mode 100644 index 000000000..8b9a62aaa --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_get_options.approved.txt @@ -0,0 +1,3 @@ +Selenium options with string. + +0) ['--proxy-server=66.97.38.58:80'] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index c695ad6ee..e628f20ec 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -1,7 +1,6 @@ import unittest import os -from robot.libraries.BuiltIn import BuiltIn from robot.utils import JYTHON try: @@ -34,26 +33,26 @@ def setUp(self): @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_parse_options_string(self): - self.results.append(self.options.parse('method:arg1')) - self.results.append(self.options.parse('method:arg1:arg2')) - self.results.append(self.options.parse('method:arg1,method:arg2')) - self.results.append(self.options.parse('method')) - self.results.append(self.options.parse('method1,method2')) - self.results.append(self.options.parse('method,method')) - self.results.append(self.options.parse('add_argument:--disable-dev-shm-usage')) - self.results.append(self.options.parse('add_argument:--proxy-server=66.97.38.58\:80')) + self.results.append(self.options._parse('method:arg1')) + self.results.append(self.options._parse('method:arg1:arg2')) + self.results.append(self.options._parse('method:arg1,method:arg2')) + self.results.append(self.options._parse('method')) + self.results.append(self.options._parse('method1,method2')) + self.results.append(self.options._parse('method,method')) + self.results.append(self.options._parse('add_argument:--disable-dev-shm-usage')) + self.results.append(self.options._parse('add_argument:--proxy-server=66.97.38.58\:80')) self.result_formatter() verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_parse_options_other_types(self): - self.results.append(self.options.parse('None')) - self.results.append(self.options.parse(None)) - self.results.append(self.options.parse(False)) - self.results.append(self.options.parse('False')) + self.results.append(self.options._parse('None')) + self.results.append(self.options._parse(None)) + self.results.append(self.options._parse(False)) + self.results.append(self.options._parse('False')) options = [{'add_argument': ['--disable-dev-shm-usage']}] - self.results.append(self.options.parse(options)) - self.results.append(self.options.parse([])) + self.results.append(self.options._parse(options)) + self.results.append(self.options._parse([])) self.result_formatter() verify_all('Selenium options other types to dict', self.results, reporter=self.reporter) @@ -117,6 +116,15 @@ def test_options_create_attribute(self): self.result_formatter() verify_all('Selenium options attribute', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') + def test_get_options(self): + options = 'add_argument:--proxy-server=66.97.38.58\:80' + sel_options = self.options.create('chrome', options) + self.results.append(sel_options.arguments) + + self.result_formatter() + verify_all('Selenium options with string.', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_importer(self): self.results.append(self.options._import_options('firefox')) From 12dcd1b7d062ab9c120cfa730669ffdb93761b9b Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 2 Jun 2019 23:38:57 +0300 Subject: [PATCH 08/52] Options for Chrome, Firefox and Ie --- .../keywords/webdrivertools.py | 28 ++++---- .../keywords/test_selenium_options_parser.py | 70 ++++++++++++++++++- .../test_webdrivercreator_service_log_path.py | 3 +- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index ad246a8ac..07a41875a 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -107,11 +107,11 @@ def create_chrome(self, desired_capabilities, remote_url, options=None, service_ return self._remote(desired_capabilities, remote_url, options=options) return webdriver.Chrome(options=options, service_log_path=service_log_path, **desired_capabilities) - def create_headless_chrome(self, desired_capabilities, remote_url, service_log_path=None): - options = webdriver.ChromeOptions() + def create_headless_chrome(self, desired_capabilities, remote_url, options=None, service_log_path=None): + chrome_options = webdriver.ChromeOptions() if not options else options # Can be changed to options.headless = True when minimum Selenium version is 3.12.0 or greater. - options.set_headless() - return self.create_chrome(desired_capabilities, remote_url, options, service_log_path) + chrome_options.set_headless() + return self.create_chrome(desired_capabilities, remote_url, chrome_options, service_log_path) def create_firefox(self, desired_capabilities, remote_url, ff_profile_dir, options=None, service_log_path=None): profile = self._get_ff_profile(ff_profile_dir) @@ -138,26 +138,30 @@ def _geckodriver_log(self): return log_file def create_headless_firefox(self, desired_capabilities, remote_url, - ff_profile_dir, service_log_path=None): - options = webdriver.FirefoxOptions() + ff_profile_dir, options=None, service_log_path=None): + ff_options = webdriver.FirefoxOptions() if not options else options # Can be changed to options.headless = True when minimum Selenium version is 3.12.0 or greater. - options.set_headless() - return self.create_firefox(desired_capabilities, remote_url, ff_profile_dir, options, service_log_path) + ff_options.set_headless() + return self.create_firefox(desired_capabilities, remote_url, ff_profile_dir, ff_options, service_log_path) - def create_ie(self, desired_capabilities, remote_url, service_log_path=None): + def create_ie(self, desired_capabilities, remote_url, options=None, service_log_path=None): if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.INTERNETEXPLORER.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) - if self._has_service_log_path(webdriver.Ie): - return webdriver.Ie(service_log_path=service_log_path, **desired_capabilities) - logger.warn('This version of Selenium does not support service_log_path argument.') + if self._has_service_log_path(webdriver.Ie) and self._has_options(webdriver.Ie): + return webdriver.Ie(options=options, service_log_path=service_log_path, **desired_capabilities) + logger.warn('This version of Selenium does not support options and service_log_path argument.') return webdriver.Ie(**desired_capabilities) def _has_service_log_path(self, web_driver): signature = inspect.getargspec(web_driver.__init__) return True if 'service_log_path' in signature.args else False + def _has_options(self, web_driver): + signature = inspect.getargspec(web_driver.__init__) + return True if 'options' in signature.args else False + def create_edge(self, desired_capabilities, remote_url, service_log_path=None): if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.EDGE.copy() diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index e628f20ec..ac3f79375 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -1,7 +1,9 @@ import unittest import os +from mockito import mock, when, unstub, ANY from robot.utils import JYTHON +from selenium import webdriver try: from approvaltests.approvals import verify_all @@ -13,7 +15,7 @@ else: raise -from SeleniumLibrary.keywords.webdrivertools import SeleniumOptions +from SeleniumLibrary.keywords.webdrivertools import SeleniumOptions, WebDriverCreator from SeleniumLibrary.utils import PY3 @@ -154,3 +156,69 @@ def error_formatter(self, method, arg): method(arg) except Exception as error: return '%s %s' % (arg, error.__str__()[:15]) + + +class UsingSeleniumOptionsTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + curr_dir = os.path.dirname(os.path.abspath(__file__)) + cls.output_dir = os.path.abspath( + os.path.join(curr_dir, '..', '..', 'output_dir')) + cls.creator = WebDriverCreator(cls.output_dir) + + def tearDown(self): + unstub() + + def test_create_chrome_with_options(self): + options = mock() + expected_webdriver = mock() + when(webdriver).Chrome(service_log_path=None, options=options).thenReturn(expected_webdriver) + driver = self.creator.create_chrome({}, None, options=options) + self.assertEqual(driver, expected_webdriver) + + def test_create_headless_chrome_with_options(self): + options = mock() + expected_webdriver = mock() + when(webdriver).Chrome(service_log_path=None, options=options).thenReturn(expected_webdriver) + driver = self.creator.create_headless_chrome({}, None, options=options) + self.assertEqual(driver, expected_webdriver) + + def test_create_firefox_with_options(self): + log_file = os.path.join(self.output_dir, 'geckodriver-1.log') + options = mock() + profile = mock() + expected_webdriver = mock() + when(webdriver).FirefoxProfile().thenReturn(profile) + when(webdriver).Firefox(options=options, firefox_profile=profile, + service_log_path=log_file).thenReturn(expected_webdriver) + driver = self.creator.create_firefox({}, None, None, options=options) + self.assertEqual(driver, expected_webdriver) + + def test_create_headless_firefox_with_options(self): + log_file = os.path.join(self.output_dir, 'geckodriver-1.log') + options = mock() + profile = mock() + expected_webdriver = mock() + when(webdriver).FirefoxProfile().thenReturn(profile) + when(webdriver).Firefox(options=options, firefox_profile=profile, + service_log_path=log_file).thenReturn(expected_webdriver) + driver = self.creator.create_headless_firefox({}, None, None, options=options) + self.assertEqual(driver, expected_webdriver) + + def test_create_ie_with_options(self): + options = mock() + expected_webdriver = mock() + when(self.creator)._has_service_log_path(ANY).thenReturn(True) + when(self.creator)._has_options(ANY).thenReturn(True) + when(webdriver).Ie(service_log_path=None, options=options).thenReturn(expected_webdriver) + driver = self.creator.create_ie({}, None, options=options) + self.assertEqual(driver, expected_webdriver) + + def test_has_options(self): + self.assertTrue(self.creator._has_options(webdriver.Chrome)) + self.assertTrue(self.creator._has_options(webdriver.Firefox)) + self.assertTrue(self.creator._has_options(webdriver.Ie)) + self.assertFalse(self.creator._has_options(webdriver.Edge)) + self.assertTrue(self.creator._has_options(webdriver.Opera)) + self.assertFalse(self.creator._has_options(webdriver.Safari)) diff --git a/utest/test/keywords/test_webdrivercreator_service_log_path.py b/utest/test/keywords/test_webdrivercreator_service_log_path.py index 9cd5e99ab..955f6a059 100644 --- a/utest/test/keywords/test_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/test_webdrivercreator_service_log_path.py @@ -112,7 +112,8 @@ def test_create_ie_with_service_log_path_real_path(self): log_file = os.path.join(self.output_dir, 'ie-1.log') expected_webdriver = mock() when(self.creator)._has_service_log_path(ANY).thenReturn(True) - when(webdriver).Ie(service_log_path=log_file).thenReturn(expected_webdriver) + when(self.creator)._has_options(ANY).thenReturn(True) + when(webdriver).Ie(options=None, service_log_path=log_file).thenReturn(expected_webdriver) driver = self.creator.create_ie({}, None, service_log_path=log_file) self.assertEqual(driver, expected_webdriver) From eefcc8ba661bc26f0f76669e45a5fa3a936eea1f Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 3 Jun 2019 23:25:51 +0300 Subject: [PATCH 09/52] Removing not needed formatter from unit test --- .../test/keywords/test_selenium_options_parser.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index ac3f79375..4e62de926 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -43,7 +43,6 @@ def test_parse_options_string(self): self.results.append(self.options._parse('method,method')) self.results.append(self.options._parse('add_argument:--disable-dev-shm-usage')) self.results.append(self.options._parse('add_argument:--proxy-server=66.97.38.58\:80')) - self.result_formatter() verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -55,7 +54,6 @@ def test_parse_options_other_types(self): options = [{'add_argument': ['--disable-dev-shm-usage']}] self.results.append(self.options._parse(options)) self.results.append(self.options._parse([])) - self.result_formatter() verify_all('Selenium options other types to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -63,7 +61,6 @@ def test_options_escape(self): self.results.append(self.options._options_escape('--proxy-server=66.97.38.58\:80'.split(':'))) self.results.append(self.options._options_escape('arg1:arg2'.split(':'))) self.results.append(self.options._options_escape('arg1'.split(':'))) - self.result_formatter() verify_all('Selenium options escape string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -80,7 +77,6 @@ def test_options_create(self): sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) - self.result_formatter() verify_all('Selenium options', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -93,7 +89,6 @@ def test_options_create_many_args(self): sel_options = self.options.create('chrome', options) self.results.append(sel_options.experimental_options) - self.result_formatter() verify_all('Selenium options', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -115,7 +110,6 @@ def test_options_create_attribute(self): except AttributeError as error: self.results.append(error) - self.result_formatter() verify_all('Selenium options attribute', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -124,7 +118,6 @@ def test_get_options(self): sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) - self.result_formatter() verify_all('Selenium options with string.', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -144,13 +137,6 @@ def test_importer(self): self.results.append(self.error_formatter(self.options._import_options, 'iphone')) verify_all('Selenium options import', self.results, reporter=self.reporter) - def result_formatter(self): - if PY3: - pass - for index, result in enumerate(self.results): - result = str(result) - self.results[index] = result.replace("=u'", "='") - def error_formatter(self, method, arg): try: method(arg) From 3cec432fa91be289704ad0af2ca92cd2a55abaac Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 3 Jun 2019 23:41:48 +0300 Subject: [PATCH 10/52] Removed because Python3.4 can not keep list order with dict --- ...ionsParserTests.test_options_create_many_args.approved.txt | 1 - utest/test/keywords/test_selenium_options_parser.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt index 6178d1a8e..cdd6dea07 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt @@ -1,4 +1,3 @@ Selenium options 0) {'profile.default_content_settings.popups': 0} -1) {'profile.default_content_settings.popups': 0, 'foo': 'bar'} diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 4e62de926..54daf65b9 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -85,10 +85,6 @@ def test_options_create_many_args(self): sel_options = self.options.create('chrome', options) self.results.append(sel_options.experimental_options) - options.append({'add_experimental_option': ['foo', 'bar']}) - sel_options = self.options.create('chrome', options) - self.results.append(sel_options.experimental_options) - verify_all('Selenium options', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 5e1eb7855cf25db4170dce1c3d2d72d7465ad5c2 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 6 Jun 2019 22:49:00 +0300 Subject: [PATCH 11/52] Cleaning unit tests --- utest/test/keywords/test_selenium_options_parser.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 54daf65b9..c0a1e885b 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -16,7 +16,6 @@ raise from SeleniumLibrary.keywords.webdrivertools import SeleniumOptions, WebDriverCreator -from SeleniumLibrary.utils import PY3 class SeleniumOptionsParserTests(unittest.TestCase): @@ -42,7 +41,7 @@ def test_parse_options_string(self): self.results.append(self.options._parse('method1,method2')) self.results.append(self.options._parse('method,method')) self.results.append(self.options._parse('add_argument:--disable-dev-shm-usage')) - self.results.append(self.options._parse('add_argument:--proxy-server=66.97.38.58\:80')) + self.results.append(self.options._parse(r'add_argument:--proxy-server=66.97.38.58\:80')) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -58,7 +57,7 @@ def test_parse_options_other_types(self): @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_options_escape(self): - self.results.append(self.options._options_escape('--proxy-server=66.97.38.58\:80'.split(':'))) + self.results.append(self.options._options_escape(r'--proxy-server=66.97.38.58\:80'.split(':'))) self.results.append(self.options._options_escape('arg1:arg2'.split(':'))) self.results.append(self.options._options_escape('arg1'.split(':'))) verify_all('Selenium options escape string to dict', self.results, reporter=self.reporter) @@ -110,7 +109,7 @@ def test_options_create_attribute(self): @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_get_options(self): - options = 'add_argument:--proxy-server=66.97.38.58\:80' + options = r'add_argument:--proxy-server=66.97.38.58\:80' sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) From 435f08df41dbfd03421b122096eacfd32bba0f08 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 6 Jun 2019 23:03:44 +0300 Subject: [PATCH 12/52] Ie supports options and service_log_path from different version --- src/SeleniumLibrary/keywords/webdrivertools.py | 7 +++++++ utest/test/keywords/test_selenium_options_parser.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 07a41875a..4b496c7af 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -150,7 +150,14 @@ def create_ie(self, desired_capabilities, remote_url, options=None, service_log_ desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) if self._has_service_log_path(webdriver.Ie) and self._has_options(webdriver.Ie): + # service_log_path is supported from Selenium 3.14 onwards + # If can be removed when minimum Selenium version is 3.14.0 or greater return webdriver.Ie(options=options, service_log_path=service_log_path, **desired_capabilities) + elif not self._has_service_log_path(webdriver.Ie) and self._has_options(webdriver.Ie): + # Options is supported from Selenium 3.10 onwards + # If can be removed when minimum Selenium version is 3.10.0 or greater + logger.warn('This version of Selenium does not support service_log_path argument.') + return webdriver.Ie(options=options, **desired_capabilities) logger.warn('This version of Selenium does not support options and service_log_path argument.') return webdriver.Ie(**desired_capabilities) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index c0a1e885b..ae9c1ea27 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -196,6 +196,15 @@ def test_create_ie_with_options(self): driver = self.creator.create_ie({}, None, options=options) self.assertEqual(driver, expected_webdriver) + def test_create_ie_with_options_and_log_path(self): + options = mock() + expected_webdriver = mock() + when(self.creator)._has_service_log_path(ANY).thenReturn(False) + when(self.creator)._has_options(ANY).thenReturn(True) + when(webdriver).Ie(options=options).thenReturn(expected_webdriver) + driver = self.creator.create_ie({}, None, options=options) + self.assertEqual(driver, expected_webdriver) + def test_has_options(self): self.assertTrue(self.creator._has_options(webdriver.Chrome)) self.assertTrue(self.creator._has_options(webdriver.Firefox)) From 1ed9761a9b6eeda092f4bacead8afaca298efc9d Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 6 Jun 2019 23:37:06 +0300 Subject: [PATCH 13/52] Ie support for Selenium options --- src/SeleniumLibrary/keywords/webdrivertools.py | 15 +++++++++++---- .../test/keywords/test_selenium_options_parser.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 4b496c7af..33422834d 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -154,7 +154,7 @@ def create_ie(self, desired_capabilities, remote_url, options=None, service_log_ # If can be removed when minimum Selenium version is 3.14.0 or greater return webdriver.Ie(options=options, service_log_path=service_log_path, **desired_capabilities) elif not self._has_service_log_path(webdriver.Ie) and self._has_options(webdriver.Ie): - # Options is supported from Selenium 3.10 onwards + # options is supported from Selenium 3.10 onwards # If can be removed when minimum Selenium version is 3.10.0 or greater logger.warn('This version of Selenium does not support service_log_path argument.') return webdriver.Ie(options=options, **desired_capabilities) @@ -169,14 +169,21 @@ def _has_options(self, web_driver): signature = inspect.getargspec(web_driver.__init__) return True if 'options' in signature.args else False - def create_edge(self, desired_capabilities, remote_url, service_log_path=None): + def create_edge(self, desired_capabilities, remote_url, options=None, service_log_path=None): if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.EDGE.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) - if self._has_service_log_path(webdriver.Ie): + if self._has_options(webdriver.Edge) and self._has_service_log_path(webdriver.Edge): + # options is supported from Selenium 4.0 onwards + # If can be removed when minimum Selenium version is 4.0 or greater + return webdriver.Edge(options=options, service_log_path=service_log_path, **desired_capabilities) + if not self._has_options(webdriver.Edge) and self._has_service_log_path(webdriver.Edge): + # service_log_path is supported from Selenium 3.14 onwards + # If can be removed when minimum Selenium version is 3.14.0 or greater + logger.warn('This version of Selenium does not support options argument.') return webdriver.Edge(service_log_path=service_log_path, **desired_capabilities) - logger.warn('This version of Selenium does not support service_log_path argument.') + logger.warn('This version of Selenium does not support options and service_log_path argument.') return webdriver.Edge(**desired_capabilities) def create_opera(self, desired_capabilities, remote_url, service_log_path=None): diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index ae9c1ea27..cb7e41c44 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -1,3 +1,4 @@ +import inspect import unittest import os @@ -212,3 +213,14 @@ def test_has_options(self): self.assertFalse(self.creator._has_options(webdriver.Edge)) self.assertTrue(self.creator._has_options(webdriver.Opera)) self.assertFalse(self.creator._has_options(webdriver.Safari)) + + @unittest.skipIf('options' not in inspect.getargspec(webdriver.Edge.__init__), "requires Selenium 4.0") + def test_create_edge_with_options(self): + # TODO: This test requires Selenium 4.0 in Travis + options = mock() + expected_webdriver = mock() + when(self.creator)._has_service_log_path(ANY).thenReturn(True) + when(self.creator)._has_options(ANY).thenReturn(True) + when(webdriver).Edge(service_log_path=None, options=options).thenReturn(expected_webdriver) + driver = self.creator.create_edge({}, None, options=options) + self.assertEqual(driver, expected_webdriver) \ No newline at end of file From 43746c51dcd529ea787ad2190ecb4dc54b4c7240 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 6 Jun 2019 23:37:33 +0300 Subject: [PATCH 14/52] Fixed test for Python 2 --- utest/test/keywords/test_webdrivercreator.py | 3 +++ utest/test/keywords/test_webdrivercreator_service_log_path.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/utest/test/keywords/test_webdrivercreator.py b/utest/test/keywords/test_webdrivercreator.py index e2afbcbfb..8c5de690c 100644 --- a/utest/test/keywords/test_webdrivercreator.py +++ b/utest/test/keywords/test_webdrivercreator.py @@ -298,6 +298,7 @@ def test_ie(self): expected_webdriver = mock() when(webdriver).Ie().thenReturn(expected_webdriver) when(self.creator)._has_service_log_path(ANY).thenReturn(False) + when(self.creator)._has_options(ANY).thenReturn(False) driver = self.creator.create_ie({}, None) self.assertEqual(driver, expected_webdriver) @@ -339,6 +340,7 @@ def test_edge(self): expected_webdriver = mock() when(webdriver).Edge(service_log_path=None).thenReturn(expected_webdriver) when(self.creator)._has_service_log_path(ANY).thenReturn(True) + when(self.creator)._has_options(ANY).thenReturn(False) driver = self.creator.create_edge({}, None) self.assertEqual(driver, expected_webdriver) @@ -591,6 +593,7 @@ def test_create_driver_firefox(self): def test_create_driver_ie(self): expected_webdriver = mock() when(self.creator)._has_service_log_path(ANY).thenReturn(False) + when(self.creator)._has_options(ANY).thenReturn(False) when(webdriver).Ie().thenReturn(expected_webdriver) for browser in ['ie', 'Internet Explorer']: driver = self.creator.create_driver(browser, None, None) diff --git a/utest/test/keywords/test_webdrivercreator_service_log_path.py b/utest/test/keywords/test_webdrivercreator_service_log_path.py index 955f6a059..c4d60e2eb 100644 --- a/utest/test/keywords/test_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/test_webdrivercreator_service_log_path.py @@ -121,6 +121,7 @@ def test_create_ie_with_service_log_path_old_selenium(self): log_file = os.path.join(self.output_dir, 'ie-1.log') expected_webdriver = mock() when(self.creator)._has_service_log_path(ANY).thenReturn(False) + when(self.creator)._has_options(ANY).thenReturn(False) when(webdriver).Ie().thenReturn(expected_webdriver) driver = self.creator.create_ie({}, None, service_log_path=log_file) self.assertEqual(driver, expected_webdriver) @@ -135,6 +136,7 @@ def test_create_edge_with_service_log_path_real_path(self): log_file = os.path.join(self.output_dir, 'ie-1.log') expected_webdriver = mock() when(self.creator)._has_service_log_path(ANY).thenReturn(True) + when(self.creator)._has_options(ANY).thenReturn(False) when(webdriver).Edge(service_log_path=log_file).thenReturn(expected_webdriver) driver = self.creator.create_edge({}, None, service_log_path=log_file) self.assertEqual(driver, expected_webdriver) @@ -143,6 +145,7 @@ def test_create_edge_with_service_log_path_old_selenium(self): log_file = os.path.join(self.output_dir, 'ie-1.log') expected_webdriver = mock() when(self.creator)._has_service_log_path(ANY).thenReturn(False) + when(self.creator)._has_options(ANY).thenReturn(False) when(webdriver).Edge().thenReturn(expected_webdriver) driver = self.creator.create_edge({}, None, service_log_path=log_file) self.assertEqual(driver, expected_webdriver) From 0dffd43e8a35a8c46b2ebeb5ec15df865cc4abd2 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 8 Jun 2019 23:31:54 +0300 Subject: [PATCH 15/52] Selenium options for Opera --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 ++-- utest/test/keywords/test_selenium_options_parser.py | 9 ++++++++- utest/test/keywords/test_webdrivercreator.py | 2 +- .../keywords/test_webdrivercreator_service_log_path.py | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 33422834d..44491cfe3 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -186,12 +186,12 @@ def create_edge(self, desired_capabilities, remote_url, options=None, service_lo logger.warn('This version of Selenium does not support options and service_log_path argument.') return webdriver.Edge(**desired_capabilities) - def create_opera(self, desired_capabilities, remote_url, service_log_path=None): + def create_opera(self, desired_capabilities, remote_url, options=None, service_log_path=None): if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.OPERA.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) - return webdriver.Opera(service_log_path=service_log_path, **desired_capabilities) + return webdriver.Opera(options=options, service_log_path=service_log_path, **desired_capabilities) def create_safari(self, desired_capabilities, remote_url): if is_truthy(remote_url): diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index cb7e41c44..9d4dd0c22 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -223,4 +223,11 @@ def test_create_edge_with_options(self): when(self.creator)._has_options(ANY).thenReturn(True) when(webdriver).Edge(service_log_path=None, options=options).thenReturn(expected_webdriver) driver = self.creator.create_edge({}, None, options=options) - self.assertEqual(driver, expected_webdriver) \ No newline at end of file + self.assertEqual(driver, expected_webdriver) + + def test_create_opera_with_options(self): + options = mock() + expected_webdriver = mock() + when(webdriver).Opera(options=options, service_log_path=None).thenReturn(expected_webdriver) + driver = self.creator.create_opera({}, None, options=options) + self.assertEqual(driver, expected_webdriver) diff --git a/utest/test/keywords/test_webdrivercreator.py b/utest/test/keywords/test_webdrivercreator.py index 8c5de690c..2f020fcf3 100644 --- a/utest/test/keywords/test_webdrivercreator.py +++ b/utest/test/keywords/test_webdrivercreator.py @@ -376,7 +376,7 @@ def test_edge_no_browser_name(self): def test_opera(self): expected_webdriver = mock() - when(webdriver).Opera(service_log_path=None).thenReturn(expected_webdriver) + when(webdriver).Opera(options=None, service_log_path=None).thenReturn(expected_webdriver) driver = self.creator.create_opera({}, None) self.assertEqual(driver, expected_webdriver) diff --git a/utest/test/keywords/test_webdrivercreator_service_log_path.py b/utest/test/keywords/test_webdrivercreator_service_log_path.py index c4d60e2eb..a28708413 100644 --- a/utest/test/keywords/test_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/test_webdrivercreator_service_log_path.py @@ -153,7 +153,7 @@ def test_create_edge_with_service_log_path_old_selenium(self): def test_create_opera_with_service_log_path_real_path(self): log_file = os.path.join(self.output_dir, 'ie-1.log') expected_webdriver = mock() - when(webdriver).Opera(service_log_path=log_file).thenReturn(expected_webdriver) + when(webdriver).Opera(options=None, service_log_path=log_file).thenReturn(expected_webdriver) driver = self.creator.create_opera({}, None, service_log_path=log_file) self.assertEqual(driver, expected_webdriver) From 0473aeec624b9cf3e091b3420ee62e395754ecc3 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 8 Jun 2019 23:38:35 +0300 Subject: [PATCH 16/52] Safari does not support Selenium options --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 +++- utest/test/keywords/test_selenium_options_parser.py | 7 +++++++ .../keywords/test_webdrivercreator_service_log_path.py | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 44491cfe3..4c4632d9f 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -193,11 +193,13 @@ def create_opera(self, desired_capabilities, remote_url, options=None, service_l return self._remote(desired_capabilities, remote_url) return webdriver.Opera(options=options, service_log_path=service_log_path, **desired_capabilities) - def create_safari(self, desired_capabilities, remote_url): + def create_safari(self, desired_capabilities, remote_url, options=None, service_log_path=None): if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.SAFARI.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) + if options or service_log_path: + logger.warn('Safari browser does not support Selenium options and service_log_path.') return webdriver.Safari(**desired_capabilities) def create_phantomjs(self, desired_capabilities, remote_url, service_log_path=None): diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 9d4dd0c22..f668864a7 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -231,3 +231,10 @@ def test_create_opera_with_options(self): when(webdriver).Opera(options=options, service_log_path=None).thenReturn(expected_webdriver) driver = self.creator.create_opera({}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_create_safari_no_options_or_service_log_path_support(self): + options = mock() + expected_webdriver = mock() + when(webdriver).Safari().thenReturn(expected_webdriver) + driver = self.creator.create_safari({}, None, options=options) + self.assertEqual(driver, expected_webdriver) diff --git a/utest/test/keywords/test_webdrivercreator_service_log_path.py b/utest/test/keywords/test_webdrivercreator_service_log_path.py index a28708413..b6228c226 100644 --- a/utest/test/keywords/test_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/test_webdrivercreator_service_log_path.py @@ -157,6 +157,13 @@ def test_create_opera_with_service_log_path_real_path(self): driver = self.creator.create_opera({}, None, service_log_path=log_file) self.assertEqual(driver, expected_webdriver) + def test_create_safari_no_support_for_service_log_path(self): + log_file = os.path.join(self.output_dir, 'ie-1.log') + expected_webdriver = mock() + when(webdriver).Safari().thenReturn(expected_webdriver) + driver = self.creator.create_safari({}, None, service_log_path=log_file) + self.assertEqual(driver, expected_webdriver) + def test_create_phantomjs_with_service_log_path_real_path(self): log_file = os.path.join(self.output_dir, 'ie-1.log') expected_webdriver = mock() From c14343f80f0976da1e6bae21509fb37f0878af60 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 8 Jun 2019 23:44:37 +0300 Subject: [PATCH 17/52] PhantomJS does not support Selenium options --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 +++- utest/test/keywords/test_selenium_options_parser.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 4c4632d9f..be3e9264b 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -202,13 +202,15 @@ def create_safari(self, desired_capabilities, remote_url, options=None, service_ logger.warn('Safari browser does not support Selenium options and service_log_path.') return webdriver.Safari(**desired_capabilities) - def create_phantomjs(self, desired_capabilities, remote_url, service_log_path=None): + def create_phantomjs(self, desired_capabilities, remote_url, options=None, service_log_path=None): warnings.warn('SeleniumLibrary support for PhantomJS has been deprecated, ' 'please use headlesschrome or headlessfirefox instead.') if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.PHANTOMJS.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) + if options: + logger.warn('PhantomJS browser does not support Selenium options.') return webdriver.PhantomJS(service_log_path=service_log_path, **desired_capabilities) def create_htmlunit(self, desired_capabilities, remote_url, service_log_path=None): diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index f668864a7..fcf6ce497 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -232,9 +232,16 @@ def test_create_opera_with_options(self): driver = self.creator.create_opera({}, None, options=options) self.assertEqual(driver, expected_webdriver) - def test_create_safari_no_options_or_service_log_path_support(self): + def test_create_safari_no_options_support(self): options = mock() expected_webdriver = mock() when(webdriver).Safari().thenReturn(expected_webdriver) driver = self.creator.create_safari({}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_create_phantomjs_no_options_support(self): + options = mock() + expected_webdriver = mock() + when(webdriver).PhantomJS(service_log_path=None).thenReturn(expected_webdriver) + driver = self.creator.create_phantomjs({}, None, options=options) + self.assertEqual(driver, expected_webdriver) From 575c5d4ac31ff737a47d77221e6e5a9e92e2b6d1 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 8 Jun 2019 23:51:32 +0300 Subject: [PATCH 18/52] Htmlunit does not support Selenium options --- src/SeleniumLibrary/keywords/webdrivertools.py | 12 ++++++------ utest/test/keywords/test_selenium_options_parser.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index be3e9264b..8fa90772a 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -158,7 +158,7 @@ def create_ie(self, desired_capabilities, remote_url, options=None, service_log_ # If can be removed when minimum Selenium version is 3.10.0 or greater logger.warn('This version of Selenium does not support service_log_path argument.') return webdriver.Ie(options=options, **desired_capabilities) - logger.warn('This version of Selenium does not support options and service_log_path argument.') + logger.warn('This version of Selenium does not support options or service_log_path argument.') return webdriver.Ie(**desired_capabilities) def _has_service_log_path(self, web_driver): @@ -183,7 +183,7 @@ def create_edge(self, desired_capabilities, remote_url, options=None, service_lo # If can be removed when minimum Selenium version is 3.14.0 or greater logger.warn('This version of Selenium does not support options argument.') return webdriver.Edge(service_log_path=service_log_path, **desired_capabilities) - logger.warn('This version of Selenium does not support options and service_log_path argument.') + logger.warn('This version of Selenium does not support options or service_log_path argument.') return webdriver.Edge(**desired_capabilities) def create_opera(self, desired_capabilities, remote_url, options=None, service_log_path=None): @@ -199,7 +199,7 @@ def create_safari(self, desired_capabilities, remote_url, options=None, service_ desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) if options or service_log_path: - logger.warn('Safari browser does not support Selenium options and service_log_path.') + logger.warn('Safari browser does not support Selenium options or service_log_path.') return webdriver.Safari(**desired_capabilities) def create_phantomjs(self, desired_capabilities, remote_url, options=None, service_log_path=None): @@ -213,9 +213,9 @@ def create_phantomjs(self, desired_capabilities, remote_url, options=None, servi logger.warn('PhantomJS browser does not support Selenium options.') return webdriver.PhantomJS(service_log_path=service_log_path, **desired_capabilities) - def create_htmlunit(self, desired_capabilities, remote_url, service_log_path=None): - if service_log_path: - logger.warn('Htmlunit does not support service_log_path argument.') + def create_htmlunit(self, desired_capabilities, remote_url, options=None, service_log_path=None): + if service_log_path or options: + logger.warn('Htmlunit does not support Selenium options or service_log_path argument.') defaul_caps = webdriver.DesiredCapabilities.HTMLUNIT.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index fcf6ce497..8cd42e194 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -245,3 +245,14 @@ def test_create_phantomjs_no_options_support(self): when(webdriver).PhantomJS(service_log_path=None).thenReturn(expected_webdriver) driver = self.creator.create_phantomjs({}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_create_htmlunit_no_options_support(self): + caps = webdriver.DesiredCapabilities.HTMLUNIT.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor='None', + desired_capabilities=caps, + browser_profile=None, + options=None).thenReturn(expected_webdriver) + driver = self.creator.create_htmlunit({'desired_capabilities': caps}, None, options=options) + self.assertEqual(driver, expected_webdriver) From ca553837b1ffcb0eecd60198a7b2b2631d75ffbe Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 8 Jun 2019 23:56:37 +0300 Subject: [PATCH 19/52] Htmlunit with JS does not support Selenium options --- src/SeleniumLibrary/keywords/webdrivertools.py | 6 +++--- utest/test/keywords/test_selenium_options_parser.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 8fa90772a..cd48e053d 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -220,9 +220,9 @@ def create_htmlunit(self, desired_capabilities, remote_url, options=None, servic desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) - def create_htmlunit_with_js(self, desired_capabilities, remote_url, service_log_path=None): - if service_log_path: - logger.warn('Htmlunit does not support service_log_path argument.') + def create_htmlunit_with_js(self, desired_capabilities, remote_url, options=None, service_log_path=None): + if service_log_path or options: + logger.warn('Htmlunit with JS does not support Selenium options or service_log_path argument.') defaul_caps = webdriver.DesiredCapabilities.HTMLUNITWITHJS.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 8cd42e194..c4f6c1946 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -256,3 +256,14 @@ def test_create_htmlunit_no_options_support(self): options=None).thenReturn(expected_webdriver) driver = self.creator.create_htmlunit({'desired_capabilities': caps}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_create_htmlunit_wiht_js_no_options_support(self): + caps = webdriver.DesiredCapabilities.HTMLUNITWITHJS.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor='None', + desired_capabilities=caps, + browser_profile=None, + options=None).thenReturn(expected_webdriver) + driver = self.creator.create_htmlunit_with_js({}, None, options=options) + self.assertEqual(driver, expected_webdriver) From f3113d31ff3a1ce1806bee9fb9b26833154f7a62 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 9 Jun 2019 01:37:09 +0300 Subject: [PATCH 20/52] Added Android to support options and fixed htmlunit also to support options --- src/SeleniumLibrary/keywords/webdrivertools.py | 10 +++++----- .../keywords/test_selenium_options_parser.py | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index cd48e053d..3eae599cc 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -218,21 +218,21 @@ def create_htmlunit(self, desired_capabilities, remote_url, options=None, servic logger.warn('Htmlunit does not support Selenium options or service_log_path argument.') defaul_caps = webdriver.DesiredCapabilities.HTMLUNIT.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) - return self._remote(desired_capabilities, remote_url) + return self._remote(desired_capabilities, remote_url, options=options) def create_htmlunit_with_js(self, desired_capabilities, remote_url, options=None, service_log_path=None): if service_log_path or options: - logger.warn('Htmlunit with JS does not support Selenium options or service_log_path argument.') + logger.warn('Htmlunit with JS does not support service_log_path argument.') defaul_caps = webdriver.DesiredCapabilities.HTMLUNITWITHJS.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) - return self._remote(desired_capabilities, remote_url) + return self._remote(desired_capabilities, remote_url, options=options) - def create_android(self, desired_capabilities, remote_url, service_log_path=None): + def create_android(self, desired_capabilities, remote_url, options=None, service_log_path=None): if service_log_path: logger.warn('Android does not support service_log_path argument.') defaul_caps = webdriver.DesiredCapabilities.ANDROID.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) - return self._remote(desired_capabilities, remote_url) + return self._remote(desired_capabilities, remote_url, options=options) def create_iphone(self, desired_capabilities, remote_url, service_log_path=None): if service_log_path: diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index c4f6c1946..81ec8371e 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -253,17 +253,28 @@ def test_create_htmlunit_no_options_support(self): when(webdriver).Remote(command_executor='None', desired_capabilities=caps, browser_profile=None, - options=None).thenReturn(expected_webdriver) + options=options).thenReturn(expected_webdriver) driver = self.creator.create_htmlunit({'desired_capabilities': caps}, None, options=options) self.assertEqual(driver, expected_webdriver) - def test_create_htmlunit_wiht_js_no_options_support(self): + def test_create_htmlunit_with_js_no_options_support(self): caps = webdriver.DesiredCapabilities.HTMLUNITWITHJS.copy() options = mock() expected_webdriver = mock() when(webdriver).Remote(command_executor='None', desired_capabilities=caps, browser_profile=None, - options=None).thenReturn(expected_webdriver) + options=options).thenReturn(expected_webdriver) driver = self.creator.create_htmlunit_with_js({}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_android_no_options_support(self): + caps = webdriver.DesiredCapabilities.ANDROID + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor='None', + desired_capabilities=caps, + browser_profile=None, + options=options).thenReturn(expected_webdriver) + driver = self.creator.create_android({}, None, options=options) + self.assertEqual(driver, expected_webdriver) From cd2ff5eda100b12efdab896c37923b32d7b1f5ab Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 9 Jun 2019 01:49:39 +0300 Subject: [PATCH 21/52] Added missing tests --- .../keywords/test_selenium_options_parser.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 81ec8371e..ac8b8af2a 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -159,6 +159,18 @@ def test_create_chrome_with_options(self): driver = self.creator.create_chrome({}, None, options=options) self.assertEqual(driver, expected_webdriver) + def test_create_chrome_with_options_and_remote_url(self): + url = 'http://localhost:4444/wd/hub' + caps = webdriver.DesiredCapabilities.CHROME.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor=url, + desired_capabilities=caps, + browser_profile=None, + options=options).thenReturn(expected_webdriver) + driver = self.creator.create_chrome({}, url, options=options) + self.assertEqual(driver, expected_webdriver) + def test_create_headless_chrome_with_options(self): options = mock() expected_webdriver = mock() @@ -177,6 +189,20 @@ def test_create_firefox_with_options(self): driver = self.creator.create_firefox({}, None, None, options=options) self.assertEqual(driver, expected_webdriver) + def test_create_firefox_with_options_and_remote_url(self): + url = 'http://localhost:4444/wd/hub' + profile = mock() + when(webdriver).FirefoxProfile().thenReturn(profile) + caps = webdriver.DesiredCapabilities.FIREFOX.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor=url, + desired_capabilities=caps, + browser_profile=profile, + options=options).thenReturn(expected_webdriver) + driver = self.creator.create_firefox({}, url, None, options=options) + self.assertEqual(driver, expected_webdriver) + def test_create_headless_firefox_with_options(self): log_file = os.path.join(self.output_dir, 'geckodriver-1.log') options = mock() @@ -269,7 +295,7 @@ def test_create_htmlunit_with_js_no_options_support(self): self.assertEqual(driver, expected_webdriver) def test_android_no_options_support(self): - caps = webdriver.DesiredCapabilities.ANDROID + caps = webdriver.DesiredCapabilities.ANDROID.copy() options = mock() expected_webdriver = mock() when(webdriver).Remote(command_executor='None', From ac5b3c7bfc137b8e00f7aa863eb4550f392a3498 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 11 Jun 2019 22:40:09 +0300 Subject: [PATCH 22/52] Remote URL and options with IE --- src/SeleniumLibrary/keywords/webdrivertools.py | 2 +- .../test/keywords/test_selenium_options_parser.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 3eae599cc..fbdef4d1a 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -148,7 +148,7 @@ def create_ie(self, desired_capabilities, remote_url, options=None, service_log_ if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.INTERNETEXPLORER.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) - return self._remote(desired_capabilities, remote_url) + return self._remote(desired_capabilities, remote_url, options=options) if self._has_service_log_path(webdriver.Ie) and self._has_options(webdriver.Ie): # service_log_path is supported from Selenium 3.14 onwards # If can be removed when minimum Selenium version is 3.14.0 or greater diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index ac8b8af2a..073aaf592 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -223,6 +223,18 @@ def test_create_ie_with_options(self): driver = self.creator.create_ie({}, None, options=options) self.assertEqual(driver, expected_webdriver) + def test_create_ie_with_options_and_remote_url(self): + url = 'http://localhost:4444/wd/hub' + caps = webdriver.DesiredCapabilities.INTERNETEXPLORER.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor=url, + desired_capabilities=caps, + browser_profile=None, + options=options).thenReturn(expected_webdriver) + driver = self.creator.create_ie({}, url, options=options) + self.assertEqual(driver, expected_webdriver) + def test_create_ie_with_options_and_log_path(self): options = mock() expected_webdriver = mock() @@ -240,7 +252,7 @@ def test_has_options(self): self.assertTrue(self.creator._has_options(webdriver.Opera)) self.assertFalse(self.creator._has_options(webdriver.Safari)) - @unittest.skipIf('options' not in inspect.getargspec(webdriver.Edge.__init__), "requires Selenium 4.0") + @unittest.skipIf('options' not in inspect.getargspec(webdriver.Edge.__init__), "Requires Selenium 4.0.") def test_create_edge_with_options(self): # TODO: This test requires Selenium 4.0 in Travis options = mock() From b6e6d38c0736e4a8e41a162e03b82da0b4465dbb Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 11 Jun 2019 22:44:59 +0300 Subject: [PATCH 23/52] Remote URL and options with Opera --- src/SeleniumLibrary/keywords/webdrivertools.py | 2 +- utest/test/keywords/test_selenium_options_parser.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index fbdef4d1a..18d9d7e51 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -190,7 +190,7 @@ def create_opera(self, desired_capabilities, remote_url, options=None, service_l if is_truthy(remote_url): defaul_caps = webdriver.DesiredCapabilities.OPERA.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) - return self._remote(desired_capabilities, remote_url) + return self._remote(desired_capabilities, remote_url, options=options) return webdriver.Opera(options=options, service_log_path=service_log_path, **desired_capabilities) def create_safari(self, desired_capabilities, remote_url, options=None, service_log_path=None): diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 073aaf592..02908772f 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -270,6 +270,18 @@ def test_create_opera_with_options(self): driver = self.creator.create_opera({}, None, options=options) self.assertEqual(driver, expected_webdriver) + def test_create_opera_with_options_and_remote_url(self): + url = 'http://localhost:4444/wd/hub' + caps = webdriver.DesiredCapabilities.OPERA.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor=url, + desired_capabilities=caps, + browser_profile=None, + options=options).thenReturn(expected_webdriver) + driver = self.creator.create_opera({}, url, options=options) + self.assertEqual(driver, expected_webdriver) + def test_create_safari_no_options_support(self): options = mock() expected_webdriver = mock() From 27dc8c1402d6c77cef3e0a6201f4ffc00a197fe6 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 11 Jun 2019 22:53:16 +0300 Subject: [PATCH 24/52] Improved test --- ...iumOptionsParserTests.test_parse_options_string.approved.txt | 2 ++ utest/test/keywords/test_selenium_options_parser.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt index b78fddf88..e0039078a 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt @@ -8,3 +8,5 @@ Selenium options string to dict 5) [{'method': []}, {'method': []}] 6) [{'add_argument': ['--disable-dev-shm-usage']}] 7) [{'add_argument': ['--proxy-server=66.97.38.58:80']}] +8) [{'add_argument': ['--arg_with_\\_one_time']}] +9) [{'add_argument': ['--arg_with_\\\\_two_times']}] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 02908772f..1306f06e1 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -43,6 +43,8 @@ def test_parse_options_string(self): self.results.append(self.options._parse('method,method')) self.results.append(self.options._parse('add_argument:--disable-dev-shm-usage')) self.results.append(self.options._parse(r'add_argument:--proxy-server=66.97.38.58\:80')) + self.results.append(self.options._parse(r'add_argument:--arg_with_\_one_time')) + self.results.append(self.options._parse(r'add_argument:--arg_with_\\_two_times')) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 8f513762cb30c651b6ef60312fcaa7706a01df4b Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 11 Jun 2019 23:24:32 +0300 Subject: [PATCH 25/52] Add support also for Selenium object instance --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 +++- ...leniumOptionsParserTests.test_options_create.approved.txt | 1 + utest/test/keywords/test_selenium_options_parser.py | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 18d9d7e51..9a0517bd1 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -338,8 +338,10 @@ def _get_index(self, alias_or_index): class SeleniumOptions(object): def create(self, browser, options): - options = self._parse(options) selenium_options = self._import_options(browser) + if isinstance(options, selenium_options): + return options + options = self._parse(options) selenium_options = selenium_options() for option in options: for key in option: diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt index 21a92c8e4..0cb5ed489 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt @@ -3,3 +3,4 @@ Selenium options 0) ['--disable-dev-shm-usage'] 1) ['--disable-dev-shm-usage', '--headless'] 2) ['--disable-dev-shm-usage', '--headless', '--proxy-server=66.97.38.58:80'] +3) ['--disable-dev-shm-usage'] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 1306f06e1..a293340e2 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -79,6 +79,11 @@ def test_options_create(self): sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) + chrome_options = webdriver.ChromeOptions() + chrome_options.add_argument('--disable-dev-shm-usage') + sel_options = self.options.create('chrome', chrome_options) + self.results.append(sel_options.arguments) + verify_all('Selenium options', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 076709778dbdc336d8746891b11c8e664377bbc4 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 13 Jun 2019 23:05:53 +0300 Subject: [PATCH 26/52] Improved attribute settting --- src/SeleniumLibrary/keywords/webdrivertools.py | 2 +- ...eniumOptionsParserTests.test_options_create.approved.txt | 3 ++- utest/test/keywords/test_selenium_options_parser.py | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 9a0517bd1..9fcb319bb 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -349,7 +349,7 @@ def create(self, browser, options): if callable(attr): attr(*option[key]) else: - setattr(selenium_options, key, option[key][0]) + setattr(selenium_options, key, *option[key]) return selenium_options def _parse(self, options): diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt index 0cb5ed489..4902d3ef2 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt @@ -3,4 +3,5 @@ Selenium options 0) ['--disable-dev-shm-usage'] 1) ['--disable-dev-shm-usage', '--headless'] 2) ['--disable-dev-shm-usage', '--headless', '--proxy-server=66.97.38.58:80'] -3) ['--disable-dev-shm-usage'] +3) setattr expected 3 arguments, got 5 +4) ['--disable-dev-shm-usage'] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index a293340e2..5eae7135d 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -79,6 +79,12 @@ def test_options_create(self): sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) + options.append({'binary_location': ['too', 'many', 'args']}) + try: + self.options.create('chrome', options) + except Exception as error: + self.results.append(error) + chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--disable-dev-shm-usage') sel_options = self.options.create('chrome', chrome_options) From 5bc3e80b7a38db00b72fc639252912728cc91d8e Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 13 Jun 2019 23:13:21 +0300 Subject: [PATCH 27/52] Options for iPhone --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 ++-- utest/test/keywords/test_selenium_options_parser.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 9fcb319bb..f9ad6e498 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -234,12 +234,12 @@ def create_android(self, desired_capabilities, remote_url, options=None, service desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) return self._remote(desired_capabilities, remote_url, options=options) - def create_iphone(self, desired_capabilities, remote_url, service_log_path=None): + def create_iphone(self, desired_capabilities, remote_url, options=None, service_log_path=None): if service_log_path: logger.warn('iPhone does not support service_log_path argument.') defaul_caps = webdriver.DesiredCapabilities.IPHONE.copy() desired_capabilities = self._remote_capabilities_resolver(desired_capabilities, defaul_caps) - return self._remote(desired_capabilities, remote_url) + return self._remote(desired_capabilities, remote_url, options=options) def _remote(self, desired_capabilities, remote_url, profile_dir=None, options=None): diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 5eae7135d..8ed4acafc 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -331,7 +331,7 @@ def test_create_htmlunit_with_js_no_options_support(self): driver = self.creator.create_htmlunit_with_js({}, None, options=options) self.assertEqual(driver, expected_webdriver) - def test_android_no_options_support(self): + def test_android_options_support(self): caps = webdriver.DesiredCapabilities.ANDROID.copy() options = mock() expected_webdriver = mock() @@ -341,3 +341,14 @@ def test_android_no_options_support(self): options=options).thenReturn(expected_webdriver) driver = self.creator.create_android({}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_iphone_options_support(self): + caps = webdriver.DesiredCapabilities.IPHONE.copy() + options = mock() + expected_webdriver = mock() + when(webdriver).Remote(command_executor='None', + desired_capabilities=caps, + browser_profile=None, + options=options).thenReturn(expected_webdriver) + driver = self.creator.create_iphone({}, None, options=options) + self.assertEqual(driver, expected_webdriver) From f668697eb93f4b3d3f0122e21560c36984381658 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 15 Jun 2019 00:21:54 +0300 Subject: [PATCH 28/52] Options support for WebDriverCreator --- .../keywords/webdrivertools.py | 14 ++++++--- ...rserTests.test_options_create.approved.txt | 2 ++ .../keywords/test_selenium_options_parser.py | 30 +++++++++++++++++++ utest/test/keywords/test_webdrivercreator.py | 15 +++++++--- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index f9ad6e498..d0e8fbdcd 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -54,20 +54,21 @@ def __init__(self, log_dir): def create_driver(self, browser, desired_capabilities, remote_url, profile_dir=None, options=None, service_log_path=None): + 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) if service_log_path: logger.info('Browser driver log file created to: %s' % service_log_path) self._create_directory(service_log_path) if (creation_method == self.create_firefox or creation_method == self.create_headless_firefox): - return creation_method(desired_capabilities, remote_url, - profile_dir, service_log_path=service_log_path) - return creation_method(desired_capabilities, remote_url, service_log_path=service_log_path) + return creation_method(desired_capabilities, remote_url, profile_dir, + options=options, service_log_path=service_log_path) + return creation_method(desired_capabilities, remote_url, options=options, service_log_path=service_log_path) def _get_creator_method(self, browser): - browser = browser.lower().replace(' ', '') if browser in self.browser_names: return getattr(self, 'create_{}'.format(self.browser_names[browser])) raise ValueError('{} is not a supported browser.'.format(browser)) @@ -265,6 +266,9 @@ def _create_directory(self, path): if not os.path.exists(target_dir): os.makedirs(target_dir) + def _normalise_browser_name(self, browser): + return browser.lower().replace(' ', '') + class WebDriverCache(ConnectionCache): @@ -338,6 +342,8 @@ def _get_index(self, alias_or_index): class SeleniumOptions(object): def create(self, browser, options): + if is_falsy(options): + return None selenium_options = self._import_options(browser) if isinstance(options, selenium_options): return options diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt index 4902d3ef2..27486d772 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt @@ -5,3 +5,5 @@ Selenium options 2) ['--disable-dev-shm-usage', '--headless', '--proxy-server=66.97.38.58:80'] 3) setattr expected 3 arguments, got 5 4) ['--disable-dev-shm-usage'] +5) None +6) None diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 8ed4acafc..a652d3a7e 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -90,6 +90,12 @@ def test_options_create(self): sel_options = self.options.create('chrome', chrome_options) self.results.append(sel_options.arguments) + sel_options = self.options.create('chrome', None) + self.results.append(sel_options) + + sel_options = self.options.create('chrome', 'None') + self.results.append(sel_options) + verify_all('Selenium options', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -352,3 +358,27 @@ def test_iphone_options_support(self): options=options).thenReturn(expected_webdriver) driver = self.creator.create_iphone({}, None, options=options) self.assertEqual(driver, expected_webdriver) + + def test_create_driver_chrome(self): + str_options = 'add_argument:--disable-dev-shm-usage' + options = mock() + expected_webdriver = mock() + when(self.creator.selenium_options).create('chrome', str_options).thenReturn(options) + when(webdriver).Chrome(service_log_path=None, options=options).thenReturn(expected_webdriver) + driver = self.creator.create_driver('Chrome', desired_capabilities={}, remote_url=None, + options=str_options) + self.assertEqual(driver, expected_webdriver) + + def test_create_driver_firefox(self): + log_file = os.path.join(self.output_dir, 'geckodriver-1.log') + str_options = 'add_argument:--disable-dev-shm-usage' + options = mock() + profile = mock() + when(webdriver).FirefoxProfile().thenReturn(profile) + expected_webdriver = mock() + when(self.creator.selenium_options).create('firefox', str_options).thenReturn(options) + when(webdriver).Firefox(options=options, firefox_profile=profile, + service_log_path=log_file).thenReturn(expected_webdriver) + driver = self.creator.create_driver('FireFox', desired_capabilities={}, remote_url=None, + options=str_options) + self.assertEqual(driver, expected_webdriver) diff --git a/utest/test/keywords/test_webdrivercreator.py b/utest/test/keywords/test_webdrivercreator.py index 2f020fcf3..7ae40e58a 100644 --- a/utest/test/keywords/test_webdrivercreator.py +++ b/utest/test/keywords/test_webdrivercreator.py @@ -17,14 +17,21 @@ def setUpClass(cls): def tearDown(self): unstub() + def test_normalise_browser_name(self): + browser = self.creator._normalise_browser_name('chrome') + self.assertEqual(browser, 'chrome') + + browser = self.creator._normalise_browser_name('ChrOmE') + self.assertEqual(browser, 'chrome') + + browser = self.creator._normalise_browser_name(' Ch rO mE ') + self.assertEqual(browser, 'chrome') + def test_get_creator_method(self): method = self.creator._get_creator_method('chrome') self.assertTrue(method) - method = self.creator._get_creator_method('Chrome') - self.assertTrue(method) - - method = self.creator._get_creator_method('Fire Fox') + method = self.creator._get_creator_method('firefox') self.assertTrue(method) with self.assertRaisesRegexp(ValueError, 'foobar is not a supported browser.'): From 4456616e249e83cda4687d915dd35b6be52648db Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 15 Jun 2019 01:04:55 +0300 Subject: [PATCH 29/52] Fix for pypy35 --- .../SeleniumOptionsParserTests.test_options_create.approved.txt | 2 +- utest/test/keywords/test_selenium_options_parser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt index 27486d772..6c55e276f 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create.approved.txt @@ -3,7 +3,7 @@ Selenium options 0) ['--disable-dev-shm-usage'] 1) ['--disable-dev-shm-usage', '--headless'] 2) ['--disable-dev-shm-usage', '--headless', '--proxy-server=66.97.38.58:80'] -3) setattr expected 3 arguments, got 5 +3) setattr 4) ['--disable-dev-shm-usage'] 5) None 6) None diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index a652d3a7e..af83ff32e 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -83,7 +83,7 @@ def test_options_create(self): try: self.options.create('chrome', options) except Exception as error: - self.results.append(error) + self.results.append(error.__str__()[:7]) chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--disable-dev-shm-usage') From 9b003d71fe8232374a8debe8a728e93a2b7d3646 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 15 Jun 2019 01:33:46 +0300 Subject: [PATCH 30/52] Support for Selenium options in Open Browser keyword --- .../multiple_browsers_options.robot | 33 +++++++++++++++++++ .../testlibs/get_selenium_options.py | 7 ++++ .../keywords/browsermanagement.py | 16 ++++----- ...est_keyword_arguments_browsermanagement.py | 4 +-- 4 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 atest/acceptance/multiple_browsers_options.robot create mode 100644 atest/resources/testlibs/get_selenium_options.py diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot new file mode 100644 index 000000000..dc4244334 --- /dev/null +++ b/atest/acceptance/multiple_browsers_options.robot @@ -0,0 +1,33 @@ +*** Settings *** +Suite Teardown Close All Browsers +Library ../resources/testlibs/get_selenium_options.py +Resource resource.robot +Documentation Creating test which would work on all browser is not possible. When testing with other +... browser than Chrome it is OK that these test will fail. SeleniumLibrary CI is run with Chrome only +... and therefore there is tests for Chrome only. + +*** Test Cases *** +Chrome Browser With Selenium Options As String + [Documentation] + ... LOG 1:2 DEBUG GLOB: *"goog:chromeOptions"* + ... LOG 1:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} + ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument:--disable-dev-shm-usage + +Chrome Browser With Selenium Options List + [Documentation] + ... LOG 4:2 DEBUG GLOB: *"goog:chromeOptions"* + ... LOG 4:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + ${argument} = Create List --disable-dev-shm-usage + ${add_argument} = Create Dictionary add_argument ${argument} + ${options} = Create List ${add_argument} + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} + ... desired_capabilities=${DESIRED_CAPABILITIES} options=${options} + +Chrome Browser With Selenium Options Object + [Documentation] + ... LOG 2:2 DEBUG GLOB: *"goog:chromeOptions"* + ... LOG 2:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + ${options} = Get Chrome Options + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} + ... desired_capabilities=${DESIRED_CAPABILITIES} options=${options} diff --git a/atest/resources/testlibs/get_selenium_options.py b/atest/resources/testlibs/get_selenium_options.py new file mode 100644 index 000000000..8cc386578 --- /dev/null +++ b/atest/resources/testlibs/get_selenium_options.py @@ -0,0 +1,7 @@ +from selenium import webdriver + + +def get_chrome_options(): + options = webdriver.ChromeOptions() + options.add_argument('--disable-dev-shm-usage') + return options \ No newline at end of file diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 9d4c6d894..f8aed8b05 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -58,7 +58,7 @@ def close_browser(self): @keyword def open_browser(self, url, browser='firefox', alias=None, remote_url=False, desired_capabilities=None, - ff_profile_dir=None, service_log_path=None): + ff_profile_dir=None, options=None, service_log_path=None): """Opens a new browser instance to the given ``url``. The ``browser`` argument specifies which browser to use, and the @@ -163,11 +163,11 @@ def open_browser(self, url, browser='firefox', alias=None, return index return self._make_new_browser(url, browser, alias, remote_url, desired_capabilities, ff_profile_dir, - service_log_path) + options, service_log_path) def _make_new_browser(self, url, browser='firefox', alias=None, remote_url=False, desired_capabilities=None, - ff_profile_dir=None, service_log_path=None): + ff_profile_dir=None, options=None, service_log_path=None): if is_truthy(remote_url): self.info("Opening browser '%s' to base url '%s' through " "remote server at '%s'." % (browser, url, remote_url)) @@ -175,7 +175,7 @@ def _make_new_browser(self, url, browser='firefox', alias=None, self.info("Opening browser '%s' to base url '%s'." % (browser, url)) driver = self._make_driver(browser, desired_capabilities, ff_profile_dir, remote_url, - service_log_path) + options, service_log_path) driver = self._wrap_event_firing_webdriver(driver) try: driver.get(url) @@ -504,11 +504,11 @@ def set_browser_implicit_wait(self, value): """ self.driver.implicitly_wait(timestr_to_secs(value)) - def _make_driver(self, browser, desired_capabilities=None, - profile_dir=None, remote=None, service_log_path=None): + def _make_driver(self, browser, desired_capabilities=None, profile_dir=None, + remote=None, options=None, service_log_path=None): driver = WebDriverCreator(self.log_dir).create_driver( - browser=browser, desired_capabilities=desired_capabilities, - remote_url=remote, profile_dir=profile_dir, service_log_path=service_log_path) + browser=browser, desired_capabilities=desired_capabilities, remote_url=remote, + profile_dir=profile_dir, options=options, service_log_path=service_log_path) driver.set_script_timeout(self.ctx.timeout) driver.implicitly_wait(self.ctx.implicit_wait) if self.ctx.speed: diff --git a/utest/test/keywords/test_keyword_arguments_browsermanagement.py b/utest/test/keywords/test_keyword_arguments_browsermanagement.py index b2daef532..5cda8a556 100644 --- a/utest/test/keywords/test_keyword_arguments_browsermanagement.py +++ b/utest/test/keywords/test_keyword_arguments_browsermanagement.py @@ -22,12 +22,12 @@ def test_open_browser(self): remote_url = '"http://localhost:4444/wd/hub"' browser = mock() when(self.brorser)._make_driver('firefox', None, - None, False, None).thenReturn(browser) + None, False, None, None).thenReturn(browser) alias = self.brorser.open_browser(url) self.assertEqual(alias, None) when(self.brorser)._make_driver('firefox', None, - None, remote_url, None).thenReturn(browser) + None, remote_url, None, None).thenReturn(browser) alias = self.brorser.open_browser(url, alias='None', remote_url=remote_url) self.assertEqual(alias, None) From 8cdd0c3a0d149b52258f039fc7b28f577aefe170 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 15 Jun 2019 21:22:10 +0300 Subject: [PATCH 31/52] Test for not existing method in Selenium Options --- atest/acceptance/multiple_browsers_options.robot | 7 +++++++ atest/resources/testlibs/get_selenium_options.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index dc4244334..cc74ad085 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -31,3 +31,10 @@ Chrome Browser With Selenium Options Object ${options} = Get Chrome Options Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=${options} + + +Chrome Browser With Selenium Options Invalid Argument + + 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:arg2 diff --git a/atest/resources/testlibs/get_selenium_options.py b/atest/resources/testlibs/get_selenium_options.py index 8cc386578..91cbef2e1 100644 --- a/atest/resources/testlibs/get_selenium_options.py +++ b/atest/resources/testlibs/get_selenium_options.py @@ -4,4 +4,4 @@ def get_chrome_options(): options = webdriver.ChromeOptions() options.add_argument('--disable-dev-shm-usage') - return options \ No newline at end of file + return options From 164a1eed4bc0847d2d1ec39318a32aaacd78bd13 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 20 Jun 2019 22:46:21 +0300 Subject: [PATCH 32/52] Removing whitespace from acceptance tests --- atest/acceptance/multiple_browsers_options.robot | 1 - 1 file changed, 1 deletion(-) diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index cc74ad085..76e3f525e 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -34,7 +34,6 @@ Chrome Browser With Selenium Options Object Chrome Browser With Selenium Options Invalid Argument - 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:arg2 From 49aac86faa2109e3856980a07dbca95aa3f9feb9 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 20 Jun 2019 23:08:48 +0300 Subject: [PATCH 33/52] Support for space before and after comma in Selenium options --- src/SeleniumLibrary/keywords/webdrivertools.py | 2 +- ...erTests.test_parse_options_string.approved.txt | 15 ++++++++------- .../test/keywords/test_selenium_options_parser.py | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index d0e8fbdcd..c5973ee09 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -367,7 +367,7 @@ def _parse(self, options): for single_option in options.split(','): options_split = single_option.split(':') options_split = self._options_escape(options_split) - argument = {options_split[0]: options_split[1:]} + argument = {options_split[0].strip(): options_split[1:]} result.append(argument) return result diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt index e0039078a..82649bc9d 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt @@ -3,10 +3,11 @@ Selenium options string to dict 0) [{'method': ['arg1']}] 1) [{'method': ['arg1', 'arg2']}] 2) [{'method': ['arg1']}, {'method': ['arg2']}] -3) [{'method': []}] -4) [{'method1': []}, {'method2': []}] -5) [{'method': []}, {'method': []}] -6) [{'add_argument': ['--disable-dev-shm-usage']}] -7) [{'add_argument': ['--proxy-server=66.97.38.58:80']}] -8) [{'add_argument': ['--arg_with_\\_one_time']}] -9) [{'add_argument': ['--arg_with_\\\\_two_times']}] +3) [{'method': [' arg1 ']}, {'method': [' arg1 ', ' arg2 ']}] +4) [{'method': []}] +5) [{'method1': []}, {'method2': []}] +6) [{'method': []}, {'method': []}] +7) [{'add_argument': ['--disable-dev-shm-usage']}] +8) [{'add_argument': ['--proxy-server=66.97.38.58:80']}] +9) [{'add_argument': ['--arg_with_\\_one_time']}] +10) [{'add_argument': ['--arg_with_\\\\_two_times']}] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index af83ff32e..18a0f7e25 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -38,6 +38,7 @@ def test_parse_options_string(self): self.results.append(self.options._parse('method:arg1')) self.results.append(self.options._parse('method:arg1:arg2')) self.results.append(self.options._parse('method:arg1,method:arg2')) + self.results.append(self.options._parse('method : arg1 , method : arg1 : arg2 ')) self.results.append(self.options._parse('method')) self.results.append(self.options._parse('method1,method2')) self.results.append(self.options._parse('method,method')) From ad58fe8d7eaa0b27cfdf0eb4b9f215b0bd51c119 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Fri, 21 Jun 2019 00:55:53 +0300 Subject: [PATCH 34/52] Documentation for Selenium options --- .../testlibs/get_selenium_options.py | 3 +- .../keywords/browsermanagement.py | 82 ++++++++++++++++++- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/atest/resources/testlibs/get_selenium_options.py b/atest/resources/testlibs/get_selenium_options.py index 91cbef2e1..1b45cffdc 100644 --- a/atest/resources/testlibs/get_selenium_options.py +++ b/atest/resources/testlibs/get_selenium_options.py @@ -3,5 +3,4 @@ def get_chrome_options(): options = webdriver.ChromeOptions() - options.add_argument('--disable-dev-shm-usage') - return options + return options.add_argument('--disable-dev-shm-usage') diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index f8aed8b05..ef734328c 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -119,6 +119,68 @@ def open_browser(self, url, browser='firefox', alias=None, ``ff_profile_dir`` can also be instance of the [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_firefox/selenium.webdriver.firefox.firefox_profile.html?highlight=firefoxprofile#selenium.webdriver.firefox.firefox_profile.FirefoxProfile|selenium.webdriver.FirefoxProfile]. + Optional ``options`` argument allows to define browser specific + Selenium options. Example for Chrome, the ``options`` argument + allows defining the following + [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_chrome/selenium.webdriver.chrome.options.html#selenium.webdriver.chrome.options.Options|methods and attributes] + and for Firefox these + [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_firefox/selenium.webdriver.firefox.options.html?highlight=firefox#selenium.webdriver.firefox.options.Options|methods and attributes] + are available. Please note that not all browsers supported by the + SeleniumLibrary have Selenium options available, please consult + the Selenium documentation which browser do support the Selenium + options. Selenium options are also supported, when ``remote_url`` + argument is used. + + The SeleniumLibrary ``options`` argument accepts Selenium + options in three different formats 1) As a sting, 2) List containing + dictionaries 3) Python object which is an instance of the + Selenium options object. + + The sting format is allows to define Selenium options methods + or attributes and it's arguments in string format. Methods and + attributes are separated by a comma and arguments are separated + with a colon. Example like this: + `argument:arg1,other_argument:arg2:arg3,attribute:value`. + + The method and attributes are case and space sensitive + and must match to the Selenium options methods and attributes names. + It is possible to have space before and after the comma for readability. + Example `argument:arg1,argument:arg2` and + `argument:arg1 , argument:arg2` are converted to same + object internally. Spaces around arguments are not removed. + Arguments are always stings and are not converted to other types. + If there is need to define a literal colon for an argument, + it can be escaped by using literal backslash. Please + note that backslash is an escape character in Robot Framework and + therefore backslash must escaped. Example: + `argument:arg1_with\\\\:colon`. + + List containing dictionaries format is similar to the earlier + string format, but defines the methods and attributes and their + arguments using Robot Framework lists and dictionaries. + Method or attribute arguments, must be defined in a list. Method + or argument must be a dictionary key and argument list must be + the dictionary value. Dictionary must contain only one key + value pair. The dictionary must be placed in a list. Example: + `[{"argument": ["arg1"]}, {"other_argument": ["arg1", "arg2"]}]` + + As in string format, dictionary key, which is the method or + attribute name, is case and space sensitive and must match to + the Selenium options methods and attributes names. Although + the format is more complex to define, it allows to use Robot Framework + Python object, like `${True}` or `${None}`. + + As last format ``options`` argument also support 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. + Example, if the following code return valua is saved to `${options}` + variable in the Robot Framework data: + | options = webdriver.ChromeOptions() + | return options.add_argument('--disable-dev-shm-usage') + Then the `${options}` variable can be used as argument to + ``options``. + Optional ``service_log_path`` argument defines the name of the file where to write the browser driver logs. If the ``service_log_path`` argument contain a marker ``{index}``, it @@ -142,6 +204,18 @@ def open_browser(self, url, browser='firefox', alias=None, | Should Be Equal | ${1_index} | ${4_index} | | | | | Should Be Equal | ${2_index} | ${2} | | | | + Example when using + [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_chrome/selenium.webdriver.chrome.options.html#selenium.webdriver.chrome.options.Options|Chrome options] + method: + | `Open Browser` | http://example.com | Chrome | options=add_argument:--disable-popup-blocking, add_argument:--ignore-certificate-errors | # Sting format | + | `Open Browser` | http://example.com | Chrome | options=proxy-server=10.122.97.38.58\\:8080 | # Escaping colon | + | ${argument} = | Create List | --disable-dev-shm-usage | | # List and dict format | + | ${method} = | Create Dictionary | add_argument | ${argument} | | + | ${options 1} = | Create List | ${add_argument} | | | + | `Open Browser` | http://example.com | Chrome | options=${options 1} | | + | ${options 2} = | Get Options | | | # Selenium options instance | + | `Open Browser` | http://example.com | Chrome | options=${options 2} | | + If the provided configuration options are not enough, it is possible to use `Create Webdriver` to customize browser initialization even more. @@ -150,10 +224,10 @@ def open_browser(self, url, browser='firefox', alias=None, new in SeleniumLibrary 3.1. Using ``alias`` to decide, is the new browser opened is new - in SeleniumLibrary 4.0. Also the ``service_log_path`` is new - in SeleniumLibrary 4.0. Support for ``ff_profile_dir`` accepting - instance of the `selenium.webdriver.FirefoxProfile` is new in - SeleniumLibrary 4.0. + in SeleniumLibrary 4.0. The ``options`` and ``service_log_path`` + are new in SeleniumLibrary 4.0. Support for ``ff_profile_dir`` + accepting instance of the `selenium.webdriver.FirefoxProfile` + is new in SeleniumLibrary 4.0. """ index = self.drivers.get_index(alias) if index: From 24702f283449c21fdd9a03964cd3ce0c996c816c Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Fri, 21 Jun 2019 01:12:39 +0300 Subject: [PATCH 35/52] Fixed bug in doc and acceptance tests --- atest/resources/testlibs/get_selenium_options.py | 3 ++- src/SeleniumLibrary/keywords/browsermanagement.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/atest/resources/testlibs/get_selenium_options.py b/atest/resources/testlibs/get_selenium_options.py index 1b45cffdc..91cbef2e1 100644 --- a/atest/resources/testlibs/get_selenium_options.py +++ b/atest/resources/testlibs/get_selenium_options.py @@ -3,4 +3,5 @@ def get_chrome_options(): options = webdriver.ChromeOptions() - return options.add_argument('--disable-dev-shm-usage') + options.add_argument('--disable-dev-shm-usage') + return options diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index ef734328c..71418a12f 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -177,7 +177,9 @@ def open_browser(self, url, browser='firefox', alias=None, Example, if the following code return valua is saved to `${options}` variable in the Robot Framework data: | options = webdriver.ChromeOptions() - | return options.add_argument('--disable-dev-shm-usage') + | options.add_argument('--disable-dev-shm-usage') + | return options + Then the `${options}` variable can be used as argument to ``options``. From 7a1c51d8d68dbfd5e3274748e1578d7f10d4fae4 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 29 Jun 2019 01:55:58 +0300 Subject: [PATCH 36/52] Changed options format --- .../keywords/webdrivertools.py | 52 ++++++------ ...test_options_create_attribute.approved.txt | 6 -- ...test_options_create_many_args.approved.txt | 3 - ...rserTests.test_options_escape.approved.txt | 5 -- ...est_parse_options_other_types.approved.txt | 8 -- ...sts.test_parse_options_string.approved.txt | 17 ++-- ...t_parse_options_string_errors.approved.txt | 3 + .../keywords/test_selenium_options_parser.py | 83 +++++-------------- 8 files changed, 58 insertions(+), 119 deletions(-) delete mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt delete mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt delete mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_escape.approved.txt delete mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index c5973ee09..104c4f9a6 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -13,13 +13,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import ast import importlib import inspect import os +import token import warnings +from tokenize import generate_tokens from robot.api import logger -from robot.utils import ConnectionCache +from robot.utils import ConnectionCache, StringIO from selenium import webdriver from selenium.webdriver import FirefoxProfile @@ -358,33 +361,30 @@ def create(self, browser, options): setattr(selenium_options, key, *option[key]) return selenium_options + def _import_options(self, browser): + browser = browser.replace('headless_', '', 1) + options = importlib.import_module('selenium.webdriver.%s.options' % browser) + return options.Options + def _parse(self, options): - if is_falsy(options): - return [] - if isinstance(options, list): - return options result = [] - for single_option in options.split(','): - options_split = single_option.split(':') - options_split = self._options_escape(options_split) - argument = {options_split[0].strip(): options_split[1:]} - result.append(argument) + for item in options.split(';'): + try: + result.append(self._parse_to_tokens(item)) + except ValueError: + raise ValueError('Unable to parse option: "%s"' % item) return result - def _options_escape(self, options_split): - escape_detected = False - result = [] - for opt in options_split: - if opt.endswith('\\'): - escape_detected = opt[:-1] - elif escape_detected: - result.append('%s:%s' % (escape_detected, opt)) - escape_detected = False - else: - result.append(opt) + def _parse_to_tokens(self, item): + result = {} + method = None + arguments = [] + for tokens in generate_tokens(StringIO(item).readline): + if tokens.type == token.NAME and not method: + method = tokens.string + elif tokens.type == token.STRING: + arguments.append(ast.literal_eval(tokens.string)) + elif tokens.type in [token.NAME, token.NUMBER] and method: + arguments.append(ast.literal_eval(tokens.string)) + result[method] = arguments return result - - def _import_options(self, browser): - browser = browser.replace('headless_', '', 1) - options = importlib.import_module('selenium.webdriver.%s.options' % browser) - return options.Options diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt deleted file mode 100644 index 9b14ed28b..000000000 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_attribute.approved.txt +++ /dev/null @@ -1,6 +0,0 @@ -Selenium options attribute - -0) ['--headless'] -1) ['--headless'] -2) chromedriver -3) 'Options' object has no attribute 'not_here' diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt deleted file mode 100644 index cdd6dea07..000000000 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_create_many_args.approved.txt +++ /dev/null @@ -1,3 +0,0 @@ -Selenium options - -0) {'profile.default_content_settings.popups': 0} diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_escape.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_escape.approved.txt deleted file mode 100644 index 6e55e644c..000000000 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_options_escape.approved.txt +++ /dev/null @@ -1,5 +0,0 @@ -Selenium options escape string to dict - -0) ['--proxy-server=66.97.38.58:80'] -1) ['arg1', 'arg2'] -2) ['arg1'] diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt deleted file mode 100644 index 9977c13ca..000000000 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_other_types.approved.txt +++ /dev/null @@ -1,8 +0,0 @@ -Selenium options other types to dict - -0) [] -1) [] -2) [] -3) [] -4) [{'add_argument': ['--disable-dev-shm-usage']}] -5) [] diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt index 82649bc9d..b6068ea74 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt @@ -2,12 +2,11 @@ Selenium options string to dict 0) [{'method': ['arg1']}] 1) [{'method': ['arg1', 'arg2']}] -2) [{'method': ['arg1']}, {'method': ['arg2']}] -3) [{'method': [' arg1 ']}, {'method': [' arg1 ', ' arg2 ']}] -4) [{'method': []}] -5) [{'method1': []}, {'method2': []}] -6) [{'method': []}, {'method': []}] -7) [{'add_argument': ['--disable-dev-shm-usage']}] -8) [{'add_argument': ['--proxy-server=66.97.38.58:80']}] -9) [{'add_argument': ['--arg_with_\\_one_time']}] -10) [{'add_argument': ['--arg_with_\\\\_two_times']}] +2) [{'method': [True]}] +3) [{'method': [1]}] +4) [{'method': ['arg1', 2, None, False, 'arg2']}] +5) [{'method': [' arg1 ', 2, None, False, ' arg2 ']}] +6) [{'attribute': ['arg1']}] +7) [{'attribute': [True]}] +8) [{'method': ['arg1']}, {'attribute': [True]}] +9) [{'method': ['arg1']}, {'attribute': [True]}, {'method': ['arg2']}] diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt new file mode 100644 index 000000000..6c2454867 --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt @@ -0,0 +1,3 @@ +Selenium options string errors + +0) method("arg1) Unable to parse option: "method("arg1)" diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 18a0f7e25..8655f66ca 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -35,52 +35,38 @@ def setUp(self): @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_parse_options_string(self): - self.results.append(self.options._parse('method:arg1')) - self.results.append(self.options._parse('method:arg1:arg2')) - self.results.append(self.options._parse('method:arg1,method:arg2')) - self.results.append(self.options._parse('method : arg1 , method : arg1 : arg2 ')) - self.results.append(self.options._parse('method')) - self.results.append(self.options._parse('method1,method2')) - self.results.append(self.options._parse('method,method')) - self.results.append(self.options._parse('add_argument:--disable-dev-shm-usage')) - self.results.append(self.options._parse(r'add_argument:--proxy-server=66.97.38.58\:80')) - self.results.append(self.options._parse(r'add_argument:--arg_with_\_one_time')) - self.results.append(self.options._parse(r'add_argument:--arg_with_\\_two_times')) + self.results.append(self.options._parse('method("arg1")')) + self.results.append(self.options._parse('method("arg1", "arg2")')) + self.results.append(self.options._parse('method(True)')) + self.results.append(self.options._parse('method(1)')) + self.results.append(self.options._parse('method("arg1", 2, None, False, "arg2")')) + self.results.append(self.options._parse('method ( " arg1 " , 2 , None , False , " arg2 " )')) + self.results.append(self.options._parse('attribute="arg1"')) + self.results.append(self.options._parse('attribute = True')) + self.results.append(self.options._parse('method("arg1");attribute=True')) + self.results.append(self.options._parse('method("arg1") ; attribute=True ; method("arg2")')) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') - def test_parse_options_other_types(self): - self.results.append(self.options._parse('None')) - self.results.append(self.options._parse(None)) - self.results.append(self.options._parse(False)) - self.results.append(self.options._parse('False')) - options = [{'add_argument': ['--disable-dev-shm-usage']}] - self.results.append(self.options._parse(options)) - self.results.append(self.options._parse([])) - verify_all('Selenium options other types to dict', self.results, reporter=self.reporter) - - @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') - def test_options_escape(self): - self.results.append(self.options._options_escape(r'--proxy-server=66.97.38.58\:80'.split(':'))) - self.results.append(self.options._options_escape('arg1:arg2'.split(':'))) - self.results.append(self.options._options_escape('arg1'.split(':'))) - verify_all('Selenium options escape string to dict', self.results, reporter=self.reporter) + def test_parse_options_string_errors(self): + self.results.append(self.error_formatter(self.options._parse, 'method("arg1)', True)) + verify_all('Selenium options string errors', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_options_create(self): - options = [{'add_argument': ['--disable-dev-shm-usage']}] + options = 'add_argument("--disable-dev-shm-usage")' sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) - options.append({'add_argument': ['--headless']}) + options = '%s;add_argument("--headless")' % options sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) - options.append({'add_argument': ['--proxy-server=66.97.38.58:80']}) + options = '%s;add_argument("--proxy-server=66.97.38.58:80")' % options sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) - options.append({'binary_location': ['too', 'many', 'args']}) + options = '%s;binary_location("too", "many", "args")' % options try: self.options.create('chrome', options) except Exception as error: @@ -99,41 +85,12 @@ def test_options_create(self): verify_all('Selenium options', self.results, reporter=self.reporter) - @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') - def test_options_create_many_args(self): - options = [{'add_experimental_option': ['profile.default_content_settings.popups', 0]}] - sel_options = self.options.create('chrome', options) - self.results.append(sel_options.experimental_options) - - verify_all('Selenium options', self.results, reporter=self.reporter) - - @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') - def test_options_create_attribute(self): - options = [{'headless': [True]}] - sel_options = self.options.create('chrome', options) - self.results.append(sel_options.arguments) - - sel_options = self.options.create('headless_chrome', options) - self.results.append(sel_options.arguments) - - options.append({'binary_location': ['chromedriver']}) - sel_options = self.options.create('chrome', options) - self.results.append(sel_options.binary_location) - - options.append({'not_here': ['tidii']}) - try: - self.options.create('chrome', options) - except AttributeError as error: - self.results.append(error) - - verify_all('Selenium options attribute', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_get_options(self): - options = r'add_argument:--proxy-server=66.97.38.58\:80' + options = 'add_argument("--proxy-server=66.97.38.58:80")' sel_options = self.options.create('chrome', options) self.results.append(sel_options.arguments) - verify_all('Selenium options with string.', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -153,10 +110,12 @@ def test_importer(self): self.results.append(self.error_formatter(self.options._import_options, 'iphone')) verify_all('Selenium options import', self.results, reporter=self.reporter) - def error_formatter(self, method, arg): + def error_formatter(self, method, arg, full=False): try: method(arg) except Exception as error: + if full: + return '%s %s' % (arg, error) return '%s %s' % (arg, error.__str__()[:15]) From c19fb3f22cff491893ce0bde2f78862c0fad5d06 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 29 Jun 2019 02:13:11 +0300 Subject: [PATCH 37/52] Python 2 support for tokens --- src/SeleniumLibrary/keywords/webdrivertools.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 104c4f9a6..54aba6c31 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -379,12 +379,13 @@ def _parse_to_tokens(self, item): result = {} method = None arguments = [] - for tokens in generate_tokens(StringIO(item).readline): - if tokens.type == token.NAME and not method: - method = tokens.string - elif tokens.type == token.STRING: - arguments.append(ast.literal_eval(tokens.string)) - elif tokens.type in [token.NAME, token.NUMBER] and method: - arguments.append(ast.literal_eval(tokens.string)) + tokens = generate_tokens(StringIO(item).readline) + for toknum, tokval, _, _, _ in tokens: + if toknum == token.NAME and not method: + method = tokval + elif toknum == token.STRING: + arguments.append(ast.literal_eval(tokval)) + elif toknum in [token.NAME, token.NUMBER] and method: + arguments.append(ast.literal_eval(tokval)) result[method] = arguments return result From bed6a6e3d9e2c8f07fca0c06ee4968b3cc192425 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 29 Jun 2019 02:44:08 +0300 Subject: [PATCH 38/52] More tests --- ...sParserTests.test_parse_options_string.approved.txt | 3 +++ ...Tests.test_parse_options_string_errors.approved.txt | 4 ++++ ...Tests.test_parse_options_string_errors.received.txt | 7 +++++++ utest/test/keywords/test_selenium_options_parser.py | 10 +++++++++- 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt index b6068ea74..d6d260c8c 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt @@ -10,3 +10,6 @@ Selenium options string to dict 7) [{'attribute': [True]}] 8) [{'method': ['arg1']}, {'attribute': [True]}] 9) [{'method': ['arg1']}, {'attribute': [True]}, {'method': ['arg2']}] +10) [{'attribute': []}] +11) [{'method': []}] +12) [{'method': ['--proxy 10.10.1.3:2345']}] diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt index 6c2454867..174e30d75 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt @@ -1,3 +1,7 @@ Selenium options string errors 0) method("arg1) Unable to parse option: "method("arg1)" +1) method(arg1") Unable to parse option: "method(arg1")" +2) method(arg1) Unable to parse option: "method(arg1)" +3) attribute=arg1 Unable to parse option: "attribute=arg1" +4) attribute=webdriver Unable to parse option: "attribute=webdriver" diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt new file mode 100644 index 000000000..174e30d75 --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt @@ -0,0 +1,7 @@ +Selenium options string errors + +0) method("arg1) Unable to parse option: "method("arg1)" +1) method(arg1") Unable to parse option: "method(arg1")" +2) method(arg1) Unable to parse option: "method(arg1)" +3) attribute=arg1 Unable to parse option: "attribute=arg1" +4) attribute=webdriver Unable to parse option: "attribute=webdriver" diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 8655f66ca..8df8decbe 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -45,11 +45,19 @@ def test_parse_options_string(self): self.results.append(self.options._parse('attribute = True')) self.results.append(self.options._parse('method("arg1");attribute=True')) self.results.append(self.options._parse('method("arg1") ; attribute=True ; method("arg2")')) + self.results.append(self.options._parse('attribute')) + self.results.append(self.options._parse('method()')) + self.results.append(self.options._parse('method("--proxy 10.10.1.3:2345")')) + self.results.append(self.options._parse('method("arg;with;semicolon")')) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_parse_options_string_errors(self): self.results.append(self.error_formatter(self.options._parse, 'method("arg1)', True)) + self.results.append(self.error_formatter(self.options._parse, 'method(arg1")', True)) + self.results.append(self.error_formatter(self.options._parse, 'method(arg1)', True)) + self.results.append(self.error_formatter(self.options._parse, 'attribute=arg1', True)) + self.results.append(self.error_formatter(self.options._parse, 'attribute=webdriver', True)) verify_all('Selenium options string errors', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -112,7 +120,7 @@ def test_importer(self): def error_formatter(self, method, arg, full=False): try: - method(arg) + return method(arg) except Exception as error: if full: return '%s %s' % (arg, error) From 603f47a37e87d404920a039283c02f4aa437b282 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 29 Jun 2019 02:58:07 +0300 Subject: [PATCH 39/52] Fixed acceptance tests --- .../multiple_browsers_options.robot | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index 76e3f525e..c90fdad20 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -12,17 +12,15 @@ Chrome Browser With Selenium Options As String ... LOG 1:2 DEBUG GLOB: *"goog:chromeOptions"* ... LOG 1:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument:--disable-dev-shm-usage + ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage") -Chrome Browser With Selenium Options List +Chrome Browser With Selenium Options As String With Attirbute As True [Documentation] - ... LOG 4:2 DEBUG GLOB: *"goog:chromeOptions"* - ... LOG 4:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* - ${argument} = Create List --disable-dev-shm-usage - ${add_argument} = Create Dictionary add_argument ${argument} - ${options} = Create List ${add_argument} + ... LOG 1:2 DEBUG GLOB: *"goog:chromeOptions"* + ... LOG 1:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + ... LOG 1:2 DEBUG GLOB: *"--headless"* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... desired_capabilities=${DESIRED_CAPABILITIES} options=${options} + ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage");set_headless(True) Chrome Browser With Selenium Options Object [Documentation] @@ -32,8 +30,13 @@ Chrome Browser With Selenium Options Object Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=${options} +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 Invalid Argument - Run Keyword And Expect Error AttributeError: 'Options' object has no attribute 'not_here_method' + Run Keyword And Expect Error ValueError: Unable to parse option: "add_argument("has" ... Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... desired_capabilities=${DESIRED_CAPABILITIES} options=not_here_method:arg1:arg2 + ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("has;semicolon") From 4c3924681489828fc8ae8d73994069c6ed728308 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sat, 29 Jun 2019 02:58:45 +0300 Subject: [PATCH 40/52] Tuning --- ...rserTests.test_parse_options_string_errors.received.txt | 7 ------- utest/test/keywords/test_selenium_options_parser.py | 1 - 2 files changed, 8 deletions(-) delete mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt deleted file mode 100644 index 174e30d75..000000000 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.received.txt +++ /dev/null @@ -1,7 +0,0 @@ -Selenium options string errors - -0) method("arg1) Unable to parse option: "method("arg1)" -1) method(arg1") Unable to parse option: "method(arg1")" -2) method(arg1) Unable to parse option: "method(arg1)" -3) attribute=arg1 Unable to parse option: "attribute=arg1" -4) attribute=webdriver Unable to parse option: "attribute=webdriver" diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 8df8decbe..f7b68dfb1 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -48,7 +48,6 @@ def test_parse_options_string(self): self.results.append(self.options._parse('attribute')) self.results.append(self.options._parse('method()')) self.results.append(self.options._parse('method("--proxy 10.10.1.3:2345")')) - self.results.append(self.options._parse('method("arg;with;semicolon")')) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 79b7c17441cd3a2af90218fc2af9ee4bc269973e Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 30 Jun 2019 00:00:17 +0300 Subject: [PATCH 41/52] Better splitting for semicolon --- atest/acceptance/multiple_browsers_options.robot | 8 +++++--- src/SeleniumLibrary/keywords/webdrivertools.py | 13 ++++++++++++- ...tionsParserTests.test_split_options.approved.txt | 7 +++++++ utest/test/keywords/test_selenium_options_parser.py | 8 ++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index c90fdad20..98a54278f 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -36,7 +36,9 @@ Chrome Browser With Selenium Options Invalid Method ... desired_capabilities=${DESIRED_CAPABILITIES} options=not_here_method("arg1") -Chrome Browser With Selenium Options Invalid Argument - Run Keyword And Expect Error ValueError: Unable to parse option: "add_argument("has" - ... Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} +Chrome Browser With Selenium Options Argument With Semicolon + [Documentation] + ... LOG 1:2 DEBUG GLOB: *"goog:chromeOptions"* + ... LOG 1:2 DEBUG GLOB: *["has;semicolon"* + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("has;semicolon") diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 54aba6c31..1eb3f83bd 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -368,7 +368,7 @@ def _import_options(self, browser): def _parse(self, options): result = [] - for item in options.split(';'): + for item in self._split(options): try: result.append(self._parse_to_tokens(item)) except ValueError: @@ -389,3 +389,14 @@ def _parse_to_tokens(self, item): arguments.append(ast.literal_eval(tokval)) result[method] = arguments return result + + def _split(self, options): + split_options = [] + start_position = 0 + tokens = generate_tokens(StringIO(options).readline) + for toknum, tokval, tokpos, _, _ in tokens: + if toknum == token.OP and tokval == ';': + split_options.append(options[start_position:tokpos[1]]) + start_position = tokpos[1] + 1 + split_options.append(options[start_position:]) + return split_options diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt new file mode 100644 index 000000000..0ef0f5a24 --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt @@ -0,0 +1,7 @@ +Selenium options string splitting + +0) ['method("arg1")', 'method("arg2")'] +1) ['method("arg1")'] +2) ['attribute=True'] +3) ['attribute="semi;colons;middle"', 'other_attribute=True'] +4) ['method("arg1;")', 'method(";arg2;")'] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index f7b68dfb1..a764f95fa 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -59,6 +59,14 @@ def test_parse_options_string_errors(self): self.results.append(self.error_formatter(self.options._parse, 'attribute=webdriver', True)) verify_all('Selenium options string errors', self.results, reporter=self.reporter) + def test_split_options(self): + self.results.append(self.options._split('method("arg1");method("arg2")')) + self.results.append(self.options._split('method("arg1")')) + self.results.append(self.options._split('attribute=True')) + self.results.append(self.options._split('attribute="semi;colons;middle";other_attribute=True')) + self.results.append(self.options._split('method("arg1;");method(";arg2;")')) + verify_all('Selenium options string splitting', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_options_create(self): options = 'add_argument("--disable-dev-shm-usage")' From 6364362554367c6d046f37cc4cdb830f5750c843 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 30 Jun 2019 00:07:02 +0300 Subject: [PATCH 42/52] More tests --- atest/acceptance/multiple_browsers_options.robot | 2 +- ...iumOptionsParserTests.test_parse_options_string.approved.txt | 1 + .../SeleniumOptionsParserTests.test_split_options.approved.txt | 1 + utest/test/keywords/test_selenium_options_parser.py | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index 98a54278f..aca76006f 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -20,7 +20,7 @@ Chrome Browser With Selenium Options As String With Attirbute As True ... LOG 1:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* ... LOG 1:2 DEBUG GLOB: *"--headless"* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage");set_headless(True) + ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage");headless=True Chrome Browser With Selenium Options Object [Documentation] diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt index d6d260c8c..6e79b1ecd 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt @@ -13,3 +13,4 @@ Selenium options string to dict 10) [{'attribute': []}] 11) [{'method': []}] 12) [{'method': ['--proxy 10.10.1.3:2345']}] +13) [{'method': [';arg1']}] diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt index 0ef0f5a24..05775fd3e 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_split_options.approved.txt @@ -5,3 +5,4 @@ Selenium options string splitting 2) ['attribute=True'] 3) ['attribute="semi;colons;middle"', 'other_attribute=True'] 4) ['method("arg1;")', 'method(";arg2;")'] +5) [' method ( " arg1 ") ', ' method ( " arg2 " ) '] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index a764f95fa..696fafafa 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -48,6 +48,7 @@ def test_parse_options_string(self): self.results.append(self.options._parse('attribute')) self.results.append(self.options._parse('method()')) self.results.append(self.options._parse('method("--proxy 10.10.1.3:2345")')) + self.results.append(self.options._parse('method(";arg1")')) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') @@ -65,6 +66,7 @@ def test_split_options(self): self.results.append(self.options._split('attribute=True')) self.results.append(self.options._split('attribute="semi;colons;middle";other_attribute=True')) self.results.append(self.options._split('method("arg1;");method(";arg2;")')) + self.results.append(self.options._split(' method ( " arg1 ") ; method ( " arg2 " ) ')) verify_all('Selenium options string splitting', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 599e4c55e2bf165eb05af2ffa71a0f630fe2f39b Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 30 Jun 2019 01:30:44 +0300 Subject: [PATCH 43/52] Fixed docs to tell new string format --- .../multiple_browsers_options.robot | 2 +- .../keywords/browsermanagement.py | 86 +++++++++---------- ...sts.test_parse_options_string.approved.txt | 2 + .../keywords/test_selenium_options_parser.py | 2 + 4 files changed, 44 insertions(+), 48 deletions(-) diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index aca76006f..680f4fdc5 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -20,7 +20,7 @@ Chrome Browser With Selenium Options As String With Attirbute As True ... LOG 1:2 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* ... LOG 1:2 DEBUG GLOB: *"--headless"* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage");headless=True + ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument ( "--disable-dev-shm-usage" ) ; headless = True Chrome Browser With Selenium Options Object [Documentation] diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 71418a12f..27349eaf8 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -127,54 +127,51 @@ def open_browser(self, url, browser='firefox', alias=None, [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_firefox/selenium.webdriver.firefox.options.html?highlight=firefox#selenium.webdriver.firefox.options.Options|methods and attributes] are available. Please note that not all browsers supported by the SeleniumLibrary have Selenium options available, please consult - the Selenium documentation which browser do support the Selenium + the Selenium documentation which browsers do support the Selenium options. Selenium options are also supported, when ``remote_url`` argument is used. The SeleniumLibrary ``options`` argument accepts Selenium - options in three different formats 1) As a sting, 2) List containing - dictionaries 3) Python object which is an instance of the - Selenium options object. - - The sting format is allows to define Selenium options methods - or attributes and it's arguments in string format. Methods and - attributes are separated by a comma and arguments are separated - with a colon. Example like this: - `argument:arg1,other_argument:arg2:arg3,attribute:value`. - - The method and attributes are case and space sensitive - and must match to the Selenium options methods and attributes names. - It is possible to have space before and after the comma for readability. - Example `argument:arg1,argument:arg2` and - `argument:arg1 , argument:arg2` are converted to same - object internally. Spaces around arguments are not removed. - Arguments are always stings and are not converted to other types. - If there is need to define a literal colon for an argument, - it can be escaped by using literal backslash. Please - note that backslash is an escape character in Robot Framework and - therefore backslash must escaped. Example: - `argument:arg1_with\\\\:colon`. - - List containing dictionaries format is similar to the earlier - string format, but defines the methods and attributes and their - arguments using Robot Framework lists and dictionaries. - Method or attribute arguments, must be defined in a list. Method - or argument must be a dictionary key and argument list must be - the dictionary value. Dictionary must contain only one key - value pair. The dictionary must be placed in a list. Example: - `[{"argument": ["arg1"]}, {"other_argument": ["arg1", "arg2"]}]` - - As in string format, dictionary key, which is the method or - attribute name, is case and space sensitive and must match to - the Selenium options methods and attributes names. Although - the format is more complex to define, it allows to use Robot Framework - Python object, like `${True}` or `${None}`. + options in two different formats: as a string and as Python object + which is an instance of the Selenium options class. + + The string format is allows to define Selenium options methods + or attributes and it's arguments in string format. The method + and attributes are case and space sensitive and must match to + the Selenium options methods and attributes names. When + defining a method, is must defined in similar way as in + python: method name, opening parenthesis, zero to many arguments + and closing parenthesis. If there is 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 similar way as in Python: attribute name, equal sing + and attribute value. Example, `headless=True`. Multiple methods + and attributes must 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 define other Python builtin + data types, example `True` or `None`, by not using quotes + around the arguments. + + The string format is space friendly and usually spaces do not alter + the defining the methods or attributes. There are two exceptions. + In some Robot Framework test data formats, two or spaces are + considered as cell separator and instead of defining a single + argument, two or more cels 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")` As last format ``options`` argument also support 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. - Example, if the following code return valua is saved to `${options}` + 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') @@ -209,14 +206,9 @@ def open_browser(self, url, browser='firefox', alias=None, Example when using [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_chrome/selenium.webdriver.chrome.options.html#selenium.webdriver.chrome.options.Options|Chrome options] method: - | `Open Browser` | http://example.com | Chrome | options=add_argument:--disable-popup-blocking, add_argument:--ignore-certificate-errors | # Sting format | - | `Open Browser` | http://example.com | Chrome | options=proxy-server=10.122.97.38.58\\:8080 | # Escaping colon | - | ${argument} = | Create List | --disable-dev-shm-usage | | # List and dict format | - | ${method} = | Create Dictionary | add_argument | ${argument} | | - | ${options 1} = | Create List | ${add_argument} | | | - | `Open Browser` | http://example.com | Chrome | options=${options 1} | | - | ${options 2} = | Get Options | | | # Selenium options instance | - | `Open Browser` | http://example.com | Chrome | options=${options 2} | | + | `Open Browser` | http://example.com | Chrome | options=add_argument("--disable-popup-blocking"); add_argument("--ignore-certificate-errors") | # Sting format | + | ${options} = | Get Options | | | # Selenium options instance | + | `Open Browser` | http://example.com | Chrome | options=${options 2} | | If the provided configuration options are not enough, it is possible to use `Create Webdriver` to customize browser initialization even diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt index 6e79b1ecd..b065be279 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string.approved.txt @@ -14,3 +14,5 @@ Selenium options string to dict 11) [{'method': []}] 12) [{'method': ['--proxy 10.10.1.3:2345']}] 13) [{'method': [';arg1']}] +14) [{'method': ['arg1', 2, 'arg2']}] +15) [{'method': ['arg1']}] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 696fafafa..da16e1299 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -49,6 +49,8 @@ def test_parse_options_string(self): self.results.append(self.options._parse('method()')) self.results.append(self.options._parse('method("--proxy 10.10.1.3:2345")')) self.results.append(self.options._parse('method(";arg1")')) + self.results.append(self.options._parse('method ( "arg1" , 2 ,"arg2" )')) + self.results.append(self.options._parse("method('arg1')")) verify_all('Selenium options string to dict', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 3f1973fb79f6a14061176d49516ddeef5541e1a1 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Sun, 30 Jun 2019 01:41:54 +0300 Subject: [PATCH 44/52] Add skip for unit test in Jython --- utest/test/keywords/test_selenium_options_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index da16e1299..0e9b40d7b 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -62,6 +62,7 @@ def test_parse_options_string_errors(self): self.results.append(self.error_formatter(self.options._parse, 'attribute=webdriver', True)) verify_all('Selenium options string errors', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_split_options(self): self.results.append(self.options._split('method("arg1");method("arg2")')) self.results.append(self.options._split('method("arg1")')) From 38e72eb307731df381a304e2bc04c882a2a923b1 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 1 Jul 2019 22:58:52 +0300 Subject: [PATCH 45/52] Improved documentation --- .../keywords/browsermanagement.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 27349eaf8..c9229d3ca 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -126,20 +126,20 @@ def open_browser(self, url, browser='firefox', alias=None, and for Firefox these [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_firefox/selenium.webdriver.firefox.options.html?highlight=firefox#selenium.webdriver.firefox.options.Options|methods and attributes] are available. Please note that not all browsers supported by the - SeleniumLibrary have Selenium options available, please consult - the Selenium documentation which browsers do support the Selenium - options. Selenium options are also supported, when ``remote_url`` - argument is used. + SeleniumLibrary have Selenium options available. Therefore please + consult the Selenium documentation which browsers do support + the Selenium options. Selenium options are also supported, + when ``remote_url`` argument is used. The SeleniumLibrary ``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. - The string format is allows to define Selenium options methods - or attributes and it's arguments in string format. The method - and attributes are case and space sensitive and must match to - the Selenium options methods and attributes names. When - defining a method, is must defined in similar way as in + The string format allows to define 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, is must defined in similar way as in python: method name, opening parenthesis, zero to many arguments and closing parenthesis. If there is need to define multiple arguments for a single method, arguments must be separated with @@ -148,7 +148,7 @@ def open_browser(self, url, browser='firefox', alias=None, defined in similar way as in Python: attribute name, equal sing and attribute value. Example, `headless=True`. Multiple methods and attributes must separated by a semicolon, example: - `add_argument("--headless");add_argument("start-maximized")`. + `add_argument("--headless");add_argument("--start-maximized")`. Arguments allow defining Python data types and arguments are evaluated by using Python @@ -160,19 +160,21 @@ def open_browser(self, url, browser='firefox', alias=None, The string format is space friendly and usually spaces do not alter the defining the methods or attributes. There are two exceptions. - In some Robot Framework test data formats, two or spaces are + 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 cels may be defined. Spaces in string + 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")` + `add_argument("--headless")`. But `add_argument(" --headless ")` is + not same same as `add_argument ( "--headless" )`, because + spaces inside of quotes are not removed. As last format ``options`` argument also support 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. - Example, if the following code return value is saved to `${options}` - variable in the Robot Framework data: + 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 From 273626a8c5e2e44d1f71bbb6d2ee1517742ceac1 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 1 Jul 2019 23:12:53 +0300 Subject: [PATCH 46/52] Improved code readability for WebDriverCreator headless brosers --- src/SeleniumLibrary/keywords/webdrivertools.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index 1eb3f83bd..a65252233 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -69,7 +69,8 @@ def create_driver(self, browser, desired_capabilities, remote_url, or creation_method == self.create_headless_firefox): return creation_method(desired_capabilities, remote_url, profile_dir, options=options, service_log_path=service_log_path) - return creation_method(desired_capabilities, remote_url, options=options, service_log_path=service_log_path) + return creation_method(desired_capabilities, remote_url, options=options, + service_log_path=service_log_path) def _get_creator_method(self, browser): if browser in self.browser_names: @@ -112,10 +113,11 @@ def create_chrome(self, desired_capabilities, remote_url, options=None, service_ return webdriver.Chrome(options=options, service_log_path=service_log_path, **desired_capabilities) def create_headless_chrome(self, desired_capabilities, remote_url, options=None, service_log_path=None): - chrome_options = webdriver.ChromeOptions() if not options else options + if not options: + options = webdriver.ChromeOptions() # Can be changed to options.headless = True when minimum Selenium version is 3.12.0 or greater. - chrome_options.set_headless() - return self.create_chrome(desired_capabilities, remote_url, chrome_options, service_log_path) + options.set_headless() + return self.create_chrome(desired_capabilities, remote_url, options, service_log_path) def create_firefox(self, desired_capabilities, remote_url, ff_profile_dir, options=None, service_log_path=None): profile = self._get_ff_profile(ff_profile_dir) @@ -143,10 +145,11 @@ def _geckodriver_log(self): def create_headless_firefox(self, desired_capabilities, remote_url, ff_profile_dir, options=None, service_log_path=None): - ff_options = webdriver.FirefoxOptions() if not options else options + if not options: + options = webdriver.FirefoxOptions() # Can be changed to options.headless = True when minimum Selenium version is 3.12.0 or greater. - ff_options.set_headless() - return self.create_firefox(desired_capabilities, remote_url, ff_profile_dir, ff_options, service_log_path) + options.set_headless() + return self.create_firefox(desired_capabilities, remote_url, ff_profile_dir, options, service_log_path) def create_ie(self, desired_capabilities, remote_url, options=None, service_log_path=None): if is_truthy(remote_url): From 30abcf23d1b7fd10055734527191c8026dfa6c14 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 1 Jul 2019 23:14:37 +0300 Subject: [PATCH 47/52] Improved WebDriverTools _has_* methods --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index a65252233..f2f891700 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -170,11 +170,11 @@ def create_ie(self, desired_capabilities, remote_url, options=None, service_log_ def _has_service_log_path(self, web_driver): signature = inspect.getargspec(web_driver.__init__) - return True if 'service_log_path' in signature.args else False + return 'service_log_path' in signature.args def _has_options(self, web_driver): signature = inspect.getargspec(web_driver.__init__) - return True if 'options' in signature.args else False + return 'options' in signature.args def create_edge(self, desired_capabilities, remote_url, options=None, service_log_path=None): if is_truthy(remote_url): From f9b1f5fcb0798681532675a97d9ee5801c9316e8 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 2 Jul 2019 22:31:30 +0300 Subject: [PATCH 48/52] Android uses Chrome options --- src/SeleniumLibrary/keywords/webdrivertools.py | 2 ++ .../SeleniumOptionsParserTests.test_importer.approved.txt | 2 +- utest/test/keywords/test_selenium_options_parser.py | 3 +-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index f2f891700..b8ebb2df0 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -365,6 +365,8 @@ def create(self, browser, options): return selenium_options def _import_options(self, browser): + if browser == 'android': + browser = 'chrome' # Android uses ChromeOptions() browser = browser.replace('headless_', '', 1) options = importlib.import_module('selenium.webdriver.%s.options' % browser) return options.Options diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt index 00305cc6f..21faaba19 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_importer.approved.txt @@ -11,5 +11,5 @@ Selenium options import 8) safari No module named 9) htmlunit No module named 10) htmlunit_with_js No module named -11) android No module named +11) 12) iphone No module named diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 0e9b40d7b..4187e083d 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -105,7 +105,6 @@ def test_options_create(self): verify_all('Selenium options', self.results, reporter=self.reporter) - @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_get_options(self): options = 'add_argument("--proxy-server=66.97.38.58:80")' @@ -126,7 +125,7 @@ def test_importer(self): self.results.append(self.error_formatter(self.options._import_options, 'safari')) self.results.append(self.error_formatter(self.options._import_options, 'htmlunit')) self.results.append(self.error_formatter(self.options._import_options, 'htmlunit_with_js')) - self.results.append(self.error_formatter(self.options._import_options, 'android')) + self.results.append(self.options._import_options('android')) self.results.append(self.error_formatter(self.options._import_options, 'iphone')) verify_all('Selenium options import', self.results, reporter=self.reporter) From d72590567d9ab9a6892168bd6ceb80bafcd946b4 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 2 Jul 2019 22:43:50 +0300 Subject: [PATCH 49/52] Not being too strict about Selenium options instances --- src/SeleniumLibrary/keywords/webdrivertools.py | 4 ++-- utest/test/keywords/test_selenium_options_parser.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools.py index b8ebb2df0..98ffedc4f 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools.py @@ -26,7 +26,7 @@ from selenium import webdriver from selenium.webdriver import FirefoxProfile -from SeleniumLibrary.utils import is_falsy, is_truthy, is_noney +from SeleniumLibrary.utils import is_falsy, is_truthy, is_noney, is_string class WebDriverCreator(object): @@ -351,7 +351,7 @@ def create(self, browser, options): if is_falsy(options): return None selenium_options = self._import_options(browser) - if isinstance(options, selenium_options): + if not is_string(options): return options options = self._parse(options) selenium_options = selenium_options() diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 4187e083d..f95ac6e61 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -105,6 +105,13 @@ def test_options_create(self): verify_all('Selenium options', self.results, reporter=self.reporter) + def test_create_with_android(self): + chrome_options = webdriver.ChromeOptions() + chrome_options.add_experimental_option('androidPackage', 'com.android.chrome') + sel_options = self.options.create('android', chrome_options) + self.results.append([sel_options.arguments, sel_options.experimental_options]) + verify_all('Selenium options with android', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_get_options(self): options = 'add_argument("--proxy-server=66.97.38.58:80")' From 5f4f1ab08debdeae5ed6e0d1616aa5a53443e31f Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Tue, 2 Jul 2019 23:52:34 +0300 Subject: [PATCH 50/52] Added missing approval test file --- ...iumOptionsParserTests.test_create_with_android.approved.txt | 3 +++ utest/test/keywords/test_selenium_options_parser.py | 1 + 2 files changed, 4 insertions(+) create mode 100644 utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_create_with_android.approved.txt diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_create_with_android.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_create_with_android.approved.txt new file mode 100644 index 000000000..4ec4a5658 --- /dev/null +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_create_with_android.approved.txt @@ -0,0 +1,3 @@ +Selenium options with android + +0) [[], {'androidPackage': 'com.android.chrome'}] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index f95ac6e61..fb5e09d79 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -105,6 +105,7 @@ def test_options_create(self): verify_all('Selenium options', self.results, reporter=self.reporter) + @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') def test_create_with_android(self): chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option('androidPackage', 'com.android.chrome') From c1b4f65e9a47d5352c5a892b4e2fdddf7c7332f3 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 4 Jul 2019 20:53:26 +0300 Subject: [PATCH 51/52] Adding test for few error cases --- ...onsParserTests.test_parse_options_string_errors.approved.txt | 2 ++ utest/test/keywords/test_selenium_options_parser.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt index 174e30d75..d83ca949e 100644 --- a/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt +++ b/utest/test/keywords/approved_files/SeleniumOptionsParserTests.test_parse_options_string_errors.approved.txt @@ -5,3 +5,5 @@ Selenium options string errors 2) method(arg1) Unable to parse option: "method(arg1)" 3) attribute=arg1 Unable to parse option: "attribute=arg1" 4) attribute=webdriver Unable to parse option: "attribute=webdriver" +5) method(argument="value") Unable to parse option: "method(argument="value")" +6) [{'method': ['key', 'value']}] diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index fb5e09d79..683da52eb 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -60,6 +60,8 @@ def test_parse_options_string_errors(self): self.results.append(self.error_formatter(self.options._parse, 'method(arg1)', True)) self.results.append(self.error_formatter(self.options._parse, 'attribute=arg1', True)) self.results.append(self.error_formatter(self.options._parse, 'attribute=webdriver', True)) + self.results.append(self.error_formatter(self.options._parse, 'method(argument="value")', True)) + self.results.append(self.error_formatter(self.options._parse, 'method({"key": "value"})', True)) verify_all('Selenium options string errors', self.results, reporter=self.reporter) @unittest.skipIf(JYTHON, 'ApprovalTest does not work with Jython') From 27e0370e5b3bc48ba8269868d1624b270108d8d5 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Thu, 4 Jul 2019 21:03:45 +0300 Subject: [PATCH 52/52] Fixed docs for Android using Chrome options --- src/SeleniumLibrary/keywords/browsermanagement.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index c9229d3ca..bc31324d3 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -128,8 +128,10 @@ def open_browser(self, url, browser='firefox', alias=None, are available. Please note that not all browsers supported by the SeleniumLibrary have Selenium options available. Therefore please consult the Selenium documentation which browsers do support - the Selenium options. Selenium options are also supported, - when ``remote_url`` argument is used. + the Selenium options. If ``browser`` argument is `android` then + [https://seleniumhq.github.io/selenium/docs/api/py/webdriver_chrome/selenium.webdriver.chrome.options.html#selenium.webdriver.chrome.options.Options|Chrome options] + is used. Selenium options are also supported, when ``remote_url`` + argument is used. The SeleniumLibrary ``options`` argument accepts Selenium options in two different formats: as a string and as Python object