Index: components/test/data/password_manager/automated_tests/environment.py |
diff --git a/components/test/data/password_manager/automated_tests/environment.py b/components/test/data/password_manager/automated_tests/environment.py |
index 45cec1cc4a59780c8ae0e429ca9711b62ee329d1..4c4fd14fce01fbdda06acfd918c2ddf508a76a60 100644 |
--- a/components/test/data/password_manager/automated_tests/environment.py |
+++ b/components/test/data/password_manager/automated_tests/environment.py |
@@ -2,7 +2,12 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
-"""The testing Environment class.""" |
+"""The testing Environment class. |
+ |
+It holds the WebsiteTest instances, provides them with credentials, |
+provides clean browser environment, runs the tests, and gathers the |
+results. |
+""" |
import os |
import shutil |
@@ -13,34 +18,18 @@ from selenium import webdriver |
from selenium.webdriver.chrome.options import Options |
-# Message strings to look for in chrome://password-manager-internals |
+# Message strings to look for in chrome://password-manager-internals. |
MESSAGE_ASK = "Message: Decision: ASK the user" |
MESSAGE_SAVE = "Message: Decision: SAVE the password" |
- |
-class TestResult: |
- """Stores the information related to a test result. """ |
- def __init__(self, name, test_type, successful, message): |
- """Creates a new TestResult. |
- |
- Args: |
- name: The tested website name. |
- test_type: The test type. |
- successful: Whether or not the test was successful. |
- message: The error message of the test. |
- """ |
- self.name = name |
- self.test_type = test_type |
- self.successful = successful |
- self.message = message |
- |
+INTERNALS_PAGE_URL = "chrome://password-manager-internals/" |
class Environment: |
"""Sets up the testing Environment. """ |
def __init__(self, chrome_path, chromedriver_path, profile_path, |
passwords_path, enable_automatic_password_saving): |
- """Creates a new testing Environment. |
+ """Creates a new testing Environment, starts Chromedriver. |
Args: |
chrome_path: The chrome binary file. |
@@ -58,39 +47,35 @@ class Environment: |
# Cleaning the chrome testing profile folder. |
if os.path.exists(profile_path): |
shutil.rmtree(profile_path) |
+ |
options = Options() |
- self.enable_automatic_password_saving = enable_automatic_password_saving |
if enable_automatic_password_saving: |
options.add_argument("enable-automatic-password-saving") |
- # Chrome path. |
+ # TODO(vabr): show_prompt is used in WebsiteTest for asserting that |
+ # Chrome set-up corresponds to the test type. Remove that knowledge |
+ # about Environment from the WebsiteTest. |
+ self.show_prompt = not enable_automatic_password_saving |
options.binary_location = chrome_path |
- # Chrome testing profile path. |
options.add_argument("user-data-dir=%s" % profile_path) |
# The webdriver. It's possible to choose the port the service is going to |
# run on. If it's left to 0, a free port will be found. |
self.driver = webdriver.Chrome(chromedriver_path, 0, options) |
- # The password internals window. |
+ |
+ # Password internals page tab/window handle. |
self.internals_window = self.driver.current_window_handle |
- if passwords_path: |
- # An xml tree filled with logins and passwords. |
- self.passwords_tree = ElementTree.parse(passwords_path).getroot() |
- else: |
- raise Exception("Error: |passwords_path| needs to be provided if" |
- "|chrome_path| is provided, otherwise the tests could not be run") |
- # Password internals page. |
- self.internals_page = "chrome://password-manager-internals/" |
- # The Website window. |
- self.website_window = None |
- # The WebsiteTests list. |
+ |
+ # An xml tree filled with logins and passwords. |
+ self.passwords_tree = ElementTree.parse(passwords_path).getroot() |
msramek
2015/03/27 11:37:06
This can throw IOError or ParseError. Why don't we
vabr (Chromium)
2015/03/27 12:14:43
This is intentional. The tests cannot be run, and
|
+ |
+ self.website_window = self._OpenNewTab() |
+ |
self.websitetests = [] |
+ |
# Map messages to the number of their appearance in the log. |
self.message_count = { MESSAGE_ASK: 0, MESSAGE_SAVE: 0 } |
- # The tests needs two tabs to work. A new tab is opened with the first |
- # GoTo. This is why we store here whether or not it's the first time to |
- # execute GoTo. |
- self.first_go_to = True |
- # List of all tests results. |
+ |
+ # A list of (test_name, test_type, test_success, failure_log). |
self.tests_results = [] |
def AddWebsiteTest(self, websitetest): |
@@ -110,51 +95,78 @@ class Environment: |
# TODO(vabr): Make driver a property of WebsiteTest. |
websitetest.driver = self.driver |
if not websitetest.username: |
- username_tag = ( |
- self.passwords_tree.find( |
- ".//*[@name='%s']/username" % websitetest.name)) |
+ username_tag = (self.passwords_tree.find( |
+ ".//*[@name='%s']/username" % websitetest.name)) |
websitetest.username = username_tag.text |
if not websitetest.password: |
- password_tag = ( |
- self.passwords_tree.find( |
- ".//*[@name='%s']/password" % websitetest.name)) |
+ password_tag = (self.passwords_tree.find( |
+ ".//*[@name='%s']/password" % websitetest.name)) |
websitetest.password = password_tag.text |
self.websitetests.append(websitetest) |
- def ClearCache(self, clear_passwords): |
- """Clear the browser cookies. If |clear_passwords| is true, clear all the |
- saved passwords too. |
+ def _ClearBrowserDataInit(self): |
+ """Opens and resets the chrome://settings/clearBrowserData dialog. |
- Args: |
- clear_passwords : Clear all the passwords if the bool value is true. |
+ It unchecks all checkboxes, and sets the time range to the "beginning of |
+ time". |
""" |
+ |
self.driver.get("chrome://settings/clearBrowserData") |
self.driver.switch_to_frame("settings") |
- script = ( |
- "if (!document.querySelector('#delete-cookies-checkbox').checked)" |
- " document.querySelector('#delete-cookies-checkbox').click();" |
- ) |
- negation = "" |
- if clear_passwords: |
- negation = "!" |
- script += ( |
- "if (%sdocument.querySelector('#delete-passwords-checkbox').checked)" |
- " document.querySelector('#delete-passwords-checkbox').click();" |
- % negation) |
- script += "document.querySelector('#clear-browser-data-commit').click();" |
- self.driver.execute_script(script) |
+ |
+ time_range_selector = "#clear-browser-data-time-period" |
+ # TODO(vabr): Wait until time_range_selector is displayed instead. |
time.sleep(2) |
- # Every time we do something to the cache let's enable password saving. |
+ set_time_range = ( |
+ "var range = document.querySelector('{0}');".format( |
+ time_range_selector) + |
+ "range.value = 4" # 4 == the beginning of time |
+ ) |
+ self.driver.execute_script(set_time_range) |
+ |
+ all_cboxes_selector = ( |
+ "#clear-data-checkboxes > * > * >[type=\"checkbox\"]") |
msramek
2015/03/27 11:37:07
Nit: Why not just
#clear-data-checkboxes [type="c
vabr (Chromium)
2015/03/27 12:14:43
Agreed and done.
(Learning CSS selectors on the fl
|
+ uncheck_all = ( |
+ "var checkboxes = document.querySelectorAll('{0}');".format( |
+ all_cboxes_selector ) + |
+ "for (var i = 0; i < checkboxes.length; ++i) {" |
+ " if (checkboxes[i].checked)" |
msramek
2015/03/27 11:37:06
Nit:
checkboxes[i].checked = false
vabr (Chromium)
2015/03/27 12:14:43
Done.
|
+ " checkboxes[i].click();" |
+ "}" |
+ ) |
+ self.driver.execute_script(uncheck_all) |
+ |
+ def _ClearDataForCheckbox(self, selector): |
+ """Causes the data associated with |selector| to be cleared. |
+ |
+ Opens chrome://settings/clearBrowserData, unchecks all checkboxes, then |
+ checks the one described by |selector|, then clears the corresponding |
+ browsing data for the full time range. |
+ |
+ Args: |
+ selector: describes the checkbox through which to delete the data. |
+ """ |
+ |
+ self._ClearBrowserDataInit() |
+ check_cookies_and_submit = ( |
+ "var cbox = document.querySelector('{0}');".format(selector) + |
+ "if (!cbox.checked)" |
msramek
2015/03/27 11:37:07
Nit:
document.querySelector().checked = true
vabr (Chromium)
2015/03/27 12:14:43
Done.
|
+ " cbox.click();" |
+ "document.querySelector('#clear-browser-data-commit').click();" |
+ ) |
+ self.driver.execute_script(check_cookies_and_submit) |
+ |
+ def _EnablePasswordSaving(self): |
+ """Make sure that password manager is enabled.""" |
+ |
# TODO(melandory): We should check why it's off in a first place. |
# TODO(melandory): Investigate, maybe there is no need to enable it that |
# often. |
- self.EnablePasswordsSaving() |
- |
- def EnablePasswordsSaving(self): |
self.driver.get("chrome://settings") |
self.driver.switch_to_frame("settings") |
script = "document.getElementById('advanced-settings-expander').click();" |
self.driver.execute_script(script) |
+ # TODO(vabr): Wait until element is displayed instead. |
time.sleep(2) |
script = ( |
"if (!document.querySelector('#password-manager-enabled').checked)" |
msramek
2015/03/27 11:37:06
Nit: As above.
vabr (Chromium)
2015/03/27 12:14:43
Done.
|
@@ -162,196 +174,122 @@ class Environment: |
self.driver.execute_script(script) |
time.sleep(2) |
- def OpenTabAndGoToInternals(self, url): |
- """If there is no |self.website_window|, opens a new tab and navigates to |
- |url| in the new tab. Navigates to the passwords internals page in the |
- first tab. Raises an exception otherwise. |
- |
- Args: |
- url: Url to go to in the new tab. |
+ def _OpenNewTab(self): |
+ """Open a new tab, and loads the internals page in the old tab. |
- Raises: |
- Exception: An exception is raised if |self.website_window| already |
- exists. |
+ Returns: |
+ A handle to the new tab. |
""" |
- if self.website_window: |
- raise Exception("Error: The window was already opened.") |
- self.driver.get("chrome://newtab") |
+ number_old_tabs = len(self.driver.window_handles) |
# There is no straightforward way to open a new tab with chromedriver. |
# One work-around is to go to a website, insert a link that is going |
- # to be opened in a new tab, click on it. |
+ # to be opened in a new tab, and click on it. |
+ self.driver.get("about:blank") |
a = self.driver.execute_script( |
"var a = document.createElement('a');" |
"a.target = '_blank';" |
- "a.href = arguments[0];" |
+ "a.href = 'about:blank';" |
"a.innerHTML = '.';" |
"document.body.appendChild(a);" |
- "return a;", |
- url) |
- |
+ "return a;") |
a.click() |
- time.sleep(1) |
+ while number_old_tabs == len(self.driver.window_handles): |
+ time.sleep(1) # Wait until the new tab is opened. |
- self.website_window = self.driver.window_handles[-1] |
- self.driver.get(self.internals_page) |
- self.driver.switch_to_window(self.website_window) |
+ new_tab = self.driver.window_handles[-1] |
+ self.driver.get(INTERNALS_PAGE_URL) |
+ self.driver.switch_to_window(new_tab) |
+ return new_tab |
- def SwitchToInternals(self): |
- """Switches from the Website window to internals tab.""" |
- self.driver.switch_to_window(self.internals_window) |
+ def _DidStringAppearUntilTimeout(self, strings, timeout): |
+ """Checks whether some of |strings| appeared in the current page. |
- def SwitchFromInternals(self): |
- """Switches from internals tab to the Website window.""" |
- self.driver.switch_to_window(self.website_window) |
- |
- def _DidMessageAppearUntilTimeout(self, log_message, timeout): |
- """Checks whether the save password prompt is shown. |
+ Waits for up to |timeout| seconds until at least one of |strings| is |
+ shown in the current page. Updates self.message_count with the current |
+ number of occurrences of the shown string. Assumes that at most |
+ one of |strings| is newly shown. |
Args: |
- log_message: Log message to look for in the password internals. |
- timeout: There is some delay between the login and the password |
- internals update. The method checks periodically during the first |
- |timeout| seconds if the internals page reports the prompt being |
- shown. If the prompt is not reported shown within the first |
- |timeout| seconds, it is considered not shown at all. |
+ strings: A list of strings to look for. |
+ timeout: If any such string does not appear within the first |timeout| |
+ seconds, it is considered a no-show. |
Returns: |
- True if the save password prompt is shown. |
- False otherwise. |
+ True if one of |strings| is observed until |timeout|, False otherwise. |
""" |
- log = self.driver.find_element_by_css_selector("#log-entries") |
- count = log.text.count(log_message) |
- if count > self.message_count[log_message]: |
- self.message_count[log_message] = count |
- return True |
- elif timeout > 0: |
+ log = self.driver.find_element_by_css_selector("#log-entries") |
+ while timeout: |
msramek
2015/03/27 11:37:06
Why don't we just sleep for |timeout| and then cou
vabr (Chromium)
2015/03/27 12:14:43
No, but in the case when they appear, they mostly
|
+ for string in strings: |
+ count = log.text.count(string) |
+ if count > self.message_count[string]: |
+ self.message_count[string] = count |
+ return True |
time.sleep(1) |
- return self._DidMessageAppearUntilTimeout(log_message, timeout - 1) |
- else: |
- return False |
+ timeout -= 1 |
+ return False |
- def CheckForNewMessage(self, log_message, message_should_show_up, |
- error_message, timeout=15): |
- """Detects whether the save password prompt is shown. |
+ def CheckForNewString(self, strings, string_should_show_up, error): |
+ """Checks that |strings| show up on the internals page as it should. |
- Args: |
- log_message: Log message to look for in the password internals. The |
- only valid values are the constants MESSAGE_* defined at the |
- beginning of this file. |
- message_should_show_up: Whether or not the message is expected to be |
- shown. |
- error_message: Error message for the exception. |
- timeout: There is some delay between the login and the password |
- internals update. The method checks periodically during the first |
- |timeout| seconds if the internals page reports the prompt being |
- shown. If the prompt is not reported shown within the first |
- |timeout| seconds, it is considered not shown at all. |
- |
- Raises: |
- Exception: An exception is raised in case the result does not match the |
- expectation |
- """ |
- if (self._DidMessageAppearUntilTimeout(log_message, timeout) != |
- message_should_show_up): |
- raise Exception(error_message) |
- |
- def AllTests(self, prompt_test): |
- """Runs the tests on all the WebsiteTests. |
- |
- TODO(vabr): Currently, "all tests" always means one. |
+ Switches to the internals page and looks for a new instances of |strings| |
+ being shown up there. It checks that |string_should_show_up| is true if |
+ and only if at leas one string from |strings| shows up, and throws an |
+ Exception if that check fails. |
Args: |
- prompt_test: If True, tests caring about showing the save-password |
- prompt are going to be run, otherwise tests which don't care about |
- the prompt are going to be run. |
+ strings: A list of strings to look for in the internals page. |
+ string_should_show_up: Whether or not at least one string from |strings| |
+ is expected to be shown. |
+ error: Error message for the exception. |
Raises: |
- Exception: An exception is raised if the tests fail. |
+ Exception: (See above.) |
""" |
- if prompt_test: |
- self.PromptTestList(self.websitetests) |
- else: |
- self.TestList(self.websitetests) |
- def Test(self, tests, prompt_test): |
- """Runs the tests on websites named in |tests|. |
+ self.driver.switch_to_window(self.internals_window) |
+ try: |
+ if (self._DidStringAppearUntilTimeout(strings, 15) != |
+ string_should_show_up): |
+ raise Exception(error) |
+ finally: |
+ self.driver.switch_to_window(self.website_window) |
- Args: |
- tests: A list of the names of the WebsiteTests that are going to be |
- tested. |
- prompt_test: If True, tests caring about showing the save-password |
- prompt are going to be run, otherwise tests which don't care about |
- the prompt are going to be executed. |
+ def DeleteCookies(self): |
+ """Deletes cookies via the settings page.""" |
- Raises: |
- Exception: An exception is raised if the tests fail. |
- """ |
- websitetests = [] |
- for websitetest in self.websitetests: |
- if websitetest.name in tests: |
- websitetests.append(websitetest) |
+ self._ClearDataForCheckbox("#delete-cookies-checkbox") |
- if prompt_test: |
- self.PromptTestList(websitetests) |
- else: |
- self.TestList(websitetests) |
+ def RunTestsOnSites(self, test_type): |
+ """Runs the specified test on the known websites. |
- def TestList(self, websitetests): |
- """Runs the tests on the websites in |websitetests|. |
+ Also saves the test results in the environment. Note that test types |
+ differ in their requirements on whether the save password prompt |
+ should be displayed. Make sure that such requirements are consistent |
+ with the enable_automatic_password_saving argument passed to |self| |
+ on construction. |
Args: |
- websitetests: A list of WebsiteTests that are going to be tested. |
- |
- Raises: |
- Exception: An exception is raised if the tests fail. |
+ test_type: A test identifier understood by WebsiteTest.run_test(). |
""" |
- self.ClearCache(True) |
- |
- for websitetest in websitetests: |
- successful = True |
- error = "" |
- try: |
- websitetest.was_run = True |
- websitetest.WrongLoginTest() |
- websitetest.SuccessfulLoginTest() |
- self.ClearCache(False) |
- websitetest.SuccessfulLoginWithAutofilledPasswordTest() |
- self.ClearCache(True) |
- websitetest.SuccessfulLoginTest() |
- self.ClearCache(True) |
- except Exception as e: |
- successful = False |
- error = e.message |
- self.tests_results.append(TestResult(websitetest.name, "normal", |
- successful, error)) |
+ self.DeleteCookies() |
+ self._ClearDataForCheckbox("#delete-passwords-checkbox") |
+ self._EnablePasswordSaving() |
- def PromptTestList(self, websitetests): |
- """Runs the prompt tests on the websites in |websitetests|. |
- |
- Args: |
- websitetests: A list of WebsiteTests that are going to be tested. |
- |
- Raises: |
- Exception: An exception is raised if the tests fail. |
- """ |
- self.ClearCache(True) |
- |
- for websitetest in websitetests: |
+ for websitetest in self.websitetests: |
successful = True |
error = "" |
try: |
- websitetest.was_run = True |
- websitetest.PromptTest() |
+ websitetest.RunTest(test_type) |
except Exception as e: |
successful = False |
error = e.message |
- self.tests_results.append(TestResult(websitetest.name, "prompt", |
- successful, error)) |
+ self.tests_results.append( |
+ (websitetest.name, test_type, successful, error)) |
def Quit(self): |
- """Closes the tests.""" |
- # Close the webdriver. |
+ """Shuts down the driver.""" |
+ |
self.driver.quit() |