1# Copyright 2008-2011 Nokia Networks 2# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors 3# Copyright 2016- Robot Framework Foundation 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from SeleniumLibrary.base import LibraryComponent, keyword 18 19 20class TableElementKeywords(LibraryComponent): 21 22 @keyword 23 def get_table_cell(self, locator, row, column, loglevel='TRACE'): 24 """Returns contents of table cell. 25 26 The table is located using the ``locator`` argument and its cell 27 found using ``row`` and ``column``. See the `Locating elements` 28 section for details about the locator syntax. 29 30 Both row and column indexes start from 1, and header and footer 31 rows are included in the count. It is possible to refer to rows 32 and columns from the end by using negative indexes so that -1 33 is the last row/column, -2 is the second last, and so on. 34 35 All ``<th>`` and ``<td>`` elements anywhere in the table are 36 considered to be cells. 37 38 See `Page Should Contain` for explanation about the ``loglevel`` 39 argument. 40 """ 41 row = int(row) 42 column = int(column) 43 if row == 0 or column == 0: 44 raise ValueError('Both row and column must be non-zero, ' 45 'got row %d and column %d.' % (row, column)) 46 try: 47 cell = self._get_cell(locator, row, column) 48 except AssertionError: 49 self.log_source(loglevel) 50 raise 51 return cell.text 52 53 def _get_cell(self, locator, row, column): 54 rows = self._get_rows(locator, row) 55 if len(rows) < abs(row): 56 raise AssertionError("Table '%s' should have had at least %d " 57 "rows but had only %d." 58 % (locator, abs(row), len(rows))) 59 index = row - 1 if row > 0 else row 60 cells = rows[index].find_elements_by_xpath('./th|./td') 61 if len(cells) < abs(column): 62 raise AssertionError("Table '%s' row %d should have had at " 63 "least %d columns but had only %d." 64 % (locator, row, abs(column), len(cells))) 65 index = column - 1 if column > 0 else column 66 return cells[index] 67 68 def _get_rows(self, locator, count): 69 # Get rows in same order as browsers render them. 70 table = self.find_element(locator, tag='table') 71 rows = table.find_elements_by_xpath("./thead/tr") 72 if count < 0 or len(rows) < count: 73 rows.extend(table.find_elements_by_xpath("./tbody/tr")) 74 if count < 0 or len(rows) < count: 75 rows.extend(table.find_elements_by_xpath("./tfoot/tr")) 76 return rows 77 78 @keyword 79 def table_cell_should_contain(self, locator, row, column, expected, loglevel='TRACE'): 80 """Verifies table cell contains text ``expected``. 81 82 See `Get Table Cell` that this keyword uses internally for 83 explanation about accepted arguments. 84 """ 85 content = self.get_table_cell(locator, row, column, loglevel) 86 if expected not in content: 87 self.ctx.log_source(loglevel) 88 raise AssertionError("Table '%s' cell on row %s and column %s " 89 "should have contained text '%s' but it had " 90 "'%s'." 91 % (locator, row, column, expected, content)) 92 self.info("Table cell contains '%s'." % content) 93 94 @keyword 95 def table_column_should_contain(self, locator, column, expected, loglevel='TRACE'): 96 """Verifies table column contains text ``expected``. 97 98 The table is located using the ``locator`` argument and its column 99 found using ``column``. See the `Locating elements` section for 100 details about the locator syntax. 101 102 Column indexes start from 1. It is possible to refer to columns 103 from the end by using negative indexes so that -1 is the last column, 104 -2 is the second last, and so on. 105 106 If a table contains cells that span multiple columns, those merged 107 cells count as a single column. 108 109 See `Page Should Contain Element` for explanation about the 110 ``loglevel`` argument. 111 """ 112 element = self._find_by_column(locator, column, expected) 113 if element is None: 114 self.ctx.log_source(loglevel) 115 raise AssertionError("Table '%s' column %s did not contain text " 116 "'%s'." % (locator, column, expected)) 117 118 @keyword 119 def table_footer_should_contain(self, locator, expected, loglevel='TRACE'): 120 """Verifies table footer contains text ``expected``. 121 122 Any ``<td>`` element inside ``<tfoot>`` element is considered to 123 be part of the footer. 124 125 The table is located using the ``locator`` argument. See the 126 `Locating elements` section for details about the locator syntax. 127 128 See `Page Should Contain Element` for explanation about the 129 ``loglevel`` argument. 130 """ 131 element = self._find_by_footer(locator, expected) 132 if element is None: 133 self.ctx.log_source(loglevel) 134 raise AssertionError("Table '%s' footer did not contain text " 135 "'%s'." % (locator, expected)) 136 137 @keyword 138 def table_header_should_contain(self, locator, expected, loglevel='TRACE'): 139 """Verifies table header contains text ``expected``. 140 141 Any ``<th>`` element anywhere in the table is considered to be 142 part of the header. 143 144 The table is located using the ``locator`` argument. See the 145 `Locating elements` section for details about the locator syntax. 146 147 See `Page Should Contain Element` for explanation about the 148 ``loglevel`` argument. 149 """ 150 element = self._find_by_header(locator, expected) 151 if element is None: 152 self.ctx.log_source(loglevel) 153 raise AssertionError("Table '%s' header did not contain text " 154 "'%s'." % (locator, expected)) 155 156 @keyword 157 def table_row_should_contain(self, locator, row, expected, loglevel='TRACE'): 158 """Verifies that table row contains text ``expected``. 159 160 The table is located using the ``locator`` argument and its column 161 found using ``column``. See the `Locating elements` section for 162 details about the locator syntax. 163 164 Row indexes start from 1. It is possible to refer to rows 165 from the end by using negative indexes so that -1 is the last row, 166 -2 is the second last, and so on. 167 168 If a table contains cells that span multiple rows, a match 169 only occurs for the uppermost row of those merged cells. 170 171 See `Page Should Contain Element` for explanation about the 172 ``loglevel`` argument. 173 """ 174 element = self._find_by_row(locator, row, expected) 175 if element is None: 176 self.ctx.log_source(loglevel) 177 raise AssertionError("Table '%s' row %s did not contain text " 178 "'%s'." % (locator, row, expected)) 179 180 @keyword 181 def table_should_contain(self, locator, expected, loglevel='TRACE'): 182 """Verifies table contains text ``expected``. 183 184 The table is located using the ``locator`` argument. See the 185 `Locating elements` section for details about the locator syntax. 186 187 See `Page Should Contain Element` for explanation about the 188 ``loglevel`` argument. 189 """ 190 element = self._find_by_content(locator, expected) 191 if element is None: 192 self.ctx.log_source(loglevel) 193 raise AssertionError("Table '%s' did not contain text '%s'." 194 % (locator, expected)) 195 196 def _find_by_content(self, table_locator, content): 197 return self._find(table_locator, '//*', content) 198 199 def _find_by_header(self, table_locator, content): 200 return self._find(table_locator, '//th', content) 201 202 def _find_by_footer(self, table_locator, content): 203 return self._find(table_locator, '//tfoot//td', content) 204 205 def _find_by_row(self, table_locator, row, content): 206 position = self._index_to_position(row) 207 locator = '//tr[{}]'.format(position) 208 return self._find(table_locator, locator, content) 209 210 def _find_by_column(self, table_locator, col, content): 211 position = self._index_to_position(col) 212 locator = '//tr//*[self::td or self::th][{}]'.format(position) 213 return self._find(table_locator, locator, content) 214 215 def _index_to_position(self, index): 216 index = int(index) 217 if index == 0: 218 raise ValueError('Row and column indexes must be non-zero.') 219 if index > 0: 220 return str(index) 221 if index == -1: 222 return 'position()=last()' 223 return 'position()=last()-{}'.format(abs(index) - 1) 224 225 def _find(self, table_locator, locator, content): 226 table = self.find_element(table_locator) 227 elements = self.find_elements(locator, parent=table) 228 for element in elements: 229 if content is None: 230 return element 231 if element.text and content in element.text: 232 return element 233 return None 234