Home | History | Annotate | Download | only in password_manager
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """WebsiteTest testing class."""
      6 
      7 import logging
      8 import sys
      9 import time
     10 
     11 sys.path.insert(0, '../../../../third_party/webdriver/pylib/')
     12 
     13 from selenium.webdriver.common.action_chains import ActionChains
     14 from selenium.webdriver.common.keys import Keys
     15 
     16 import environment
     17 
     18 
     19 def _IsOneSubstringOfAnother(s1, s2):
     20   """Checks if one of the string arguements is substring of the other.
     21 
     22   Args:
     23       s1: The first string.
     24       s2: The second string.
     25   Returns:
     26 
     27     True if one of the string arguements is substring of the other.
     28     False otherwise.
     29   """
     30   return s1 in s2 or s2 in s1
     31 
     32 
     33 class WebsiteTest:
     34   """Handles a tested WebsiteTest."""
     35 
     36   class Mode:
     37     """Test mode."""
     38     # Password and username are expected to be autofilled.
     39     AUTOFILLED = 1
     40     # Password and username are not expected to be autofilled.
     41     NOT_AUTOFILLED = 2
     42 
     43     def __init__(self):
     44       pass
     45 
     46   def __init__(self, name, username_not_auto=False):
     47     """Creates a new WebsiteTest.
     48 
     49     Args:
     50       name: The website name.
     51       username_not_auto: Username inputs in some websites (like wikipedia) are
     52           sometimes filled with some messages and thus, the usernames are not
     53           automatically autofilled. This flag handles that and disables us from
     54           checking if the state of the DOM is the same as the username of
     55           website.
     56     """
     57     # Name of the website
     58     self.name = name
     59     # Username of the website.
     60     self.username = None
     61     # Password of the website.
     62     self.password = None
     63     # Username is not automatically filled.
     64     self.username_not_auto = username_not_auto
     65     # Autofilling mode.
     66     self.mode = self.Mode.NOT_AUTOFILLED
     67     # The |remaining_time_to_wait| limits the total time in seconds spent in
     68     # potentially infinite loops.
     69     self.remaining_time_to_wait = 200
     70     # The testing Environment.
     71     self.environment = None
     72     # The webdriver.
     73     self.driver = None
     74     # Whether or not the test was run.
     75     self.was_run = False
     76 
     77   # Mouse/Keyboard actions.
     78 
     79   def Click(self, selector):
     80     """Clicks on an element.
     81 
     82     Args:
     83       selector: The element CSS selector.
     84     """
     85     logging.info("action: Click %s" % selector)
     86     self.WaitUntilDisplayed(selector)
     87     element = self.driver.find_element_by_css_selector(selector)
     88     element.click()
     89 
     90   def ClickIfClickable(self, selector):
     91     """Clicks on an element if it's clickable: If it doesn't exist in the DOM,
     92     it's covered by another element or it's out viewing area, nothing is
     93     done and False is returned. Otherwise, even if the element is 100%
     94     transparent, the element is going to receive a click and a True is
     95     returned.
     96 
     97     Args:
     98       selector: The element CSS selector.
     99 
    100     Returns:
    101       True if the click happens.
    102       False otherwise.
    103     """
    104     logging.info("action: ClickIfVisible %s" % selector)
    105     self.WaitUntilDisplayed(selector)
    106     try:
    107       element = self.driver.find_element_by_css_selector(selector)
    108       element.click()
    109       return True
    110     except Exception:
    111       return False
    112 
    113   def GoTo(self, url):
    114     """Navigates the main frame to the |url|.
    115 
    116     Args:
    117       url: The URL.
    118     """
    119     logging.info("action: GoTo %s" % self.name)
    120     if self.environment.first_go_to:
    121       self.environment.OpenTabAndGoToInternals(url)
    122       self.environment.first_go_to = False
    123     else:
    124       self.driver.get(url)
    125 
    126   def HoverOver(self, selector):
    127     """Hovers over an element.
    128 
    129     Args:
    130       selector: The element CSS selector.
    131     """
    132     logging.info("action: Hover %s" % selector)
    133     self.WaitUntilDisplayed(selector)
    134     element = self.driver.find_element_by_css_selector(selector)
    135     hover = ActionChains(self.driver).move_to_element(element)
    136     hover.perform()
    137 
    138   def SendEnterTo(self, selector):
    139     """Sends an enter key to an element.
    140 
    141     Args:
    142       selector: The element CSS selector.
    143     """
    144     logging.info("action: SendEnterTo %s" % selector)
    145     body = self.driver.find_element_by_tag_name("body")
    146     body.send_keys(Keys.ENTER)
    147 
    148   # Waiting/Displaying actions.
    149 
    150   def IsDisplayed(self, selector):
    151     """Returns False if an element doesn't exist in the DOM or is 100%
    152     transparent. Otherwise, returns True even if it's covered by another
    153     element or it's out viewing area.
    154 
    155     Args:
    156       selector: The element CSS selector.
    157     """
    158     logging.info("action: IsDisplayed %s" % selector)
    159     try:
    160       element = self.driver.find_element_by_css_selector(selector)
    161       return element.is_displayed()
    162     except Exception:
    163       return False
    164 
    165   def Wait(self, duration):
    166     """Wait for a duration in seconds. This needs to be used in potentially
    167     infinite loops, to limit their running time.
    168 
    169     Args:
    170       duration: The time to wait in seconds.
    171     """
    172     logging.info("action: Wait %s" % duration)
    173     time.sleep(duration)
    174     self.remaining_time_to_wait -= 1
    175     if self.remaining_time_to_wait < 0:
    176       raise Exception("Tests took more time than expected for the following "
    177                       "website : %s \n" % self.name)
    178 
    179   def WaitUntilDisplayed(self, selector, timeout=10):
    180     """Waits until an element is displayed.
    181 
    182     Args:
    183       selector: The element CSS selector.
    184       timeout: The maximum waiting time in seconds before failing.
    185     """
    186     if not self.IsDisplayed(selector):
    187       self.Wait(1)
    188       timeout = timeout - 1
    189       if (timeout <= 0):
    190         raise Exception("Error: Element %s not shown before timeout is "
    191                         "finished for the following website: %s"
    192                         % (selector, self.name))
    193       else:
    194         self.WaitUntilDisplayed(selector, timeout)
    195 
    196   # Form actions.
    197 
    198   def FillPasswordInto(self, selector):
    199     """If the testing mode is the Autofilled mode, compares the website
    200     password to the DOM state.
    201     If the testing mode is the NotAutofilled mode, checks that the DOM state
    202     is empty.
    203     Then, fills the input with the Website password.
    204 
    205     Args:
    206       selector: The password input CSS selector.
    207 
    208     Raises:
    209       Exception: An exception is raised if the DOM value of the password is
    210           different than the one we expected.
    211     """
    212     logging.info("action: FillPasswordInto %s" % selector)
    213     self.WaitUntilDisplayed(selector)
    214     password_element = self.driver.find_element_by_css_selector(selector)
    215     # Chrome protects the password inputs and doesn't fill them until
    216     # the user interacts with the page. To be sure that such thing has
    217     # happened we perform |Keys.CONTROL| keypress.
    218     action_chains = ActionChains(self.driver)
    219     action_chains.key_down(Keys.CONTROL).key_up(Keys.CONTROL).perform()
    220     if self.mode == self.Mode.AUTOFILLED:
    221       autofilled_password = password_element.get_attribute("value")
    222       if autofilled_password != self.password:
    223         raise Exception("Error: autofilled password is different from the one "
    224                         "we just saved for the following website : %s p1: %s "
    225                         "p2:%s \n" % (self.name,
    226                                       password_element.get_attribute("value"),
    227                                       self.password))
    228 
    229     elif self.mode == self.Mode.NOT_AUTOFILLED:
    230       autofilled_password = password_element.get_attribute("value")
    231       if autofilled_password:
    232         raise Exception("Error: password is autofilled when it shouldn't  be "
    233                         "for the following website : %s \n"
    234                         % self.name)
    235 
    236       password_element.send_keys(self.password)
    237 
    238   def FillUsernameInto(self, selector):
    239     """If the testing mode is the Autofilled mode, compares the website
    240     username to the input value. Then, fills the input with the website
    241     username.
    242 
    243     Args:
    244       selector: The username input CSS selector.
    245 
    246     Raises:
    247       Exception: An exception is raised if the DOM value of the username is
    248           different that the one we expected.
    249     """
    250     logging.info("action: FillUsernameInto %s" % selector)
    251     self.WaitUntilDisplayed(selector)
    252     username_element = self.driver.find_element_by_css_selector(selector)
    253 
    254     if (self.mode == self.Mode.AUTOFILLED and not self.username_not_auto):
    255       if not (username_element.get_attribute("value") == self.username):
    256         raise Exception("Error: autofilled username is different form the one "
    257                         "we just saved for the following website : %s \n" %
    258                         self.name)
    259     else:
    260       username_element.clear()
    261       username_element.send_keys(self.username)
    262 
    263   def Submit(self, selector):
    264     """Finds an element using CSS Selector and calls its submit() handler.
    265 
    266     Args:
    267       selector: The input CSS selector.
    268     """
    269     logging.info("action: Submit %s" % selector)
    270     self.WaitUntilDisplayed(selector)
    271     element = self.driver.find_element_by_css_selector(selector)
    272     element.submit()
    273 
    274   # Login/Logout Methods
    275 
    276   def Login(self):
    277     """Login Method. Has to be overloaded by the WebsiteTest test."""
    278     raise NotImplementedError("Login is not implemented.")
    279 
    280   def LoginWhenAutofilled(self):
    281     """Logs in and checks that the password is autofilled."""
    282     self.mode = self.Mode.AUTOFILLED
    283     self.Login()
    284 
    285   def LoginWhenNotAutofilled(self):
    286     """Logs in and checks that the password is not autofilled."""
    287     self.mode = self.Mode.NOT_AUTOFILLED
    288     self.Login()
    289 
    290   def Logout(self):
    291     """Logout Method."""
    292 
    293   # Tests
    294 
    295   def WrongLoginTest(self):
    296     """Does the wrong login test: Tries to login with a wrong password and
    297     checks that the password is not saved.
    298 
    299     Raises:
    300       Exception: An exception is raised if the test fails: If there is a
    301           problem when performing the login (ex: the login button is not
    302           available ...), if the state of the username and password fields is
    303           not like we expected or if the password is saved.
    304     """
    305     logging.info("\nWrong Login Test for %s \n" % self.name)
    306     try:
    307       correct_password = self.password
    308       self.password = self.password + "1"
    309       self.LoginWhenNotAutofilled()
    310       self.password = correct_password
    311       self.Wait(2)
    312       self.environment.SwitchToInternals()
    313       self.environment.CheckForNewMessage(
    314           environment.MESSAGE_SAVE,
    315           False,
    316           "Error: password manager thinks that a login with wrong password was "
    317           "successful for the following website : %s \n" % self.name)
    318     finally:
    319       self.environment.SwitchFromInternals()
    320 
    321   def SuccessfulLoginTest(self):
    322     """Does the successful login when the password is not expected to be
    323     autofilled test: Checks that the password is not autofilled, tries to login
    324     with a right password and checks if the password is saved. Then logs out.
    325 
    326     Raises:
    327       Exception: An exception is raised if the test fails: If there is a
    328           problem when performing the login and the logout (ex: the login
    329           button is not available ...), if the state of the username and
    330           password fields is not like we expected or if the password is not
    331           saved.
    332     """
    333     logging.info("\nSuccessful Login Test for %s \n" % self.name)
    334     try:
    335       self.LoginWhenNotAutofilled()
    336       self.Wait(2)
    337       self.environment.SwitchToInternals()
    338       self.environment.CheckForNewMessage(
    339           environment.MESSAGE_SAVE,
    340           True,
    341           "Error: password manager hasn't detected a successful login for the "
    342           "following website : %s \n"
    343           % self.name)
    344     finally:
    345       self.environment.SwitchFromInternals()
    346       self.Logout()
    347 
    348   def SuccessfulLoginWithAutofilledPasswordTest(self):
    349     """Does the successful login when the password is expected to be autofilled
    350     test: Checks that the password is autofilled, tries to login with the
    351     autofilled password and checks if the password is saved. Then logs out.
    352 
    353     Raises:
    354       Exception: An exception is raised if the test fails: If there is a
    355           problem when performing the login and the logout (ex: the login
    356           button is not available ...), if the state of the username and
    357           password fields is not like we expected or if the password is not
    358           saved.
    359     """
    360     logging.info("\nSuccessful Login With Autofilled Password"
    361                         " Test %s \n" % self.name)
    362     try:
    363       self.LoginWhenAutofilled()
    364       self.Wait(2)
    365       self.environment.SwitchToInternals()
    366       self.environment.CheckForNewMessage(
    367           environment.MESSAGE_SAVE,
    368           True,
    369           "Error: password manager hasn't detected a successful login for the "
    370           "following website : %s \n"
    371           % self.name)
    372     finally:
    373       self.environment.SwitchFromInternals()
    374       self.Logout()
    375 
    376   def PromptTest(self):
    377     """Does the prompt test: Tries to login with a wrong password and
    378     checks that the prompt is not shown. Then tries to login with a right
    379     password and checks that the prompt is not shown.
    380 
    381     Raises:
    382       Exception: An exception is raised if the test fails: If there is a
    383           problem when performing the login (ex: the login button is not
    384           available ...), if the state of the username and password fields is
    385           not like we expected or if the prompt is not shown for the right
    386           password or is shown for a wrong one.
    387     """
    388     logging.info("\nPrompt Test for %s \n" % self.name)
    389     try:
    390       correct_password = self.password
    391       self.password = self.password + "1"
    392       self.LoginWhenNotAutofilled()
    393       self.password = correct_password
    394       self.Wait(2)
    395       self.environment.SwitchToInternals()
    396       self.environment.CheckForNewMessage(
    397           environment.MESSAGE_ASK,
    398           False,
    399           "Error: password manager thinks that a login with wrong password was "
    400           "successful for the following website : %s \n" % self.name)
    401       self.environment.SwitchFromInternals()
    402 
    403       self.LoginWhenNotAutofilled()
    404       self.Wait(2)
    405       self.environment.SwitchToInternals()
    406       self.environment.CheckForNewMessage(
    407           environment.MESSAGE_ASK,
    408           True,
    409           "Error: password manager hasn't detected a successful login for the "
    410           "following website : %s \n" % self.name)
    411     finally:
    412       self.environment.SwitchFromInternals()
    413