From faddc1b0ec0d453815e3cfffce95d1b345249bc3 Mon Sep 17 00:00:00 2001 From: pinterior Date: Mon, 27 Nov 2023 20:03:12 +0900 Subject: [PATCH 1/6] add failing test to check RelativeLocator#near accept single int --- py/test/selenium/webdriver/support/relative_by_tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py/test/selenium/webdriver/support/relative_by_tests.py b/py/test/selenium/webdriver/support/relative_by_tests.py index 6fbd1e0344cf4..1be31f729ce6f 100644 --- a/py/test/selenium/webdriver/support/relative_by_tests.py +++ b/py/test/selenium/webdriver/support/relative_by_tests.py @@ -89,6 +89,13 @@ def test_no_such_element_is_raised_rather_than_index_error(driver, pages): assert "Cannot locate relative element with: {'id': 'nonexistentid'}" in exc.value.msg +# this test will fail with InvalidArgumentException +def test_near_locator_should_accept_single_int(driver, pages): + pages.load("relative_locators.html") + + driver.find_element(locate_with(By.ID, "rect2").near(123)) + + def test_near_locator_should_find_near_elements(driver, pages): pages.load("relative_locators.html") rect1 = driver.find_element(By.ID, "rect1") From 8d6c2f50b7087c993ad4f4ddc89366ea7040a5ac Mon Sep 17 00:00:00 2001 From: pinterior Date: Thu, 2 Nov 2023 03:17:54 +0900 Subject: [PATCH 2/6] fix s.w.support.RelativeBy#near to take 2 parameters --- py/selenium/webdriver/support/relative_locator.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index e1455790dbbd5..5dab29c3410fd 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -130,16 +130,19 @@ def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Re self.filters.append({"kind": "right", "args": [element_or_locator]}) return self - def near(self, element_or_locator_distance: Union[WebElement, Dict, int] = None) -> "RelativeBy": + def near(self, element_or_locator: Union[WebElement, Dict, None] = None, distance: int = 50) -> "RelativeBy": """Add a filter to look for elements near. :Args: - - element_or_locator_distance: Element to look near by the element or within a distance + - element_or_locator: Element to look near by the element or within a distance + - distance: distance in pixel """ - if not element_or_locator_distance: - raise WebDriverException("Element or locator or distance must be given when calling near method") + if not element_or_locator: + raise WebDriverException("Element or locator must be given when calling near method") + if distance <= 0: + raise WebDriverException("Distance must be positive") - self.filters.append({"kind": "near", "args": [element_or_locator_distance]}) + self.filters.append({"kind": "near", "args": [element_or_locator, distance]}) return self def to_dict(self) -> Dict: From f212c2d8bbc388d288029dc93aefeb87b728721f Mon Sep 17 00:00:00 2001 From: pinterior Date: Thu, 2 Nov 2023 03:26:57 +0900 Subject: [PATCH 3/6] more strict typing on s.w.support.relative_locator --- py/selenium/webdriver/common/by.py | 5 ++ .../webdriver/support/relative_locator.py | 61 ++++++++++++++++--- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/py/selenium/webdriver/common/by.py b/py/selenium/webdriver/common/by.py index f24a113ddb1be..56a3f96d6fbb8 100644 --- a/py/selenium/webdriver/common/by.py +++ b/py/selenium/webdriver/common/by.py @@ -16,6 +16,8 @@ # under the License. """The By implementation.""" +from typing import Literal + class By: """Set of supported locator strategies.""" @@ -28,3 +30,6 @@ class By: TAG_NAME = "tag name" CLASS_NAME = "class name" CSS_SELECTOR = "css selector" + + +ByType = Literal["id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector"] diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index 5dab29c3410fd..f5976e6debf49 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -16,11 +16,14 @@ # under the License. from typing import Dict from typing import List +from typing import NoReturn from typing import Optional from typing import Union +from typing import overload from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.by import By +from selenium.webdriver.common.by import ByType from selenium.webdriver.remote.webelement import WebElement @@ -37,10 +40,10 @@ def with_tag_name(tag_name: str) -> "RelativeBy": """ if not tag_name: raise WebDriverException("tag_name can not be null") - return RelativeBy({"css selector": tag_name}) + return RelativeBy({By.CSS_SELECTOR: tag_name}) -def locate_with(by: By, using: str) -> "RelativeBy": +def locate_with(by: ByType, using: str) -> "RelativeBy": """Start searching for relative objects your search criteria with By. :Args: @@ -70,7 +73,9 @@ class RelativeBy: assert "mid" in ids """ - def __init__(self, root: Optional[Dict[Union[By, str], str]] = None, filters: Optional[List] = None): + LocatorType = Dict[ByType, str] + + def __init__(self, root: Optional[Dict[ByType, str]] = None, filters: Optional[List] = None): """Creates a new RelativeBy object. It is preferred if you use the `locate_with` method as this signature could change. @@ -82,7 +87,15 @@ def __init__(self, root: Optional[Dict[Union[By, str], str]] = None, filters: Op self.root = root self.filters = filters or [] - def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + @overload + def above(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": + ... + + @overload + def above(self, element_or_locator: None = None) -> "NoReturn": + ... + + def above(self, element_or_locator: Union[WebElement, LocatorType, None] = None) -> "RelativeBy": """Add a filter to look for elements above. :Args: @@ -94,7 +107,15 @@ def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative self.filters.append({"kind": "above", "args": [element_or_locator]}) return self - def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + @overload + def below(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": + ... + + @overload + def below(self, element_or_locator: None = None) -> "NoReturn": + ... + + def below(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy": """Add a filter to look for elements below. :Args: @@ -106,7 +127,15 @@ def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative self.filters.append({"kind": "below", "args": [element_or_locator]}) return self - def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + @overload + def to_left_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": + ... + + @overload + def to_left_of(self, element_or_locator: None = None) -> "NoReturn": + ... + + def to_left_of(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy": """Add a filter to look for elements to the left of. :Args: @@ -118,7 +147,15 @@ def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Rel self.filters.append({"kind": "left", "args": [element_or_locator]}) return self - def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + @overload + def to_right_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": + ... + + @overload + def to_right_of(self, element_or_locator: None = None) -> "NoReturn": + ... + + def to_right_of(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy": """Add a filter to look for elements right of. :Args: @@ -130,7 +167,15 @@ def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Re self.filters.append({"kind": "right", "args": [element_or_locator]}) return self - def near(self, element_or_locator: Union[WebElement, Dict, None] = None, distance: int = 50) -> "RelativeBy": + @overload + def near(self, element_or_locator: Union[WebElement, LocatorType], distance: int = 50) -> "RelativeBy": + ... + + @overload + def near(self, element_or_locator: None = None, distance: int = 50) -> "NoReturn": + ... + + def near(self, element_or_locator: Union[WebElement, LocatorType, None] = None, distance: int = 50) -> "RelativeBy": """Add a filter to look for elements near. :Args: From 435edc97fa49fbfe8340efda67ad2f48ab9fe37a Mon Sep 17 00:00:00 2001 From: pinterior Date: Thu, 2 Nov 2023 03:29:23 +0900 Subject: [PATCH 4/6] add some tests for s.w.support.relative_locator --- .../webdriver/support/relative_by_tests.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/py/test/selenium/webdriver/support/relative_by_tests.py b/py/test/selenium/webdriver/support/relative_by_tests.py index 1be31f729ce6f..a265e25f582a7 100644 --- a/py/test/selenium/webdriver/support/relative_by_tests.py +++ b/py/test/selenium/webdriver/support/relative_by_tests.py @@ -31,6 +31,14 @@ def test_should_be_able_to_find_first_one(driver, pages): assert el.get_attribute("id") == "mid" +def test_should_be_able_to_find_first_one_by_locator(driver, pages): + pages.load("relative_locators.html") + + el = driver.find_element(with_tag_name("p").above({By.ID: "below"})) + + assert el.get_attribute("id") == "mid" + + def test_should_be_able_to_find_elements_above_another(driver, pages): pages.load("relative_locators.html") lowest = driver.find_element(By.ID, "below") @@ -42,6 +50,16 @@ def test_should_be_able_to_find_elements_above_another(driver, pages): assert "mid" in ids +def test_should_be_able_to_find_elements_above_another_by_locator(driver, pages): + pages.load("relative_locators.html") + + elements = driver.find_elements(with_tag_name("p").above({By.ID: "below"})) + + 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") @@ -55,6 +73,15 @@ def test_should_be_able_to_combine_filters(driver, pages): assert "third" in ids +def test_should_be_able_to_combine_filters_by_locator(driver, pages): + pages.load("relative_locators.html") + + elements = driver.find_elements(with_tag_name("td").above({By.ID: "center"}).to_right_of({By.ID: "second"})) + + ids = [el.get_attribute("id") for el in elements] + assert "third" in ids + + def test_should_be_able_to_use_css_selectors(driver, pages): pages.load("relative_locators.html") @@ -68,6 +95,17 @@ def test_should_be_able_to_use_css_selectors(driver, pages): assert "third" in ids +def test_should_be_able_to_use_css_selectors_by_locator(driver, pages): + pages.load("relative_locators.html") + + elements = driver.find_elements( + locate_with(By.CSS_SELECTOR, "td").above({By.ID: "center"}).to_right_of({By.ID: "second"}) + ) + + ids = [el.get_attribute("id") for el in elements] + assert "third" in ids + + def test_should_be_able_to_use_xpath(driver, pages): pages.load("relative_locators.html") @@ -81,6 +119,15 @@ def test_should_be_able_to_use_xpath(driver, pages): assert "fourth" in ids +def test_should_be_able_to_use_xpath_by_locator(driver, pages): + pages.load("relative_locators.html") + + elements = driver.find_elements(locate_with(By.XPATH, "//td[1]").below({By.ID: "second"}).above({By.ID: "seventh"})) + + ids = [el.get_attribute("id") for el in elements] + assert "fourth" in ids + + def test_no_such_element_is_raised_rather_than_index_error(driver, pages): pages.load("relative_locators.html") with pytest.raises(NoSuchElementException) as exc: @@ -89,6 +136,13 @@ def test_no_such_element_is_raised_rather_than_index_error(driver, pages): assert "Cannot locate relative element with: {'id': 'nonexistentid'}" in exc.value.msg +def test_no_such_element_is_raised_rather_than_index_error_by_locator(driver, pages): + pages.load("relative_locators.html") + with pytest.raises(NoSuchElementException) as exc: + driver.find_element(locate_with(By.ID, "nonexistentid").above({By.ID: "second"})) + assert "Cannot locate relative element with: {'id': 'nonexistentid'}" in exc.value.msg + + # this test will fail with InvalidArgumentException def test_near_locator_should_accept_single_int(driver, pages): pages.load("relative_locators.html") @@ -105,6 +159,14 @@ def test_near_locator_should_find_near_elements(driver, pages): assert el.get_attribute("id") == "rect2" +def test_near_locator_should_find_near_elements_by_locator(driver, pages): + pages.load("relative_locators.html") + + el = driver.find_element(locate_with(By.ID, "rect2").near({By.ID: "rect1"})) + + assert el.get_attribute("id") == "rect2" + + def test_near_locator_should_not_find_far_elements(driver, pages): pages.load("relative_locators.html") rect3 = driver.find_element(By.ID, "rect3") @@ -113,3 +175,29 @@ def test_near_locator_should_not_find_far_elements(driver, pages): driver.find_element(locate_with(By.ID, "rect4").near(rect3)) assert "Cannot locate relative element with: {'id': 'rect4'}" in exc.value.msg + + +def test_near_locator_should_not_find_far_elements_by_locator(driver, pages): + pages.load("relative_locators.html") + + with pytest.raises(NoSuchElementException) as exc: + driver.find_element(locate_with(By.ID, "rect4").near({By.ID: "rect3"})) + + assert "Cannot locate relative element with: {'id': 'rect4'}" in exc.value.msg + + +def test_near_locator_should_find_far_elements(driver, pages): + pages.load("relative_locators.html") + rect3 = driver.find_element(By.ID, "rect3") + + el = driver.find_element(locate_with(By.ID, "rect4").near(rect3, 100)) + + assert el.get_attribute("id") == "rect4" + + +def test_near_locator_should_find_far_elements_by_locator(driver, pages): + pages.load("relative_locators.html") + + el = driver.find_element(locate_with(By.ID, "rect4").near({By.ID: "rect3"}, 100)) + + assert el.get_attribute("id") == "rect4" From 7f9cf886148ecf9a76f66593d6008c851537e31c Mon Sep 17 00:00:00 2001 From: pinterior Date: Mon, 27 Nov 2023 20:07:00 +0900 Subject: [PATCH 5/6] remove test case calling RelativeLocator#near wrong way --- py/test/selenium/webdriver/support/relative_by_tests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/py/test/selenium/webdriver/support/relative_by_tests.py b/py/test/selenium/webdriver/support/relative_by_tests.py index a265e25f582a7..d90d3fedae1d4 100644 --- a/py/test/selenium/webdriver/support/relative_by_tests.py +++ b/py/test/selenium/webdriver/support/relative_by_tests.py @@ -143,13 +143,6 @@ def test_no_such_element_is_raised_rather_than_index_error_by_locator(driver, pa assert "Cannot locate relative element with: {'id': 'nonexistentid'}" in exc.value.msg -# this test will fail with InvalidArgumentException -def test_near_locator_should_accept_single_int(driver, pages): - pages.load("relative_locators.html") - - driver.find_element(locate_with(By.ID, "rect2").near(123)) - - def test_near_locator_should_find_near_elements(driver, pages): pages.load("relative_locators.html") rect1 = driver.find_element(By.ID, "rect1") From c850fe24b52cc0169c5e6cacc36346d10f7a6550 Mon Sep 17 00:00:00 2001 From: pinterior Date: Fri, 12 Jul 2024 19:44:29 +0900 Subject: [PATCH 6/6] fix linting issues --- .../webdriver/support/relative_locator.py | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index f5976e6debf49..3b7766a7de3de 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -88,12 +88,10 @@ def __init__(self, root: Optional[Dict[ByType, str]] = None, filters: Optional[L self.filters = filters or [] @overload - def above(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": - ... + def above(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ... @overload - def above(self, element_or_locator: None = None) -> "NoReturn": - ... + def above(self, element_or_locator: None = None) -> "NoReturn": ... def above(self, element_or_locator: Union[WebElement, LocatorType, None] = None) -> "RelativeBy": """Add a filter to look for elements above. @@ -108,12 +106,10 @@ def above(self, element_or_locator: Union[WebElement, LocatorType, None] = None) return self @overload - def below(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": - ... + def below(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ... @overload - def below(self, element_or_locator: None = None) -> "NoReturn": - ... + def below(self, element_or_locator: None = None) -> "NoReturn": ... def below(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy": """Add a filter to look for elements below. @@ -128,12 +124,10 @@ def below(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "Re return self @overload - def to_left_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": - ... + def to_left_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ... @overload - def to_left_of(self, element_or_locator: None = None) -> "NoReturn": - ... + def to_left_of(self, element_or_locator: None = None) -> "NoReturn": ... def to_left_of(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy": """Add a filter to look for elements to the left of. @@ -148,12 +142,10 @@ def to_left_of(self, element_or_locator: Union[WebElement, Dict, None] = None) - return self @overload - def to_right_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": - ... + def to_right_of(self, element_or_locator: Union[WebElement, LocatorType]) -> "RelativeBy": ... @overload - def to_right_of(self, element_or_locator: None = None) -> "NoReturn": - ... + def to_right_of(self, element_or_locator: None = None) -> "NoReturn": ... def to_right_of(self, element_or_locator: Union[WebElement, Dict, None] = None) -> "RelativeBy": """Add a filter to look for elements right of. @@ -168,12 +160,10 @@ def to_right_of(self, element_or_locator: Union[WebElement, Dict, None] = None) return self @overload - def near(self, element_or_locator: Union[WebElement, LocatorType], distance: int = 50) -> "RelativeBy": - ... + def near(self, element_or_locator: Union[WebElement, LocatorType], distance: int = 50) -> "RelativeBy": ... @overload - def near(self, element_or_locator: None = None, distance: int = 50) -> "NoReturn": - ... + def near(self, element_or_locator: None = None, distance: int = 50) -> "NoReturn": ... def near(self, element_or_locator: Union[WebElement, LocatorType, None] = None, distance: int = 50) -> "RelativeBy": """Add a filter to look for elements near.