1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# found in the LICENSE file.
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)"""WebsiteTest testing class."""
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import logging
8116680a4aac90f2aa7413d9095a592090648e557Ben Murdochimport sys
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import time
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
11116680a4aac90f2aa7413d9095a592090648e557Ben Murdochsys.path.insert(0, '../../../../third_party/webdriver/pylib/')
12116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from selenium.webdriver.common.action_chains import ActionChains
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from selenium.webdriver.common.keys import Keys
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import environment
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _IsOneSubstringOfAnother(s1, s2):
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Checks if one of the string arguements is substring of the other.
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      s1: The first string.
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      s2: The second string.
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    True if one of the string arguements is substring of the other.
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    False otherwise.
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return s1 in s2 or s2 in s1
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)class WebsiteTest:
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Handles a tested WebsiteTest."""
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  class Mode:
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Test mode."""
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Password and username are expected to be autofilled.
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    AUTOFILLED = 1
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Password and username are not expected to be autofilled.
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    NOT_AUTOFILLED = 2
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    def __init__(self):
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      pass
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def __init__(self, name, username_not_auto=False):
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Creates a new WebsiteTest.
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      name: The website name.
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      username_not_auto: Username inputs in some websites (like wikipedia) are
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          sometimes filled with some messages and thus, the usernames are not
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          automatically autofilled. This flag handles that and disables us from
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          checking if the state of the DOM is the same as the username of
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          website.
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Name of the website
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.name = name
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Username of the website.
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.username = None
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Password of the website.
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.password = None
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Username is not automatically filled.
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.username_not_auto = username_not_auto
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Autofilling mode.
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.mode = self.Mode.NOT_AUTOFILLED
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # The |remaining_time_to_wait| limits the total time in seconds spent in
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # potentially infinite loops.
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.remaining_time_to_wait = 200
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # The testing Environment.
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.environment = None
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # The webdriver.
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.driver = None
74116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    # Whether or not the test was run.
75116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self.was_run = False
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Mouse/Keyboard actions.
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def Click(self, selector):
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Clicks on an element.
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The element CSS selector.
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: Click %s" % selector)
861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.WaitUntilDisplayed(selector)
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    element = self.driver.find_element_by_css_selector(selector)
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    element.click()
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def ClickIfClickable(self, selector):
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Clicks on an element if it's clickable: If it doesn't exist in the DOM,
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    it's covered by another element or it's out viewing area, nothing is
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    done and False is returned. Otherwise, even if the element is 100%
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    transparent, the element is going to receive a click and a True is
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    returned.
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The element CSS selector.
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Returns:
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      True if the click happens.
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      False otherwise.
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: ClickIfVisible %s" % selector)
1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.WaitUntilDisplayed(selector)
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    try:
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      element = self.driver.find_element_by_css_selector(selector)
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      element.click()
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return True
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    except Exception:
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return False
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def GoTo(self, url):
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Navigates the main frame to the |url|.
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      url: The URL.
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: GoTo %s" % self.name)
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if self.environment.first_go_to:
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      self.environment.OpenTabAndGoToInternals(url)
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      self.environment.first_go_to = False
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    else:
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      self.driver.get(url)
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def HoverOver(self, selector):
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Hovers over an element.
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The element CSS selector.
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: Hover %s" % selector)
1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.WaitUntilDisplayed(selector)
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    element = self.driver.find_element_by_css_selector(selector)
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    hover = ActionChains(self.driver).move_to_element(element)
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    hover.perform()
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def SendEnterTo(self, selector):
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Sends an enter key to an element.
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The element CSS selector.
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: SendEnterTo %s" % selector)
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    body = self.driver.find_element_by_tag_name("body")
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    body.send_keys(Keys.ENTER)
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Waiting/Displaying actions.
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def IsDisplayed(self, selector):
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Returns False if an element doesn't exist in the DOM or is 100%
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    transparent. Otherwise, returns True even if it's covered by another
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    element or it's out viewing area.
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The element CSS selector.
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: IsDisplayed %s" % selector)
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    try:
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      element = self.driver.find_element_by_css_selector(selector)
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return element.is_displayed()
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    except Exception:
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return False
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def Wait(self, duration):
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Wait for a duration in seconds. This needs to be used in potentially
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    infinite loops, to limit their running time.
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      duration: The time to wait in seconds.
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: Wait %s" % duration)
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    time.sleep(duration)
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.remaining_time_to_wait -= 1
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if self.remaining_time_to_wait < 0:
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      raise Exception("Tests took more time than expected for the following "
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                      "website : %s \n" % self.name)
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def WaitUntilDisplayed(self, selector, timeout=10):
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Waits until an element is displayed.
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The element CSS selector.
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      timeout: The maximum waiting time in seconds before failing.
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if not self.IsDisplayed(selector):
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      self.Wait(1)
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      timeout = timeout - 1
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (timeout <= 0):
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        raise Exception("Error: Element %s not shown before timeout is "
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        "finished for the following website: %s"
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        % (selector, self.name))
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      else:
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        self.WaitUntilDisplayed(selector, timeout)
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Form actions.
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def FillPasswordInto(self, selector):
199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """If the testing mode is the Autofilled mode, compares the website
200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    password to the DOM state.
201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    If the testing mode is the NotAutofilled mode, checks that the DOM state
202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    is empty.
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Then, fills the input with the Website password.
204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
205cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The password input CSS selector.
207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Raises:
209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Exception: An exception is raised if the DOM value of the password is
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          different than the one we expected.
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: FillPasswordInto %s" % selector)
2131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.WaitUntilDisplayed(selector)
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    password_element = self.driver.find_element_by_css_selector(selector)
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Chrome protects the password inputs and doesn't fill them until
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # the user interacts with the page. To be sure that such thing has
21703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    # happened we perform |Keys.CONTROL| keypress.
21803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    action_chains = ActionChains(self.driver)
21903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    action_chains.key_down(Keys.CONTROL).key_up(Keys.CONTROL).perform()
220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if self.mode == self.Mode.AUTOFILLED:
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      autofilled_password = password_element.get_attribute("value")
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if autofilled_password != self.password:
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        raise Exception("Error: autofilled password is different from the one "
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        "we just saved for the following website : %s p1: %s "
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        "p2:%s \n" % (self.name,
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                      password_element.get_attribute("value"),
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                      self.password))
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    elif self.mode == self.Mode.NOT_AUTOFILLED:
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      autofilled_password = password_element.get_attribute("value")
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if autofilled_password:
232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        raise Exception("Error: password is autofilled when it shouldn't  be "
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        "for the following website : %s \n"
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        % self.name)
235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      password_element.send_keys(self.password)
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def FillUsernameInto(self, selector):
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """If the testing mode is the Autofilled mode, compares the website
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    username to the input value. Then, fills the input with the website
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    username.
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The username input CSS selector.
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Raises:
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Exception: An exception is raised if the DOM value of the username is
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          different that the one we expected.
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: FillUsernameInto %s" % selector)
2511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.WaitUntilDisplayed(selector)
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    username_element = self.driver.find_element_by_css_selector(selector)
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
254cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (self.mode == self.Mode.AUTOFILLED and not self.username_not_auto):
255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if not (username_element.get_attribute("value") == self.username):
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        raise Exception("Error: autofilled username is different form the one "
257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        "we just saved for the following website : %s \n" %
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        self.name)
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    else:
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      username_element.clear()
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      username_element.send_keys(self.username)
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def Submit(self, selector):
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Finds an element using CSS Selector and calls its submit() handler.
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      selector: The input CSS selector.
268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("action: Submit %s" % selector)
2701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.WaitUntilDisplayed(selector)
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    element = self.driver.find_element_by_css_selector(selector)
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    element.submit()
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Login/Logout Methods
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def Login(self):
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Login Method. Has to be overloaded by the WebsiteTest test."""
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    raise NotImplementedError("Login is not implemented.")
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def LoginWhenAutofilled(self):
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Logs in and checks that the password is autofilled."""
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.mode = self.Mode.AUTOFILLED
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.Login()
284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def LoginWhenNotAutofilled(self):
286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Logs in and checks that the password is not autofilled."""
287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.mode = self.Mode.NOT_AUTOFILLED
288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.Login()
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def Logout(self):
2916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    """Logout Method."""
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Tests
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def WrongLoginTest(self):
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Does the wrong login test: Tries to login with a wrong password and
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    checks that the password is not saved.
298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Raises:
300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Exception: An exception is raised if the test fails: If there is a
301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          problem when performing the login (ex: the login button is not
302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          available ...), if the state of the username and password fields is
303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          not like we expected or if the password is saved.
304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("\nWrong Login Test for %s \n" % self.name)
3061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    try:
3071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      correct_password = self.password
3081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.password = self.password + "1"
3091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.LoginWhenNotAutofilled()
3101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.password = correct_password
3111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Wait(2)
3121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchToInternals()
3131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.CheckForNewMessage(
3141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          environment.MESSAGE_SAVE,
3151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          False,
3161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "Error: password manager thinks that a login with wrong password was "
3171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "successful for the following website : %s \n" % self.name)
3181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    finally:
3191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchFromInternals()
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def SuccessfulLoginTest(self):
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Does the successful login when the password is not expected to be
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    autofilled test: Checks that the password is not autofilled, tries to login
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    with a right password and checks if the password is saved. Then logs out.
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Raises:
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Exception: An exception is raised if the test fails: If there is a
328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          problem when performing the login and the logout (ex: the login
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          button is not available ...), if the state of the username and
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          password fields is not like we expected or if the password is not
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          saved.
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("\nSuccessful Login Test for %s \n" % self.name)
3341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    try:
3351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.LoginWhenNotAutofilled()
3361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Wait(2)
3371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchToInternals()
3381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.CheckForNewMessage(
3391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          environment.MESSAGE_SAVE,
3401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          True,
3411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "Error: password manager hasn't detected a successful login for the "
3421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "following website : %s \n"
3431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          % self.name)
3441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    finally:
3451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchFromInternals()
3461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Logout()
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def SuccessfulLoginWithAutofilledPasswordTest(self):
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Does the successful login when the password is expected to be autofilled
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    test: Checks that the password is autofilled, tries to login with the
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    autofilled password and checks if the password is saved. Then logs out.
352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Raises:
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Exception: An exception is raised if the test fails: If there is a
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          problem when performing the login and the logout (ex: the login
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          button is not available ...), if the state of the username and
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          password fields is not like we expected or if the password is not
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          saved.
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("\nSuccessful Login With Autofilled Password"
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        " Test %s \n" % self.name)
3621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    try:
3631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.LoginWhenAutofilled()
3641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Wait(2)
3651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchToInternals()
3661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.CheckForNewMessage(
3671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          environment.MESSAGE_SAVE,
3681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          True,
3691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "Error: password manager hasn't detected a successful login for the "
3701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "following website : %s \n"
3711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          % self.name)
3721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    finally:
3731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchFromInternals()
3741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Logout()
375cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
376cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def PromptTest(self):
377cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Does the prompt test: Tries to login with a wrong password and
378cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    checks that the prompt is not shown. Then tries to login with a right
379cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    password and checks that the prompt is not shown.
380cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
381cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Raises:
382cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Exception: An exception is raised if the test fails: If there is a
383cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          problem when performing the login (ex: the login button is not
384cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          available ...), if the state of the username and password fields is
385cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          not like we expected or if the prompt is not shown for the right
386cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          password or is shown for a wrong one.
387cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
388cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    logging.info("\nPrompt Test for %s \n" % self.name)
3891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    try:
3901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      correct_password = self.password
3911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.password = self.password + "1"
3921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.LoginWhenNotAutofilled()
3931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.password = correct_password
3941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Wait(2)
3951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchToInternals()
3961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.CheckForNewMessage(
3971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          environment.MESSAGE_ASK,
3981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          False,
3991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "Error: password manager thinks that a login with wrong password was "
4001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "successful for the following website : %s \n" % self.name)
4011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchFromInternals()
4021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
4031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.LoginWhenNotAutofilled()
4041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.Wait(2)
4051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchToInternals()
4061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.CheckForNewMessage(
4071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          environment.MESSAGE_ASK,
4081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          True,
4091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "Error: password manager hasn't detected a successful login for the "
4101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "following website : %s \n" % self.name)
4111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    finally:
4121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.environment.SwitchFromInternals()
413