Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
7 changes: 6 additions & 1 deletion src/SeleniumLibrary/base/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 0 additions & 3 deletions src/SeleniumLibrary/base/librarycomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/SeleniumLibrary/keywords/tableelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions src/SeleniumLibrary/locators/customlocator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
92 changes: 51 additions & 41 deletions src/SeleniumLibrary/locators/elementfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand All @@ -121,73 +125,80 @@ 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 []
if not isinstance(result, list):
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:
Expand All @@ -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)
Expand Down
90 changes: 25 additions & 65 deletions src/SeleniumLibrary/locators/tableelementfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,94 +21,54 @@ 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"
row = str(row)
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"
col = str(col)
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
Expand Down
12 changes: 4 additions & 8 deletions test/acceptance/keywords/tables/col_should_contain.robot
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
Loading