From bddd02a058905f3f2ae07c18479e00211c98fd3d Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Wed, 2 Oct 2019 12:27:44 +0100 Subject: [PATCH] [py] Add support for relative locators --- javascript/atoms/fragments/BUILD.bazel | 1 + py/BUILD.bazel | 7 ++ py/selenium/webdriver/remote/webdriver.py | 9 +++ py/selenium/webdriver/remote/webelement.py | 2 +- .../webdriver/support/relative_locator.py | 76 +++++++++++++++++++ .../webdriver/support/relative_by_tests.py | 42 ++++++++++ 6 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 py/selenium/webdriver/support/relative_locator.py create mode 100644 py/test/selenium/webdriver/support/relative_by_tests.py diff --git a/javascript/atoms/fragments/BUILD.bazel b/javascript/atoms/fragments/BUILD.bazel index 5903dacee82f3..4ea60cb86587e 100644 --- a/javascript/atoms/fragments/BUILD.bazel +++ b/javascript/atoms/fragments/BUILD.bazel @@ -189,6 +189,7 @@ closure_fragment( "//dotnet/src/webdriver:__pkg__", "//java/client/src/org/openqa/selenium/support/locators:__pkg__", "//javascript/chrome-driver:__pkg__", + "//py:__pkg__", ], deps = [ "//javascript/atoms:locators", diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 66665ae99285f..a959a44faa7a8 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -14,6 +14,12 @@ copy_file( out = "selenium/webdriver/remote/isDisplayed.js", ) +copy_file( + name = "find-elements", + src = "//javascript/atoms/fragments:find-elements.js", + out = "selenium/webdriver/remote/findElements.js", +) + copy_file( name = "firefox-driver-prefs", src = "//third_party/js/selenium:webdriver_json", @@ -27,6 +33,7 @@ py_library( ":firefox-driver-prefs", ":get-attribute", ":is-displayed", + ":find-elements", ], imports = ["."], visibility = ["//visibility:public"], diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 1370e52669b33..2a5bd809cb0cf 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -20,6 +20,7 @@ import base64 import copy from contextlib import contextmanager +import pkgutil import warnings from .command import Command @@ -36,6 +37,8 @@ from selenium.webdriver.common.by import By from selenium.webdriver.common.html5.application_cache import ApplicationCache +from selenium.webdriver.support.relative_locator import RelativeBy + try: str = basestring except NameError: @@ -1039,6 +1042,12 @@ def find_elements(self, by=By.ID, value=None): :rtype: list of WebElement """ + if isinstance(by, RelativeBy): + _pkg = '.'.join(__name__.split('.')[:-1]) + raw_function = pkgutil.get_data(_pkg, 'findElements.js').decode('utf8') + find_element_js = "return (%s).apply(null, arguments);" % raw_function + return self.execute_script(find_element_js, by.to_dict()) + if self.w3c: if by == By.ID: by = By.CSS_SELECTOR diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index 8f29b846ccd11..6fbde93532ddd 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -692,7 +692,7 @@ def find_element(self, by=By.ID, value=None): if by == By.ID: by = By.CSS_SELECTOR value = '[id="%s"]' % value - elif by == By.TAG_NAME: + elif by == By.TAG_NAME: by = By.CSS_SELECTOR elif by == By.CLASS_NAME: by = By.CSS_SELECTOR diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py new file mode 100644 index 0000000000000..86283db119530 --- /dev/null +++ b/py/selenium/webdriver/support/relative_locator.py @@ -0,0 +1,76 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, 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 json +import pkgutil + +from selenium.common.exceptions import WebDriverException + + +def with_tag_name(tag_name): + if tag_name is None: + raise WebDriverException("tag_name can not be null") + return RelativeBy({"css selector": tag_name}) + +class RelativeBy(object): + + def __init__(self, root = None, filters = []): + self.root = root + self.filters = filters + + def above(self, element_or_locator = None): + if element_or_locator is None: + raise WebDriverException("Element or locator must be given when calling above method") + + self.filters.append({"kind": "above", "args": [element_or_locator]}) + return self + + def below(self, element_or_locator = None): + if element_or_locator is None: + raise WebDriverException("Element or locator must be given when calling above method") + + self.filters.append({"kind": "below", "args": [element_or_locator]}) + return self + + def to_left_of(self, element_or_locator = None): + if element_or_locator is None: + raise WebDriverException("Element or locator must be given when calling above method") + + self.filters.append({"kind": "left", "args": [element_or_locator]}) + return self + + def to_right_of(self, element_or_locator): + if element_or_locator is None: + raise WebDriverException("Element or locator must be given when calling above method") + + self.filters.append({"kind": "right", "args": [element_or_locator]}) + return self + + def near(self, element_or_locator_distance = None): + if element_or_locator is None: + raise WebDriverException("Element or locator or distance must be given when calling above method") + + self.filters.append({"kind": "near", "args": [element_or_locator]}) + return self + + def to_dict(self): + return { + 'relative': { + 'root': self.root, + 'filters': self.filters + } + } diff --git a/py/test/selenium/webdriver/support/relative_by_tests.py b/py/test/selenium/webdriver/support/relative_by_tests.py new file mode 100644 index 0000000000000..af3108ebf1147 --- /dev/null +++ b/py/test/selenium/webdriver/support/relative_by_tests.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, 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 pytest + +from selenium.webdriver.common.by import By +from selenium.webdriver.support.relative_locator import with_tag_name + +def test_should_be_able_to_find_elements_above_another(driver, pages): + pages.load("relative_locators.html") + lowest = driver.find_element(By.ID, "below") + + elements = driver.find_elements(with_tag_name("p").above(lowest)) + + ids = [el.get_attribute('id') for el in elements] + assert "above" in ids + assert "mid" in ids + + +def test_should_be_able_to_combine_filters(driver, pages): + pages.load("relative_locators.html") + + elements = driver.find_elements(with_tag_name("td").above(driver.find_element(By.ID, "center")) + .to_right_of(driver.find_element(By.ID, "second"))) + + ids = [el.get_attribute('id') for el in elements] + assert "third" in ids +