blob: 8bc619a2d0c2a25e831f7b42f27f21a0cd361a83 [file] [log] [blame]
# 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 sys
import time
sys.path.insert(0, '../../../../third_party/webdriver/pylib/')
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
# Whether or not the test was run.
self.was_run = False
# 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()