diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py index 1b37c8cb7..99bb5297c 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -61,10 +61,7 @@ def find(self, locator, tag=None, first_only=True, required=True): if isinstance(locator, WebElement): return locator prefix, criteria = self._parse_locator(locator) - if prefix not in self._strategies: - raise ValueError("Element locator with prefix '%s' " - "is not supported." % prefix) - strategy = self._strategies.get(prefix) + strategy = self._strategies[prefix] tag, constraints = self._get_tag_and_constraints(tag) elements = strategy(criteria, tag, constraints) if required and not elements: @@ -124,9 +121,6 @@ def unregister(self, strategy_name): else: del self._strategies[strategy_name] - def has_strategy(self, strategy_name): - return strategy_name in self.strategies - def _find_by_identifier(self, criteria, tag, constraints): elements = self._normalize_result( self.browser.find_elements_by_id(criteria)) @@ -257,10 +251,19 @@ def _get_tag_and_constraints(self, tag): def _parse_locator(self, locator): if locator.startswith(('//', '(//')): return 'xpath', locator + index = self._get_locator_separator_index(locator) + if index != -1: + prefix = locator[:index].strip() + if prefix in self._strategies: + return prefix, locator[index+1:].lstrip() + return 'default', locator + + def _get_locator_separator_index(self, locator): if '=' not in locator: - return 'default', locator - prefix, criteria = locator.split('=', 1) - return prefix.strip(), criteria.lstrip() + return locator.find(':') + if ':' not in locator: + return locator.find('=') + return min(locator.find('='), locator.find(':')) def _element_matches(self, element, tag, constraints): if not element.tag_name.lower() == tag: diff --git a/test/acceptance/locators/locator_parsing.robot b/test/acceptance/locators/locator_parsing.robot new file mode 100644 index 000000000..0d7acab85 --- /dev/null +++ b/test/acceptance/locators/locator_parsing.robot @@ -0,0 +1,37 @@ +*** Settings *** +Documentation Tests different supported xpath strategies +Test Setup Go To Page "links.html" +Resource ../resource.robot + +*** Test Cases *** +xpath with prefix should work + Page Should Contain Element xpath=//div[@id="div_id"]/a + Page Should Contain Element xpath://div[@id="div_id"]/a + +xpath with // and without prefix should work + Page Should Contain Element //div[@id="div_id"]/a + +xpath with (// and without prefix should work + Page Should Contain Element (//div[@id="div_id"]/a)[1] + +Locator without prefix + Page Should Contain Element div_id + +Locator with prefix + Page Should Contain Element id:div_id + Page Should Contain Element id=div_id + Page Should Contain Element id:foo:bar + Page Should Contain Element id=foo:bar + Page Should Contain Element id:bar=foo + Page Should Contain Element id=bar=foo + +Locator with separator but without matching prefix is not special + Page Should Contain Element foo:bar + Page Should Contain Element bar=foo + +Locator with separator and with matchign prefix cannot be used as-is + Page Should Contain Element id:id:problematic + Page Should Contain Element id=id:problematic + Run Keyword And Expect Error + ... Page should have contained element 'id:problematic' but did not + ... Page Should Contain Element id:problematic diff --git a/test/acceptance/locators/xpath.robot b/test/acceptance/locators/xpath.robot deleted file mode 100644 index 728501616..000000000 --- a/test/acceptance/locators/xpath.robot +++ /dev/null @@ -1,14 +0,0 @@ -*** Settings *** -Documentation Tests different supported xpath strategies -Test Setup Go To Page "links.html" -Resource ../resource.robot - -*** Test Cases *** -xpath with prefix should work - Page Should Contain Element xpath=//div[@id="div_id"]/a - -xpath with // and without prefix should work - Page Should Contain Element //div[@id="div_id"]/a - -xpath with (// and without prefix should work - Page Should Contain Element (//div[@id="div_id"]/a)[1] diff --git a/test/resources/html/links.html b/test/resources/html/links.html index 183c3bb5c..bf9c0eafc 100644 --- a/test/resources/html/links.html +++ b/test/resources/html/links.html @@ -17,13 +17,13 @@ Link with asterisk * and another *
Link with Unicode äöüÄÖÜß -
- - Link with whitespace +
+ + Link with whitespace
- - Link with - whitespace within + + Link with + whitespace within
Link with bolded text
diff --git a/test/unit/locators/test_elementfinder.py b/test/unit/locators/test_elementfinder.py index 43754bfd9..98f896f9a 100644 --- a/test/unit/locators/test_elementfinder.py +++ b/test/unit/locators/test_elementfinder.py @@ -5,6 +5,58 @@ from SeleniumLibrary.locators.elementfinder import ElementFinder +class ParseLocatorTests(unittest.TestCase): + + def setUp(self): + self.finder = ElementFinder(None) + + def test_implicit_xpath(self): + self._verify_parse_locator('//foo', 'xpath', '//foo') + self._verify_parse_locator('(//foo)', 'xpath', '(//foo)') + self._verify_parse_locator('//id=bar', 'xpath', '//id=bar') + + def test_no_separator(self): + self._verify_parse_locator('foo', 'default', 'foo') + self._verify_parse_locator('', 'default', '') + + def test_equal_sign_as_separator(self): + self._verify_parse_locator('class=foo', 'class', 'foo') + self._verify_parse_locator('id=foo=bar', 'id', 'foo=bar') + + def test_colon_as_separator(self): + self._verify_parse_locator('class:foo', 'class', 'foo') + self._verify_parse_locator('id:foo:bar', 'id', 'foo:bar') + + def test_use_first_separator_when_both_are_used(self): + self._verify_parse_locator('id:foo=bar', 'id', 'foo=bar') + self._verify_parse_locator('id=foo:bar', 'id', 'foo:bar') + + def test_preserve_trailing_whitespace(self): + self._verify_parse_locator('//foo/bar ', 'xpath', '//foo/bar ') + self._verify_parse_locator('class=foo ', 'class', 'foo ') + + def test_remove_whitespace_around_prefix_and_separator(self): + self._verify_parse_locator('class = foo', 'class', 'foo') + self._verify_parse_locator('class : foo', 'class', 'foo') + self._verify_parse_locator(' id = foo = bar ', 'id', 'foo = bar ') + self._verify_parse_locator(' id : foo : bar ', 'id', 'foo : bar ') + + def test_separator_without_matching_prefix_is_ignored(self): + self._verify_parse_locator('no=match', 'default', 'no=match') + self._verify_parse_locator('no:match', 'default', 'no:match') + + def test_registered_strategy_can_be_used_as_prefix(self): + self._verify_parse_locator('registered=no', 'default', 'registered=no') + self._verify_parse_locator('registered:no', 'default', 'registered:no') + self.finder.register('registered', lambda *args: None, persist=True) + self._verify_parse_locator('registered=yes!!', 'registered', 'yes!!') + self._verify_parse_locator('registered:yes!!', 'registered', 'yes!!') + + def _verify_parse_locator(self, locator, prefix, criteria): + parse_locator = self.finder._parse_locator + self.assertEqual(parse_locator(locator), (prefix, criteria)) + + class ElementFinderTests(unittest.TestCase): def setUp(self): diff --git a/test/unit/locators/test_locator_parser.py b/test/unit/locators/test_locator_parser.py deleted file mode 100644 index 32795e7ba..000000000 --- a/test/unit/locators/test_locator_parser.py +++ /dev/null @@ -1,79 +0,0 @@ -import unittest - -from mockito import mock - -from SeleniumLibrary.locators.elementfinder import ElementFinder - - -class LocatorParserTests(unittest.TestCase): - - @classmethod - def setUpClass(cls): - ctx = mock() - cls.parse = ElementFinder(ctx)._parse_locator - - def test_parse_xpath(self): - prefix, criteria = self.parse('//foo/bar') - self.assertEqual(prefix, 'xpath') - self.assertEqual(criteria, '//foo/bar') - - prefix, criteria = self.parse('(//foo/bar)[2]') - self.assertEqual(prefix, 'xpath') - self.assertEqual(criteria, '(//foo/bar)[2]') - - prefix, criteria = self.parse('xpath=//foo[@attr=value]') - self.assertEqual(prefix, 'xpath') - self.assertEqual(criteria, '//foo[@attr=value]') - - def test_parse_dom(self): - prefix, criteria = self.parse( - 'dom=document.images[56]') - self.assertEqual(prefix, 'dom') - self.assertEqual(criteria, 'document.images[56]') - - def test_parse_link(self): - prefix, criteria = self.parse('link=My Link') - self.assertEqual(prefix, 'link') - self.assertEqual(criteria, 'My Link') - - prefix, criteria = self.parse('partial link=y Lin') - self.assertEqual(prefix, 'partial link') - self.assertEqual(criteria, 'y Lin') - - def test_parse_id(self): - prefix, criteria = self.parse('id=my_element') - self.assertEqual(prefix, 'id') - self.assertEqual(criteria, 'my_element') - - prefix, criteria = self.parse('my_element') - self.assertEqual(prefix, 'default') - self.assertEqual(criteria, 'my_element') - - def test_parse_leave_trailing_spaces(self): - prefix, criteria = self.parse('id= test1 ') - self.assertEqual(prefix, 'id') - self.assertEqual(criteria, 'test1 ') - - prefix, criteria = self.parse('//foo/bar ') - self.assertEqual(prefix, 'xpath') - self.assertEqual(criteria, '//foo/bar ') - - def test_parse_identifier(self): - prefix, criteria = self.parse('identifier=my_element') - self.assertEqual(prefix, 'identifier') - self.assertEqual(criteria, 'my_element') - - def test_parse_default(self): - prefix, criteria = self.parse('default=page?a=b') - self.assertEqual(prefix, 'default') - self.assertEqual(criteria, 'page?a=b') - - def test_parse_invalid(self): - prefix, criteria = self.parse('foo=bar') - self.assertEqual(prefix, 'foo') - self.assertEqual(criteria, 'bar') - - def test_parse_empty(self): - prefix, criteria = self.parse('') - self.assertEqual(prefix, 'default') - self.assertEqual(criteria, '')