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
256 changes: 126 additions & 130 deletions src/SeleniumLibrary/keywords/tableelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,179 +20,175 @@
class TableElementKeywords(LibraryComponent):

@keyword
def get_table_cell(self, table_locator, row, column, loglevel='INFO'):
"""Returns the content from a table cell.
def get_table_cell(self, locator, row, column, loglevel='INFO'):
"""Returns contents of table cell.

Row and column number start from 1. Header and footer rows are
included in the count. A negative row or column number can be used
to get rows counting from the end (end: -1). Cell content from header
or footer rows can be obtained with this keyword. To understand how
tables are identified, please take a look at the `introduction`.
The table is located using the ``locator`` argument and its cell
found using ``row`` and ``column``. See the `Locating elements`
section for details about the locator syntax.

See `Page Should Contain` for explanation about `loglevel` argument.
Both row and column indexes start from 1, and header and footer
rows are included in the count. It is possible to refer to rows
and columns from the end by using negative indexes so that -1
is the last row/column, -2 is the second last, and so on.

All ``<th>`` and ``<td>`` elements anywhere in the table are
considered to be cells.

See `Page Should Contain` for explanation about the ``loglevel``
argument.
"""
row = int(row)
row_index = row
if row > 0:
row_index = row - 1
column = int(column)
column_index = column
if column > 0:
column_index = column - 1
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:
rows.extend(table.find_elements_by_xpath("./tbody/tr"))
if row_index >= len(rows) or row_index < 0:
rows.extend(table.find_elements_by_xpath("./tfoot/tr"))
if row_index < len(rows):
columns = rows[row_index].find_elements_by_tag_name('th')
if column_index >= len(columns) or column_index < 0:
columns.extend(rows[row_index].find_elements_by_tag_name('td'))
if column_index < len(columns):
return columns[column_index].text
self.ctx.log_source(loglevel)
raise AssertionError("Cell in table %s in row #%s and column #%s "
"could not be found."
% (table_locator, str(row), str(column)))
if row == 0 or column == 0:
raise ValueError('Both row and column must be non-zero, '
'got row %d and column %d.' % (row, column))
try:
cell = self._get_cell(locator, row, column)
except AssertionError:
self.log_source(loglevel)
raise
return cell.text

def _get_cell(self, locator, row, column):
rows = self._get_rows(locator, row)
if len(rows) < abs(row):
raise AssertionError("Table '%s' should have had at least %d "
"rows but had only %d."
% (locator, abs(row), len(rows)))
index = row - 1 if row > 0 else row
cells = rows[index].find_elements_by_xpath('./th|./td')
if len(cells) < abs(column):
raise AssertionError("Table '%s' row %d should have had at "
"least %d columns but had only %d."
% (locator, row, abs(column), len(cells)))
index = column - 1 if column > 0 else column
return cells[index]

def _get_rows(self, locator, count):
# Get rows in same order as browsers render them.
table = self.find_element(locator, tag='table')
rows = table.find_elements_by_xpath("./thead/tr")
if count < 0 or len(rows) < count:
rows.extend(table.find_elements_by_xpath("./tbody/tr"))
if count < 0 or len(rows) < count:
rows.extend(table.find_elements_by_xpath("./tfoot/tr"))
return rows

@keyword
def table_cell_should_contain(self, table_locator, row, column, expected, loglevel='INFO'):
"""Verifies that a certain cell in a table contains `expected`.

Row and column number start from 1. This keyword passes if the
specified cell contains the given content. If you want to test
that the cell content matches exactly, or that it e.g. starts
with some text, use `Get Table Cell` keyword in combination
with built-in keywords such as `Should Be Equal` or `Should
Start With`.
def table_cell_should_contain(self, locator, row, column, expected, loglevel='INFO'):
"""Verifies table cell contains text ``expected``.

To understand how tables are identified, please take a look at
the `introduction`.

See `Page Should Contain` for explanation about `loglevel` argument.
See `Get Table Cell` that this keyword uses internally for
explanation about accepted arguments.
"""
message = ("Cell in table '%s' in row #%s and column #%s "
"should have contained text '%s'."
% (table_locator, row, column, expected))
try:
content = self.get_table_cell(table_locator, row, column, loglevel='NONE')
except AssertionError as err:
self.info(err)
self.ctx.log_source(loglevel)
raise AssertionError(message)
self.info("Cell contains %s." % (content))
content = self.get_table_cell(locator, row, column, loglevel)
if expected not in content:
self.ctx.log_source(loglevel)
raise AssertionError(message)
raise AssertionError("Table '%s' cell on row %s and column %s "
"should have contained text '%s' but it had "
"'%s'."
% (locator, row, column, expected, content))
self.info("Table cell contains '%s'." % content)

@keyword
def table_column_should_contain(self, table_locator, col, expected, loglevel='INFO'):
"""Verifies that a specific column contains `expected`.
def table_column_should_contain(self, locator, column, expected, loglevel='INFO'):
"""Verifies table column contains text ``expected``.

The first leftmost column is column number 1. A negative column
number can be used to get column counting from the end of the row (end: -1).
If the table contains cells that span multiple columns, those merged cells
count as a single column. For example both tests below work,
if in one row columns A and B are merged with colspan="2", and
the logical third column contains "C".
The table is located using the ``locator`` argument and its column
found using ``column``. See the `Locating elements` section for
details about the locator syntax.

Example:
| Table Column Should Contain | tableId | 3 | C |
| Table Column Should Contain | tableId | 2 | C |
Column indexes start from 1. It is possible to refer to columns
from the end by using negative indexes so that -1 is the last column,
-2 is the second last, and so on.

To understand how tables are identified, please take a look at
the `introduction`.
If a table contains cells that span multiple columns, those merged
cells count as a single column.

See `Page Should Contain Element` for explanation about
`loglevel` argument.
See `Page Should Contain Element` for explanation about the
``loglevel`` argument.
"""
element = self.table_element_finder.find_by_col(table_locator, col,
expected)
element = self.table_element_finder.find_by_column(locator, column, expected)
if element is None:
self.ctx.log_source(loglevel)
raise AssertionError("Column #%s in table identified by '%s' "
"should have contained text '%s'."
% (col, table_locator, expected))
raise AssertionError("Table '%s' column %s did not contain text "
"'%s'." % (locator, column, expected))

@keyword
def table_footer_should_contain(self, table_locator, expected, loglevel='INFO'):
"""Verifies that the table footer contains `expected`.
def table_footer_should_contain(self, locator, expected, loglevel='INFO'):
"""Verifies table footer contains text ``expected``.

With table footer can be described as any <td>-element that is
child of a <tfoot>-element. To understand how tables are
identified, please take a look at the `introduction`.
Any ``<td>`` element inside ``<tfoot>`` element is considered to
be part of the footer.

See `Page Should Contain Element` for explanation about
`loglevel` argument.
The table is located using the ``locator`` argument. See the
`Locating elements` section for details about the locator syntax.

See `Page Should Contain Element` for explanation about the
``loglevel`` argument.
"""
element = self.table_element_finder.find_by_footer(table_locator,
expected)
element = self.table_element_finder.find_by_footer(locator, expected)
if element is None:
self.ctx.log_source(loglevel)
raise AssertionError("Footer in table identified by '%s' "
"should have contained "
"text '%s'." % (table_locator, expected))
raise AssertionError("Table '%s' footer did not contain text "
"'%s'." % (locator, expected))

@keyword
def table_header_should_contain(self, table_locator, expected, loglevel='INFO'):
"""Verifies that the table header, i.e. any <th>...</th> element, contains `expected`.
def table_header_should_contain(self, locator, expected, loglevel='INFO'):
"""Verifies table header contains text ``expected``.

Any ``<th>`` element anywhere in the table is considered to be
part of the header.

To understand how tables are identified, please take a look at
the `introduction`.
The table is located using the ``locator`` argument. See the
`Locating elements` section for details about the locator syntax.

See `Page Should Contain Element` for explanation about
`loglevel` argument.
See `Page Should Contain Element` for explanation about the
``loglevel`` argument.
"""
element = self.table_element_finder.find_by_header(table_locator,
expected)
element = self.table_element_finder.find_by_header(locator, expected)
if element is None:
self.ctx.log_source(loglevel)
raise AssertionError("Header in table identified by '%s' should "
"have contained "
"text '%s'." % (table_locator, expected))
raise AssertionError("Table '%s' header did not contain text "
"'%s'." % (locator, expected))

@keyword
def table_row_should_contain(self, table_locator, row, expected, loglevel='INFO'):
"""Verifies that a specific table row contains `expected`.

The uppermost row is row number 1. A negative column
number can be used to get column counting from the end of the row
(end: -1). For tables that are structured with thead, tbody and tfoot,
only the tbody section is searched. Please use `Table Header Should Contain`
or `Table Footer Should Contain` for tests against the header or
footer content.

If the table contains cells that span multiple rows, a match
only occurs for the uppermost row of those merged cells. To
understand how tables are identified, please take a look at
the `introduction`.

See `Page Should Contain Element` for explanation about `loglevel` argument.
def table_row_should_contain(self, locator, row, expected, loglevel='INFO'):
"""Verifies that table row contains text ``expected``.

The table is located using the ``locator`` argument and its column
found using ``column``. See the `Locating elements` section for
details about the locator syntax.

Row indexes start from 1. It is possible to refer to rows
from the end by using negative indexes so that -1 is the last row,
-2 is the second last, and so on.

If a table contains cells that span multiple rows, a match
only occurs for the uppermost row of those merged cells.

See `Page Should Contain Element` for explanation about the
``loglevel`` argument.
"""
element = self.table_element_finder.find_by_row(table_locator,
row, expected)
element = self.table_element_finder.find_by_row(locator, row, expected)
if element is None:
self.ctx.log_source(loglevel)
raise AssertionError("Row #%s in table identified by '%s' "
"should have contained "
"text '%s'." % (row, table_locator, expected))
raise AssertionError("Table '%s' row %s did not contain text "
"'%s'." % (locator, row, expected))

@keyword
def table_should_contain(self, table_locator, expected, loglevel='INFO'):
"""Verifies that `expected` can be found somewhere in the table.
def table_should_contain(self, locator, expected, loglevel='INFO'):
"""Verifies table contains text ``expected``.

To understand how tables are identified, please take a look at
the `introduction`.
The table is located using the ``locator`` argument. See the
`Locating elements` section for details about the locator syntax.

See `Page Should Contain Element` for explanation about
`loglevel` argument.
See `Page Should Contain Element` for explanation about the
``loglevel`` argument.
"""
element = self.table_element_finder.find_by_content(table_locator,
expected)
element = self.table_element_finder.find_by_content(locator, expected)
if element is None:
self.ctx.log_source(loglevel)
raise AssertionError("Table identified by '%s' should have "
"contained text '%s'."
% (table_locator, expected))
raise AssertionError("Table '%s' did not contain text '%s'."
% (locator, expected))
73 changes: 29 additions & 44 deletions src/SeleniumLibrary/locators/tableelementfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,42 @@


class TableElementFinder(ContextAware):
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_by_content(self, table_locator, content):
locators = self.locators['content']
return self._search_in_locators(table_locator, locators, content)
return self._find(table_locator, '//*', content)

def find_by_header(self, table_locator, content):
locators = self.locators['header']
return self._search_in_locators(table_locator, locators, content)
return self._find(table_locator, '//th', content)

def find_by_footer(self, table_locator, content):
locators = self.locators['footer']
return self._search_in_locators(table_locator, locators, content)
return self._find(table_locator, '//tfoot//td', 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 = [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 = [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, table_locator, locators, content):
position = self._index_to_position(row)
locator = '//tr[{}]'.format(position)
return self._find(table_locator, locator, content)

def find_by_column(self, table_locator, col, content):
position = self._index_to_position(col)
locator = '//tr//*[self::td or self::th][{}]'.format(position)
return self._find(table_locator, locator, content)

def _index_to_position(self, index):
index = int(index)
if index == 0:
raise ValueError('Row and column indexes must be non-zero.')
if index > 0:
return str(index)
if index == -1:
return 'position()=last()'
return 'position()=last()-{}'.format(abs(index) - 1)

def _find(self, table_locator, locator, content):
table = self.find_element(table_locator)
for locator in locators:
elements = self.find_elements(locator, parent=table)
for element in elements:
if content is None:
return element
element_text = element.text
if element_text and content in element_text:
return element
elements = self.find_elements(locator, parent=table)
for element in elements:
if content is None:
return element
if element.text and content in element.text:
return element
return None
Loading