# Copyright 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """WebsiteTest testing class.""" import logging import time from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys import environment def _IsOneSubstringOfAnother(s1, s2): """Checks if one of the string arguements is substring of the other. Args: s1: The first string. s2: The second string. Returns: True if one of the string arguements is substring of the other. False otherwise. """ return s1 in s2 or s2 in s1 class WebsiteTest: """Handles a tested WebsiteTest.""" class Mode: """Test mode.""" # Password and username are expected to be autofilled. AUTOFILLED = 1 # Password and username are not expected to be autofilled. NOT_AUTOFILLED = 2 def __init__(self): pass def __init__(self, name, username_not_auto=False): """Creates a new WebsiteTest. Args: name: The website name. username_not_auto: Username inputs in some websites (like wikipedia) are sometimes filled with some messages and thus, the usernames are not automatically autofilled. This flag handles that and disables us from checking if the state of the DOM is the same as the username of website. """ # Name of the website self.name = name # Username of the website. self.username = None # Password of the website. self.password = None # Username is not automatically filled. self.username_not_auto = username_not_auto # Autofilling mode. self.mode = self.Mode.NOT_AUTOFILLED # The |remaining_time_to_wait| limits the total time in seconds spent in # potentially infinite loops. self.remaining_time_to_wait = 200 # The testing Environment. self.environment = None # The webdriver. self.driver = None # Mouse/Keyboard actions. def Click(self, selector): """Clicks on an element. Args: selector: The element CSS selector. """ logging.info("action: Click %s" % selector) element = self.driver.find_element_by_css_selector(selector) element.click() def ClickIfClickable(self, selector): """Clicks on an element if it's clickable: If it doesn't exist in the DOM, it's covered by another element or it's out viewing area, nothing is done and False is returned. Otherwise, even if the element is 100% transparent, the element is going to receive a click and a True is returned. Args: selector: The element CSS selector. Returns: True if the click happens. False otherwise. """ logging.info("action: ClickIfVisible %s" % selector) try: element = self.driver.find_element_by_css_selector(selector) element.click() return True except Exception: return False def GoTo(self, url): """Navigates the main frame to the |url|. Args: url: The URL. """ logging.info("action: GoTo %s" % self.name) if self.environment.first_go_to: self.environment.OpenTabAndGoToInternals(url) self.environment.first_go_to = False else: self.driver.get(url) def HoverOver(self, selector): """Hovers over an element. Args: selector: The element CSS selector. """ logging.info("action: Hover %s" % selector) element = self.driver.find_element_by_css_selector(selector) hover = ActionChains(self.driver).move_to_element(element) hover.perform() def SendEnterTo(self, selector): """Sends an enter key to an element. Args: selector: The element CSS selector. """ logging.info("action: SendEnterTo %s" % selector) body = self.driver.find_element_by_tag_name("body") body.send_keys(Keys.ENTER) # Waiting/Displaying actions. def IsDisplayed(self, selector): """Returns False if an element doesn't exist in the DOM or is 100% transparent. Otherwise, returns True even if it's covered by another element or it's out viewing area. Args: selector: The element CSS selector. """ logging.info("action: IsDisplayed %s" % selector) try: element = self.driver.find_element_by_css_selector(selector) return element.is_displayed() except Exception: return False def Wait(self, duration): """Wait for a duration in seconds. This needs to be used in potentially infinite loops, to limit their running time. Args: duration: The time to wait in seconds. """ logging.info("action: Wait %s" % duration) time.sleep(duration) self.remaining_time_to_wait -= 1 if self.remaining_time_to_wait < 0: raise Exception("Tests took more time than expected for the following " "website : %s \n" % self.name) def WaitUntilDisplayed(self, selector, timeout=10): """Waits until an element is displayed. Args: selector: The element CSS selector. timeout: The maximum waiting time in seconds before failing. """ if not self.IsDisplayed(selector): self.Wait(1) timeout = timeout - 1 if (timeout <= 0): raise Exception("Error: Element %s not shown before timeout is " "finished for the following website: %s" % (selector, self.name)) else: self.WaitUntilDisplayed(selector, timeout) # Form actions. def FillPasswordInto(self, selector): """If the testing mode is the Autofilled mode, compares the website password to the DOM state. If the testing mode is the NotAutofilled mode, checks that the DOM state is empty. Then, fills the input with the Website password. Args: selector: The password input CSS selector. Raises: Exception: An exception is raised if the DOM value of the password is different than the one we expected. """ logging.info("action: FillPasswordInto %s" % selector) password_element = self.driver.find_element_by_css_selector(selector) # Chrome protects the password inputs and doesn't fill them until # the user interacts with the page. To be sure that such thing has # happened we click on the password fields or one of its ancestors. element = password_element while True: try: element.click() break except Exception: try: element = element.parent except AttributeError: raise Exception("Error: unable to find a clickable element to " "release the password protection for the following website: %s \n" % (self.name)) if self.mode == self.Mode.AUTOFILLED: autofilled_password = password_element.get_attribute("value") if autofilled_password != self.password: raise Exception("Error: autofilled password is different from the one " "we just saved for the following website : %s p1: %s " "p2:%s \n" % (self.name, password_element.get_attribute("value"), self.password)) elif self.mode == self.Mode.NOT_AUTOFILLED: autofilled_password = password_element.get_attribute("value") if autofilled_password: raise Exception("Error: password is autofilled when it shouldn't be " "for the following website : %s \n" % self.name) password_element.send_keys(self.password) def FillUsernameInto(self, selector): """If the testing mode is the Autofilled mode, compares the website username to the input value. Then, fills the input with the website username. Args: selector: The username input CSS selector. Raises: Exception: An exception is raised if the DOM value of the username is different that the one we expected. """ logging.info("action: FillUsernameInto %s" % selector) username_element = self.driver.find_element_by_css_selector(selector) if (self.mode == self.Mode.AUTOFILLED and not self.username_not_auto): if not (username_element.get_attribute("value") == self.username): raise Exception("Error: autofilled username is different form the one " "we just saved for the following website : %s \n" % self.name) else: username_element.clear() username_element.send_keys(self.username) def Submit(self, selector): """Finds an element using CSS Selector and calls its submit() handler. Args: selector: The input CSS selector. """ logging.info("action: Submit %s" % selector) element = self.driver.find_element_by_css_selector(selector) element.submit() # Login/Logout Methods def Login(self): """Login Method. Has to be overloaded by the WebsiteTest test.""" raise NotImplementedError("Login is not implemented.") def LoginWhenAutofilled(self): """Logs in and checks that the password is autofilled.""" self.mode = self.Mode.AUTOFILLED self.Login() def LoginWhenNotAutofilled(self): """Logs in and checks that the password is not autofilled.""" self.mode = self.Mode.NOT_AUTOFILLED self.Login() def Logout(self): """Logout Method. Has to be overloaded by the Website test.""" raise NotImplementedError("Logout is not implemented.") # Tests def WrongLoginTest(self): """Does the wrong login test: Tries to login with a wrong password and checks that the password is not saved. Raises: Exception: An exception is raised if the test fails: If there is a problem when performing the login (ex: the login button is not available ...), if the state of the username and password fields is not like we expected or if the password is saved. """ logging.info("\nWrong Login Test for %s \n" % self.name) correct_password = self.password self.password = self.password + "1" self.LoginWhenNotAutofilled() self.password = correct_password self.Wait(2) self.environment.SwitchToInternals() self.environment.CheckForNewMessage( environment.MESSAGE_SAVE, False, "Error: password manager thinks that a login with wrong password was " "successful for the following website : %s \n" % self.name) self.environment.SwitchFromInternals() def SuccessfulLoginTest(self): """Does the successful login when the password is not expected to be autofilled test: Checks that the password is not autofilled, tries to login with a right password and checks if the password is saved. Then logs out. Raises: Exception: An exception is raised if the test fails: If there is a problem when performing the login and the logout (ex: the login button is not available ...), if the state of the username and password fields is not like we expected or if the password is not saved. """ logging.info("\nSuccessful Login Test for %s \n" % self.name) self.LoginWhenNotAutofilled() self.Wait(2) self.environment.SwitchToInternals() self.environment.CheckForNewMessage( environment.MESSAGE_SAVE, True, "Error: password manager hasn't detected a successful login for the " "following website : %s \n" % self.name) self.environment.SwitchFromInternals() self.Logout() def SuccessfulLoginWithAutofilledPasswordTest(self): """Does the successful login when the password is expected to be autofilled test: Checks that the password is autofilled, tries to login with the autofilled password and checks if the password is saved. Then logs out. Raises: Exception: An exception is raised if the test fails: If there is a problem when performing the login and the logout (ex: the login button is not available ...), if the state of the username and password fields is not like we expected or if the password is not saved. """ logging.info("\nSuccessful Login With Autofilled Password" " Test %s \n" % self.name) self.LoginWhenAutofilled() self.Wait(2) self.environment.SwitchToInternals() self.environment.CheckForNewMessage( environment.MESSAGE_SAVE, True, "Error: password manager hasn't detected a successful login for the " "following website : %s \n" % self.name) self.environment.SwitchFromInternals() self.Logout() def PromptTest(self): """Does the prompt test: Tries to login with a wrong password and checks that the prompt is not shown. Then tries to login with a right password and checks that the prompt is not shown. Raises: Exception: An exception is raised if the test fails: If there is a problem when performing the login (ex: the login button is not available ...), if the state of the username and password fields is not like we expected or if the prompt is not shown for the right password or is shown for a wrong one. """ logging.info("\nPrompt Test for %s \n" % self.name) correct_password = self.password self.password = self.password + "1" self.LoginWhenNotAutofilled() self.password = correct_password self.Wait(2) self.environment.SwitchToInternals() self.environment.CheckForNewMessage( environment.MESSAGE_ASK, False, "Error: password manager thinks that a login with wrong password was " "successful for the following website : %s \n" % self.name) self.environment.SwitchFromInternals() self.LoginWhenNotAutofilled() self.Wait(2) self.environment.SwitchToInternals() self.environment.CheckForNewMessage( environment.MESSAGE_ASK, True, "Error: password manager thinks that a login with wrong password was " "successful for the following website : %s \n" % self.name) self.environment.SwitchFromInternals()