Skip to content

Commit

Permalink
[py] Add support for relative locators
Browse files Browse the repository at this point in the history
  • Loading branch information
AutomatedTester committed Feb 24, 2020
1 parent 55aaf10 commit bddd02a
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 1 deletion.
1 change: 1 addition & 0 deletions javascript/atoms/fragments/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -27,6 +33,7 @@ py_library(
":firefox-driver-prefs",
":get-attribute",
":is-displayed",
":find-elements",
],
imports = ["."],
visibility = ["//visibility:public"],
Expand Down
9 changes: 9 additions & 0 deletions py/selenium/webdriver/remote/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import base64
import copy
from contextlib import contextmanager
import pkgutil
import warnings

from .command import Command
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion py/selenium/webdriver/remote/webelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions py/selenium/webdriver/support/relative_locator.py
Original file line number Diff line number Diff line change
@@ -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
}
}
42 changes: 42 additions & 0 deletions py/test/selenium/webdriver/support/relative_by_tests.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit bddd02a

Please sign in to comment.