diff --git a/atest/acceptance/locators/locator_parsing.robot b/atest/acceptance/locators/locator_parsing.robot index 3f13561cb..eca57d054 100644 --- a/atest/acceptance/locators/locator_parsing.robot +++ b/atest/acceptance/locators/locator_parsing.robot @@ -14,6 +14,11 @@ xpath with // and without prefix should work xpath with (// and without prefix should work Page Should Contain Element (//div[@id="div_id"]/a)[1] +Locator with with data prefix + Page Should Contain Element data:id:my_id + Page Should Contain Element data:automation:my_automation_id + Page Should Not Contain Element data:non_existent:some_random_id + Locator without prefix Page Should Contain Element div_id diff --git a/atest/resources/html/links.html b/atest/resources/html/links.html index bf38d6a4c..bfde01584 100644 --- a/atest/resources/html/links.html +++ b/atest/resources/html/links.html @@ -36,9 +36,10 @@ Image Link -
+
Target opens in new window
+ diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index 6bb072885..c47ee4100 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -136,6 +136,7 @@ class SeleniumLibrary(DynamicCore): | link | Exact text a link has. | ``link:The example`` | | partial link | Partial link text. | ``partial link:he ex`` | | sizzle | Sizzle selector deprecated. | ``sizzle:div.example`` | + | data | Element ``data-*`` attribute | ``data:id:my_id`` | | jquery | jQuery expression. | ``jquery:div.example`` | | default | Keyword specific default behavior. | ``default:example`` | @@ -171,6 +172,9 @@ class SeleniumLibrary(DynamicCore): the system under test contains the jQuery library. - Prior to SeleniumLibrary 3.0, table related keywords only supported ``xpath``, ``css`` and ``sizzle/jquery`` strategies. + - ``data`` strategy is conveniance locator that will construct xpath from the parameters. + If you have element like `
`, you locate the element via + ``data:automation:automation-id-2``. This feature was added in SeleniumLibrary 5.2.0 === Implicit XPath strategy === diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py index 6b904ac2d..d74486a3e 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -46,6 +46,7 @@ def __init__(self, ctx): "sizzle": self._find_by_jquery_selector, "tag": self._find_by_tag_name, "scLocator": self._find_by_sc_locator, + "data": self._find_by_data_locator, "default": self._find_by_default, } self._strategies = NormalizedDict( @@ -220,6 +221,19 @@ def _find_by_tag_name(self, criteria, tag, constraints, parent): parent.find_elements(By.TAG_NAME, criteria), tag, constraints ) + def _find_by_data_locator(self, criteria, tag, constraints, parent): + try: + name, value = criteria.split(":", 2) + if "" in [name, value]: + raise ValueError + except ValueError: + raise ValueError( + f"Provided selector ({criteria}) is malformed. Correct format: name:value." + ) + + local_criteria = f'//*[@data-{name}="{value}"]' + return self._find_by_xpath(local_criteria, tag, constraints, parent) + def _find_by_sc_locator(self, criteria, tag, constraints, parent): self._disallow_webelement_parent(parent) criteria = criteria.replace("'", "\\'") diff --git a/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt b/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt index 0603c647b..d9699f302 100644 --- a/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt +++ b/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt @@ -79,6 +79,7 @@ below. In addition to them, it is possible to register `custom locators`. | link | Exact text a link has. | ``link:The example`` | | partial link | Partial link text. | ``partial link:he ex`` | | sizzle | Sizzle selector deprecated. | ``sizzle:div.example`` | +| data | Element ``data-*`` attribute | ``data:id:my_id`` | | jquery | jQuery expression. | ``jquery:div.example`` | | default | Keyword specific default behavior. | ``default:example`` | @@ -114,6 +115,9 @@ Examples: the system under test contains the jQuery library. - Prior to SeleniumLibrary 3.0, table related keywords only supported ``xpath``, ``css`` and ``sizzle/jquery`` strategies. +- ``data`` strategy is conveniance locator that will construct xpath from the parameters. + If you have element like `
`, you locate the element via + ``data:automation:automation-id-2``. This feature was added in SeleniumLibrary 5.2.0 === Implicit XPath strategy === diff --git a/utest/test/locators/test_elementfinder.py b/utest/test/locators/test_elementfinder.py index 1c5958484..98f5ddc87 100644 --- a/utest/test/locators/test_elementfinder.py +++ b/utest/test/locators/test_elementfinder.py @@ -68,6 +68,13 @@ def test_strategy_case_is_not_changed(): _verify_parse_locator("XPATH=//foo/bar ", "XPATH", "//foo/bar ") +def test_data_locator_parsing(): + _verify_parse_locator("data:id:my_id", "data", "id:my_id") + _verify_parse_locator( + "data:automation:my_automation_id", "data", "automation:my_automation_id" + ) + + def test_remove_whitespace_around_prefix_and_separator(): _verify_parse_locator("class = foo", "class", "foo") _verify_parse_locator("class : foo", "class", "foo") @@ -271,6 +278,26 @@ def test_find_with_tag(finder): verify(driver).find_elements(By.XPATH, "//div[(@id='test1' or @name='test1')]") +def test_find_with_data(finder): + driver = _get_driver(finder) + finder.find("data:id:my_id", tag="div", required=False) + verify(driver).find_elements(By.XPATH, '//*[@data-id="my_id"]') + + +def test_find_with_invalid_data(finder): + with pytest.raises( + ValueError, + match=r"^Provided selector \(id:\) is malformed\. Correct format: name:value\.", + ): + finder.find("data:id:", tag="div", required=False) + + with pytest.raises( + ValueError, + match=r"^Provided selector \(\) is malformed\. Correct format: name:value\.", + ): + finder.find("data:", tag="div", required=False) + + def test_find_with_locator_with_apos(finder): driver = _get_driver(finder) finder.find("test '1'", required=False)