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 sys
9import time
10
11sys.path.insert(0, '../../../../third_party/webdriver/pylib/')
12
13from selenium.webdriver.common.action_chains import ActionChains
14from selenium.webdriver.common.keys import Keys
15
16import environment
17
18
19def _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
33class 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