From 264e38c459216246148883c0bf9e6a66aa952d62 Mon Sep 17 00:00:00 2001 From: Gaurang033 Date: Wed, 9 Mar 2016 00:32:56 +0530 Subject: [PATCH 01/31] Added following keywords and their acceptance test cases to fix the issue #463 table_cell_should_not_contain table_column_should_not_contain table_footer_should_not_contain table_header_should_not_contain table_row_should_not_contain table_should_not_contain --- .../keywords/_tableelement.py | 127 ++++++++++++++++++ .../tables/table_should_not_contain.robot | 111 +++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 test/acceptance/keywords/tables/table_should_not_contain.robot diff --git a/src/Selenium2Library/keywords/_tableelement.py b/src/Selenium2Library/keywords/_tableelement.py index 55eea39c8..04447d76e 100644 --- a/src/Selenium2Library/keywords/_tableelement.py +++ b/src/Selenium2Library/keywords/_tableelement.py @@ -73,6 +73,37 @@ def table_cell_should_contain(self, table_locator, row, column, expected, loglev self.log_source(loglevel) raise AssertionError(message) + def table_cell_should_not_contain(self, table_locator, row, column, expected, loglevel='INFO'): + """ + Verifies that a certain cell in a table does not contain `expected`. + + Row and column number start from 1. This keyword passes if the + specified cell does not contain the given content. If you want + to test that the cell content does not match exactly, or that + it e.g. starts with some text, use `Get Table Cell` keyword in + combination with built-in keywords such as `Should Not Be Equal` + or `Should Not Start With`. + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain` for explanation about `loglevel` argument. + """ + message = ("Cell in table '%s' in row #%s and column #%s " + "should not have contained text '%s'." + % (table_locator, row, column, expected)) + try: + content = self.get_table_cell(table_locator, row, column, loglevel='NONE') + except AssertionError, err: + self._info(err) + self.log_source(loglevel) + raise AssertionError(message) + self._info("Cell contains %s." % content) + + if expected in content: + self.log_source(loglevel) + raise AssertionError(message) + def table_column_should_contain(self, table_locator, col, expected, loglevel='INFO'): """Verifies that a specific column contains `expected`. @@ -100,6 +131,33 @@ def table_column_should_contain(self, table_locator, col, expected, loglevel='IN "should have contained text '%s'." % (col, table_locator, expected)) + def table_column_should_not_contain(self, table_locator, col, expected, loglevel='INFO'): + """Verifies that a specific column does not contain `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". + + Example: + | Table Column Should Not Contain | tableId | 3 | C | + | Table Column Should Not Contain | tableId | 2 | C | + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_col(self._current_browser(), table_locator, col, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Column #%s in table identified by '%s' " + "should not have contained text '%s'." + % (col, table_locator, expected)) + def table_footer_should_contain(self, table_locator, expected, loglevel='INFO'): """Verifies that the table footer contains `expected`. @@ -116,6 +174,22 @@ def table_footer_should_contain(self, table_locator, expected, loglevel='INFO'): raise AssertionError("Footer in table identified by '%s' should have contained " "text '%s'." % (table_locator, expected)) + def table_footer_should_not_contain(self, table_locator, expected, loglevel='INFO'): + """Verifies that the table footer does not contain `expected`. + + With table footer can be described as any -element that is + child of a -element. To understand how tables are + identified, please take a look at the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_footer(self._current_browser(), table_locator, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Footer in table identified by '%s' should not have contained " + "text '%s'." % (table_locator, expected)) + def table_header_should_contain(self, table_locator, expected, loglevel='INFO'): """Verifies that the table header, i.e. any ... element, contains `expected`. @@ -131,6 +205,21 @@ def table_header_should_contain(self, table_locator, expected, loglevel='INFO'): raise AssertionError("Header in table identified by '%s' should have contained " "text '%s'." % (table_locator, expected)) + def table_header_should_not_contain(self, table_locator, expected, loglevel='INFO'): + """Verifies that the table header, i.e. any ... element, does not contain `expected`. + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_header(self._current_browser(), table_locator, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Header in table identified by '%s' should not have contained " + "text '%s'." % (table_locator, expected)) + def table_row_should_contain(self, table_locator, row, expected, loglevel='INFO'): """Verifies that a specific table row contains `expected`. @@ -154,6 +243,29 @@ def table_row_should_contain(self, table_locator, row, expected, loglevel='INFO' raise AssertionError("Row #%s in table identified by '%s' should have contained " "text '%s'." % (row, table_locator, expected)) + def table_row_should_not_contain(self, table_locator, row, expected, loglevel='INFO'): + """Verifies that a specific table row does not contain `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. + """ + element = self._table_element_finder.find_by_row(self._current_browser(), table_locator, row, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Row #%s in table identified by '%s' should have contained " + "text '%s'." % (row, table_locator, expected)) + def table_should_contain(self, table_locator, expected, loglevel='INFO'): """Verifies that `expected` can be found somewhere in the table. @@ -168,3 +280,18 @@ def table_should_contain(self, table_locator, expected, loglevel='INFO'): self.log_source(loglevel) raise AssertionError("Table identified by '%s' should have contained text '%s'." \ % (table_locator, expected)) + + def table_should_not_contain(self, table_locator, expected, loglevel='INFO'): + """Verifies that `expected` should not be present anywhere in the table. + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_content(self._current_browser(), table_locator, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Table identified by '%s' should not have contained text '%s'." \ + % (table_locator, expected)) diff --git a/test/acceptance/keywords/tables/table_should_not_contain.robot b/test/acceptance/keywords/tables/table_should_not_contain.robot new file mode 100644 index 000000000..fec4a0081 --- /dev/null +++ b/test/acceptance/keywords/tables/table_should_not_contain.robot @@ -0,0 +1,111 @@ +*** Settings *** +Documentation Tests table contents for should not keyowrds +Resource table_resource.robot +Default Tags table_not_contain + + +*** Test Cases *** +Should Give Error Message When Content Found In Table + [Documentation] Should Give Error Message When Content Found In Table + [Template] Verify Table Does Contain With CSS And XPath Locators + simpleTable simpleTable_A1 + simpleTable simpleTable_C3 + simpleWithNested simpleWithNested_A1 + simpleWithNested nestedTable_A1 + nestedTable nestedTable_A1 + nestedTable nestedTable_C3 + tableWithSingleHeader tableWithSingleHeader_B1 + tableWithSingleHeader tableWithSingleHeader_B2 + withHeadAndFoot withHeadAndFoot_AH1 + withHeadAndFoot withHeadAndFoot_B2 + withHeadAndFoot withHeadAndFoot_CF1 + mergedRows mergedRows_A1 + mergedCols mergedCols_B2 + formattedTable formattedTable_A1 + formattedTable formattedTable_B1 + formattedTable formattedTable_C1 + formattedTable formattedTable_A2 + formattedTable formattedTable_B2 + formattedTable formattedTable_ÄÖÜäöüß + formattedTable äöü€&äöü€& + +Test Table Cell Should Not Contain + [Documentation] Test Table Cell Should Not Contain + ${table}= set variable simpleTable + ${row}= set variable 1 + ${col}= set variable 1 + ${expected} = set variable simpleTable_A2 + table cell should not contain ${table} ${row} ${col} ${expected} + +Test Table Cell Give Error When It Contains + [Documentation] Test Table Cell Give Error When It Contains + ${table}= set variable simpleTable + ${row}= set variable 1 + ${col}= set variable 1 + ${expected} = set variable simpleTable_A1 + ${err}= Set Variable Cell in table '${table}' in row #${row} and column #${col} should not have contained text '${expected}'. + Run Keyword And Expect Error ${err} table cell should not contain ${table} ${row} ${col} ${expected} + +Test Table Row Should Not Contain + [Documentation] Test Table Row Should Not Contain + ${table}= set variable simpleTable + ${row}= set variable 1 + ${expected} = set variable simpleTable_A2 + table row should not contain ${table} ${row} ${expected} + +Test Table Row Should Give Error When It Contains + [Documentation] Test Table Row Should Give Error When It Contains + ${table}= set variable tableWithSingleHeader + ${expected} = set variable tableWithSingleHeader_A1 + ${row}= set variable 1 + ${err}= Set Variable Row #${row} in table identified by '${table}' should have contained text '${expected}'. + run keyword and expect error ${err} table row should not contain ${table} ${row} ${expected} + +Test Table column Should Not Contain + [Documentation] Test Table column Should Not Contain + ${table}= set variable simpleTable + ${column}= set variable 1 + ${expected} = set variable simpleTable_B2 + table column should not contain ${table} ${column} ${expected} + +Test Table column Should Give Error When It Contains + [Documentation] Test Table column Should Give Error When It Contains + ${table}= set variable tableWithSingleHeader + ${column}= set variable 1 + ${expected} = set variable tableWithSingleHeader_A1 + ${err}= Set Variable Column #${column} in table identified by '${table}' should not have contained text '${expected}'. + run keyword and expect error ${err} table column should not contain ${table} ${column} ${expected} + +Test Table Header Should Not Contain + [Documentation] Test Table Header Should Not Contain + ${table}= set variable tableWithSingleHeader + ${expected} = set variable tableWithSingleHeader_A3 + table header should not contain ${table} ${expected} + +Test Table Header Should Give Error When It Contains + [Documentation] Test Table Header Should Give Error When It Contains + ${table}= set variable tableWithSingleHeader + ${expected} = set variable tableWithSingleHeader_A1 + ${err}= Set Variable Header in table identified by '${table}' should not have contained text '${expected}'. + run keyword and expect error ${err} table header should not contain ${table} ${expected} + +Test Table Footer Should Not Contain + [Documentation] Test Table Footer Should Not Contain + ${table}= set variable withHeadAndFoot + ${expected} = set variable withHeadAndFoot_A1 + table footer should not contain ${table} ${expected} + +Test Table Footer Should Give Error When It Contains + [Documentation] Test Table Footer Should Give Error When It Contains + ${table}= set variable withHeadAndFoot + ${expected} = set variable withHeadAndFoot_AF1 + ${err}= Set Variable Footer in table identified by '${table}' should not have contained text '${expected}'. + run keyword and expect error ${err} table footer should not contain ${table} ${expected} + + +*** Keywords *** +Verify Table Does Contain With CSS And XPath Locators + [Arguments] ${locator} ${expected} + [Documentation] Verify Table Contains With CSS And XPath Locators + ${err}= Set Variable Table identified by '${locator}' should not have contained text '${expected}'. + Run Keyword And Expect Error ${err} Table Should Not Contain ${locator} ${expected} From f8ec2ed4f362765c73a1267c8288cef5a4b2f1ca Mon Sep 17 00:00:00 2001 From: Gaurang Shah Date: Thu, 6 Oct 2016 21:14:32 +0530 Subject: [PATCH 02/31] Feature Request - Click Elements #585 --- src/Selenium2Library/keywords/_element.py | 13 +++++++++++++ test/acceptance/keywords/click_element.robot | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Selenium2Library/keywords/_element.py b/src/Selenium2Library/keywords/_element.py index 357ff1bd5..98d0e6632 100644 --- a/src/Selenium2Library/keywords/_element.py +++ b/src/Selenium2Library/keywords/_element.py @@ -819,3 +819,16 @@ def _page_should_not_contain_element(self, locator, tag, message, loglevel): raise AssertionError(message) self._info("Current page does not contain %s '%s'." % (element_name, locator)) + + def click_elements(self, locator): + """ + Clicks all the elements identified by `locator`. + + Key attributes for arbitrary elements are `id` and `name`. See + `introduction` for details about locating elements. + """ + elements = self._element_find(locator, False, True) + self._info("total %s elements found with matching locator %s" % (len(elements), locator)) + self._info("clicking on all elements found with matching locator %s" % locator) + for element in elements: + element.click() diff --git a/test/acceptance/keywords/click_element.robot b/test/acceptance/keywords/click_element.robot index c820b14a0..931a2ea25 100644 --- a/test/acceptance/keywords/click_element.robot +++ b/test/acceptance/keywords/click_element.robot @@ -16,6 +16,14 @@ Double Click Element Double Click Element doubleClickButton Element Text Should Be output double clicked +Click All Element + [Documentation] Test Click Elements keyword + Go To Page "forms/prefilled_email_form.html" + Click Elements //*[@type='checkbox'] + checkbox should not be selected can_send_email + Checkbox Should Be Selected can_send_sms + Go To Page "javascript/click.html" + *** Keywords *** Initialize Page [Documentation] Initialize Page From e66862f8cd3d4be6b8bafd86b71ea33d7e80de4d Mon Sep 17 00:00:00 2001 From: Gaurang Shah Date: Thu, 13 Oct 2016 18:28:38 +0530 Subject: [PATCH 03/31] Added method to find element by class name #673 --- src/Selenium2Library/locators/elementfinder.py | 6 ++++++ test/unit/locators/test_elementfinder.py | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Selenium2Library/locators/elementfinder.py b/src/Selenium2Library/locators/elementfinder.py index 041471370..cebbc2845 100644 --- a/src/Selenium2Library/locators/elementfinder.py +++ b/src/Selenium2Library/locators/elementfinder.py @@ -15,6 +15,7 @@ def __init__(self): 'link': self._find_by_link_text, 'partial link': self._find_by_partial_link_text, 'css': self._find_by_css_selector, + 'class': self._find_by_class_name, 'jquery': self._find_by_sizzle_selector, 'sizzle': self._find_by_sizzle_selector, 'tag': self._find_by_tag_name, @@ -108,6 +109,11 @@ def _find_by_css_selector(self, browser, criteria, tag, constraints): browser.find_elements_by_css_selector(criteria), tag, constraints) + def _find_by_class_name(self, browser, criteria, tag, constraints): + return self._filter_elements( + browser.find_elements_by_class_name(criteria), + tag, constraints) + def _find_by_tag_name(self, browser, criteria, tag, constraints): return self._filter_elements( browser.find_elements_by_tag_name(criteria), diff --git a/test/unit/locators/test_elementfinder.py b/test/unit/locators/test_elementfinder.py index 28505f1d1..fba91e30e 100644 --- a/test/unit/locators/test_elementfinder.py +++ b/test/unit/locators/test_elementfinder.py @@ -271,7 +271,6 @@ def test_find_by_partial_link_text(self): result = finder.find(browser, "partial link=my link", tag='a') self.assertEqual(result, [elements[1], elements[3]]) - def test_find_by_css_selector(self): finder = ElementFinder() browser = mock() @@ -284,6 +283,18 @@ def test_find_by_css_selector(self): result = finder.find(browser, "css=#test1", tag='a') self.assertEqual(result, [elements[1], elements[3]]) + def test_find_by_class_names(self): + finder = ElementFinder() + browser = mock() + + elements = self._make_mock_elements('div', 'a', 'span', 'a') + when(browser).find_elements_by_class_name("test1").thenReturn(elements) + + result = finder.find(browser, "class=test1") + self.assertEqual(result, elements) + result = finder.find(browser, "class=test1", tag='a') + self.assertEqual(result, [elements[1], elements[3]]) + def test_find_by_tag_name(self): finder = ElementFinder() browser = mock() From aafe5238ba292a8b0a7e974485e0386569e8b746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lio=20Guilherme?= Date: Fri, 14 Oct 2016 13:41:42 +0100 Subject: [PATCH 04/31] Changes to use Firefox ESR (45.4.0) While we are not having Selenium 3.0 working, with external `geckodriver`, we use lower versions of stable Firefox. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d9b956d00..b1ca3f66a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +addons: + firefox: latest-esr language: python sudo: false python: From a45ce8d8a15d7126ff971949d56daea744505c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lio=20Guilherme?= Date: Fri, 14 Oct 2016 13:45:35 +0100 Subject: [PATCH 05/31] Changes to use Selenium 2.53.6 (and not 3.0) We are not ready for Selenium 3.0 yet. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b1ca3f66a..92e5beafa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ install: #- pip install pep8 --use-mirrors #- pip install pyflakes --use-mirrors - pip install robotframework - - pip install selenium + - pip install selenium==2.53.6 - pip install decorator - pip install mockito #- "if [[ ${BROWSER} == 'ie' ]]; then pip install ie_only_pkg; fi" From 5ce853cebf899cbd9bd1112372c1a75838518682 Mon Sep 17 00:00:00 2001 From: Gaurang033 Date: Wed, 9 Mar 2016 00:32:56 +0530 Subject: [PATCH 06/31] Added following keywords and their acceptance test cases to fix the issue #463 table_cell_should_not_contain table_column_should_not_contain table_footer_should_not_contain table_header_should_not_contain table_row_should_not_contain table_should_not_contain --- .../keywords/_tableelement.py | 127 ++++++++++++++++++ .../tables/table_should_not_contain.robot | 111 +++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 test/acceptance/keywords/tables/table_should_not_contain.robot diff --git a/src/Selenium2Library/keywords/_tableelement.py b/src/Selenium2Library/keywords/_tableelement.py index 55eea39c8..04447d76e 100644 --- a/src/Selenium2Library/keywords/_tableelement.py +++ b/src/Selenium2Library/keywords/_tableelement.py @@ -73,6 +73,37 @@ def table_cell_should_contain(self, table_locator, row, column, expected, loglev self.log_source(loglevel) raise AssertionError(message) + def table_cell_should_not_contain(self, table_locator, row, column, expected, loglevel='INFO'): + """ + Verifies that a certain cell in a table does not contain `expected`. + + Row and column number start from 1. This keyword passes if the + specified cell does not contain the given content. If you want + to test that the cell content does not match exactly, or that + it e.g. starts with some text, use `Get Table Cell` keyword in + combination with built-in keywords such as `Should Not Be Equal` + or `Should Not Start With`. + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain` for explanation about `loglevel` argument. + """ + message = ("Cell in table '%s' in row #%s and column #%s " + "should not have contained text '%s'." + % (table_locator, row, column, expected)) + try: + content = self.get_table_cell(table_locator, row, column, loglevel='NONE') + except AssertionError, err: + self._info(err) + self.log_source(loglevel) + raise AssertionError(message) + self._info("Cell contains %s." % content) + + if expected in content: + self.log_source(loglevel) + raise AssertionError(message) + def table_column_should_contain(self, table_locator, col, expected, loglevel='INFO'): """Verifies that a specific column contains `expected`. @@ -100,6 +131,33 @@ def table_column_should_contain(self, table_locator, col, expected, loglevel='IN "should have contained text '%s'." % (col, table_locator, expected)) + def table_column_should_not_contain(self, table_locator, col, expected, loglevel='INFO'): + """Verifies that a specific column does not contain `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". + + Example: + | Table Column Should Not Contain | tableId | 3 | C | + | Table Column Should Not Contain | tableId | 2 | C | + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_col(self._current_browser(), table_locator, col, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Column #%s in table identified by '%s' " + "should not have contained text '%s'." + % (col, table_locator, expected)) + def table_footer_should_contain(self, table_locator, expected, loglevel='INFO'): """Verifies that the table footer contains `expected`. @@ -116,6 +174,22 @@ def table_footer_should_contain(self, table_locator, expected, loglevel='INFO'): raise AssertionError("Footer in table identified by '%s' should have contained " "text '%s'." % (table_locator, expected)) + def table_footer_should_not_contain(self, table_locator, expected, loglevel='INFO'): + """Verifies that the table footer does not contain `expected`. + + With table footer can be described as any -element that is + child of a -element. To understand how tables are + identified, please take a look at the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_footer(self._current_browser(), table_locator, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Footer in table identified by '%s' should not have contained " + "text '%s'." % (table_locator, expected)) + def table_header_should_contain(self, table_locator, expected, loglevel='INFO'): """Verifies that the table header, i.e. any ... element, contains `expected`. @@ -131,6 +205,21 @@ def table_header_should_contain(self, table_locator, expected, loglevel='INFO'): raise AssertionError("Header in table identified by '%s' should have contained " "text '%s'." % (table_locator, expected)) + def table_header_should_not_contain(self, table_locator, expected, loglevel='INFO'): + """Verifies that the table header, i.e. any ... element, does not contain `expected`. + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_header(self._current_browser(), table_locator, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Header in table identified by '%s' should not have contained " + "text '%s'." % (table_locator, expected)) + def table_row_should_contain(self, table_locator, row, expected, loglevel='INFO'): """Verifies that a specific table row contains `expected`. @@ -154,6 +243,29 @@ def table_row_should_contain(self, table_locator, row, expected, loglevel='INFO' raise AssertionError("Row #%s in table identified by '%s' should have contained " "text '%s'." % (row, table_locator, expected)) + def table_row_should_not_contain(self, table_locator, row, expected, loglevel='INFO'): + """Verifies that a specific table row does not contain `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. + """ + element = self._table_element_finder.find_by_row(self._current_browser(), table_locator, row, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Row #%s in table identified by '%s' should have contained " + "text '%s'." % (row, table_locator, expected)) + def table_should_contain(self, table_locator, expected, loglevel='INFO'): """Verifies that `expected` can be found somewhere in the table. @@ -168,3 +280,18 @@ def table_should_contain(self, table_locator, expected, loglevel='INFO'): self.log_source(loglevel) raise AssertionError("Table identified by '%s' should have contained text '%s'." \ % (table_locator, expected)) + + def table_should_not_contain(self, table_locator, expected, loglevel='INFO'): + """Verifies that `expected` should not be present anywhere in the table. + + To understand how tables are identified, please take a look at + the `introduction`. + + See `Page Should Contain Element` for explanation about + `loglevel` argument. + """ + element = self._table_element_finder.find_by_content(self._current_browser(), table_locator, expected) + if element is not None: + self.log_source(loglevel) + raise AssertionError("Table identified by '%s' should not have contained text '%s'." \ + % (table_locator, expected)) diff --git a/test/acceptance/keywords/tables/table_should_not_contain.robot b/test/acceptance/keywords/tables/table_should_not_contain.robot new file mode 100644 index 000000000..fec4a0081 --- /dev/null +++ b/test/acceptance/keywords/tables/table_should_not_contain.robot @@ -0,0 +1,111 @@ +*** Settings *** +Documentation Tests table contents for should not keyowrds +Resource table_resource.robot +Default Tags table_not_contain + + +*** Test Cases *** +Should Give Error Message When Content Found In Table + [Documentation] Should Give Error Message When Content Found In Table + [Template] Verify Table Does Contain With CSS And XPath Locators + simpleTable simpleTable_A1 + simpleTable simpleTable_C3 + simpleWithNested simpleWithNested_A1 + simpleWithNested nestedTable_A1 + nestedTable nestedTable_A1 + nestedTable nestedTable_C3 + tableWithSingleHeader tableWithSingleHeader_B1 + tableWithSingleHeader tableWithSingleHeader_B2 + withHeadAndFoot withHeadAndFoot_AH1 + withHeadAndFoot withHeadAndFoot_B2 + withHeadAndFoot withHeadAndFoot_CF1 + mergedRows mergedRows_A1 + mergedCols mergedCols_B2 + formattedTable formattedTable_A1 + formattedTable formattedTable_B1 + formattedTable formattedTable_C1 + formattedTable formattedTable_A2 + formattedTable formattedTable_B2 + formattedTable formattedTable_ÄÖÜäöüß + formattedTable äöü€&äöü€& + +Test Table Cell Should Not Contain + [Documentation] Test Table Cell Should Not Contain + ${table}= set variable simpleTable + ${row}= set variable 1 + ${col}= set variable 1 + ${expected} = set variable simpleTable_A2 + table cell should not contain ${table} ${row} ${col} ${expected} + +Test Table Cell Give Error When It Contains + [Documentation] Test Table Cell Give Error When It Contains + ${table}= set variable simpleTable + ${row}= set variable 1 + ${col}= set variable 1 + ${expected} = set variable simpleTable_A1 + ${err}= Set Variable Cell in table '${table}' in row #${row} and column #${col} should not have contained text '${expected}'. + Run Keyword And Expect Error ${err} table cell should not contain ${table} ${row} ${col} ${expected} + +Test Table Row Should Not Contain + [Documentation] Test Table Row Should Not Contain + ${table}= set variable simpleTable + ${row}= set variable 1 + ${expected} = set variable simpleTable_A2 + table row should not contain ${table} ${row} ${expected} + +Test Table Row Should Give Error When It Contains + [Documentation] Test Table Row Should Give Error When It Contains + ${table}= set variable tableWithSingleHeader + ${expected} = set variable tableWithSingleHeader_A1 + ${row}= set variable 1 + ${err}= Set Variable Row #${row} in table identified by '${table}' should have contained text '${expected}'. + run keyword and expect error ${err} table row should not contain ${table} ${row} ${expected} + +Test Table column Should Not Contain + [Documentation] Test Table column Should Not Contain + ${table}= set variable simpleTable + ${column}= set variable 1 + ${expected} = set variable simpleTable_B2 + table column should not contain ${table} ${column} ${expected} + +Test Table column Should Give Error When It Contains + [Documentation] Test Table column Should Give Error When It Contains + ${table}= set variable tableWithSingleHeader + ${column}= set variable 1 + ${expected} = set variable tableWithSingleHeader_A1 + ${err}= Set Variable Column #${column} in table identified by '${table}' should not have contained text '${expected}'. + run keyword and expect error ${err} table column should not contain ${table} ${column} ${expected} + +Test Table Header Should Not Contain + [Documentation] Test Table Header Should Not Contain + ${table}= set variable tableWithSingleHeader + ${expected} = set variable tableWithSingleHeader_A3 + table header should not contain ${table} ${expected} + +Test Table Header Should Give Error When It Contains + [Documentation] Test Table Header Should Give Error When It Contains + ${table}= set variable tableWithSingleHeader + ${expected} = set variable tableWithSingleHeader_A1 + ${err}= Set Variable Header in table identified by '${table}' should not have contained text '${expected}'. + run keyword and expect error ${err} table header should not contain ${table} ${expected} + +Test Table Footer Should Not Contain + [Documentation] Test Table Footer Should Not Contain + ${table}= set variable withHeadAndFoot + ${expected} = set variable withHeadAndFoot_A1 + table footer should not contain ${table} ${expected} + +Test Table Footer Should Give Error When It Contains + [Documentation] Test Table Footer Should Give Error When It Contains + ${table}= set variable withHeadAndFoot + ${expected} = set variable withHeadAndFoot_AF1 + ${err}= Set Variable Footer in table identified by '${table}' should not have contained text '${expected}'. + run keyword and expect error ${err} table footer should not contain ${table} ${expected} + + +*** Keywords *** +Verify Table Does Contain With CSS And XPath Locators + [Arguments] ${locator} ${expected} + [Documentation] Verify Table Contains With CSS And XPath Locators + ${err}= Set Variable Table identified by '${locator}' should not have contained text '${expected}'. + Run Keyword And Expect Error ${err} Table Should Not Contain ${locator} ${expected} From 25982b4fe0a8c03343e4146bdf77c4f2b0fb8049 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Fri, 29 Apr 2016 17:45:01 +0100 Subject: [PATCH 07/31] Fixes `Unselect From List` due to Selenium changes. Since Selenium version 2.53.0 deselect_by_value/label returns boolean, and Exception if element is not found. This fix keeps compatibility with versions <=2.52.0. --- src/Selenium2Library/keywords/_selectelement.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Selenium2Library/keywords/_selectelement.py b/src/Selenium2Library/keywords/_selectelement.py index e63bfd466..496289bed 100644 --- a/src/Selenium2Library/keywords/_selectelement.py +++ b/src/Selenium2Library/keywords/_selectelement.py @@ -271,8 +271,17 @@ def unselect_from_list(self, locator, *items): select, options = self._get_select_list_options(select) for item in items: - select.deselect_by_value(item) - select.deselect_by_visible_text(item) + try: + select.deselect_by_value(item) + # Code for selenium <= 2.52.0 + select.deselect_by_visible_text(item) + except: # Code for selenium > 2.52.0 + try: + select.deselect_by_visible_text(item) + except: + self._warn("Tried to unselect missing selection item, \ +'%s' from locator '%s'." % (item, locator)) + continue def unselect_from_list_by_index(self, locator, *indexes): """Unselects `*indexes` from list identified by `locator` From b29f5f30e6ef2cc3cc3db086dce22151863e6d92 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Fri, 11 Sep 2015 22:43:30 +0300 Subject: [PATCH 08/31] CONTRIBUTING.md file to how to make pull request --- CONTRIBUTING.md | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..265c5a3c0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,197 @@ +# Contribution guidelines + +These guidelines instruct how to submit issues and contribute code to the +[Selenium2Library project](https://github.com/robotframework/Selenium2Library). +Other great ways to contribute include answering questions and participating +discussion on +[robotframework-users](https://groups.google.com/forum/#!forum/robotframework-users) +mailing list and other forums as well as spreading the word about the framework one way or +the other. + + +# Submitting issues + +Bugs and enhancements are tracked in the +[issue tracker](https://github.com/robotframework/Selenium2Library/issues). +If you are unsure if something is a bug or is a feature worth implementing, you can +first ask on +[robotframework-users](https://groups.google.com/forum/#!forum/robotframework-users) +list. This and other similar forums, not the issue tracker, are also places where to ask +general questions. + +Before submitting a new issue, it is always a good idea to check is the +same bug or enhancement already reported. If it is, please add your comments +to the existing issue instead of creating a new one. + +## Reporting bugs +Explain the bug you have encountered so that others can understand it +and preferably also reproduce it. Key things to have in good bug report: + +- Version information + - Selenium2Library, Selenium and Robot Framework version + - Browser type and version. + - Also the driver version, example ChromeDriver version +- Steps to reproduce the problem. With more complex problems it is often a good idea + to create a [short, self contained, correct example (SSCCE)](http://sscce.org). +- Possible error message and traceback. + +Notice that all information in the issue tracker is public. Do not include +any confidential information there. + +# Enhancement requests +Describe the new feature and use cases for it in as much detail as possible. +Especially with larger enhancements, be prepared to contribute the code +in form of a pull request as explained below or to pay someone for the work. +Consider also would it be better to implement this functionality as a separate +tool outside the core framework. + +# Code contributions + +If you have fixed a bug or implemented an enhancement, you can contribute +your changes via GitHub's pull requests. This is not restricted to code, +on the contrary, fixes and enhancements to documentation_ and tests_ alone +are also very valuable. + +## Choosing something to work on +Often you already have a bug or an enhancement you want to work on in your +mind, but you can also look at the +[issue tracker](https://github.com/robotframework/Selenium2Library/issues) +to find bugs and enhancements submitted by others. The issues vary significantly +in complexity and difficulty, so you can try to find something that matches +your skill level and knowledge. + +## Pull requests +On GitHub pull requests are the main mechanism to contribute code. They +are easy to use both for the contributor and for person accepting +the contribution, and with more complex contributions it is easy also +for others to join the discussion. Preconditions for creating a pull +requests are having a [GitHub account](https://github.com/), +installing [Git](https://git-scm.com) and forking the +[Selenium2Library project](https://github.com/robotframework/Selenium2Library). + +GitHub has good articles explaining how to +[set up Git](https://help.github.com/articles/set-up-git/), +[fork a repository](https://help.github.com/articles/fork-a-repo/) and +[use pull requests](https://help.github.com/articles/using-pull-requests) +and we do not go through them in more detail. We do, however, +recommend to create dedicated branches for pull requests instead of creating +them based on the master branch. This is especially important if you plan to +work on multiple pull requests at the same time. + +## Coding conventions +Selenium2Library uses the general Python code conventions defined in +[PEP-8](https://www.python.org/dev/peps/pep-0008/). In addition to that, we try +to write +[idiomatic Python](http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html) +and follow the +[SOLID principles](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) +with all new code. An important guideline is that the code should be clear enough that +comments are generally not needed. + +Docstrings should be added to public keywords but are not generally needed in +internal code. When docstrings are added, they should follow +[PEP-257](https://www.python.org/dev/peps/pep-0257/). +See [Documentation](#documentation) section below for more details about +documentation syntax, generating docs, etc. + +We are pretty picky about using whitespace. We use blank lines and whitespace +in expressions as dictated by [PEP-8](https://www.python.org/dev/peps/pep-0008/) +, but we also follow these rules: + +- Indentation using spaces, not tabs. +- No trailing spaces. +- No extra empty lines at the end of the file. +- Files must end with a newline. + +The above rules are good with most other code too. Any good editor or IDE +can be configured to automatically format files according to them. + +## Documentation +With new features or enhancements adequate documentation is as important as the +actual functionality. Different documentation is needed depending on the issue. + +The main source of documentation should be placed in to the library and +individual keywords. But enhancing the internal +[wiki](https://github.com/robotframework/Selenium2Library/wiki) +or other documentation is equally important. + +- Other keywords and sections in the library introduction can be referenced + with internal links created with backticks like \`Example Keyword\` + +- When referring to arguments, argument names must use in inline code style + created with double backticks like \`\`argument\`\`. + +- Examples are recommended whenever the new keyword or enhanced functionality is + not trivial. + +- All new enhancements or changes should have a note telling when the change + was introduced. Often adding something like ``New in Selenium2Libray 1.8.`` + is enough. + +Keyword documentation can be easily created using ``_ +script. Resulting docs should be verified before the code is committed. + +## Tests +When submitting a pull request with a new feature or a fix, you should +always include tests for your changes. These tests prove that your changes +work, help prevent bugs in the future, and help document what your changes +do. Depending an the change, you may need `acceptance tests`_, `unit tests`_ +or both. + +Make sure to run all of the tests before submitting a pull request to be sure +that your changes do not break anything. If you can, test in multiple +browsers and versions (Firefox, Chrome, IE, etc). Pull requests are also +automatically tested on `continuous integration`_. + +### Acceptance tests +Most of Selenium2Library's testing is done using acceptance tests that +naturally use Robot Framework itself for testing. Every new functionality +or fix should generally get one or more acceptance tests. See +[Unit and acceptance tests](https://github.com/robotframework/Selenium2Library/blob/master/BUILD.rst#unit-and-acceptance-tests>) +for more details about creating and executing them. + +### Unit tests + + +Unit tests are great for testing internal logic and should be added when +appropriate. For more details see +[Unit and acceptance tests](https://github.com/robotframework/Selenium2Library/blob/master/BUILD.rst#unit-and-acceptance-tests>). + +## Continuous integration +Selenium2Library's continuous integration (CI) servers are visible through +[travis-ci](https://travis-ci.org/robotframework/Selenium2Library). +They automatically test all new pull request to the repository with Firefox on Python 2.6 +and 2.7. + +## Finalizing pull requests +Once you have code, documentation and tests ready, it is time to finalize +the pull request. + +### AUTHORS.txt +If you have done any non-trivial change and would like to be credited, +add yourself to +[AUTHORS.txt](https://github.com/robotframework/Selenium2Library/blob/master/AUTHORS.txt) +file. + +### Resolving conflicts +Conflicts can occur if there are new changes to the master that touch the +same code as your changes. In that case you should +[sync your fork](https://help.github.com/articles/syncing-a-fork>) and +[resolve conflicts](https://help.github.com/articles/resolving-a-merge-conflict-from-the-command-line) +to allow for an easy merge. + +The most common conflicting file is the aforementioned +[AUTHORS.txt](https://github.com/robotframework/Selenium2Library/blob/master/AUTHORS.txt) +, but luckily fixing those conflicts is typically easy. + +### Squashing commits +If the pull request contains multiple commits, it is recommended that you +squash them into a single commit before the pull request is merged. +See +[Squashing Github pull requests into a single commit](http://eli.thegreenplace.net/2014/02/19/squashing-github-pull-requests-into-a-single-commit) +article for more details about why and how. + +Squashing is especially important if the pull request contains lots of +temporary commits and changes that have been later reverted or redone. +Squashing is not needed if the commit history is clean and individual +commits are meaningful alone. From b6de0533ebec452efab1449b8a285df7b741d2bf Mon Sep 17 00:00:00 2001 From: FX Soubirou Date: Mon, 11 Jan 2016 17:24:03 +0100 Subject: [PATCH 09/31] Edge browser compatibility Revert setup.py and doc/Selenium2Library.html to previous version. Check Edge compatibility of Selenium webdriver. --- src/Selenium2Library/keywords/_browsermanagement.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Selenium2Library/keywords/_browsermanagement.py b/src/Selenium2Library/keywords/_browsermanagement.py index 339028804..a4fbc7a65 100644 --- a/src/Selenium2Library/keywords/_browsermanagement.py +++ b/src/Selenium2Library/keywords/_browsermanagement.py @@ -23,7 +23,8 @@ 'htmlunitwithjs' : "_make_htmlunitwithjs", 'android': "_make_android", 'iphone': "_make_iphone", - 'safari': "_make_safari" + 'safari': "_make_safari", + 'edge': "_make_edge" } class _BrowserManagementKeywords(KeywordGroup): @@ -85,6 +86,7 @@ def open_browser(self, url, browser='firefox', alias=None,remote_url=False, | android | Android | | iphone | Iphone | | safari | Safari | + | edge | Edge | Note, that you will encounter strange behavior, if you open @@ -157,7 +159,7 @@ def create_webdriver(self, driver_name, alias=None, kwargs={}, **init_kwargs): | Create Webdriver | PhantomJS | service_args=${service args} | | Example for Robot Framework < 2.8: - | # debug IE driver | | | | + | # debug IE driver | | | | | ${kwargs}= | Create Dictionary | log_level=DEBUG | log_file=%{HOMEPATH}${/}ie.log | | Create Webdriver | Ie | kwargs=${kwargs} | | """ @@ -580,6 +582,13 @@ def _make_safari(self , remote , desired_capabilities , profile_dir): return self._generic_make_browser(webdriver.Safari, webdriver.DesiredCapabilities.SAFARI, remote, desired_capabilities) + def _make_edge(self , remote , desired_capabilities , profile_dir): + if hasattr(webdriver, 'Edge'): + return self._generic_make_browser(webdriver.Edge, + webdriver.DesiredCapabilities.EDGE, remote, desired_capabilities) + else: + raise ValueError("Edge is not a supported browser with your version of Selenium python library. Please, upgrade to minimum required version 2.47.0.") + def _generic_make_browser(self, webdriver_type , desired_cap_type, remote_url, desired_caps): '''most of the make browser functions just call this function which creates the appropriate web-driver''' From 562bc6daa3c34175177ed2ea608f9ddb3aeaeedd Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 8 Aug 2016 16:20:52 +0300 Subject: [PATCH 10/31] Fixes #257 by removing the docutils dependency The docutils is only needed for Selenium2Library development, therefore removed the dependency from setup.py. Also added meaningfull error message to development tools, if the docutils is not installed. --- doc/buildhtml.py | 17 ++++++++++------- setup.py | 3 +-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/buildhtml.py b/doc/buildhtml.py index 5c5db1ead..42efbfe5e 100644 --- a/doc/buildhtml.py +++ b/doc/buildhtml.py @@ -26,13 +26,16 @@ import os.path import copy from fnmatch import fnmatch -import docutils -from docutils import ApplicationError -from docutils import core, frontend, utils -from docutils.error_reporting import ErrorOutput, ErrorString -from docutils.parsers import rst -from docutils.readers import standalone, pep -from docutils.writers import html4css1, pep_html +try: + import docutils + from docutils import ApplicationError + from docutils import core, frontend, utils + from docutils.error_reporting import ErrorOutput, ErrorString + from docutils.parsers import rst + from docutils.readers import standalone, pep + from docutils.writers import html4css1, pep_html +except ImportError: + raise ImportError('Importing `docutils` failed. Install it with `pip install docutils`.') usage = '%prog [options] [ ...]' diff --git a/setup.py b/setup.py index 3b58879c9..ec5943478 100755 --- a/setup.py +++ b/setup.py @@ -35,8 +35,7 @@ install_requires = [ 'decorator >= 3.3.2', 'selenium >= 2.32.0', - 'robotframework >= 2.6.0', - 'docutils >= 0.8.1' + 'robotframework >= 2.6.0' ], py_modules=['ez_setup'], package_dir = {'' : 'src'}, From 0604fbd5b23085e8693b73f0b3dde669dbb099ff Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 8 Aug 2016 18:19:01 +0300 Subject: [PATCH 11/31] Changed AUTHORS.txt to CHANGES.rst in CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 265c5a3c0..37c51785f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -167,10 +167,10 @@ and 2.7. Once you have code, documentation and tests ready, it is time to finalize the pull request. -### AUTHORS.txt +### CHANGES.rst If you have done any non-trivial change and would like to be credited, add yourself to -[AUTHORS.txt](https://github.com/robotframework/Selenium2Library/blob/master/AUTHORS.txt) +[CHANGES.rst](https://github.com/robotframework/Selenium2Library/blob/master/CHANGES.rst) file. ### Resolving conflicts @@ -181,7 +181,7 @@ same code as your changes. In that case you should to allow for an easy merge. The most common conflicting file is the aforementioned -[AUTHORS.txt](https://github.com/robotframework/Selenium2Library/blob/master/AUTHORS.txt) +[CHANGES.rst](https://github.com/robotframework/Selenium2Library/blob/master/CHANGES.rst) , but luckily fixing those conflicts is typically easy. ### Squashing commits From 91cb8c0b7d147257544eee49296327d033c52526 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 18:33:18 +0300 Subject: [PATCH 12/31] Fixes #629 by expecting NoSuchElementException (#630) * Fixes #629 by expecting NoSuchElementException Empty expect are not coding practices. The deselect_by_value and the deselect_by_visible_text raises NoSuchElementException if it can not deselect the items. --- src/Selenium2Library/keywords/_selectelement.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Selenium2Library/keywords/_selectelement.py b/src/Selenium2Library/keywords/_selectelement.py index 496289bed..a89553d84 100644 --- a/src/Selenium2Library/keywords/_selectelement.py +++ b/src/Selenium2Library/keywords/_selectelement.py @@ -1,5 +1,7 @@ from selenium.webdriver.support.ui import Select from keywordgroup import KeywordGroup +from selenium.common.exceptions import NoSuchElementException + class _SelectElementKeywords(KeywordGroup): @@ -271,17 +273,16 @@ def unselect_from_list(self, locator, *items): select, options = self._get_select_list_options(select) for item in items: + # Only Selenium 2.52 and newer raise exceptions when there is no match. + # For backwards compatibility reasons we want to ignore them. try: select.deselect_by_value(item) - # Code for selenium <= 2.52.0 + except NoSuchElementException: + pass + try: select.deselect_by_visible_text(item) - except: # Code for selenium > 2.52.0 - try: - select.deselect_by_visible_text(item) - except: - self._warn("Tried to unselect missing selection item, \ -'%s' from locator '%s'." % (item, locator)) - continue + except NoSuchElementException: + pass def unselect_from_list_by_index(self, locator, *indexes): """Unselects `*indexes` from list identified by `locator` From 9bba1511eb6b5e86fb6bddf1e9ef84d740436a8d Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 19:02:55 +0300 Subject: [PATCH 13/31] Bump version 1.8.0 --- src/Selenium2Library/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Selenium2Library/version.py b/src/Selenium2Library/version.py index 631735687..bc1888c31 100644 --- a/src/Selenium2Library/version.py +++ b/src/Selenium2Library/version.py @@ -1 +1 @@ -VERSION = '1.7.4' +VERSION = '1.8.0' From 2ed8a63afb89128a761515b6102975f0cf00dbc1 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 19:05:06 +0300 Subject: [PATCH 14/31] Generated documentation only needed in gh-pages branch --- doc/Selenium2Library.html | 832 -------------------------------------- 1 file changed, 832 deletions(-) delete mode 100644 doc/Selenium2Library.html diff --git a/doc/Selenium2Library.html b/doc/Selenium2Library.html deleted file mode 100644 index c0c9a79fc..000000000 --- a/doc/Selenium2Library.html +++ /dev/null @@ -1,832 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -
-

Opening library documentation failed

-
    -
  • Verify that you have JavaScript enabled in your browser.
  • -
  • Make sure you are using a modern enough browser. Firefox 3.5, IE 8, or equivalent is required, newer browsers are recommended.
  • -
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • -
-
- - - - - - - - - - - - - - - - - - - - - - From aadfca081158e5bef51339acaeb4578338f4f6e7 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 19:22:17 +0300 Subject: [PATCH 15/31] Moving library documentation from doc to docs This hopefully allows github serve library documentation without using separate gh-pages branch. More details: https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/ --- docs/Selenium2Library.html | 832 +++++++++++++++++++++++++++++++++++++ {doc => docs}/generate.py | 0 2 files changed, 832 insertions(+) create mode 100644 docs/Selenium2Library.html rename {doc => docs}/generate.py (100%) diff --git a/docs/Selenium2Library.html b/docs/Selenium2Library.html new file mode 100644 index 000000000..2dfa31909 --- /dev/null +++ b/docs/Selenium2Library.html @@ -0,0 +1,832 @@ + + + + + + + + + + + + + + + + + + + + + + +
+

Opening library documentation failed

+
    +
  • Verify that you have JavaScript enabled in your browser.
  • +
  • Make sure you are using a modern enough browser. Firefox 3.5, IE 8, or equivalent is required, newer browsers are recommended.
  • +
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/generate.py b/docs/generate.py similarity index 100% rename from doc/generate.py rename to docs/generate.py From 8d8b4712f3ce9625ca7eb427c274ecdf5eab5d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Mon, 22 Aug 2016 20:14:20 +0300 Subject: [PATCH 16/31] Creates docs/index.html This is served when accessing http://robotframework.org/Selenium2Library/. Contains links to keyword docs and other related info. --- docs/index.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/index.html diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..060023cb9 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,16 @@ + + + + Robot Framework Selenium2Library + + + +

Robot Framework Selenium2Library

+ + + From 672768d98096cc887427dec6b5e7b81c2d482bf0 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 20:14:18 +0300 Subject: [PATCH 17/31] Fixed README.rst to point new keyword documentation location --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index acea1cc28..590f38224 100644 --- a/README.rst +++ b/README.rst @@ -153,7 +153,7 @@ The `user group for Robot Framework`_ is the best place to get help. Consider in .. _Selenium 2 (WebDriver): http://seleniumhq.org/docs/03_webdriver.html .. _SeleniumLibrary: http://code.google.com/p/robotframework-seleniumlibrary/ .. _Wiki: https://github.com/robotframework/Selenium2Library/wiki -.. _Keyword Documentation: http://robotframework.org/Selenium2Library/doc/Selenium2Library.html +.. _Keyword Documentation: http://robotframework.org/Selenium2Library/Selenium2Library.html .. _INSTALL.rst: https://github.com/robotframework/Selenium2Library/blob/master/INSTALL.rst .. _BUILD.rst: https://github.com/robotframework/Selenium2Library/blob/master/BUILD.rst .. _Robot Framework User Guide: http://code.google.com/p/robotframework/wiki/UserGuide From 88cb6fe6a64d5ddf6690a9036e0dd89e44760dd8 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 20:26:25 +0300 Subject: [PATCH 18/31] Removed not needed doc folder Github renders *.rst files automatically and there is not needed create separate files. --- doc/buildhtml.py | 253 ---------------------------------------- doc/generate_readmes.py | 63 ---------- 2 files changed, 316 deletions(-) delete mode 100644 doc/buildhtml.py delete mode 100644 doc/generate_readmes.py diff --git a/doc/buildhtml.py b/doc/buildhtml.py deleted file mode 100644 index 42efbfe5e..000000000 --- a/doc/buildhtml.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python - -# $Id: buildhtml.py 7037 2011-05-19 08:56:27Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -Generates .html from all the .txt files in a directory. - -Ordinary .txt files are understood to be standalone reStructuredText. -Files named ``pep-*.txt`` are interpreted as reStructuredText PEPs. -""" -# Once PySource is here, build .html from .py as well. - -__docformat__ = 'reStructuredText' - - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -import sys -import os -import os.path -import copy -from fnmatch import fnmatch -try: - import docutils - from docutils import ApplicationError - from docutils import core, frontend, utils - from docutils.error_reporting import ErrorOutput, ErrorString - from docutils.parsers import rst - from docutils.readers import standalone, pep - from docutils.writers import html4css1, pep_html -except ImportError: - raise ImportError('Importing `docutils` failed. Install it with `pip install docutils`.') - - -usage = '%prog [options] [ ...]' -description = ('Generates .html from all the reStructuredText .txt files ' - '(including PEPs) in each ' - '(default is the current directory).') - - -class SettingsSpec(docutils.SettingsSpec): - - """ - Runtime settings & command-line options for the front end. - """ - - # Can't be included in OptionParser below because we don't want to - # override the base class. - settings_spec = ( - 'Build-HTML Options', - None, - (('Recursively scan subdirectories for files to process. This is ' - 'the default.', - ['--recurse'], - {'action': 'store_true', 'default': 1, - 'validator': frontend.validate_boolean}), - ('Do not scan subdirectories for files to process.', - ['--local'], {'dest': 'recurse', 'action': 'store_false'}), - ('BROKEN Do not process files in . This option may be used ' - 'more than once to specify multiple directories.', - ['--prune'], - {'metavar': '', 'action': 'append', - 'validator': frontend.validate_colon_separated_string_list}), - ('BROKEN Recursively ignore files or directories matching any of the given ' - 'wildcard (shell globbing) patterns (separated by colons). ' - 'Default: ".svn:CVS"', - ['--ignore'], - {'metavar': '', 'action': 'append', - 'default': ['.svn', 'CVS'], - 'validator': frontend.validate_colon_separated_string_list}), - ('Work silently (no progress messages). Independent of "--quiet".', - ['--silent'], - {'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Do not process files, show files that would be processed.', - ['--dry-run'], - {'action': 'store_true', 'validator': frontend.validate_boolean}),)) - - relative_path_settings = ('prune',) - config_section = 'buildhtml application' - config_section_dependencies = ('applications',) - - -class OptionParser(frontend.OptionParser): - - """ - Command-line option processing for the ``buildhtml.py`` front end. - """ - - def check_values(self, values, args): - frontend.OptionParser.check_values(self, values, args) - values._source = None - return values - - def check_args(self, args): - source = destination = None - if args: - self.values._directories = args - else: - self.values._directories = [os.getcwd()] - return source, destination - - -class Struct: - - """Stores data attributes for dotted-attribute access.""" - - def __init__(self, **keywordargs): - self.__dict__.update(keywordargs) - - -class Builder: - - def __init__(self): - self.publishers = { - '': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer, - SettingsSpec)), - '.txt': Struct(components=(rst.Parser, standalone.Reader, - html4css1.Writer, SettingsSpec), - reader_name='standalone', - writer_name='html'), - 'PEPs': Struct(components=(rst.Parser, pep.Reader, - pep_html.Writer, SettingsSpec), - reader_name='pep', - writer_name='pep_html')} - """Publisher-specific settings. Key '' is for the front-end script - itself. ``self.publishers[''].components`` must contain a superset of - all components used by individual publishers.""" - - self.setup_publishers() - - def setup_publishers(self): - """ - Manage configurations for individual publishers. - - Each publisher (combination of parser, reader, and writer) may have - its own configuration defaults, which must be kept separate from those - of the other publishers. Setting defaults are combined with the - config file settings and command-line options by - `self.get_settings()`. - """ - for name, publisher in self.publishers.items(): - option_parser = OptionParser( - components=publisher.components, read_config_files=1, - usage=usage, description=description) - publisher.option_parser = option_parser - publisher.setting_defaults = option_parser.get_default_values() - frontend.make_paths_absolute(publisher.setting_defaults.__dict__, - option_parser.relative_path_settings) - publisher.config_settings = ( - option_parser.get_standard_config_settings()) - self.settings_spec = self.publishers[''].option_parser.parse_args( - values=frontend.Values()) # no defaults; just the cmdline opts - self.initial_settings = self.get_settings('') - - def get_settings(self, publisher_name, directory=None): - """ - Return a settings object, from multiple sources. - - Copy the setting defaults, overlay the startup config file settings, - then the local config file settings, then the command-line options. - Assumes the current directory has been set. - """ - publisher = self.publishers[publisher_name] - settings = frontend.Values(publisher.setting_defaults.__dict__) - settings.update(publisher.config_settings, publisher.option_parser) - if directory: - local_config = publisher.option_parser.get_config_file_settings( - os.path.join(directory, 'docutils.conf')) - frontend.make_paths_absolute( - local_config, publisher.option_parser.relative_path_settings, - directory) - settings.update(local_config, publisher.option_parser) - settings.update(self.settings_spec.__dict__, publisher.option_parser) - return settings - - def run(self, directory=None, recurse=1): - recurse = recurse and self.initial_settings.recurse - if directory: - self.directories = [directory] - elif self.settings_spec._directories: - self.directories = self.settings_spec._directories - else: - self.directories = [os.getcwd()] - for directory in self.directories: - for root, dirs, files in os.walk(directory): - # os.walk by default this recurses down the tree, - # influence by modifying dirs. - if not recurse: - del dirs[:] - self.visit(root, files) - - def visit(self, directory, names): - # BUG prune and ignore do not work - settings = self.get_settings('', directory) - errout = ErrorOutput(encoding=settings.error_encoding) - if settings.prune and (os.path.abspath(directory) in settings.prune): - print >>errout, ('/// ...Skipping directory (pruned): %s' % - directory) - sys.stderr.flush() - names[:] = [] - return - if not self.initial_settings.silent: - print >>errout, '/// Processing directory: %s' % directory - sys.stderr.flush() - # settings.ignore grows many duplicate entries as we recurse - # if we add patterns in config files or on the command line. - for pattern in utils.uniq(settings.ignore): - for i in range(len(names) - 1, -1, -1): - if fnmatch(names[i], pattern): - # Modify in place! - del names[i] - prune = 0 - for name in names: - if name.endswith('.txt'): - prune = self.process_txt(directory, name) - if prune: - break - - def process_txt(self, directory, name): - if name.startswith('pep-'): - publisher = 'PEPs' - else: - publisher = '.txt' - settings = self.get_settings(publisher, directory) - errout = ErrorOutput(encoding=settings.error_encoding) - pub_struct = self.publishers[publisher] - if settings.prune and (directory in settings.prune): - return 1 - settings._source = os.path.normpath(os.path.join(directory, name)) - settings._destination = settings._source[:-4]+'.html' - if not self.initial_settings.silent: - print >>errout, ' ::: Processing: %s' % name - sys.stderr.flush() - try: - if not settings.dry_run: - core.publish_file(source_path=settings._source, - destination_path=settings._destination, - reader_name=pub_struct.reader_name, - parser_name='restructuredtext', - writer_name=pub_struct.writer_name, - settings=settings) - except ApplicationError, error: - print >>errout, ' %s' % ErrorString(error) - - -if __name__ == "__main__": - Builder().run() diff --git a/doc/generate_readmes.py b/doc/generate_readmes.py deleted file mode 100644 index be6828b74..000000000 --- a/doc/generate_readmes.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python - -import os, shutil -from buildhtml import Builder - -THIS_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT_DIR = os.path.join(THIS_DIR, "..") -SRC_DIR = os.path.join(ROOT_DIR, "src") -LIB_DIR = os.path.join(SRC_DIR, "Selenium2Library") - -README_FILES = [ - "README.rst", - "INSTALL.rst", - "BUILD.rst", - "CHANGES.rst" -] - -def main(): - try: - import docutils - except: - print "Readme files will not be built into HTML, docutils not installed" - return - for readme_relative_path in README_FILES: - (readme_dir, readme_name) = _parse_readme_path(readme_relative_path) - readme_txt_name = _make_txt_file(readme_dir, readme_name) - Builder().process_txt(readme_dir, readme_txt_name) - _cleanup_txt_file(readme_dir, readme_txt_name) - - readme_html_name = os.path.splitext(readme_name)[0] + '.html' - readme_html_path = os.path.join(readme_dir, readme_html_name) - target_readme_html_name = os.path.splitext(readme_relative_path.replace('/', '-'))[0] + '.html' - target_readme_html_path = os.path.join(THIS_DIR, target_readme_html_name) - - if os.path.exists(target_readme_html_path): - os.remove(target_readme_html_path) - os.rename(readme_html_path, target_readme_html_path) - - print " ::: Saved: %s" % target_readme_html_name - -def _parse_readme_path(readme_relative_path): - readme_abs_path = os.path.join(ROOT_DIR, readme_relative_path.replace('/', os.sep)) - readme_path_parts = os.path.split(readme_abs_path) - readme_dir = readme_path_parts[0] - readme_name = readme_path_parts[1] - return (readme_dir, readme_name) - -def _make_txt_file(readme_dir, readme_name): - readme_txt_name = os.path.splitext(readme_name)[0] + '.txt' - _cleanup_txt_file(readme_dir, readme_txt_name) - shutil.copyfile( - os.path.join(readme_dir, readme_name), - os.path.join(readme_dir, readme_txt_name)) - return readme_txt_name - -def _cleanup_txt_file(readme_dir, readme_txt_name): - readme_txt_abs_path = os.path.join(readme_dir, readme_txt_name) - if os.path.exists(readme_txt_abs_path): - os.remove(readme_txt_abs_path) - - -if __name__ == '__main__': - main() From ae3c313d772ae2e37d450060761c3aefe96557f3 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 20:38:56 +0300 Subject: [PATCH 19/31] Removes the project demo and points to joint project with RF Use project from https://bitbucket.org/robotframework/webdemo because it has better documentation and usage possibilities. --- MANIFEST.in | 8 +- README.rst | 28 +- demo/demoapp/html/demo.css | 21 - demo/demoapp/html/error.html | 12 - demo/demoapp/html/index.html | 40 - demo/demoapp/html/welcome.html | 12 - demo/demoapp/server.py | 85 -- demo/log.html | 1795 -------------------------- demo/login_tests/invalid_login.txt | 31 - demo/login_tests/resource.txt | 49 - demo/login_tests/valid_login.txt | 16 - demo/package.py | 42 - demo/report.html | 1868 ---------------------------- demo/rundemo.py | 74 -- 14 files changed, 6 insertions(+), 4075 deletions(-) delete mode 100644 demo/demoapp/html/demo.css delete mode 100644 demo/demoapp/html/error.html delete mode 100644 demo/demoapp/html/index.html delete mode 100644 demo/demoapp/html/welcome.html delete mode 100644 demo/demoapp/server.py delete mode 100644 demo/log.html delete mode 100644 demo/login_tests/invalid_login.txt delete mode 100644 demo/login_tests/resource.txt delete mode 100644 demo/login_tests/valid_login.txt delete mode 100755 demo/package.py delete mode 100644 demo/report.html delete mode 100755 demo/rundemo.py diff --git a/MANIFEST.in b/MANIFEST.in index 69b58e50b..5a8ab0b85 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,14 +3,8 @@ include *.txt include *.rst exclude */*.txt # limit previous command to include only *.txt files in root folder exclude */*.rst # limit previous command to include only *.rst files in root folder -include selenium.bmp -recursive-include demo *.txt *.rst *.py *.sh *.bat *.html *.css *.js -prune demo/reports -prune demo/selenium_log.txt -prune demo/output.xml - -include doc/Selenium2Library.html +include docs/Selenium2Library.html recursive-include src *.py graft src/Selenium2Library/resources diff --git a/README.rst b/README.rst index 590f38224..01fa1636a 100644 --- a/README.rst +++ b/README.rst @@ -107,19 +107,6 @@ certain projects, you can use them instead of source distributions. Just download 32bit or 64bit installer depending on your system, double-click it, and follow the instructions. -Directory Layout ----------------- - -demo/ - A simple demonstration, with an application running on localhost - -doc/ - Keyword documentation - -src/ - Python source code - - Usage ----- @@ -128,17 +115,12 @@ Selenium2Library must be imported into your Robot test suite. See `Robot Framework User Guide`_ for more information. -Running the Demo ----------------- - -The demo directory contains an easily executable demo for Robot Framework -using Selenium2Library. To run the demo, run:: - - python demo/rundemo.py - -E.g.:: +Demo project +------------ +A demo project illustrating how to use this library can be found from: +https://bitbucket.org/robotframework/webdemo - python demo/rundemo.py demo/login_tests +Please see the usage instructions from the demo project pages. Getting Help ------------ diff --git a/demo/demoapp/html/demo.css b/demo/demoapp/html/demo.css deleted file mode 100644 index 2c86b8e48..000000000 --- a/demo/demoapp/html/demo.css +++ /dev/null @@ -1,21 +0,0 @@ -/* Simple styles for demo - * - * Setting text-align is a workaround for IE. See for example - * http://bluerobot.com/web/css/center1.html - */ - -body { - font-family: sans-serif; - color: black; - background: #DDDDDD; - text-align: center; -} -#container { - width: 30em; - height: 15em; - margin: 5em auto; - background: white; - border: 1px solid gray; - padding: 10px 30px; - text-align: left; -} diff --git a/demo/demoapp/html/error.html b/demo/demoapp/html/error.html deleted file mode 100644 index d52feed29..000000000 --- a/demo/demoapp/html/error.html +++ /dev/null @@ -1,12 +0,0 @@ - - - Error Page - - - -
-

Error Page

-

Login failed. Invalid user name and/or password.

-
- - diff --git a/demo/demoapp/html/index.html b/demo/demoapp/html/index.html deleted file mode 100644 index f51cb9546..000000000 --- a/demo/demoapp/html/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - Login Page - - - - -
-

Login Page

-

Please input your user name and password and click the login button.

-
- - - - - - - - - - - - - -
User Name:
Password:
 
-
-
- - diff --git a/demo/demoapp/html/welcome.html b/demo/demoapp/html/welcome.html deleted file mode 100644 index 1677f3879..000000000 --- a/demo/demoapp/html/welcome.html +++ /dev/null @@ -1,12 +0,0 @@ - - - Welcome Page - - - -
-

Welcome Page

-

Login succeeded. Now you can logout.

-
- - diff --git a/demo/demoapp/server.py b/demo/demoapp/server.py deleted file mode 100644 index 08576e0c9..000000000 --- a/demo/demoapp/server.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2008-2011 Nokia Siemens Networks Oyj -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Simple HTTP server requiring only Python and no other preconditions. - -Server is started by running this script with argument 'start' and -optional port number (default port 7272). Server root is the same -directory where this script is situated. Server can be stopped either -using Ctrl-C or running this script with argument 'stop' and same port -number as when starting it. -""" - -import os -import sys -import httplib -import BaseHTTPServer -import SimpleHTTPServer - - -DEFAULT_PORT = 7272 -DEFAULT_HOST = 'localhost' - - -class StoppableHttpServer(BaseHTTPServer.HTTPServer): - - def serve_forever(self): - self.stop = False - while not self.stop: - try: - self.handle_request() - except KeyboardInterrupt: - break - - -class StoppableHttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - - def do_QUIT(self): - self.send_response(200) - self.end_headers() - self.server.stop = True - - def do_POST(self): - # We could also process paremeters here using something like below. - # length = self.headers['Content-Length'] - # print self.rfile.read(int(length)) - self.do_GET() - - -def start_server(host=DEFAULT_HOST, port=DEFAULT_PORT): - print "Demo application starting on port %s" % port - root = os.path.dirname(os.path.abspath(__file__)) - os.chdir(root) - server = StoppableHttpServer((host, int(port)), StoppableHttpRequestHandler) - server.serve_forever() - -def stop_server(host=DEFAULT_HOST, port=DEFAULT_PORT): - print "Demo application on port %s stopping" % port - conn = httplib.HTTPConnection("%s:%s" % (host, port)) - conn.request("QUIT", "/") - conn.getresponse() - -def print_help(): - print __doc__ - - -if __name__ == '__main__': - try: - {'start': start_server, - 'stop': stop_server, - 'help': print_help}[sys.argv[1]](*sys.argv[2:]) - except (IndexError, KeyError, TypeError): - print 'Usage: %s start|stop|help [port]' % os.path.basename(sys.argv[0]) diff --git a/demo/log.html b/demo/log.html deleted file mode 100644 index 440eb0ada..000000000 --- a/demo/log.html +++ /dev/null @@ -1,1795 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Opening Robot Framework log failed

-
    -
  • Verify that you have JavaScript enabled in your browser.
  • -
  • Make sure you are using a modern enough browser. Firefox 3.5, IE 8, or equivalent is required, newer browsers are recommended.
  • -
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - diff --git a/demo/login_tests/invalid_login.txt b/demo/login_tests/invalid_login.txt deleted file mode 100644 index 19ea6ef9d..000000000 --- a/demo/login_tests/invalid_login.txt +++ /dev/null @@ -1,31 +0,0 @@ -*** Settings *** - -Documentation A test suite containing tests related to invalid login. These -... tests are data-driven by they nature. They use a single -... keyword, specified with Test Template setting, that is called -... with different arguments to cover different scenarios. -Suite Setup Open Browser To Login Page -Test Setup Go To Login Page -Test Template Login With Invalid Credentials Should Fail -Suite Teardown Close Browser -Resource resource.txt - - -*** Test Cases *** User Name Password - -Invalid Username invalid ${VALID PASSWD} -Invalid Password ${VALID USER} invalid -Invalid Username And Password invalid whatever -Empty Username ${EMPTY} ${VALID PASSWD} -Empty Password ${VALID USER} ${EMPTY} -Empty Username And Password ${EMPTY} ${EMPTY} - - -*** Keywords *** - -Login With Invalid Credentials Should Fail - [Arguments] ${username} ${password} - Input Username ${username} - Input Password ${password} - Submit Credentials - Login Should Have Failed diff --git a/demo/login_tests/resource.txt b/demo/login_tests/resource.txt deleted file mode 100644 index 651b9b8cb..000000000 --- a/demo/login_tests/resource.txt +++ /dev/null @@ -1,49 +0,0 @@ -*** Settings *** - -Documentation A resource file containing the application specific keywords -... that create our own domain specific language. This resource -... implements keywords for testing HTML version of the test -... application. -Library Selenium2Library - - -*** Variables *** - -${SERVER} localhost:7272 -${BROWSER} firefox -${DELAY} 0 -${VALID USER} demo -${VALID PASSWD} mode -${LOGIN URL} http://${SERVER}/html/ -${WELCOME URL} http://${SERVER}/html/welcome.html -${ERROR URL} http://${SERVER}/html/error.html - - -*** Keywords *** - -Open Browser To Login Page - Open Browser ${LOGIN URL} ${BROWSER} - Maximize Browser Window - Set Selenium Speed ${DELAY} - Title Should Be Login Page - -Go To Login Page - Go To ${LOGIN URL} - Title Should Be Login Page - -Input Username [Arguments] ${username} - Input Text username_field ${username} - -Input Password [Arguments] ${password} - Input Text password_field ${password} - -Submit Credentials - Click Button login_button - -Welcome Page Should Be Open - Location Should Be ${WELCOME URL} - Title Should Be Welcome Page - -Login Should Have Failed - Location Should Be ${ERROR URL} - Title Should Be Error Page diff --git a/demo/login_tests/valid_login.txt b/demo/login_tests/valid_login.txt deleted file mode 100644 index d8a773615..000000000 --- a/demo/login_tests/valid_login.txt +++ /dev/null @@ -1,16 +0,0 @@ -*** Settings *** - -Documentation A test suite with a single test for valid login. This test has -... a workflow that is created using keywords from the resource file. -Resource resource.txt - - -*** Test Cases *** - -Valid Login - Open Browser To Login Page - Input Username demo - Input Password mode - Submit Credentials - Welcome Page Should Be Open - [Teardown] Close Browser diff --git a/demo/package.py b/demo/package.py deleted file mode 100755 index e02a706e7..000000000 --- a/demo/package.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python - -import os, sys -from time import localtime -from zipfile import ZipFile, ZIP_DEFLATED - -THIS_DIR = os.path.dirname(os.path.abspath(__file__)) -execfile(os.path.join(THIS_DIR, '..', 'src', 'Selenium2Library', 'version.py')) - -FILES = { - '': ['rundemo.py'], - 'login_tests': ['valid_login.txt', 'invalid_login.txt', 'resource.txt'], - 'demoapp': ['server.py'], - 'demoapp/html': ['index.html', 'welcome.html', 'error.html', 'demo.css'] -} - -def main(): - cwd = os.getcwd() - try: - os.chdir(THIS_DIR) - name = 'robotframework-selenium2library-%s-demo' % VERSION - zipname = '%s.zip' % name - if os.path.exists(zipname): - os.remove(zipname) - zipfile = ZipFile(zipname, 'w', ZIP_DEFLATED) - for dirname in FILES: - for filename in FILES[dirname]: - path = os.path.join('.', dirname.replace('/', os.sep), filename) - print 'Adding: ', os.path.normpath(path) - zipfile.write(path, os.path.join(name, path)) - zipfile.close() - target_path = os.path.join('..', 'dist', zipname) - if os.path.exists(target_path): - os.remove(target_path) - os.rename(zipname, target_path) - print 'Created: ', os.path.abspath(target_path) - finally: - os.chdir(cwd) - - -if __name__ == '__main__': - main() diff --git a/demo/report.html b/demo/report.html deleted file mode 100644 index 124f2c9d8..000000000 --- a/demo/report.html +++ /dev/null @@ -1,1868 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Opening Robot Framework report failed

-
    -
  • Verify that you have JavaScript enabled in your browser.
  • -
  • Make sure you are using a modern enough browser. Firefox 3.5, IE 8, or equivalent is required, newer browsers are recommended.
  • -
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • -
-
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/demo/rundemo.py b/demo/rundemo.py deleted file mode 100755 index 3ed0051c2..000000000 --- a/demo/rundemo.py +++ /dev/null @@ -1,74 +0,0 @@ -#! /usr/bin/env python - -"""Runner Script for Robot Framework SeleniumLibrary Demo - -Tests are run by giving a path to the tests to be executed as an argument to -this script. Possible Robot Framework options are given before the path. - -Examples: - rundemo.py login_tests # Run all tests in a directory - rundemo.py login_tests/valid_login.text # Run tests in a specific file - rundemo.py --variable BROWSER:IE login_tests # Override variable - rundemo.py -v BROWSER:IE -v DELAY:0.25 login_tests - -By default tests are executed with Firefox browser, but this can be changed -by overriding the `BROWSER` variable as illustrated above. Similarly it is -possible to slow down the test execution by overriding the `DELAY` variable -with a non-zero value. - -When tests are run, the demo application is started and stopped automatically. -It is also possible to start and stop the application separately -by using `demoapp` options. This allows running tests with the -normal `pybot` start-up script, as well as investigating the demo application. - -Running the demo requires that Robot Framework, Selenium2Library, Python, and -Java to be installed. -""" - -import os -import sys -from tempfile import TemporaryFile -from subprocess import Popen, call, STDOUT - -try: - import Selenium2Library -except ImportError, e: - print 'Importing Selenium2Library module failed (%s).' % e - print 'Please make sure you have Selenium2Library properly installed.' - print 'See INSTALL.rst for troubleshooting information.' - sys.exit(1) - - -ROOT = os.path.dirname(os.path.abspath(__file__)) -DEMOAPP = os.path.join(ROOT, 'demoapp', 'server.py') - - -def run_tests(args): - start_demo_application() - call(['pybot'] + args, shell=(os.sep == '\\')) - stop_demo_application() - -def start_demo_application(): - Popen(['python', DEMOAPP, 'start'], stdout=TemporaryFile(), stderr=STDOUT) - -def stop_demo_application(): - call(['python', DEMOAPP, 'stop'], stdout=TemporaryFile(), stderr=STDOUT) - -def print_help(): - print __doc__ - -def print_usage(): - print 'Usage: rundemo.py [options] datasource' - print ' or: rundemo.py demoapp start|stop' - print ' or: rundemo.py help' - - -if __name__ == '__main__': - action = {'demoapp-start': start_demo_application, - 'demoapp-stop': stop_demo_application, - 'help': print_help, - '': print_usage}.get('-'.join(sys.argv[1:])) - if action: - action() - else: - run_tests(sys.argv[1:]) From e3d301b34109a041d7b5710a1e50433cc3061156 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Mon, 22 Aug 2016 20:45:12 +0300 Subject: [PATCH 20/31] Removed README on favor of README.rst --- README | 59 ---------------------------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 README diff --git a/README b/README deleted file mode 100644 index 4452b177c..000000000 --- a/README +++ /dev/null @@ -1,59 +0,0 @@ -Selenium 2 (WebDriver) library for Robot Framework -================================================== - - -Introduction ------------- - -Selenium2Library is a web testing library for Robot Framework -that leverages the `Selenium 2 (WebDriver)`_ libraries from the -Selenium_ project. - -It is modeled after (and forked from) the SeleniumLibrary_ library, -but re-implemented to use Selenium 2 and WebDriver technologies. - -- More information about this library can be found on the Wiki_ and in the `Keyword Documentation`_. -- Installation information is found in the `INSTALL.rst` file. -- Developer information is found in `BUILD.rst` file. - - -Directory Layout ----------------- - -demo/ - A simple demonstration, with an application running on localhost - -doc/ - Keyword documentation - -src/ - Python source code - - -Usage ------ - -To write tests with Robot Framework and Selenium2Library, -Selenium2Library must be imported into your Robot test suite. -See `Robot Framework User Guide`_ for more information. - - -Running the Demo ----------------- - -The demo directory contains an easily executable demo for Robot Framework -using Selenium2Library. To run the demo, run:: - - python demo/rundemo.py - -E.g.:: - - python demo/rundemo.py demo/login_tests - - -.. _Selenium: http://docs.seleniumhq.org -.. _Selenium 2 (WebDriver): http://docs.seleniumhq.org/docs/03_webdriver.jsp -.. _SeleniumLibrary: http://code.google.com/p/robotframework-seleniumlibrary/ -.. _Wiki: https://github.com/robotframework/Selenium2Library/wiki -.. _Keyword Documentation: http://rtomac.github.io/robotframework-selenium2library/doc/Selenium2Library.html -.. _Robot Framework User Guide: http://code.google.com/p/robotframework/wiki/UserGuide From 3ab9b3ca9f99c2bbe43c2fa6a8c8d0bcc2e23726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 23 Aug 2016 19:01:51 +0300 Subject: [PATCH 21/31] Add note about new keyword doc location --- docs/doc/Selenium2Library.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/doc/Selenium2Library.html diff --git a/docs/doc/Selenium2Library.html b/docs/doc/Selenium2Library.html new file mode 100644 index 000000000..e570ff13b --- /dev/null +++ b/docs/doc/Selenium2Library.html @@ -0,0 +1,17 @@ + + + + Keyword documentation moved + + + +

Keyword documentation moved

+

+ Selenium2Library keyword documentation has moved. New location is + http://robotframework.org/Selenium2Library/Selenium2Library.html. +

+

+ Please update your bookmarks and other references. +

+ + From 5662c740819457790587cfeab34cfa45dcb49e53 Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Wed, 24 Aug 2016 22:13:18 +0300 Subject: [PATCH 22/31] Release notes for 1.8.0 release --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c5c62f247..bc79d5108 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,21 @@ Release Notes ============= +1.8.0 +----- +- Moved keyword documentation to: + http://robotframework.org/Selenium2Library/Selenium2Library.html +- Library demo project was removed, users shoud use demo from: + https://bitbucket.org/robotframework/webdemo +- docutils was removed as installation dependency. +- Edge browser support was added to Open Browser keyword [jfx]. +- Unselect From List keyword was updated because selenium 2.53.0 or greater + raised an exception if item can not be unselected. +- Get Element Size keyword was added [SergiuTudos] +- Alert handling was improved to wait alert be present +- Capture Page Screenshot keyword was enhanced to support custom index definition + in the filename + 1.7.4 ------------------- - Reverted 'Press Keys' because of backwards incompatible changes [zephraph] From 9aef12509ad9749ecd5c508ea50b208b4b62d108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Mon, 5 Sep 2016 20:56:08 +0300 Subject: [PATCH 23/31] Remove ez_setup.py and references to it in setup.py. (#647) Fixes #646. --- setup.py | 5 - src/ez_setup.py | 284 ------------------------------------------------ 2 files changed, 289 deletions(-) delete mode 100644 src/ez_setup.py diff --git a/setup.py b/setup.py index ec5943478..f4728811c 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -import sys from os.path import join, dirname -sys.path.append(join(dirname(__file__), 'src')) -from ez_setup import use_setuptools -use_setuptools() from setuptools import setup execfile(join(dirname(__file__), 'src', 'Selenium2Library', 'version.py')) @@ -37,7 +33,6 @@ 'selenium >= 2.32.0', 'robotframework >= 2.6.0' ], - py_modules=['ez_setup'], package_dir = {'' : 'src'}, packages = ['Selenium2Library','Selenium2Library.keywords','Selenium2Library.locators', 'Selenium2Library.utils'], diff --git a/src/ez_setup.py b/src/ez_setup.py deleted file mode 100644 index b74adc065..000000000 --- a/src/ez_setup.py +++ /dev/null @@ -1,284 +0,0 @@ -#!python -"""Bootstrap setuptools installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from ez_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import sys -DEFAULT_VERSION = "0.6c11" -DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', - 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', - 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', - 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', - 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', - 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', - 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', - 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', - 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', - 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', - 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', - 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', - 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', - 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', - 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', - 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', - 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', - 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', - 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', - 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', - 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', - 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', - 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', - 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', - 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', - 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', - 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', -} - -import sys, os -try: from hashlib import md5 -except ImportError: from md5 import md5 - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end with - a '/'). `to_dir` is the directory where setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules - def do_download(): - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg - try: - import pkg_resources - except ImportError: - return do_download() - try: - pkg_resources.require("setuptools>="+version); return - except pkg_resources.VersionConflict, e: - if was_imported: - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first, using 'easy_install -U setuptools'." - "\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - except pkg_resources.DistributionNotFound: - pass - - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return do_download() - -def download_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - delay = 15 -): - """Download setuptools from a specified location and return its filename - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download attempt. - """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) - log.warn("Downloading %s", url) - src = urllib2.urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) - finally: - if src: src.close() - if dst: dst.close() - return os.path.realpath(saveto) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - - From 439dae7acf1f850937cbe3148838a5392cfbc34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eino=20M=C3=A4kitalo?= Date: Mon, 5 Sep 2016 21:19:48 +0300 Subject: [PATCH 24/31] Print fix (#626) * 2to3.py runned for print & except * 2to3.py runned for except * 2to3.py -f dict --- .../keywords/_runonfailure.py | 2 +- .../keywords/_tableelement.py | 2 +- src/Selenium2Library/keywords/keywordgroup.py | 4 +-- .../locators/elementfinder.py | 6 ++--- test/lib/mockito/invocation.py | 4 +-- test/lib/mockito/mock_registry.py | 2 +- test/lib/mockito/mockito.py | 2 +- test/resources/statuschecker.py | 6 ++--- test/resources/testserver/testserver.py | 2 +- test/run_tests.py | 26 +++++++++---------- test/run_unit_tests.py | 4 +-- test/unit/keywords/test_browsermanagement.py | 2 +- 12 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/Selenium2Library/keywords/_runonfailure.py b/src/Selenium2Library/keywords/_runonfailure.py index 4e9057107..737e02f91 100644 --- a/src/Selenium2Library/keywords/_runonfailure.py +++ b/src/Selenium2Library/keywords/_runonfailure.py @@ -57,7 +57,7 @@ def _run_on_failure(self): self._running_on_failure_routine = True try: BUILTIN.run_keyword(self._run_on_failure_keyword) - except Exception, err: + except Exception as err: self._run_on_failure_error(err) finally: self._running_on_failure_routine = False diff --git a/src/Selenium2Library/keywords/_tableelement.py b/src/Selenium2Library/keywords/_tableelement.py index 04447d76e..524bda280 100644 --- a/src/Selenium2Library/keywords/_tableelement.py +++ b/src/Selenium2Library/keywords/_tableelement.py @@ -64,7 +64,7 @@ def table_cell_should_contain(self, table_locator, row, column, expected, loglev % (table_locator, row, column, expected)) try: content = self.get_table_cell(table_locator, row, column, loglevel='NONE') - except AssertionError, err: + except AssertionError as err: self._info(err) self.log_source(loglevel) raise AssertionError(message) diff --git a/src/Selenium2Library/keywords/keywordgroup.py b/src/Selenium2Library/keywords/keywordgroup.py index 672603afd..dab2af51d 100644 --- a/src/Selenium2Library/keywords/keywordgroup.py +++ b/src/Selenium2Library/keywords/keywordgroup.py @@ -13,7 +13,7 @@ def _run_on_failure_decorator(method, *args, **kwargs): self._already_in_keyword = True # Set a flag on the instance so that as we call keywords inside this call and this gets run again, we know we're at least one level in. try: return method(*args, **kwargs) - except Exception, err: + except Exception as err: if hasattr(self, '_run_on_failure') and not self._has_run_on_failure: # If we're in an inner keyword, track the fact that we've already run on failure once self._has_run_on_failure = True @@ -28,7 +28,7 @@ def _run_on_failure_decorator(method, *args, **kwargs): class KeywordGroupMetaClass(type): def __new__(cls, clsname, bases, dict): if decorator: - for name, method in dict.items(): + for name, method in list(dict.items()): if not name.startswith('_') and inspect.isroutine(method): dict[name] = decorator(_run_on_failure_decorator, method) return type.__new__(cls, clsname, bases, dict) diff --git a/src/Selenium2Library/locators/elementfinder.py b/src/Selenium2Library/locators/elementfinder.py index 041471370..81da96539 100644 --- a/src/Selenium2Library/locators/elementfinder.py +++ b/src/Selenium2Library/locators/elementfinder.py @@ -22,7 +22,7 @@ def __init__(self): 'default': self._find_by_default } self._strategies = NormalizedDict(initial=strategies, caseless=True, spaceless=True) - self._default_strategies = strategies.keys() + self._default_strategies = list(strategies.keys()) def find(self, browser, locator, tag=None): assert browser is not None @@ -190,9 +190,7 @@ def _element_matches(self, element, tag, constraints): def _filter_elements(self, elements, tag, constraints): elements = self._normalize_result(elements) if tag is None: return elements - return filter( - lambda element: self._element_matches(element, tag, constraints), - elements) + return [element for element in elements if self._element_matches(element, tag, constraints)] def _get_attrs_with_url(self, key_attrs, criteria, browser): attrs = [] diff --git a/test/lib/mockito/invocation.py b/test/lib/mockito/invocation.py index 067881b99..159a56863 100644 --- a/test/lib/mockito/invocation.py +++ b/test/lib/mockito/invocation.py @@ -47,14 +47,14 @@ def matches(self, invocation): return False if len(self.named_params) != len(invocation.named_params): return False - if self.named_params.keys() != invocation.named_params.keys(): + if list(self.named_params.keys()) != list(invocation.named_params.keys()): return False for x, p1 in enumerate(self.params): if not self.compare(p1, invocation.params[x]): return False - for x, p1 in self.named_params.iteritems(): + for x, p1 in self.named_params.items(): if not self.compare(p1, invocation.named_params[x]): return False diff --git a/test/lib/mockito/mock_registry.py b/test/lib/mockito/mock_registry.py index c7a0208f6..5b702e209 100644 --- a/test/lib/mockito/mock_registry.py +++ b/test/lib/mockito/mock_registry.py @@ -12,7 +12,7 @@ def mock_for(self, cls): return self.mocks.get(cls, None) def unstub_all(self): - for mock in self.mocks.itervalues(): + for mock in self.mocks.values(): mock.unstub() self.mocks.clear() diff --git a/test/lib/mockito/mockito.py b/test/lib/mockito/mockito.py index 30f188a4f..50f820021 100644 --- a/test/lib/mockito/mockito.py +++ b/test/lib/mockito/mockito.py @@ -15,7 +15,7 @@ class ArgumentError(Exception): pass def _multiple_arguments_in_use(*args): - return len(filter(lambda x: x, args)) > 1 + return len([x for x in args if x]) > 1 def _invalid_argument(value): return (value is not None and value < 1) or value == 0 diff --git a/test/resources/statuschecker.py b/test/resources/statuschecker.py index 20b6a849d..5af72b86c 100755 --- a/test/resources/statuschecker.py +++ b/test/resources/statuschecker.py @@ -177,14 +177,14 @@ def _get_log_message(self, msg_str): import os if not 2 <= len(sys.argv) <= 3 or '--help' in sys.argv: - print __doc__ + print(__doc__) sys.exit(1) infile = sys.argv[1] outfile = sys.argv[2] if len(sys.argv) == 3 else None - print "Checking %s" % os.path.abspath(infile) + print("Checking %s" % os.path.abspath(infile)) rc = process_output(infile, outfile) if outfile: - print "Output: %s" % os.path.abspath(outfile) + print("Output: %s" % os.path.abspath(outfile)) if rc > 255: rc = 255 sys.exit(rc) diff --git a/test/resources/testserver/testserver.py b/test/resources/testserver/testserver.py index 3d33a24ca..c67ca3c4a 100644 --- a/test/resources/testserver/testserver.py +++ b/test/resources/testserver/testserver.py @@ -94,7 +94,7 @@ def start_server(port=7000): if __name__ == '__main__': import sys if len(sys.argv) != 2 or sys.argv[1] not in [ 'start', 'stop' ]: - print 'usage: %s start|stop' % sys.argv[0] + print('usage: %s start|stop' % sys.argv[0]) sys.exit(1) if sys.argv[1] == 'start': start_server() diff --git a/test/run_tests.py b/test/run_tests.py index ac7f69c8c..673db64e6 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -53,8 +53,8 @@ def execute_tests(runner, args): if not os.path.exists(env.RESULTS_DIR): os.mkdir(env.RESULTS_DIR) command = [runner] + [arg % ARG_VALUES for arg in ROBOT_ARGS] + args + [env.ACCEPTANCE_TEST_DIR] - print '' - print 'Starting test execution with command:\n' + ' '.join(command) + print('') + print('Starting test execution with command:\n' + ' '.join(command)) syslog = os.path.join(env.RESULTS_DIR, 'syslog.txt') call(command, shell=os.sep=='\\', env=dict(os.environ, ROBOT_SYSLOG_FILE=syslog)) @@ -62,20 +62,20 @@ def stop_http_server(): call(['python', env.HTTP_SERVER_FILE, 'stop']) def process_output(args): - print + print() if _has_robot_27(): call(['python', os.path.join(env.RESOURCES_DIR, 'statuschecker.py'), os.path.join(env.RESULTS_DIR, 'output.xml')]) rebot = 'rebot' if os.sep == '/' else 'rebot.bat' rebot_cmd = [rebot] + [ arg % ARG_VALUES for arg in REBOT_ARGS ] + args + \ [os.path.join(ARG_VALUES['outdir'], 'output.xml') ] - print '' - print 'Starting output processing with command:\n' + ' '.join(rebot_cmd) + print('') + print('Starting output processing with command:\n' + ' '.join(rebot_cmd)) rc = call(rebot_cmd, env=os.environ) if rc == 0: - print 'All critical tests passed' + print('All critical tests passed') else: - print '%d critical test%s failed' % (rc, 's' if rc != 1 else '') + print('%d critical test%s failed' % (rc, 's' if rc != 1 else '')) return rc def _has_robot_27(): @@ -89,18 +89,18 @@ def _exit(rc): sys.exit(rc) def _help(): - print 'Usage: python run_tests.py python|jython browser [options]' - print - print 'See README.txt for details.' + print('Usage: python run_tests.py python|jython browser [options]') + print() + print('See README.txt for details.') return 255 def _run_unit_tests(): - print 'Running unit tests' + print('Running unit tests') failures = run_unit_tests() if failures != 0: - print '\n%d unit tests failed - not running acceptance tests!' % failures + print('\n%d unit tests failed - not running acceptance tests!' % failures) else: - print 'All unit tests passed' + print('All unit tests passed') return failures diff --git a/test/run_unit_tests.py b/test/run_unit_tests.py index aa417c192..3b5a0399f 100755 --- a/test/run_unit_tests.py +++ b/test/run_unit_tests.py @@ -10,9 +10,9 @@ def run_unit_tests(modules_to_run=[]): bad_modules_to_run = [module_to_run for module_to_run in modules_to_run if module_to_run not in test_module_names] if bad_modules_to_run: - print "Specified test module%s not exist: %s" % ( + print("Specified test module%s not exist: %s" % ( ' does' if len(bad_modules_to_run) == 1 else 's do', - ', '.join(bad_modules_to_run)) + ', '.join(bad_modules_to_run))) return -1 tests = [unittest.defaultTestLoader.loadTestsFromModule(test_module) diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py index fd6062c60..782491730 100644 --- a/test/unit/keywords/test_browsermanagement.py +++ b/test/unit/keywords/test_browsermanagement.py @@ -96,7 +96,7 @@ def test_bad_browser_name(self): try: bm._make_browser("fireox") self.fail("Exception not raised") - except ValueError, e: + except ValueError as e: self.assertEquals("fireox is not a supported browser.", e.message) def test_create_webdriver(self): From f3f40cdd8542b0fd9817ad57de3dafcc8f5467d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 6 Sep 2016 23:42:23 +0300 Subject: [PATCH 25/31] Remove vendored mockito and list in requirements-dev.txt instead. (#660) * Remove vendored mockito and list in requirements-dev.txt instead. There absolutely no need for us to keep an old copy of mockito when it can be easily pip installed. This also removes the need to make our copy Python 3 compatible. * Install mockito on travis (and don't install docutils) --- .travis.yml | 2 +- requirements-dev.txt | 1 + test/env.py | 2 - test/lib/mockito/__init__.py | 27 ----- test/lib/mockito/inorder.py | 15 --- test/lib/mockito/invocation.py | 175 ------------------------------ test/lib/mockito/matchers.py | 58 ---------- test/lib/mockito/mock_registry.py | 19 ---- test/lib/mockito/mocking.py | 106 ------------------ test/lib/mockito/mockito.py | 93 ---------------- test/lib/mockito/spying.py | 41 ------- test/lib/mockito/verification.py | 81 -------------- 12 files changed, 2 insertions(+), 618 deletions(-) create mode 100644 requirements-dev.txt delete mode 100644 test/lib/mockito/__init__.py delete mode 100644 test/lib/mockito/inorder.py delete mode 100644 test/lib/mockito/invocation.py delete mode 100644 test/lib/mockito/matchers.py delete mode 100644 test/lib/mockito/mock_registry.py delete mode 100644 test/lib/mockito/mocking.py delete mode 100644 test/lib/mockito/mockito.py delete mode 100644 test/lib/mockito/spying.py delete mode 100644 test/lib/mockito/verification.py diff --git a/.travis.yml b/.travis.yml index 3125407a4..84068f7f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ install: - pip install robotframework - pip install selenium - pip install decorator - - pip install docutils + - pip install mockito #- "if [[ ${BROWSER} == 'ie' ]]; then pip install ie_only_pkg; fi" before_script: - "export DISPLAY=:99.0" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 000000000..936a01bfd --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +mockito diff --git a/test/env.py b/test/env.py index efc6c4148..f29a509cc 100755 --- a/test/env.py +++ b/test/env.py @@ -4,7 +4,6 @@ ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) UNIT_TEST_DIR = os.path.join(ROOT_DIR, "unit") ACCEPTANCE_TEST_DIR = os.path.join(ROOT_DIR, "acceptance") -LIB_DIR = os.path.join(ROOT_DIR, "lib") RESOURCES_DIR = os.path.join(ROOT_DIR, "resources") TEST_LIBS_DIR = os.path.join(RESOURCES_DIR, "testlibs") RESULTS_DIR = os.path.join(ROOT_DIR, "results") @@ -12,5 +11,4 @@ SRC_DIR = os.path.join(ROOT_DIR, "..", "src") sys.path.insert(0, SRC_DIR) -sys.path.append(LIB_DIR) sys.path.append(UNIT_TEST_DIR) diff --git a/test/lib/mockito/__init__.py b/test/lib/mockito/__init__.py deleted file mode 100644 index 3a3c29bce..000000000 --- a/test/lib/mockito/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -'''Mockito is a Test Spy framework.''' - -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -from mockito import mock, verify, verifyNoMoreInteractions, verifyZeroInteractions, when, unstub, ArgumentError -import inorder -from spying import spy -from verification import VerificationError - -# Imports for compatibility -from mocking import Mock -from matchers import any, contains, times # use package import (``from mockito.matchers import any, contains``) instead of ``from mockito import any, contains`` -from verification import never - -__all__ = ['mock', 'spy', 'verify', 'verifyNoMoreInteractions', 'verifyZeroInteractions', 'inorder', 'when', 'unstub', 'VerificationError', 'ArgumentError', - 'Mock', # deprecated - 'any', # compatibility - 'contains', # compatibility - 'never', # compatibility - 'times' # deprecated - ] diff --git a/test/lib/mockito/inorder.py b/test/lib/mockito/inorder.py deleted file mode 100644 index 8c9d801d0..000000000 --- a/test/lib/mockito/inorder.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# coding: utf-8 - -from mockito import verify as verify_main - -__author__ = "Serhiy Oplakanets " -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -def verify(object, *args, **kwargs): - kwargs['inorder'] = True - return verify_main(object, *args, **kwargs) - diff --git a/test/lib/mockito/invocation.py b/test/lib/mockito/invocation.py deleted file mode 100644 index 159a56863..000000000 --- a/test/lib/mockito/invocation.py +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import matchers - -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -class InvocationError(AssertionError): - pass - -class Invocation(object): - def __init__(self, mock, method_name): - self.method_name = method_name - self.mock = mock - self.verified = False - self.verified_inorder = False - self.params = () - self.named_params = {} - self.answers = [] - self.strict = mock.strict - - def _remember_params(self, params, named_params): - self.params = params - self.named_params = named_params - - def __repr__(self): - return self.method_name + "(" + ", ".join([repr(p) for p in self.params]) + ")" - - def answer_first(self): - return self.answers[0].answer() - -class MatchingInvocation(Invocation): - @staticmethod - def compare(p1, p2): - if isinstance(p1, matchers.Matcher): - if not p1.matches(p2): return False - elif p1 != p2: return False - return True - - def matches(self, invocation): - if self.method_name != invocation.method_name: - return False - if len(self.params) != len(invocation.params): - return False - if len(self.named_params) != len(invocation.named_params): - return False - if list(self.named_params.keys()) != list(invocation.named_params.keys()): - return False - - for x, p1 in enumerate(self.params): - if not self.compare(p1, invocation.params[x]): - return False - - for x, p1 in self.named_params.items(): - if not self.compare(p1, invocation.named_params[x]): - return False - - return True - -class RememberedInvocation(Invocation): - def __call__(self, *params, **named_params): - self._remember_params(params, named_params) - self.mock.remember(self) - - for matching_invocation in self.mock.stubbed_invocations: - if matching_invocation.matches(self): - return matching_invocation.answer_first() - - return None - -class RememberedProxyInvocation(Invocation): - '''Remeber params and proxy to method of original object. - - Calls method on original object and returns it's return value. - ''' - def __call__(self, *params, **named_params): - self._remember_params(params, named_params) - self.mock.remember(self) - obj = self.mock.original_object - try: - method = getattr(obj, self.method_name) - except AttributeError: - raise AttributeError("You tried to call method '%s' which '%s' instance does not have." % (self.method_name, obj.__class__.__name__)) - return method(*params, **named_params) - -class VerifiableInvocation(MatchingInvocation): - def __call__(self, *params, **named_params): - self._remember_params(params, named_params) - matched_invocations = [] - for invocation in self.mock.invocations: - if self.matches(invocation): - matched_invocations.append(invocation) - - verification = self.mock.pull_verification() - verification.verify(self, len(matched_invocations)) - - for invocation in matched_invocations: - invocation.verified = True - -class StubbedInvocation(MatchingInvocation): - def __init__(self, *params): - super(StubbedInvocation, self).__init__(*params) - if self.mock.strict: - self.ensure_mocked_object_has_method(self.method_name) - - def ensure_mocked_object_has_method(self, method_name): - if not self.mock.has_method(method_name): - raise InvocationError("You tried to stub a method '%s' the object (%s) doesn't have." - % (method_name, self.mock.mocked_obj)) - - - def __call__(self, *params, **named_params): - self._remember_params(params, named_params) - return AnswerSelector(self) - - def stub_with(self, answer): - self.answers.append(answer) - self.mock.stub(self.method_name) - self.mock.finish_stubbing(self) - -class AnswerSelector(object): - def __init__(self, invocation): - self.invocation = invocation - self.answer = None - - def thenReturn(self, *return_values): - for return_value in return_values: - self.__then(Return(return_value)) - return self - - def thenRaise(self, *exceptions): - for exception in exceptions: - self.__then(Raise(exception)) - return self - - def __then(self, answer): - if not self.answer: - self.answer = CompositeAnswer(answer) - self.invocation.stub_with(self.answer) - else: - self.answer.add(answer) - - return self - -class CompositeAnswer(object): - def __init__(self, answer): - self.answers = [answer] - - def add(self, answer): - self.answers.insert(0, answer) - - def answer(self): - if len(self.answers) > 1: - a = self.answers.pop() - else: - a = self.answers[0] - - return a.answer() - -class Raise(object): - def __init__(self, exception): - self.exception = exception - - def answer(self): - raise self.exception - -class Return(object): - def __init__(self, return_value): - self.return_value = return_value - - def answer(self): - return self.return_value diff --git a/test/lib/mockito/matchers.py b/test/lib/mockito/matchers.py deleted file mode 100644 index fcfb3d313..000000000 --- a/test/lib/mockito/matchers.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -'''Matchers for stubbing and verifications. - -Common matchers for use in stubbing and verifications. -''' - -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -__all__ = ['any', 'contains', 'times'] - -class Matcher: - def matches(self, arg): - pass - -class Any(Matcher): - def __init__(self, wanted_type=None): - self.wanted_type = wanted_type - - def matches(self, arg): - if self.wanted_type: - return isinstance(arg, self.wanted_type) - else: - return True - - def __repr__(self): - return "" % self.wanted_type - -class Contains(Matcher): - def __init__(self, sub): - self.sub = sub - - def matches(self, arg): - if not hasattr(arg, 'find'): - return - return self.sub and len(self.sub) > 0 and arg.find(self.sub) > -1 - - def __repr__(self): - return "" % self.sub - - -def any(wanted_type=None): - """Matches any() argument OR any(SomeClass) argument - Examples: - when(mock).foo(any()).thenReturn(1) - verify(mock).foo(any(int)) - """ - return Any(wanted_type) - -def contains(sub): - return Contains(sub) - -def times(count): - return count diff --git a/test/lib/mockito/mock_registry.py b/test/lib/mockito/mock_registry.py deleted file mode 100644 index 5b702e209..000000000 --- a/test/lib/mockito/mock_registry.py +++ /dev/null @@ -1,19 +0,0 @@ -class MockRegistry: - """Registers mock()s, ensures that we only have one mock() per mocked_obj, and - iterates over them to unstub each stubbed method. """ - - def __init__(self): - self.mocks = {} - - def register(self, mock): - self.mocks[mock.mocked_obj] = mock - - def mock_for(self, cls): - return self.mocks.get(cls, None) - - def unstub_all(self): - for mock in self.mocks.values(): - mock.unstub() - self.mocks.clear() - -mock_registry = MockRegistry() \ No newline at end of file diff --git a/test/lib/mockito/mocking.py b/test/lib/mockito/mocking.py deleted file mode 100644 index 9539ddd62..000000000 --- a/test/lib/mockito/mocking.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import inspect -import invocation -from mock_registry import mock_registry -import warnings - -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -__all__ = ['mock', 'Mock'] - -class _Dummy(object): pass - -class TestDouble(object): pass - -class mock(TestDouble): - def __init__(self, mocked_obj=None, strict=True): - self.invocations = [] - self.stubbed_invocations = [] - self.original_methods = [] - self.stubbing = None - self.verification = None - if mocked_obj is None: - mocked_obj = _Dummy() - strict = False - self.mocked_obj = mocked_obj - self.strict = strict - self.stubbing_real_object = False - - mock_registry.register(self) - - def __getattr__(self, method_name): - if self.stubbing is not None: - return invocation.StubbedInvocation(self, method_name) - - if self.verification is not None: - return invocation.VerifiableInvocation(self, method_name) - - return invocation.RememberedInvocation(self, method_name) - - def remember(self, invocation): - self.invocations.insert(0, invocation) - - def finish_stubbing(self, stubbed_invocation): - self.stubbed_invocations.insert(0, stubbed_invocation) - self.stubbing = None - - def expect_stubbing(self): - self.stubbing = True - - def pull_verification(self): - v = self.verification - self.verification = None - return v - - def has_method(self, method_name): - return hasattr(self.mocked_obj, method_name) - - def get_method(self, method_name): - return self.mocked_obj.__dict__.get(method_name) - - def set_method(self, method_name, new_method): - setattr(self.mocked_obj, method_name, new_method) - - def replace_method(self, method_name, original_method): - - def new_mocked_method(*args, **kwargs): - # we throw away the first argument, if it's either self or cls - if inspect.isclass(self.mocked_obj) and not isinstance(original_method, staticmethod): - args = args[1:] - call = self.__getattr__(method_name) # that is: invocation.RememberedInvocation(self, method_name) - return call(*args, **kwargs) - - if isinstance(original_method, staticmethod): - new_mocked_method = staticmethod(new_mocked_method) - elif isinstance(original_method, classmethod): - new_mocked_method = classmethod(new_mocked_method) - - self.set_method(method_name, new_mocked_method) - - def stub(self, method_name): - original_method = self.get_method(method_name) - original = (method_name, original_method) - self.original_methods.append(original) - - # If we're trying to stub real object(not a generated mock), then we should patch object to use our mock method. - # TODO: Polymorphism was invented long time ago. Refactor this. - if self.stubbing_real_object: - self.replace_method(method_name, original_method) - - def unstub(self): - while self.original_methods: - method_name, original_method = self.original_methods.pop() - self.set_method(method_name, original_method) - -def Mock(*args, **kwargs): - '''A ``mock``() alias. - - Alias for compatibility. To be removed in version 1.0. - ''' - warnings.warn("\n`Mock()` is deprecated, please use `mock()` (lower 'm') instead.", DeprecationWarning) - return mock(*args, **kwargs) diff --git a/test/lib/mockito/mockito.py b/test/lib/mockito/mockito.py deleted file mode 100644 index 50f820021..000000000 --- a/test/lib/mockito/mockito.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import verification -from mocking import mock, TestDouble -from mock_registry import mock_registry -from verification import VerificationError - -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -class ArgumentError(Exception): - pass - -def _multiple_arguments_in_use(*args): - return len([x for x in args if x]) > 1 - -def _invalid_argument(value): - return (value is not None and value < 1) or value == 0 - -def _invalid_between(between): - if between is not None: - start, end = between - if start > end or start < 0: - return True - return False - -def verify(obj, times=1, atleast=None, atmost=None, between=None, inorder=False): - if times < 0: - raise ArgumentError("""'times' argument has invalid value. - It should be at least 0. You wanted to set it to: %i""" % times) - if _multiple_arguments_in_use(atleast, atmost, between): - raise ArgumentError("""Sure you know what you are doing? - You can set only one of the arguments: 'atleast', 'atmost' or 'between'.""") - if _invalid_argument(atleast): - raise ArgumentError("""'atleast' argument has invalid value. - It should be at least 1. You wanted to set it to: %i""" % atleast) - if _invalid_argument(atmost): - raise ArgumentError("""'atmost' argument has invalid value. - It should be at least 1. You wanted to set it to: %i""" % atmost) - if _invalid_between(between): - raise ArgumentError("""'between' argument has invalid value. - It should consist of positive values with second number not greater than first - e.g. [1, 4] or [0, 3] or [2, 2] - You wanted to set it to: %s""" % between) - - if isinstance(obj, TestDouble): - mocked_object = obj - else: - mocked_object = mock_registry.mock_for(obj) - - if atleast: - mocked_object.verification = verification.AtLeast(atleast) - elif atmost: - mocked_object.verification = verification.AtMost(atmost) - elif between: - mocked_object.verification = verification.Between(*between) - else: - mocked_object.verification = verification.Times(times) - - if inorder: - mocked_object.verification = verification.InOrder(mocked_object.verification) - - return mocked_object - -def when(obj, strict=True): - if isinstance(obj, mock): - theMock = obj - else: - theMock = mock_registry.mock_for(obj) - if theMock is None: - theMock = mock(obj, strict=strict) - # If we call when on something that is not TestDouble that means we're trying to stub real object, - # (class, module etc.). Not to be confused with generating stubs from real classes. - theMock.stubbing_real_object = True - - theMock.expect_stubbing() - return theMock - -def unstub(): - """Unstubs all stubbed methods and functions""" - mock_registry.unstub_all() - -def verifyNoMoreInteractions(*mocks): - for mock in mocks: - for i in mock.invocations: - if not i.verified: - raise VerificationError("\nUnwanted interaction: " + str(i)) - -def verifyZeroInteractions(*mocks): - verifyNoMoreInteractions(*mocks) diff --git a/test/lib/mockito/spying.py b/test/lib/mockito/spying.py deleted file mode 100644 index fcb3ded30..000000000 --- a/test/lib/mockito/spying.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -'''Spying on real objects.''' - -from invocation import RememberedProxyInvocation, VerifiableInvocation -from mocking import TestDouble - -__author__ = "Serhiy Oplakanets " -__copyright__ = "Copyright 2009-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -__all__ = ['spy'] - -def spy(original_object): - return Spy(original_object) - -class Spy(TestDouble): - strict = True # spies always have to check if method exists - - def __init__(self, original_object): - self.original_object = original_object - self.invocations = [] - self.verification = None - - def __getattr__(self, name): - if self.verification: - return VerifiableInvocation(self, name) - else: - return RememberedProxyInvocation(self, name) - - def remember(self, invocation): - self.invocations.insert(0, invocation) - - def pull_verification(self): - v = self.verification - self.verification = None - return v - \ No newline at end of file diff --git a/test/lib/mockito/verification.py b/test/lib/mockito/verification.py deleted file mode 100644 index 4acf0b42f..000000000 --- a/test/lib/mockito/verification.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -__copyright__ = "Copyright 2008-2010, Mockito Contributors" -__license__ = "MIT" -__maintainer__ = "Mockito Maintainers" -__email__ = "mockito-python@googlegroups.com" - -__all__ = ['never', 'VerificationError'] - -class VerificationError(AssertionError): - '''Indicates error during verification of invocations. - - Raised if verification fails. Error message contains the cause. - ''' - pass - -class AtLeast(object): - def __init__(self, wanted_count): - self.wanted_count = wanted_count - - def verify(self, invocation, actual_count): - if actual_count < self.wanted_count: - raise VerificationError("\nWanted at least: %i, actual times: %i" % (self.wanted_count, actual_count)) - -class AtMost(object): - def __init__(self, wanted_count): - self.wanted_count = wanted_count - - def verify(self, invocation, actual_count): - if actual_count > self.wanted_count: - raise VerificationError("\nWanted at most: %i, actual times: %i" % (self.wanted_count, actual_count)) - -class Between(object): - def __init__(self, wanted_from, wanted_to): - self.wanted_from = wanted_from - self.wanted_to = wanted_to - - def verify(self, invocation, actual_count): - if actual_count < self.wanted_from or actual_count > self.wanted_to: - raise VerificationError("\nWanted between: [%i, %i], actual times: %i" % (self.wanted_from, self.wanted_to, actual_count)) - -class Times(object): - def __init__(self, wanted_count): - self.wanted_count = wanted_count - - def verify(self, invocation, actual_count): - if actual_count == self.wanted_count: - return - if actual_count == 0: - raise VerificationError("\nWanted but not invoked: %s" % (invocation)) - else: - if self.wanted_count == 0: - raise VerificationError("\nUnwanted invocation of %s, times: %i" % (invocation, actual_count)) - else: - raise VerificationError("\nWanted times: %i, actual times: %i" % (self.wanted_count, actual_count)) - -class InOrder(object): - ''' - Verifies invocations in order. - - Verifies if invocation was in expected order, and if yes -- degrades to original Verifier (AtLeast, Times, Between, ...). - ''' - - def __init__(self, original_verification): - ''' - @param original_verification: Original verifiaction to degrade to if order of invocation was ok. - ''' - self.original_verification = original_verification - - def verify(self, wanted_invocation, count): - for invocation in reversed(wanted_invocation.mock.invocations): - if not invocation.verified_inorder: - if not wanted_invocation.matches(invocation): - raise VerificationError("\nWanted %s to be invoked, got %s instead" % (wanted_invocation, invocation)) - invocation.verified_inorder = True - break - # proceed with original verification - self.original_verification.verify(wanted_invocation, count) - -never = 0 From 97f817a0be145f5f50cf615704cac8825dcd66ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 6 Sep 2016 23:46:55 +0300 Subject: [PATCH 26/31] Add requirements.txt. (#659) Eases installing project requirements e.g. when doing development. To avoid listing requirements twice, changed setup.py to read install_requires content from requirements.txt directly. --- requirements.txt | 3 +++ setup.py | 16 +++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..b266dec99 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +decorator >= 3.3.2 +selenium >= 2.32.0 +robotframework >= 2.6.0 diff --git a/setup.py b/setup.py index f4728811c..1897429ed 100755 --- a/setup.py +++ b/setup.py @@ -1,16 +1,22 @@ #!/usr/bin/env python -from os.path import join, dirname +from os.path import abspath, dirname, join from setuptools import setup -execfile(join(dirname(__file__), 'src', 'Selenium2Library', 'version.py')) + +CURDIR = dirname(abspath(__file__)) + +execfile(join(CURDIR, 'src', 'Selenium2Library', 'version.py')) DESCRIPTION = """ Selenium2Library is a web testing library for Robot Framework that leverages the Selenium 2 (WebDriver) libraries. """[1:-1] +with open(join(CURDIR, 'requirements.txt')) as f: + REQUIREMENTS = f.read().splitlines() + setup(name = 'robotframework-selenium2library', version = VERSION, description = 'Web testing library for Robot Framework', @@ -28,11 +34,7 @@ "Programming Language :: Python", "Topic :: Software Development :: Testing" ], - install_requires = [ - 'decorator >= 3.3.2', - 'selenium >= 2.32.0', - 'robotframework >= 2.6.0' - ], + install_requires = REQUIREMENTS, package_dir = {'' : 'src'}, packages = ['Selenium2Library','Selenium2Library.keywords','Selenium2Library.locators', 'Selenium2Library.utils'], From 3f578e712840a8b0afbc01e58c8b4ae3ffb0314a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Thu, 8 Sep 2016 14:35:16 +0300 Subject: [PATCH 27/31] Clean up run_unit_tests.py. (#662) Finding unit tests is rewritten by using test discovery functionality provided by the unittest module in Python 2.7 and newer. This means unit tests cannot be executed with Python 2.6 anymore, but we've decided to drop its support anyway (#620). Tests won't be run with Python 2.6 on Travis anymore either. Also removed utils related to loading modules from S2L.utils. This unit test related code didn't belong under the main project in the first place. --- .travis.yml | 1 - src/Selenium2Library/utils/__init__.py | 79 -------------------------- test/run_unit_tests.py | 39 ++++++------- 3 files changed, 16 insertions(+), 103 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84068f7f7..d9b956d00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python sudo: false python: - - "2.6" - "2.7" install: #- pip install pep8 --use-mirrors diff --git a/src/Selenium2Library/utils/__init__.py b/src/Selenium2Library/utils/__init__.py index 042d053e3..ec116c39d 100644 --- a/src/Selenium2Library/utils/__init__.py +++ b/src/Selenium2Library/utils/__init__.py @@ -1,47 +1,7 @@ -import os -from fnmatch import fnmatch from browsercache import BrowserCache from librarylistener import LibraryListener import events -__all__ = [ - "get_child_packages_in", - "get_module_names_under", - "import_modules_under", - "escape_xpath_value", - "BrowserCache", - "LibraryListener", - "events" -] - -# Public - -def get_child_packages_in(root_dir, include_root_package_name=True, exclusions=None): - packages = [] - root_package_str = os.path.basename(root_dir) + '.' if include_root_package_name else "" - _discover_child_package_dirs( - root_dir, - _clean_exclusions(exclusions), - lambda abs_path, relative_path, name: - packages.append(root_package_str + relative_path.replace(os.sep, '.'))) - return packages - -def get_module_names_under(root_dir, include_root_package_name=True, exclusions=None, pattern=None): - module_names = [] - root_package_str = os.path.basename(root_dir) + '.' if include_root_package_name else "" - _discover_module_files_in( - root_dir, - _clean_exclusions(exclusions), - pattern if pattern is not None else "*.*", - lambda abs_path, relative_path, name: - module_names.append(root_package_str + os.path.splitext(relative_path)[0].replace(os.sep, '.'))) - return module_names - -def import_modules_under(root_dir, include_root_package_name=True, exclusions=None, pattern=None): - module_names = get_module_names_under(root_dir, include_root_package_name, exclusions, pattern) - modules = [ __import__(module_name, globals(), locals(), ['*'], -1) - for module_name in module_names ] - return (module_names, modules) def escape_xpath_value(value): value = unicode(value) @@ -51,42 +11,3 @@ def escape_xpath_value(value): if '\'' in value: return "\"%s\"" % value return "'%s'" % value - -# Private - -def _clean_exclusions(exclusions): - if exclusions is None: exclusions = [] - if not isinstance(exclusions, list): exclusions = [ exclusions ] - exclusions = [ os.sep + exclusion.lower().strip(os.sep) + os.sep - for exclusion in exclusions ] - return exclusions - -def _discover_child_package_dirs(root_dir, exclusions, callback, relative_dir=None): - relative_dir = relative_dir if relative_dir is not None else '' - abs_dir = os.path.join(root_dir, relative_dir) - for item in os.listdir(abs_dir): - item_relative_path = os.path.join(relative_dir, item) - item_abs_path = os.path.join(root_dir, item_relative_path) - if os.path.isdir(item_abs_path): - if os.path.exists(os.path.join(item_abs_path, "__init__.py")): - exclusion_matches = [ exclusion for exclusion in exclusions - if os.sep + item_relative_path.lower() + os.sep == exclusion ] - if not exclusion_matches: - callback(item_abs_path, item_relative_path, item) - _discover_child_package_dirs(root_dir, exclusions, callback, item_relative_path) - -def _discover_module_files_in(root_dir, exclusions, pattern, callback): - def find_matching_files(relative_dir): - abs_dir = os.path.join(root_dir, relative_dir) - for item in os.listdir(abs_dir): - item_relative_path = os.path.join(relative_dir, item) - item_abs_path = os.path.join(root_dir, item_relative_path) - if os.path.isfile(item_abs_path) and fnmatch(item, pattern): - callback(item_abs_path, item_relative_path, item) - - find_matching_files('') - - _discover_child_package_dirs( - root_dir, - _clean_exclusions(exclusions), - lambda abs_path, relative_path, name: find_matching_files(relative_path)) diff --git a/test/run_unit_tests.py b/test/run_unit_tests.py index 3b5a0399f..e4cdb4fa6 100755 --- a/test/run_unit_tests.py +++ b/test/run_unit_tests.py @@ -1,29 +1,22 @@ -import env -import os, sys -import unittest -from Selenium2Library import utils +#!/usr/bin/env python -def run_unit_tests(modules_to_run=[]): - (test_module_names, test_modules) = utils.import_modules_under( - env.UNIT_TEST_DIR, include_root_package_name = False, pattern="test*.py") +from os.path import abspath, dirname, join +from unittest import defaultTestLoader, TextTestRunner +import sys - bad_modules_to_run = [module_to_run for module_to_run in modules_to_run - if module_to_run not in test_module_names] - if bad_modules_to_run: - print("Specified test module%s not exist: %s" % ( - ' does' if len(bad_modules_to_run) == 1 else 's do', - ', '.join(bad_modules_to_run))) - return -1 - tests = [unittest.defaultTestLoader.loadTestsFromModule(test_module) - for test_module in test_modules] +CURDIR = dirname(abspath(__file__)) - runner = unittest.TextTestRunner() - result = runner.run(unittest.TestSuite(tests)) - rc = len(result.failures) + len(result.errors) - if rc > 255: rc = 255 - return rc -if __name__ == '__main__': - sys.exit(run_unit_tests(sys.argv[1:])) +def run_unit_tests(): + sys.path.insert(0, join(CURDIR, '..', 'src')) + try: + suite = defaultTestLoader.discover(join(CURDIR, 'unit'), 'test_*.py') + result = TextTestRunner().run(suite) + finally: + sys.path.pop(0) + return min(len(result.failures) + len(result.errors), 255) + +if __name__ == '__main__': + sys.exit(run_unit_tests()) From 3b7c14f5fae5575f1215207d93611eceab8c4f0e Mon Sep 17 00:00:00 2001 From: Tatu Aalto Date: Fri, 16 Sep 2016 21:42:29 +0300 Subject: [PATCH 28/31] Fix alerts on chrome (#661) * Improves alert hadling for Chrome (#652) In Chrome is often a random WebDriverException which caused by unknow reson. This commit creates a retry functionality that improves alert handling for Crome. Fixes #652 * Refactored the code to simplify the logic * Changed class attribute to better describe what it does * Updates alert handling based on the comment review * Changed class atributes names to describe better what they do * Fixed based on the comments --- src/Selenium2Library/keywords/_alert.py | 78 +++++++++++++------------ 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/src/Selenium2Library/keywords/_alert.py b/src/Selenium2Library/keywords/_alert.py index f7a245f1f..07f6faf30 100644 --- a/src/Selenium2Library/keywords/_alert.py +++ b/src/Selenium2Library/keywords/_alert.py @@ -1,3 +1,4 @@ +import time from selenium.common.exceptions import WebDriverException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC @@ -6,14 +7,16 @@ class _AlertKeywords(KeywordGroup): + __ACCEPT_ALERT = 'accept' + __DISMISS_ALERT = 'dismiss' + def __init__(self): - self._cancel_on_next_confirmation = False + self._next_alert_dismiss_type = self.__ACCEPT_ALERT # Public def input_text_into_prompt(self, text): """Types the given `text` into alert box. """ - alert = None try: alert = self._wait_alert() alert.send_keys(text) @@ -30,7 +33,7 @@ def alert_should_be_present(self, text=''): will fail unless the alert is dismissed by this keyword or another like `Get Alert Message`. """ - alert_text = self.get_alert_message() + alert_text = self._handle_alert(self.__ACCEPT_ALERT) if text and alert_text != text: raise AssertionError("Alert text should have been " "'%s' but was '%s'" @@ -38,7 +41,7 @@ def alert_should_be_present(self, text=''): def choose_cancel_on_next_confirmation(self): """Cancel will be selected the next time `Confirm Action` is used.""" - self._cancel_on_next_confirmation = True + self._next_alert_dismiss_type = self.__DISMISS_ALERT def choose_ok_on_next_confirmation(self): """Undo the effect of using keywords `Choose Cancel On Next Confirmation`. Note @@ -55,7 +58,7 @@ def choose_ok_on_next_confirmation(self): consume it by using a keywords such as `Get Alert Message`, or else the following selenium operations will fail. """ - self._cancel_on_next_confirmation = False + self._next_alert_dismiss_type = self.__ACCEPT_ALERT def confirm_action(self): """Dismisses currently shown confirmation dialog and returns it's message. @@ -74,8 +77,8 @@ def confirm_action(self): | Click Button | Send | # Shows a confirmation dialog | | Confirm Action | | # Chooses Cancel | """ - text = self._close_alert(not self._cancel_on_next_confirmation) - self._cancel_on_next_confirmation = False + text = self._handle_alert(self._next_alert_dismiss_type) + self._next_alert_dismiss_type = self.__DISMISS_ALERT return text def get_alert_message(self, dismiss=True): @@ -87,9 +90,9 @@ def get_alert_message(self, dismiss=True): dismissed by this keyword or another like `Get Alert Message`. """ if dismiss: - return self._close_alert() + return self._handle_alert(self.__DISMISS_ALERT) else: - return self._read_alert() + return self._handle_alert() def dismiss_alert(self, accept=True): """ Returns true if alert was confirmed, false if it was dismissed @@ -98,38 +101,39 @@ def dismiss_alert(self, accept=True): following keywords will fail unless the alert is dismissed by this keyword or another like `Get Alert Message`. """ - return self._handle_alert(accept) + if accept: + return self._handle_alert(self.__ACCEPT_ALERT) + else: + return self._handle_alert() - # Private + def _handle_alert(self, dismiss_type=None): + """Alert re-try for Chrome - def _close_alert(self, confirm=True): - try: - text = self._read_alert() - self._handle_alert(confirm) - return text - except WebDriverException: - raise RuntimeError('There were no alerts') + Because Chrome has difficulties to handle alerts, like:: - def _read_alert(self): - alert = None - try: - alert = self._wait_alert() - # collapse new lines chars - return ' '.join(alert.text.splitlines()) - except WebDriverException: - raise RuntimeError('There were no alerts') + alert.text + alert.dismiss - def _handle_alert(self, confirm=True): - try: - alert = self._wait_alert() - if not confirm: - alert.dismiss() - return False - else: - alert.accept() - return True - except WebDriverException: - raise RuntimeError('There were no alerts') + This function creates a re-try funtionality to better support + alerts in Chrome. + """ + retry = 0 + while retry < 4: + try: + return self._alert_worker(dismiss_type) + except WebDriverException: + time.sleep(0.2) + retry += 1 + raise RuntimeError('There were no alerts') + + def _alert_worker(self, dismiss_type=None): + alert = self._wait_alert() + text = ' '.join(alert.text.splitlines()) + if dismiss_type == self.__DISMISS_ALERT: + alert.dismiss() + elif dismiss_type == self.__ACCEPT_ALERT: + alert.accept() + return text def _wait_alert(self): return WebDriverWait(self._current_browser(), 1).until( From 57b99719428dc4bc0d9d6aab418d1defdd4c5101 Mon Sep 17 00:00:00 2001 From: Kevin Ormbrek Date: Sun, 9 Oct 2016 15:02:18 -0500 Subject: [PATCH 29/31] Libdoc updates - link to project and fix 404 (#668) * Fix 404 due to RF migration to Github * Link to project page * Format corrections per PR comments * fix pronoun usage --- docs/Selenium2Library.html | 4 ++-- src/Selenium2Library/__init__.py | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/Selenium2Library.html b/docs/Selenium2Library.html index 2dfa31909..ab468cb8c 100644 --- a/docs/Selenium2Library.html +++ b/docs/Selenium2Library.html @@ -5,7 +5,7 @@ - +