Repository: robotframework/SeleniumLibrary

Issue:
Add support for xpath starting with ((//
Currently, we are only treating `//` and `(//` as xpath expression

Please help to add support also for `((//`

For example, my xpath having union operator:
```((//label[contains(.,"ABC")]/input[@type="checkbox"]|//label[contains(.,"ABC")]/preceding-sibling::*[@type="checkbox"]))[1]```

It's still a vaid xpath expression, but unable to be detected in SeleniumLibrary

Thank you


Discussion:

Patch:
diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py
index 67fc57cd..de09287d 100644
--- a/src/SeleniumLibrary/__init__.py
+++ b/src/SeleniumLibrary/__init__.py
@@ -174,9 +174,10 @@ class SeleniumLibrary(DynamicCore):
 
     === Implicit XPath strategy ===
 
-    If the locator starts with ``//`` or ``(//``, the locator is considered
-    to be an XPath expression. In other words, using ``//div`` is equivalent
-    to using explicit ``xpath://div``.
+    If the locator starts with ``//``  or multiple opening parenthesis in front
+    of the ``//``, the locator is considered to be an XPath expression. In other
+    words, using ``//div`` is equivalent to using explicit ``xpath://div`` and
+    ``((//div))`` is equivalent to using explicit ``xpath:((//div))``
 
     Examples:
 
@@ -184,6 +185,7 @@ class SeleniumLibrary(DynamicCore):
     | `Click Element` | (//div)[2]           |
 
     The support for the ``(//`` prefix is new in SeleniumLibrary 3.0.
+    Supporting multiple opening parenthesis is new in SeleniumLibrary 5.0.
 
     === Chaining locators ===
 
diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py
index 7bd0c58d..6b904ac2 100644
--- a/src/SeleniumLibrary/locators/elementfinder.py
+++ b/src/SeleniumLibrary/locators/elementfinder.py
@@ -300,7 +300,7 @@ class ElementFinder(ContextAware):
         return tag, constraints
 
     def _parse_locator(self, locator):
-        if locator.startswith(("//", "(//")):
+        if re.match(r"\(*//", locator):
             return "xpath", locator
         index = self._get_locator_separator_index(locator)
         if index != -1:


Test patch:
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 35722d29..0603c647 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
@@ -117,9 +117,10 @@ Examples:
 
 === Implicit XPath strategy ===
 
-If the locator starts with ``//`` or ``(//``, the locator is considered
-to be an XPath expression. In other words, using ``//div`` is equivalent
-to using explicit ``xpath://div``.
+If the locator starts with ``//``  or multiple opening parenthesis in front
+of the ``//``, the locator is considered to be an XPath expression. In other
+words, using ``//div`` is equivalent to using explicit ``xpath://div`` and
+``((//div))`` is equivalent to using explicit ``xpath:((//div))``
 
 Examples:
 
@@ -127,6 +128,7 @@ Examples:
 | `Click Element` | (//div)[2]           |
 
 The support for the ``(//`` prefix is new in SeleniumLibrary 3.0.
+Supporting multiple opening parenthesis is new in SeleniumLibrary 5.0.
 
 === Chaining locators ===
 
diff --git a/utest/test/locators/test_elementfinder.py b/utest/test/locators/test_elementfinder.py
index 75cb3318..1c595848 100644
--- a/utest/test/locators/test_elementfinder.py
+++ b/utest/test/locators/test_elementfinder.py
@@ -33,6 +33,8 @@ def teardown_function():
 def test_implicit_xpath():
     _verify_parse_locator("//foo", "xpath", "//foo")
     _verify_parse_locator("(//foo)", "xpath", "(//foo)")
+    _verify_parse_locator("((//foo))", "xpath", "((//foo))")
+    _verify_parse_locator("((((//foo))", "xpath", "((((//foo))")
     _verify_parse_locator("//id=bar", "xpath", "//id=bar")
 
 
@@ -90,8 +92,9 @@ def test_registered_strategy_can_be_used_as_prefix():
 def _verify_parse_locator(locator, prefix, criteria, finder=None):
     if not finder:
         finder = ElementFinder(None)
-    parse_locator = finder._parse_locator
-    assert parse_locator(locator), (prefix, criteria)
+    get_prefix, get_criteria = finder._parse_locator(locator)
+    assert get_prefix == prefix
+    assert get_criteria == criteria
 
 
 def test_parent_is_not_webelement(finder):