diff --git a/src/Selenium2Library/keywords/_tableelement.py b/src/Selenium2Library/keywords/_tableelement.py index 5306a1158..e7cbd43e3 100644 --- a/src/Selenium2Library/keywords/_tableelement.py +++ b/src/Selenium2Library/keywords/_tableelement.py @@ -16,23 +16,28 @@ def get_table_cell(self, table_locator, row, column, loglevel='INFO'): """Returns the content from a table cell. Row and column number start from 1. Header and footer rows are - included in the count. This means that also 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`. + 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`. """ row = int(row) - row_index = row - 1 + row_index = row + if row > 0: row_index = row - 1 column = int(column) - column_index = column - 1 + column_index = column + if column > 0: column_index = column - 1 table = self._table_element_finder.find(self._current_browser(), table_locator) if table is not None: rows = table.find_elements_by_xpath("./thead/tr") - if row_index >= len(rows): rows.extend(table.find_elements_by_xpath("./tbody/tr")) - if row_index >= len(rows): rows.extend(table.find_elements_by_xpath("./tfoot/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): columns.extend(rows[row_index].find_elements_by_tag_name('td')) + 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.log_source(loglevel) @@ -69,8 +74,9 @@ def table_cell_should_contain(self, table_locator, row, column, expected, loglev def table_column_should_contain(self, table_locator, col, expected, loglevel='INFO'): """Verifies that a specific column contains `expected`. - The first leftmost column is column number 1. If the table - contains cells that span multiple columns, those merged cells + 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". @@ -126,10 +132,11 @@ def table_header_should_contain(self, table_locator, expected, loglevel='INFO'): 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. 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 + 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 diff --git a/src/Selenium2Library/locators/tableelementfinder.py b/src/Selenium2Library/locators/tableelementfinder.py index 8737ebf5e..91c4b97c9 100644 --- a/src/Selenium2Library/locators/tableelementfinder.py +++ b/src/Selenium2Library/locators/tableelementfinder.py @@ -15,7 +15,9 @@ def __init__(self, element_finder=None): ('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'): [''], @@ -36,7 +38,9 @@ def __init__(self, element_finder=None): ('xpath', 'header'): ['//th'], ('xpath', 'footer'): ['//tfoot//td'], ('xpath', 'row'): ['//tr[%s]//*'], - ('xpath', 'col'): ['//tr//*[self::td or self::th][%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)]'] }; def find(self, browser, table_locator): @@ -55,13 +59,23 @@ def find_by_footer(self, browser, table_locator, content): locators = self._parse_table_locator(table_locator, 'footer') return self._search_in_locators(browser, locators, content) - def find_by_row(self, browser, table_locator, col, content): - locators = self._parse_table_locator(table_locator, 'row') - locators = [locator % str(col) for locator in locators] + def find_by_row(self, browser, 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(browser, locators, content) def find_by_col(self, browser, table_locator, col, content): - locators = self._parse_table_locator(table_locator, 'col') + 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(browser, locators, content) diff --git a/test/acceptance/create_webdriver.txt b/test/acceptance/create_webdriver.txt index 7aba6256e..6f7fe2134 100644 --- a/test/acceptance/create_webdriver.txt +++ b/test/acceptance/create_webdriver.txt @@ -4,7 +4,7 @@ Library Collections *Test Cases* Create Webdriver Creates Functioning WebDriver - [Documentation] LOG 2:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver LOG 2:3 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+ + [Documentation] LOG 2:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver LOG 2:4 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+ [Setup] Set Driver Variables Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS} Go To ${FRONT PAGE} diff --git a/test/acceptance/keywords/content_assertions.txt b/test/acceptance/keywords/content_assertions.txt index fec2d1d35..d7a546a95 100644 --- a/test/acceptance/keywords/content_assertions.txt +++ b/test/acceptance/keywords/content_assertions.txt @@ -6,28 +6,28 @@ Resource ../resource.txt *** Test Cases *** Location Should Be - [Documentation] LOG 2:2 Current location is '${FRONT PAGE}'. + [Documentation] LOG 2:3 Current location is '${FRONT PAGE}'. Location Should Be ${FRONT PAGE} Run Keyword And Expect Error Location should have been 'non existing' but was '${FRONT PAGE}' Location Should Be non existing Location Should Contain - [Documentation] LOG 2:2 Current location contains 'html'. + [Documentation] LOG 2:3 Current location contains 'html'. Location Should Contain html Run Keyword And Expect Error Location should have contained 'not a location' but it was '${FRONT PAGE}'. Location Should Contain not a location Title Should Be - [Documentation] LOG 2:2 Page title is '(root)/index.html'. + [Documentation] LOG 2:3 Page title is '(root)/index.html'. Title Should Be (root)/index.html Run Keyword And Expect Error Title should have been 'not a title' but was '(root)/index.html' Title Should Be not a title Page Should Contain - [Documentation] LOG 2:3 Current page contains text 'needle'. LOG 4.1:6 REGEXP: (?i) + [Documentation] LOG 2:5 Current page contains text 'needle'. LOG 4.1:10 REGEXP: (?i) Page Should Contain needle Page Should Contain This is the haystack Run Keyword And Expect Error Page should have contained text 'non existing text' but did not Page Should Contain non existing text Page Should Contain With Custom Log Level - [Documentation] LOG 2.1:6 DEBUG REGEXP: (?i) + [Documentation] LOG 2.1:10 DEBUG REGEXP: (?i) Run Keyword And Expect Error Page should have contained text 'non existing text' but did not Page Should Contain non existing text DEBUG Page Should Contain With Disabling Source Logging @@ -41,12 +41,12 @@ Page Should Contain With Frames Page Should Contain You're looking at right. Page Should Not Contain - [Documentation] LOG 2:5 Current page does not contain text 'non existing text'. LOG 3.1:4 REGEXP: (?i) + [Documentation] LOG 2:8 Current page does not contain text 'non existing text'. LOG 3.1:7 REGEXP: (?i) Page Should Not Contain non existing text Run Keyword And Expect Error Page should not have contained text 'needle' Page Should Not Contain needle Page Should Not Contain With Custom Log Level - [Documentation] LOG 2.1:4 DEBUG REGEXP: (?i) + [Documentation] LOG 2.1:7 DEBUG REGEXP: (?i) Run Keyword And Expect Error Page should not have contained text 'needle' Page Should Not Contain needle DEBUG Page Should Not Contain With Disabling Source Logging @@ -103,14 +103,14 @@ Element Should Not Be Visible Run Keyword And Expect Error The element 'i_am_visible' should not be visible, but it is. Element Should Not Be Visible i_am_visible Page Should Contain Checkbox - [Documentation] LOG 2:3 Current page contains checkbox 'can_send_email'. + [Documentation] LOG 2:5 Current page contains checkbox 'can_send_email'. [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Contain Checkbox can_send_email Page Should Contain Checkbox xpath=//input[@type='checkbox' and @name='can_send_sms'] Run Keyword And Expect Error Page should have contained checkbox 'non-existing' but did not Page Should Contain Checkbox non-existing Page Should Not Contain Checkbox - [Documentation] LOG 2:3 Current page does not contain checkbox 'non-existing'. + [Documentation] LOG 2:5 Current page does not contain checkbox 'non-existing'. [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Not Contain Checkbox non-existing Run Keyword And Expect Error Page should not have contained checkbox 'can_send_email' Page Should Not Contain Checkbox can_send_email @@ -169,7 +169,7 @@ Page Should Not Contain Text Field Run Keyword And Expect Error Page should not have contained text field 'name' Page Should Not Contain Text Field name TextField Should Contain - [Documentation] LOG 2:4 Text field 'name' contains text ''. + [Documentation] LOG 2:7 Text field 'name' contains text ''. [Setup] Go To Page "forms/email_form.html" TextField Should contain name ${EMPTY} Input Text name my name @@ -177,7 +177,7 @@ TextField Should Contain Run Keyword And Expect Error Text field 'name' should have contained text 'non-existing' but it contained 'my name' TextField Should contain name non-existing TextField Value Should Be - [Documentation] LOG 2:4 Content of text field 'name' is ''. + [Documentation] LOG 2:7 Content of text field 'name' is ''. [Setup] Go To Page "forms/email_form.html" textfield Value Should Be name ${EMPTY} Input Text name my name diff --git a/test/acceptance/keywords/forms_and_buttons.txt b/test/acceptance/keywords/forms_and_buttons.txt index 73deadf02..5eea3965d 100644 --- a/test/acceptance/keywords/forms_and_buttons.txt +++ b/test/acceptance/keywords/forms_and_buttons.txt @@ -52,10 +52,11 @@ Click button created with