diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index bf1bac81f..2b22e07d6 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -260,7 +260,7 @@ def __init__(self, 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 c55634494..169a0e071 100644 --- a/src/SeleniumLibrary/base/context.py +++ b/src/SeleniumLibrary/base/context.py @@ -32,6 +32,11 @@ def browsers(self): 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 235185e18..b51ef3831 100644 --- a/src/SeleniumLibrary/base/librarycomponent.py +++ b/src/SeleniumLibrary/base/librarycomponent.py @@ -44,9 +44,6 @@ def log(self, msg, level='INFO', html=False): 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 c087a8086..ffb0bcbfe 100644 --- a/src/SeleniumLibrary/keywords/tableelement.py +++ b/src/SeleniumLibrary/keywords/tableelement.py @@ -39,7 +39,7 @@ def get_table_cell(self, table_locator, row, column, loglevel='INFO'): 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 c45a22739..6c6775833 100644 --- a/src/SeleniumLibrary/locators/customlocator.py +++ b/src/SeleniumLibrary/locators/customlocator.py @@ -32,13 +32,13 @@ def __init__(self, ctx, name, finder): 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 99bb5297c..801ad158a 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -57,13 +57,18 @@ def __init__(self, ctx): '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 @@ def find(self, locator, tag=None, first_only=True, required=True): 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 @@ def unregister(self, strategy_name): 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 @@ def _find_by_dom(self, criteria, tag, constraints): 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 @@ def _find_by_default(self, criteria, tag, constraints): ' 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 e71403286..dc4b18e3a 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 @@ def find_by_row(self, table_locator, row, content): 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 @@ def find_by_col(self, table_locator, col, content): 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/test/acceptance/keywords/tables/col_should_contain.robot b/test/acceptance/keywords/tables/col_should_contain.robot index 7b7eb676a..ddcffaedb 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 5b9888758..7a930bb7b 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 3d4f0bac2..3554212eb 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 f6f8ea344..125ec38bf 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 7520265e5..d624e989e 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 835c20deb..01ce91539 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 3864db953..544b9a293 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 31f037cb4..651b03c47 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 bb97d3b16..6d75030e9 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 25a66c0da..60864dd4f 100644 --- a/test/resources/html/tables/tables.html +++ b/test/resources/html/tables/tables.html @@ -7,7 +7,7 @@
| simpleTable_A1 | simpleTable_B1 | diff --git a/test/unit/api/test_public_api_objects.py b/test/unit/api/test_public_api_objects.py index 10df765ff..8a02c034d 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 98f896f9a..df73c76ce 100644 --- a/test/unit/locators/test_elementfinder.py +++ b/test/unit/locators/test_elementfinder.py @@ -57,6 +57,137 @@ def _verify_parse_locator(self, locator, prefix, criteria): 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 65a7166a8..410661f27 100644 --- a/test/unit/locators/test_tableelementfinder.py +++ b/test/unit/locators/test_tableelementfinder.py @@ -5,144 +5,51 @@ 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')