Repository: robotframework/SeleniumLibrary
Pull request #926 (2017-09-26 19:51:33)

Fixes documentation for locating tables
Before this one can be merged, the https://github.com/robotframework/SeleniumLibrary/pull/921 should be merged too. 


Patch:
diff --git a/BUILD.rst b/BUILD.rst
index c3b27a1c..b0792e81 100644
--- a/BUILD.rst
+++ b/BUILD.rst
@@ -223,11 +223,12 @@ Post actions
       git commit -m "Back to dev version" src/SeleniumLibrary/__init__.py
       git push
 
-   For example, ``3.2.1`` is changed to ``3.2.2.dev`` with the current date
-   appended.
+   For example, ``1.2.3`` is changed to ``1.2.4.dev1`` and ``2.0.1a1``
+   to ``2.0.1a2.dev1``.
 
 3. Close the `issue tracker milestone
    <https://github.com/robotframework/SeleniumLibrary/milestones>`_.
+   Create also new milestone for the next release unless one exists already.
 
 Announcements
 -------------
diff --git a/requirements-build.txt b/requirements-build.txt
index 23403ac4..209e5b8d 100644
--- a/requirements-build.txt
+++ b/requirements-build.txt
@@ -1,6 +1,6 @@
 # Requirements needed when generating releases. See BUILD.rst for details.
 invoke >= 0.20
-rellu >= 0.5
+rellu >= 0.6
 twine
 wheel
 
diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py
index 3869e896..25ed7702 100644
--- a/src/SeleniumLibrary/__init__.py
+++ b/src/SeleniumLibrary/__init__.py
@@ -198,25 +198,10 @@ class SeleniumLibrary(DynamicCore):
 
     == Locating tables ==
 
-    Locating tables when using table related keywords, such as
-    `Table Should Contain`, works differently than locating other elements.
-    The main differences are:
-
-    - The default strategy is to search only by ``id``, not by ``name``.
-    - Implicit XPath strategies using format ``//example`` is not supported.
-    - Only explicit strategies that are supported are ``xpath``, ``css``
-      and ``sizzle/jquery``.
-    - Spaces are not allowed around the separator between the strategy and
-      the value.
-
-    Examples:
-
-    | `Table Should Contain` | example                         | text | # Match based on ``id``.        |
-    | `Table Should Contain` | css=table.example               | text | # Match using CSS selector.     |
-    | `Table Should Contain` | xpath=//table/[@name="example"] | text | # Match using XPath expression. |
-
-    The plan is to uniform locating tables and locating other elements in
-    the future.
+    Starting from release 3.0.0, it is possible to locate table using all
+    strategies supported by the library. Prior the release 3.0 tables
+    could have been located with limited set of strategies and with
+    limitations in the strategy.
 
     = Timeouts, waits and delays =
 
@@ -345,7 +330,7 @@ class SeleniumLibrary(DynamicCore):
         DynamicCore.__init__(self, libraries)
         self.ROBOT_LIBRARY_LISTENER = LibraryListener()
         self.element_finder = ElementFinder(self)
-        self._table_element_finder = TableElementFinder(self)
+        self.table_element_finder = TableElementFinder(self)
 
     _speed_in_secs = Deprecated('_speed_in_secs', 'speed')
     _timeout_in_secs = Deprecated('_timeout_in_secs', 'timeout')
diff --git a/src/SeleniumLibrary/base/context.py b/src/SeleniumLibrary/base/context.py
index 8a1e844a..52e16e16 100644
--- a/src/SeleniumLibrary/base/context.py
+++ b/src/SeleniumLibrary/base/context.py
@@ -37,6 +37,11 @@ class ContextAware(object):
     def element_finder(self):
         return self.ctx.element_finder
 
+    def find_element(self, locator, tag=None, first_only=True, required=True,
+                     parent=None):
+        return self.element_finder.find(locator, tag, first_only,
+                                        required, parent)
+
     @property
     def table_element_finder(self):
-        return self.ctx._table_element_finder
+        return self.ctx.table_element_finder
diff --git a/src/SeleniumLibrary/base/librarycomponent.py b/src/SeleniumLibrary/base/librarycomponent.py
index 235185e1..b51ef383 100644
--- a/src/SeleniumLibrary/base/librarycomponent.py
+++ b/src/SeleniumLibrary/base/librarycomponent.py
@@ -44,9 +44,6 @@ class LibraryComponent(ContextAware):
     def warn(self, msg, html=False):
         logger.warn(msg, html)
 
-    def find_element(self, locator, tag=None, first_only=True, required=True):
-        return self.element_finder.find(locator, tag, first_only, required)
-
     def assert_page_contains(self, locator, tag=None, message=None,
                              loglevel='INFO'):
         self.element_finder.assert_page_contains(locator, tag, message,
diff --git a/src/SeleniumLibrary/keywords/tableelement.py b/src/SeleniumLibrary/keywords/tableelement.py
index c087a808..ffb0bcbf 100644
--- a/src/SeleniumLibrary/keywords/tableelement.py
+++ b/src/SeleniumLibrary/keywords/tableelement.py
@@ -39,7 +39,7 @@ class TableElementKeywords(LibraryComponent):
         column_index = column
         if column > 0:
             column_index = column - 1
-        table = self.table_element_finder.find(table_locator)
+        table = self.element_finder.find(table_locator)
         if table:
             rows = table.find_elements_by_xpath("./thead/tr")
             if row_index >= len(rows) or row_index < 0:
diff --git a/src/SeleniumLibrary/locators/customlocator.py b/src/SeleniumLibrary/locators/customlocator.py
index c45a2273..6c677583 100644
--- a/src/SeleniumLibrary/locators/customlocator.py
+++ b/src/SeleniumLibrary/locators/customlocator.py
@@ -32,13 +32,13 @@ class CustomLocator(ContextAware):
         self.name = name
         self.finder = finder
 
-    def find(self, criteria, tag, constraints):
+    def find(self, criteria, tag, constraints, parent):
         # Allow custom locators to be keywords or normal methods
         if isinstance(self.finder, basestring):
-            element = BuiltIn().run_keyword(self.finder, self.browser,
+            element = BuiltIn().run_keyword(self.finder, parent,
                                             criteria, tag, constraints)
         elif hasattr(self.finder, '__call__'):
-            element = self.finder(self.browser, criteria, tag, constraints)
+            element = self.finder(parent, criteria, tag, constraints)
         else:
             raise AttributeError('Invalid type provided for Custom Locator %s'
                                  % self.name)
diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py
index 99bb5297..801ad158 100644
--- a/src/SeleniumLibrary/locators/elementfinder.py
+++ b/src/SeleniumLibrary/locators/elementfinder.py
@@ -57,13 +57,18 @@ class ElementFinder(ContextAware):
                        'normalize-space(descendant-or-self::text())']
         }
 
-    def find(self, locator, tag=None, first_only=True, required=True):
-        if isinstance(locator, WebElement):
+    def find(self, locator, tag=None, first_only=True, required=True,
+             parent=None):
+        if parent and not self._is_webelement(parent):
+            raise ValueError('Parent must be Selenium WebElement but it '
+                             'was {}'.format(type(parent)))
+        if self._is_webelement(locator):
             return locator
         prefix, criteria = self._parse_locator(locator)
         strategy = self._strategies[prefix]
         tag, constraints = self._get_tag_and_constraints(tag)
-        elements = strategy(criteria, tag, constraints)
+        elements = strategy(criteria, tag, constraints,
+                            parent=parent or self.browser)
         if required and not elements:
             raise ValueError("Element locator '{}' did not match any "
                              "elements.".format(locator))
@@ -75,26 +80,25 @@ class ElementFinder(ContextAware):
 
     def assert_page_contains(self, locator, tag=None, message=None,
                              loglevel='INFO'):
-        element_name = tag if tag else 'element'
         if not self.find(locator, tag, required=False):
             if is_falsy(message):
                 message = ("Page should have contained %s '%s' but did not"
-                           % (element_name, locator))
+                           % (tag or 'element', locator))
             self.ctx.log_source(loglevel)  # TODO: Could this moved to base
             raise AssertionError(message)
-        logger.info("Current page contains %s '%s'." % (element_name, locator))
+        logger.info("Current page contains %s '%s'."
+                    % (tag or 'element', locator))
 
     def assert_page_not_contains(self, locator, tag=None, message=None,
                                  loglevel='INFO'):
-        element_name = tag if tag else 'element'
         if self.find(locator, tag, required=False):
             if is_falsy(message):
                 message = ("Page should not have contained %s '%s'"
-                           % (element_name, locator))
+                           % (tag or 'element', locator))
             self.ctx.log_source(loglevel)  # TODO: Could this moved to base
             raise AssertionError(message)
         logger.info("Current page does not contain %s '%s'."
-                    % (element_name, locator))
+                    % (tag or 'element', locator))
 
     def get_value(self, locator, tag=None):
         element = self.find(locator, tag, required=False)
@@ -121,29 +125,34 @@ class ElementFinder(ContextAware):
         else:
             del self._strategies[strategy_name]
 
-    def _find_by_identifier(self, criteria, tag, constraints):
-        elements = self._normalize_result(
-            self.browser.find_elements_by_id(criteria))
+    def _is_webelement(self, element):
+        # Hook for unit tests
+        return isinstance(element, WebElement)
+
+    def _disallow_webelement_parent(self, element):
+        if self._is_webelement(element):
+            raise ValueError('This method does not allow WebElement as parent')
+
+    def _find_by_identifier(self, criteria, tag, constraints, parent):
+        elements = self._normalize_result(parent.find_elements_by_id(criteria))
         elements.extend(self._normalize_result(
-            self.browser.find_elements_by_name(criteria)))
+            parent.find_elements_by_name(criteria)))
         return self._filter_elements(elements, tag, constraints)
 
-    def _find_by_id(self, criteria, tag, constraints):
-        return self._filter_elements(
-            self.browser.find_elements_by_id(criteria),
-            tag, constraints)
+    def _find_by_id(self, criteria, tag, constraints, parent):
+        return self._filter_elements(parent.find_elements_by_id(criteria),
+                                     tag, constraints)
 
-    def _find_by_name(self, criteria, tag, constraints):
-        return self._filter_elements(
-            self.browser.find_elements_by_name(criteria),
-            tag, constraints)
+    def _find_by_name(self, criteria, tag, constraints, parent):
+        return self._filter_elements(parent.find_elements_by_name(criteria),
+                                     tag, constraints)
 
-    def _find_by_xpath(self, criteria, tag, constraints):
-        return self._filter_elements(
-            self.browser.find_elements_by_xpath(criteria),
-            tag, constraints)
+    def _find_by_xpath(self, criteria, tag, constraints, parent):
+        return self._filter_elements(parent.find_elements_by_xpath(criteria),
+                                     tag, constraints)
 
-    def _find_by_dom(self, criteria, tag, constraints):
+    def _find_by_dom(self, criteria, tag, constraints, parent):
+        self._disallow_webelement_parent(parent)
         result = self.browser.execute_script("return %s;" % criteria)
         if result is None:
             return []
@@ -151,43 +160,45 @@ class ElementFinder(ContextAware):
             result = [result]
         return self._filter_elements(result, tag, constraints)
 
-    def _find_by_sizzle_selector(self, criteria, tag, constraints):
+    def _find_by_sizzle_selector(self, criteria, tag, constraints, parent):
+        self._disallow_webelement_parent(parent)
         js = "return jQuery('%s').get();" % criteria.replace("'", "\\'")
         return self._filter_elements(
             self.browser.execute_script(js),
             tag, constraints)
 
-    def _find_by_link_text(self, criteria, tag, constraints):
+    def _find_by_link_text(self, criteria, tag, constraints, parent):
         return self._filter_elements(
-            self.browser.find_elements_by_link_text(criteria),
+            parent.find_elements_by_link_text(criteria),
             tag, constraints)
 
-    def _find_by_partial_link_text(self, criteria, tag, constraints):
+    def _find_by_partial_link_text(self, criteria, tag, constraints, parent):
         return self._filter_elements(
-            self.browser.find_elements_by_partial_link_text(criteria),
+            parent.find_elements_by_partial_link_text(criteria),
             tag, constraints)
 
-    def _find_by_css_selector(self, criteria, tag, constraints):
+    def _find_by_css_selector(self, criteria, tag, constraints, parent):
         return self._filter_elements(
-            self.browser.find_elements_by_css_selector(criteria),
+            parent.find_elements_by_css_selector(criteria),
             tag, constraints)
 
-    def _find_by_class_name(self, criteria, tag, constraints):
+    def _find_by_class_name(self, criteria, tag, constraints, parent):
         return self._filter_elements(
-            self.browser.find_elements_by_class_name(criteria),
+            parent.find_elements_by_class_name(criteria),
             tag, constraints)
 
-    def _find_by_tag_name(self, criteria, tag, constraints):
+    def _find_by_tag_name(self, criteria, tag, constraints, parent):
         return self._filter_elements(
-            self.browser.find_elements_by_tag_name(criteria),
+            parent.find_elements_by_tag_name(criteria),
             tag, constraints)
 
-    def _find_by_sc_locator(self, criteria, tag, constraints):
+    def _find_by_sc_locator(self, criteria, tag, constraints, parent):
+        self._disallow_webelement_parent(parent)
         js = "return isc.AutoTest.getElement('%s')" % criteria.replace("'", "\\'")
         return self._filter_elements([self.browser.execute_script(js)],
                                      tag, constraints)
 
-    def _find_by_default(self, criteria, tag, constraints):
+    def _find_by_default(self, criteria, tag, constraints, parent):
         if tag in self._key_attrs:
             key_attrs = self._key_attrs[tag]
         else:
@@ -203,8 +214,7 @@ class ElementFinder(ContextAware):
             ' and ' if xpath_constraints else '',
             ' or '.join(xpath_searchers)
         )
-        return self._normalize_result(
-            self.browser.find_elements_by_xpath(xpath))
+        return self._normalize_result(parent.find_elements_by_xpath(xpath))
 
     def _get_xpath_constraints(self, constraints):
         xpath_constraints = [self._get_xpath_constraint(name, value)
diff --git a/src/SeleniumLibrary/locators/tableelementfinder.py b/src/SeleniumLibrary/locators/tableelementfinder.py
index e7140328..dc4b18e3 100644
--- a/src/SeleniumLibrary/locators/tableelementfinder.py
+++ b/src/SeleniumLibrary/locators/tableelementfinder.py
@@ -21,55 +21,28 @@ class TableElementFinder(ContextAware):
 
     def __init__(self, ctx):
         ContextAware.__init__(self, ctx)
-        self._locator_suffixes = {
-            ('css', 'default'): [''],
-            ('css', 'content'): [''],
-            ('css', 'header'): [' th'],
-            ('css', 'footer'): [' tfoot td'],
-            ('css', 'row'): [' tr:nth-child(%s)'],
-            ('css', 'last-row'): [' tr:nth-last-child(%s)'],
-            ('css', 'col'): [' tr td:nth-child(%s)', ' tr th:nth-child(%s)'],
-            ('css', 'last-col'): [' tr td:nth-last-child(%s)', ' tr th:nth-last-child(%s)'],
-
-            ('jquery', 'default'): [''],
-            ('jquery', 'content'): [''],
-            ('jquery', 'header'): [' th'],
-            ('jquery', 'footer'): [' tfoot td'],
-            ('jquery', 'row'): [' tr:nth-child(%s)'],
-            ('jquery', 'col'): [' tr td:nth-child(%s)', ' tr th:nth-child(%s)'],
-
-            ('sizzle', 'default'): [''],
-            ('sizzle', 'content'): [''],
-            ('sizzle', 'header'): [' th'],
-            ('sizzle', 'footer'): [' tfoot td'],
-            ('sizzle', 'row'): [' tr:nth-child(%s)'],
-            ('sizzle', 'col'): [' tr td:nth-child(%s)', ' tr th:nth-child(%s)'],
-
-            ('xpath', 'default'): [''],
-            ('xpath', 'content'): ['//*'],
-            ('xpath', 'header'): ['//th'],
-            ('xpath', 'footer'): ['//tfoot//td'],
-            ('xpath', 'row'): ['//tr[%s]//*'],
-            ('xpath', 'last-row'): [' //tbody/tr[position()=last()-(%s-1)]'],
-            ('xpath', 'col'): ['//tr//*[self::td or self::th][%s]'],
-            ('xpath', 'last-col'): [' //tbody/tr/td[position()=last()-(%s-1)]', ' //tbody/tr/td[position()=last()-(%s-1)]']
+        self._locators = {
+            'content': ['//*'],
+            'header': ['//th'],
+            'footer': ['//tfoot//td'],
+            'row': ['//tr[{row}]//*'],
+            'last-row': ['//tbody/tr[position()=last()-({row}-1)]'],
+            'col': ['//tr//*[self::td or self::th][{col}]'],
+            'last-col': ['//tbody/tr/td[position()=last()-({col}-1)]',
+                         '//tbody/tr/td[position()=last()-({col}-1)]']
         }
 
-    def find(self, table_locator):
-        locators = self._parse_table_locator(table_locator, 'default')
-        return self._search_in_locators(locators, None)
-
     def find_by_content(self, table_locator, content):
-        locators = self._parse_table_locator(table_locator, 'content')
-        return self._search_in_locators(locators, content)
+        locators = self._locators['content']
+        return self._search_in_locators(table_locator, locators, content)
 
     def find_by_header(self, table_locator, content):
-        locators = self._parse_table_locator(table_locator, 'header')
-        return self._search_in_locators(locators, content)
+        locators = self._locators['header']
+        return self._search_in_locators(table_locator, locators, content)
 
     def find_by_footer(self, table_locator, content):
-        locators = self._parse_table_locator(table_locator, 'footer')
-        return self._search_in_locators(locators, content)
+        locators = self._locators['footer']
+        return self._search_in_locators(table_locator, locators, content)
 
     def find_by_row(self, table_locator, row, content):
         location_method = "row"
@@ -77,9 +50,9 @@ class TableElementFinder(ContextAware):
         if row[0] == "-":
             row = row[1:]
             location_method = "last-row"
-        locators = self._parse_table_locator(table_locator, location_method)
-        locators = [locator % str(row) for locator in locators]
-        return self._search_in_locators(locators, content)
+        locators = [locator.format(row=row)
+                    for locator in self._locators[location_method]]
+        return self._search_in_locators(table_locator, locators, content)
 
     def find_by_col(self, table_locator, col, content):
         location_method = "col"
@@ -87,28 +60,15 @@ class TableElementFinder(ContextAware):
         if col[0] == "-":
             col = col[1:]
             location_method = "last-col"
-        locators = self._parse_table_locator(table_locator, location_method)
-        locators = [locator % str(col) for locator in locators]
-        return self._search_in_locators(locators, content)
-
-    def _parse_table_locator(self, table_locator, location_method):
-        if table_locator.startswith('xpath='):
-            table_locator_type = 'xpath'
-        elif table_locator.startswith('jquery=') or table_locator.startswith('sizzle='):
-            table_locator_type = 'sizzle'
-        else:
-            if not table_locator.startswith('css='):
-                table_locator = "css=table#%s" % table_locator
-            table_locator_type = 'css'
-        locator_suffixes = self._locator_suffixes[(table_locator_type, location_method)]
-        return map(
-            lambda locator_suffix: table_locator + locator_suffix,
-            locator_suffixes)
+        locators = [locator.format(col=col)
+                    for locator in self._locators[location_method]]
+        return self._search_in_locators(table_locator, locators, content)
 
-    def _search_in_locators(self, locators, content):
+    def _search_in_locators(self, table_locator, locators, content):
+        table = self.find_element(table_locator)
         for locator in locators:
-            elements = self.element_finder.find(locator, first_only=False,
-                                                required=False)
+            elements = self.find_element(locator, first_only=False,
+                                         required=False, parent=table)
             for element in elements:
                 if content is None:
                     return element
diff --git a/tasks.py b/tasks.py
index 62b96cbc..256f91d8 100644
--- a/tasks.py
+++ b/tasks.py
@@ -26,9 +26,17 @@ from the `issue tracker`_.
 
 **REMOVE ``--pre`` from the next command with final releases.**
 If you have pip_ installed, just run
-``pip install --pre --upgrade robotframework-seleniumlibrary``
-to install the latest release or use
-``pip install robotframework-seleniumlibrary=={version}``
+
+::
+
+   pip install --pre --upgrade robotframework-seleniumlibrary
+
+to install the latest available release or use
+
+::
+
+   pip install robotframework-seleniumlibrary=={version}
+
 to install exactly this version. Alternatively you can download the source
 distribution from PyPI_ and install it manually.
 


Test patch:
diff --git a/test/acceptance/keywords/tables/col_should_contain.robot b/test/acceptance/keywords/tables/col_should_contain.robot
index 7b7eb676..ddcffaed 100644
--- a/test/acceptance/keywords/tables/col_should_contain.robot
+++ b/test/acceptance/keywords/tables/col_should_contain.robot
@@ -1,10 +1,8 @@
 *** Settings ***
-Documentation     Tests columns content
 Resource          table_resource.robot
 
 *** Test Cases ***
 Should Find Text In Specific Column
-    [Documentation]    Should Find Text In Specific Column
     [Template]    Table Column Should Contain With CSS And XPath Locators
     simpleTable    2    simpleTable_B2
     simpleWithNested    2    nestedTable_A1
@@ -14,16 +12,14 @@ Should Find Text In Specific Column
     tableWithTwoHeaders    2    tableWithTwoHeaders_B2
 
 Should Give Error Message When Content Not Found In Table Column
-    [Documentation]    Should Give Error Message When Content Not Found In Table Column
     Run Keyword And Expect Error
-    ...    Column #2 in table identified by 'simpleTable' should have contained text 'simpleTable_C3'.
-    ...    Table Column Should Contain    simpleTable    2    simpleTable_C3
+    ...    Column #2 in table identified by 'id:simpleTable' should have contained text 'simpleTable_C3'.
+    ...    Table Column Should Contain    id:simpleTable    2    simpleTable_C3
 
 Should Give Error Message When Column Number Out Of Bounds
-    [Documentation]    Should Give Error Message When Column Number Out Of Bounds
     Run Keyword And Expect Error
-    ...    Column #20 in table identified by 'simpleTable' should have contained text 'simpleTable_B3'.
-    ...    Table Column Should Contain    simpleTable    20    simpleTable_B3
+    ...    Column #20 in table identified by 'xpath=//*[@id="simpleTable"]' should have contained text 'simpleTable_B3'.
+    ...    Table Column Should Contain    xpath=//*[@id="simpleTable"]    20    simpleTable_B3
 
 *** Keywords ***
 Table Column Should Contain With CSS And XPath Locators
diff --git a/test/acceptance/keywords/tables/finding_tables.robot b/test/acceptance/keywords/tables/finding_tables.robot
index 5b988875..7a930bb7 100644
--- a/test/acceptance/keywords/tables/finding_tables.robot
+++ b/test/acceptance/keywords/tables/finding_tables.robot
@@ -1,15 +1,25 @@
 *** Settings ***
-Documentation     Tests finding
 Resource          table_resource.robot
 
 *** Test Cases ***
 Should Identify Table By CSS
-    [Documentation]    Should Identify Table By CSS
     Table Should Contain    css=table#simpleTable    simpleTable
-    Table Header Should Contain    css=table#tableWithTwoHeaders    tableWithTwoHeaders_C2
+    Table Header Should Contain    css:table#tableWithTwoHeaders    tableWithTwoHeaders_C2
     Table Footer Should Contain    css=table#withHeadAndFoot    withHeadAndFoot_AF1
     Table Row Should Contain    css=table#mergedRows    1    mergedRows_D1
     Table Column Should Contain    css=table#mergedCols    1    mergedCols_D1
     Table Cell Should Contain    css=table#formattedTable    1    1    formattedTable_A1
     Table Cell Should Contain    css=table#formattedTable    2    4    äöü€&äöü€&
     Table Cell Should Contain    css=h2.someClass ~ table:last-child    2    4    äöü€&äöü€&
+
+Should Identify Table By xpath
+    Table Should Contain    xpath://table[@id="simpleTable"]    simpleTable
+
+Should Identify Table By id
+    Table Should Contain    id:simpleTable    simpleTable
+
+Should Identify Table By name
+    Table Should Contain    name:simpleTable    simpleTable
+
+Should Identify Table By default
+    Table Should Contain    simpleTable    simpleTable
diff --git a/test/acceptance/keywords/tables/footer_should_contain.robot b/test/acceptance/keywords/tables/footer_should_contain.robot
index 3d4f0bac..3554212e 100644
--- a/test/acceptance/keywords/tables/footer_should_contain.robot
+++ b/test/acceptance/keywords/tables/footer_should_contain.robot
@@ -1,10 +1,9 @@
 *** Settings ***
-Documentation     Tests footer
+Documentation     Tests for table footer
 Resource          table_resource.robot
 
 *** Test Cases ***
 Should Find Text In Footer
-    [Documentation]    Should Find Text In Footer
     [Template]    Table Footer Should Contain With CSS And XPath Locators
     withHeadAndFoot    withHeadAndFoot_AF1
     withHeadAndFoot    withHeadAndFoot_CF1
@@ -12,14 +11,12 @@ Should Find Text In Footer
     withHeadAndFoot    withHeadAndFoot_CF2
 
 Should Give Error Message When Content Not Found In Table Footer
-    [Documentation]    Should Give Error Message When Content Not Found In Table Footer
     Run Keyword And Expect Error
     ...    Footer in table identified by 'withHeadAndFoot' should have contained text 'withHeadAndFoot_B2'.
     ...    Table Footer Should Contain    withHeadAndFoot    withHeadAndFoot_B2
 
 *** Keywords ***
 Table Footer Should Contain With CSS And XPath Locators
-    [Documentation]    Table Footer Should Contain With CSS And XPath Locators
     [Arguments]    ${tableId}    ${content}
     Run Table Keyword With CSS And XPath Locators
     ...    Table Footer Should Contain    ${table id}    ${content}
diff --git a/test/acceptance/keywords/tables/get_table_cell.robot b/test/acceptance/keywords/tables/get_table_cell.robot
index f6f8ea34..125ec38b 100644
--- a/test/acceptance/keywords/tables/get_table_cell.robot
+++ b/test/acceptance/keywords/tables/get_table_cell.robot
@@ -4,7 +4,6 @@ Resource          table_resource.robot
 
 *** Test Cases ***
 Should Retrieve Text From Cell
-    [Documentation]    Should Retrieve Text From Cell
     [Template]    Table Cell Should Be Equal With CSS And XPath Locators
     simpleTable    1    1    simpleTable_A1
     tableWithSingleHeader    1    1    tableWithSingleHeader_A1
@@ -21,26 +20,22 @@ Should Retrieve Text From Cell
     formattedTable    2    4    äöü€&äöü€&
 
 Should Give Error Message When Content Not Found In Table Cell
-    [Documentation]    Should Give Error Message When Content Not Found In Table Cell
     Run Keyword And Expect Error
     ...    Cell in table 'simpleTable' in row #1 and column #2 should have contained text 'simpleTable_B3'.
     ...    Table Cell Should Contain    simpleTable    1    2    simpleTable_B3
 
 Should Give Error Message When Index Out Of Bounds
-    [Documentation]    Should Give Error Message When Index Out Of Bounds
     Run Keyword And Expect Error
     ...    Cell in table 'simpleTable' in row #10 and column #20 should have contained text 'simpleTable_B3'.
     ...    Table Cell Should Contain    simpleTable    10    20    simpleTable_B3
 
 *** Keywords ***
 Table Cell Should Be Equal With CSS And XPath Locators
-    [Documentation]    Should Give Error Message When Index Out Of Bounds
     [Arguments]    ${tableId}    ${row}    ${col}    ${content}
     Run Table Keyword With CSS And XPath Locators
     ...    Table Cell Should Be Equal    ${table id}    ${row}    ${col}    ${content}
 
 Table Cell Should Be Equal
-    [Documentation]    Table Cell Should Be Equal
     [Arguments]    ${tableId}    ${row}    ${col}    ${content}
     ${cell}=    Get Table Cell    ${tableId}    ${row}    ${col}
     Should Be Equal    ${cell}    ${content}
diff --git a/test/acceptance/keywords/tables/header_should_contain.robot b/test/acceptance/keywords/tables/header_should_contain.robot
index 7520265e..d624e989 100644
--- a/test/acceptance/keywords/tables/header_should_contain.robot
+++ b/test/acceptance/keywords/tables/header_should_contain.robot
@@ -1,10 +1,8 @@
 *** Settings ***
-Documentation     Tests header
 Resource          table_resource.robot
 
 *** Test Cases ***
 Should Find Text In Header
-    [Documentation]    Should Find Text In Header
     [Template]    Table Header Should Contain With CSS And XPath Locators
     tableWithSingleHeader    tableWithSingleHeader_A1
     tableWithSingleHeader    tableWithSingleHeader_B1
@@ -16,14 +14,12 @@ Should Find Text In Header
     withHeadAndFoot    withHeadAndFoot_BH2
 
 Should Give Error Message When Content Not Found In Table Header
-    [Documentation]    Should Give Error Message When Content Not Found In Table Header
     Run Keyword And Expect Error
     ...    Header in table identified by 'withHeadAndFoot' should have contained text 'withHeadAndFoot_B2'.
     ...    Table Header Should Contain    withHeadAndFoot    withHeadAndFoot_B2
 
 *** Keywords ***
 Table Header Should Contain With CSS And XPath Locators
-    [Documentation]    Table Header Should Contain With CSS And XPath Locators
     [Arguments]    ${table id}    ${expected}
     Run Table Keyword With CSS And XPath Locators    Table Header Should Contain
     ...    ${table id}    ${expected}
diff --git a/test/acceptance/keywords/tables/negative_indexes.robot b/test/acceptance/keywords/tables/negative_indexes.robot
index 835c20de..01ce9153 100644
--- a/test/acceptance/keywords/tables/negative_indexes.robot
+++ b/test/acceptance/keywords/tables/negative_indexes.robot
@@ -1,33 +1,30 @@
 *** Settings ***
-Documentation     Tests negative indexes in Robot keywords
 Resource          table_resource.robot
 
 *** Test Cases ***
 Test negative index for CSS strategy in 'Table Row Should Contain'
-    [Documentation]    Test negative index for CSS strategy in 'Table Row Should Contain'
     Table Row Should Contain    tableWithSingleHeader
     ...    -1    tableWithSingleHeader_A3 tableWithSingleHeader_B3 tableWithSingleHeader_C3
+    Table Row Should Contain    id:tableWithSingleHeader
+    ...    -1    tableWithSingleHeader_A3 tableWithSingleHeader_B3 tableWithSingleHeader_C3
+    Table Row Should Contain    xpath://*[@id="tableWithSingleHeader"]
+    ...    -1    tableWithSingleHeader_A3 tableWithSingleHeader_B3 tableWithSingleHeader_C3
 
 Test negative index for XPath strategy in 'Table Row Should Contain'
-    [Documentation]    Test negative index for XPath strategy in 'Table Row Should Contain'
     Table Row Should Contain    xpath=//*[@id='tableWithSingleHeader']
     ...    -1    tableWithSingleHeader_A3 tableWithSingleHeader_B3 tableWithSingleHeader_C3
 
 Test negative index for CSS strategy in 'Table Column Should Contain'
-    [Documentation]    Test negative index for CSS strategy in 'Table Column Should Contain'
     Table Column Should Contain    tableWithSingleHeader
     ...    -1    tableWithSingleHeader_C2
 
 Test negative index for XPath strategy in 'Table Column Should Contain'
-    [Documentation]    Test negative index for XPath strategy in 'Table Column Should Contain'
     Table Column Should Contain    xpath=//*[@id='tableWithSingleHeader']
     ...    -1    tableWithSingleHeader_C2
 
 Test negative index for XPath strategy in 'Table Cell Should Contain'
-    [Documentation]    Test negative index for XPath strategy in 'Table Cell Should Contain'
     Table Cell Should Contain    simpleTable    -1    -2    simpleTable_B3
 
 Test negative index for CSS strategy in 'Table Cell Should Contain'
-    [Documentation]    Test negative index for CSS strategy in 'Table Cell Should Contain'
     Table Cell Should Contain    xpath=//*[@name='simpleTableName']
     ...    -1    -2    simpleTableName_B3
diff --git a/test/acceptance/keywords/tables/row_should_contain.robot b/test/acceptance/keywords/tables/row_should_contain.robot
index 3864db95..544b9a29 100644
--- a/test/acceptance/keywords/tables/row_should_contain.robot
+++ b/test/acceptance/keywords/tables/row_should_contain.robot
@@ -1,10 +1,9 @@
 *** Settings ***
-Documentation     Tests rows content
+Documentation     Tests table rows content
 Resource          table_resource.robot
 
 *** Test Cases ***
 Should Find Text In Specific Row
-    [Documentation]    Should Find Text In Specific Row
     [Template]    Table Row Should Contain With CSS And XPath Locators
     simpleTable    1    simpleTable_A1
     simpleTable    3    simpleTable_C3
@@ -26,24 +25,20 @@ Should Find Text In Specific Row
     formattedTable    2    äöü€&äöü€&
 
 Should Find Text In Specific Row with CSS Specific Mechanics
-    [Documentation]    Should Find Text In Specific Row with CSS Specific Mechanics
     Table Row Should Contain    formattedTable    1    formattedTable_D1
 
 Should Give Error Message When Content Not Found In Table Row
-    [Documentation]    Should Give Error Message When Content Not Found In Table Row
     Run Keyword And Expect Error
     ...    Row #2 in table identified by 'simpleTable' should have contained text 'simpleTable_B3'.
     ...    Table Row Should Contain    simpleTable    2    simpleTable_B3
 
 Should Give Error Message When Row Number Out Of Bounds
-    [Documentation]    Should Give Error Message When Row Number Out Of Bounds
     Run Keyword And Expect Error
     ...    Row #20 in table identified by 'simpleTable' should have contained text 'simpleTable_B3'.
     ...    Table Row Should Contain    simpleTable    20    simpleTable_B3
 
 *** Keywords ***
 Table Row Should Contain With CSS And XPath Locators
-    [Documentation]    Table Row Should Contain With CSS And XPath Locators
     [Arguments]    ${table id}    ${row}    ${expected}
     Run Table Keyword With CSS And XPath Locators    Table Row Should Contain
     ...    ${table id}    ${row}    ${expected}
diff --git a/test/acceptance/keywords/tables/table_resource.robot b/test/acceptance/keywords/tables/table_resource.robot
index 31f037cb..651b03c4 100644
--- a/test/acceptance/keywords/tables/table_resource.robot
+++ b/test/acceptance/keywords/tables/table_resource.robot
@@ -4,13 +4,11 @@ Resource          ../../resource.robot
 
 *** Keywords ***
 Run Table Keyword With CSS And XPath Locators
-    [Documentation]    Run Table Keyword With CSS And XPath Locators
     [Arguments]    ${keyword}    ${table id}    @{args}
     Run Keyword    ${keyword}    ${table id}    @{args}
     ${xpath}=    Get Table XPath    ${table id}
     Run Keyword    ${keyword}    ${xpath}    @{args}
 
 Get Table XPath
-    [Documentation]    Get Table XPath
     [Arguments]    ${table id}
     [Return]    xpath=//table[@id="${table id}"]
diff --git a/test/acceptance/keywords/tables/table_should_contain.robot b/test/acceptance/keywords/tables/table_should_contain.robot
index bb97d3b1..6d75030e 100644
--- a/test/acceptance/keywords/tables/table_should_contain.robot
+++ b/test/acceptance/keywords/tables/table_should_contain.robot
@@ -4,7 +4,6 @@ Resource          table_resource.robot
 
 *** Test Cases ***
 Should Find Text In Table Content
-    [Documentation]    Should Find Text In Table Content
     [Template]    Verify Table Contains With CSS And XPath Locators
     simpleTable    simpleTable_A1
     simpleTable    simpleTable_C3
@@ -28,29 +27,24 @@ Should Find Text In Table Content
     formattedTable    äöü€&äöü€&
 
 Should Find Text In Table Content with CSS Specific Mechanics
-    [Documentation]    Should Find Text In Table Content with CSS Specific Mechanics
     Table Should Contain    formattedTable    formattedTable_D1
 
 Should Give Error Message When Content Not Found In Table
-    [Documentation]    Should Give Error Message When Content Not Found In Table
     [Template]    Table Should Contain Fails With CSS And XPath Locators
     simpleTable    Not here
 
 *** Keywords ***
 Verify Table Contains With CSS And XPath Locators
-    [Documentation]    Verify Table Contains With CSS And XPath Locators
     [Arguments]    ${table id}    ${expected}
     Run Table Keyword With CSS And XPath Locators    Table Should Contain
     ...    ${table id}    ${expected}
 
 Table Should Contain Fails With CSS And XPath Locators
-    [Documentation]    Table Should Contain Fails With CSS And XPath Locators
     [Arguments]    ${table id}    ${expected}
     Run Table Keyword With CSS And XPath Locators    Table Should Contain Fails
     ...    ${table id}    ${expected}
 
 Table Should Contain Fails
-    [Documentation]    Table Should Contain Fails
     [Arguments]    ${locator}    ${expected}
     ${err}=    Set Variable
     ...    Table identified by '${locator}' should have contained text '${expected}'.
diff --git a/test/resources/html/tables/tables.html b/test/resources/html/tables/tables.html
index 25a66c0d..60864dd4 100644
--- a/test/resources/html/tables/tables.html
+++ b/test/resources/html/tables/tables.html
@@ -7,7 +7,7 @@
 </head>
 <body>
 <h2>Simple Table</h2>
-<table id="simpleTable" border="1">
+<table id="simpleTable" name="simpleTable" border="1">
   <tr>
     <td>simpleTable_A1</td>
     <td>simpleTable_B1</td>
diff --git a/test/unit/api/test_public_api_objects.py b/test/unit/api/test_public_api_objects.py
index 10df765f..8a02c034 100644
--- a/test/unit/api/test_public_api_objects.py
+++ b/test/unit/api/test_public_api_objects.py
@@ -8,7 +8,7 @@ class TableElementFinderInAPITest(unittest.TestCase):
 
     def test_table_finder(self):
         sl = SeleniumLibrary()
-        self.assertIsInstance(sl._table_element_finder, TableElementFinder)
+        self.assertIsInstance(sl.table_element_finder, TableElementFinder)
 
 
 class ElementFinderInAPITest(unittest.TestCase):
diff --git a/test/unit/locators/test_elementfinder.py b/test/unit/locators/test_elementfinder.py
index 98f896f9..df73c76c 100644
--- a/test/unit/locators/test_elementfinder.py
+++ b/test/unit/locators/test_elementfinder.py
@@ -57,6 +57,137 @@ class ParseLocatorTests(unittest.TestCase):
         self.assertEqual(parse_locator(locator), (prefix, criteria))
 
 
+class ElementFinderParentTests(unittest.TestCase):
+
+    def setUp(self):
+        self.ctx = mock()
+        self.ctx.browser = self.browser = mock()
+        self.finder = ElementFinder(self.ctx)
+
+    def tearDown(self):
+        unstub()
+
+    def test_parent_is_not_webelement(self):
+        with self.assertRaises(ValueError):
+            self.finder.find("//div", parent='//button')
+
+    def test_find_by_xpath_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('//div').thenReturn(False)
+        when(webelement).find_elements_by_xpath('//div').thenReturn([mock()])
+        self.finder.find('//div', parent=webelement)
+        verify(webelement).find_elements_by_xpath('//div')
+
+    def test_find_by_identifier_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('identifier=value').thenReturn(False)
+        when(webelement).find_elements_by_id('value').thenReturn([mock()])
+        when(webelement).find_elements_by_name('value').thenReturn([mock()])
+        self.finder.find('identifier=value', parent=webelement)
+        verify(webelement).find_elements_by_name('value')
+        verify(webelement).find_elements_by_id('value')
+
+    def test_find_by_id_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('id=value').thenReturn(False)
+        when(webelement).find_elements_by_id('value').thenReturn([mock()])
+        self.finder.find('id=value', parent=webelement)
+        verify(webelement).find_elements_by_id("value")
+
+    def test_find_by_name_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('name=value').thenReturn(False)
+        when(webelement).find_elements_by_name('value').thenReturn([mock()])
+        self.finder.find('name=value', parent=webelement)
+        verify(webelement).find_elements_by_name("value")
+
+    def test_find_by_dom__parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('dom=value').thenReturn(False)
+        when(self.finder)._disallow_webelement_parent(webelement).thenRaise(
+            ValueError('This method does not allow webelement as parent'))
+        with self.assertRaisesRegexp(ValueError, 'not allow webelement as parent'):
+            self.finder.find('dom=value', parent=webelement)
+
+    def test_find_by_sizzle_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('sizzle=div.class').thenReturn(False)
+        when(self.finder)._disallow_webelement_parent(webelement).thenRaise(
+            ValueError('This method does not allow webelement as parent'))
+        with self.assertRaisesRegexp(ValueError, 'not allow webelement as parent'):
+            self.finder.find('sizzle=div.class', parent=webelement)
+
+    def test_find_by_link_text_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('link=My Link').thenReturn(False)
+        when(webelement).find_elements_by_link_text(
+            'My Link').thenReturn([mock()])
+        self.finder.find('link=My Link', parent=webelement)
+        verify(webelement).find_elements_by_link_text("My Link")
+
+    def test_find_by_partial_link_text_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('partial link=My L').thenReturn(False)
+        when(webelement).find_elements_by_partial_link_text(
+            'My L').thenReturn([mock()])
+        self.finder.find('partial link=My L', parent=webelement)
+        verify(webelement).find_elements_by_partial_link_text("My L")
+
+    def test_find_by_css_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('css=div').thenReturn(False)
+        when(webelement).find_elements_by_css_selector(
+            'div').thenReturn([mock()])
+        self.finder.find('css=div', parent=webelement)
+        verify(webelement).find_elements_by_css_selector("div")
+
+    def test_find_by_class_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('class=name').thenReturn(False)
+        when(webelement).find_elements_by_class_name(
+            'name').thenReturn([mock()])
+        self.finder.find('class=name', parent=webelement)
+        verify(webelement).find_elements_by_class_name("name")
+
+    def test_find_by_tag_name_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('tag=name').thenReturn(False)
+        when(webelement).find_elements_by_tag_name(
+            'name').thenReturn([mock()])
+        self.finder.find('tag=name', parent=webelement)
+        verify(webelement).find_elements_by_tag_name("name")
+
+    def test_find_sc_locator_parent_is_webelement(self):
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('scLocator=div').thenReturn(False)
+        when(self.finder)._disallow_webelement_parent(webelement).thenRaise(
+            ValueError('This method does not allow webelement as parent'))
+        with self.assertRaisesRegexp(ValueError, 'not allow webelement as parent'):
+            self.finder.find('scLocator=div', parent=webelement)
+
+    def test_find_by_default_parent_is_webelement(self):
+        xpath = "//*[(@id='name' or @name='name')]"
+        webelement = mock()
+        when(self.finder)._is_webelement(webelement).thenReturn(True)
+        when(self.finder)._is_webelement('default=name').thenReturn(False)
+        when(webelement).find_elements_by_xpath(
+            xpath).thenReturn([mock()])
+        self.finder.find('default=name', parent=webelement)
+        verify(webelement).find_elements_by_xpath(xpath)
+
+
 class ElementFinderTests(unittest.TestCase):
 
     def setUp(self):
diff --git a/test/unit/locators/test_tableelementfinder.py b/test/unit/locators/test_tableelementfinder.py
index 65a7166a..410661f2 100644
--- a/test/unit/locators/test_tableelementfinder.py
+++ b/test/unit/locators/test_tableelementfinder.py
@@ -5,144 +5,51 @@ from mockito import mock, verify, when, unstub
 from SeleniumLibrary.locators.tableelementfinder import TableElementFinder
 
 
-class ElementFinderTests(unittest.TestCase):
+class ElementFinderTest(unittest.TestCase):
 
     def setUp(self):
         self.ctx = mock()
         self.ctx.element_finder = mock()
         self.finder = TableElementFinder(self.ctx)
-        self.args = {'first_only': False, 'required': False}
 
     def tearDown(self):
         unstub()
 
-    def test_find_with_implicit_css_locator(self):
-        when(self.ctx.element_finder).find("css=table#test1",
-                                           **self.args).thenReturn([])
-        self.finder.find("test1")
-        verify(self.ctx.element_finder).find("css=table#test1", **self.args)
-
-    def test_find_with_css_selector(self):
-        elements = self._make_mock_elements('table', 'table', 'table')
-        when(self.ctx.element_finder).find("css=table#test1",
-                                           **self.args).thenReturn(elements)
-        self.assertEqual(self.finder.find("css=table#test1"),
-                         elements[0])
-        verify(self.ctx.element_finder).find("css=table#test1", **self.args)
-
-    def test_find_with_xpath_selector(self):
-        elements = self._make_mock_elements('table', 'table', 'table')
-        when(self.ctx.element_finder).find("xpath=//table[@id='test1']",
-                                           **self.args).thenReturn(elements)
-        self.assertEqual(self.finder.find("xpath=//table[@id='test1']"),
-                         elements[0])
-        verify(self.ctx.element_finder).find("xpath=//table[@id='test1']",
-                                             **self.args)
-
-    def test_find_with_content_constraint(self):
-        elements = self._make_mock_elements('td', 'td', 'td')
-        elements[1].text = 'hi'
-        when(self.ctx.element_finder).find("css=table#test1",
-                                           **self.args).thenReturn(elements)
-        self.assertEqual(self.finder.find_by_content("test1", 'hi'),
-                         elements[1])
-        verify(self.ctx.element_finder).find("css=table#test1",
-                                             **self.args)
-
-    def test_find_with_null_content_constraint(self):
-        elements = self._make_mock_elements('td', 'td', 'td')
-        elements[1].text = 'hi'
-        when(self.ctx.element_finder).find("css=table#test1",
-                                           **self.args).thenReturn(elements)
-        self.assertEqual(self.finder.find_by_content("test1", None),
-                         elements[0])
-        verify(self.ctx.element_finder).find("css=table#test1", **self.args)
-
-    def test_find_by_content_with_css_locator(self):
-        when(self.ctx.element_finder).find("css=table#test1",
-                                           **self.args).thenReturn([])
-        self.finder.find_by_content("css=table#test1", 'hi')
-        verify(self.ctx.element_finder).find("css=table#test1", **self.args)
-
-    def test_find_by_content_with_xpath_locator(self):
-        when(self.ctx.element_finder).find("xpath=//table[@id='test1']//*",
-                                           **self.args).thenReturn([])
-        self.finder.find_by_content("xpath=//table[@id='test1']", 'hi')
-        verify(self.ctx.element_finder).find("xpath=//table[@id='test1']//*",
-                                             **self.args)
-
-    def test_find_by_header_with_css_locator(self):
-        when(self.ctx.element_finder).find("css=table#test1 th",
-                                           **self.args).thenReturn([])
-        self.finder.find_by_header("css=table#test1", 'hi')
-        verify(self.ctx.element_finder).find("css=table#test1 th", **self.args)
-
-    def test_find_by_header_with_xpath_locator(self):
-        when(self.ctx.element_finder).find("xpath=//table[@id='test1']//th",
-                                           **self.args).thenReturn([])
-        self.finder.find_by_header("xpath=//table[@id='test1']", 'hi')
-        verify(self.ctx.element_finder).find("xpath=//table[@id='test1']//th",
-                                             **self.args)
-
-    def test_find_by_footer_with_css_locator(self):
-        when(self.ctx.element_finder).find("css=table#test1 tfoot td",
-                                           **self.args).thenReturn([])
-        self.finder.find_by_footer("css=table#test1", 'hi')
-        verify(self.ctx.element_finder).find("css=table#test1 tfoot td",
-                                             **self.args)
-
-    def test_find_by_footer_with_xpath_locator(self):
-        xpath = "xpath=//table[@id='test1']//tfoot//td"
-        when(self.ctx.element_finder).find(xpath, **self.args).thenReturn([])
-        self.finder.find_by_footer("xpath=//table[@id='test1']", 'hi')
-        verify(self.ctx.element_finder).find(xpath, **self.args)
-
-    def test_find_by_row_with_css_locator(self):
-        when(self.ctx.element_finder).find("css=table#test1 tr:nth-child(2)",
-                                           **self.args).thenReturn([])
-        self.finder.find_by_row("css=table#test1", 2, 'hi')
-        verify(self.ctx.element_finder).find("css=table#test1 tr:nth-child(2)",
-                                             **self.args)
-
-    def test_find_by_row_with_xpath_locator(self):
-        xpath = "xpath=//table[@id='test1']//tr[2]//*"
-        when(self.ctx.element_finder).find(xpath, **self.args).thenReturn([])
-        self.finder.find_by_row("xpath=//table[@id='test1']", 2, 'hi')
-        verify(self.ctx.element_finder).find(xpath, **self.args)
-
-    def test_find_by_col_with_css_locator(self):
-        css1 = "css=table#test1 tr td:nth-child(2)"
-        css2 = "css=table#test1 tr th:nth-child(2)"
-        when(self.ctx.element_finder).find(css1, **self.args).thenReturn([])
-        when(self.ctx.element_finder).find(css2, **self.args).thenReturn([])
-        self.finder.find_by_col("css=table#test1", 2, 'hi')
-        verify(self.ctx.element_finder).find(css1, **self.args)
-        verify(self.ctx.element_finder).find(css2, **self.args)
-
-    def test_find_by_col_with_xpath_locator(self):
-        xpath = "xpath=//table[@id='test1']//tr//*[self::td or self::th][2]"
-        when(self.ctx.element_finder).find(xpath, **self.args).thenReturn([])
-        self.finder.find_by_col("xpath=//table[@id='test1']", 2, 'hi')
-        verify(self.ctx.element_finder).find(xpath, **self.args)
-
-    def _make_mock_elements(self, *tags):
-        elements = []
-        for tag in tags:
-            element = self._make_mock_element(tag)
-            elements.append(element)
-        return elements
-
-    def _make_mock_element(self, tag):
+    def test_find_by_col(self):
         element = mock()
-        element.tag_name = tag
-        element.attributes = {}
-        element.text = None
-
-        def set_attribute(name, value):
-            element.attributes[name] = value
-        element.set_attribute = set_attribute
-
-        def get_attribute(name):
-            return element.attributes[name]
-        element.get_attribute = get_attribute
-        return element
+        xpath = ['//tr//*[self::td or self::th][1]']
+        when(self.finder)._search_in_locators('id:table', xpath,
+                                              'content').thenReturn(element)
+        self.finder.find_by_col('id:table', 1, 'content')
+
+        xpath = ['//tbody/tr/td[position()=last()-(2-1)]',
+                 '//tbody/tr/td[position()=last()-(2-1)]']
+        when(self.finder)._search_in_locators('id:table', xpath,
+                                              'content').thenReturn(element)
+        self.finder.find_by_col('id:table', 1, 'content')
+
+    def test_find_by_row(self):
+        element = mock()
+        xpath = ['//tr[2]//*']
+        when(self.finder)._search_in_locators('xpath=//table', xpath,
+                                              'content').thenReturn(element)
+        self.finder.find_by_row('xpath=//table', 2, 'content')
+
+        xpath = ['//tbody/tr[position()=last()-(3-1)]']
+        when(self.finder)._search_in_locators('xpath=//table', xpath,
+                                              'content').thenReturn(element)
+        self.finder.find_by_row('xpath=//table', -3, 'content')
+
+    def test_by_search_in_locators(self):
+        xpath = ['//th']
+        table = mock()
+        element1 = mock()
+        element2 = mock()
+        element1.text = 'not here'
+        element2.text = 'content'
+        table_elements = [element1, element2]
+        when(self.ctx.element_finder).find(
+            'css=table', None, True, True, None).thenReturn(table)
+        when(self.ctx.element_finder).find(
+            xpath[0], None, False, False, table).thenReturn(table_elements)
+        self.finder._search_in_locators('css=table', xpath, 'content')