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