Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / components / test / data / password_manager / automated_tests / websitetest.py
blob7e165a2455c2d6e0a67b7371d4daf2a373444624
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.
5 """WebsiteTest testing class."""
7 import logging
8 import time
10 from selenium.webdriver.common.action_chains import ActionChains
11 from selenium.webdriver.common.keys import Keys
13 import environment
15 SCRIPT_DEBUG = 9 # TODO(vabr) -- make this consistent with run_tests.py.
17 class WebsiteTest:
18 """WebsiteTest testing class.
20 Represents one website, defines some generic operations on that site.
21 To customise for a particular website, this class needs to be inherited
22 and the Login() method overridden.
23 """
25 # Possible values of self.autofill_expectation.
26 AUTOFILLED = 1 # Expect password and username to be autofilled.
27 NOT_AUTOFILLED = 2 # Expect password and username not to be autofilled.
29 # The maximal accumulated time to spend in waiting for website UI
30 # interaction.
31 MAX_WAIT_TIME_IN_SECONDS = 200
33 def __init__(self, name, username_not_auto=False, password_not_auto=False):
34 """Creates a new WebsiteTest.
36 Args:
37 name: The website name, identifying it in the test results.
38 username_not_auto: Expect that the tested website fills username field
39 on load, and Chrome cannot autofill in that case.
40 password_not_auto: Expect that the tested website fills password field
41 on load, and Chrome cannot autofill in that case.
42 """
43 self.name = name
44 self.username = None
45 self.password = None
46 self.username_not_auto = username_not_auto
47 self.password_not_auto = password_not_auto
49 # Specify, whether it is expected that credentials get autofilled.
50 self.autofill_expectation = WebsiteTest.NOT_AUTOFILLED
51 self.remaining_seconds_to_wait = WebsiteTest.MAX_WAIT_TIME_IN_SECONDS
52 # The testing Environment, if added to any.
53 self.environment = None
54 # The webdriver from the environment.
55 self.driver = None
57 # Mouse/Keyboard actions.
59 def Click(self, selector):
60 """Clicks on the element described by |selector|.
62 Args:
63 selector: The clicked element's CSS selector.
64 """
66 logging.log(SCRIPT_DEBUG, "action: Click %s" % selector)
67 element = self.WaitUntilDisplayed(selector)
68 element.click()
70 def ClickIfClickable(self, selector):
71 """Clicks on the element described by |selector| if it is clickable.
73 The driver's find_element_by_css_selector method defines what is clickable
74 -- anything for which it does not throw, is clickable. To be clickable,
75 the element must:
76 * exist in the DOM,
77 * be not covered by another element
78 * be inside the visible area.
79 Note that transparency does not influence clickability.
81 Args:
82 selector: The clicked element's CSS selector.
84 Returns:
85 True if the element is clickable (and was clicked on).
86 False otherwise.
87 """
89 logging.log(SCRIPT_DEBUG, "action: ClickIfVisible %s" % selector)
90 element = self.WaitUntilDisplayed(selector)
91 try:
92 element.click()
93 return True
94 except Exception:
95 return False
97 def GoTo(self, url):
98 """Navigates the main frame to |url|.
100 Args:
101 url: The URL of where to go to.
104 logging.log(SCRIPT_DEBUG, "action: GoTo %s" % self.name)
105 self.driver.get(url)
107 def HoverOver(self, selector):
108 """Hovers over the element described by |selector|.
110 Args:
111 selector: The CSS selector of the element to hover over.
114 logging.log(SCRIPT_DEBUG, "action: Hover %s" % selector)
115 element = self.WaitUntilDisplayed(selector)
116 hover = ActionChains(self.driver).move_to_element(element)
117 hover.perform()
119 # Waiting/Displaying actions.
121 def _ReturnElementIfDisplayed(self, selector):
122 """Returns the element described by |selector|, if displayed.
124 Note: This takes neither overlapping among elements nor position with
125 regards to the visible area into account.
127 Args:
128 selector: The CSS selector of the checked element.
130 Returns:
131 The element if displayed, None otherwise.
134 try:
135 element = self.driver.find_element_by_css_selector(selector)
136 return element if element.is_displayed() else None
137 except Exception:
138 return None
140 def IsDisplayed(self, selector):
141 """Check if the element described by |selector| is displayed.
143 Note: This takes neither overlapping among elements nor position with
144 regards to the visible area into account.
146 Args:
147 selector: The CSS selector of the checked element.
149 Returns:
150 True if the element is in the DOM and less than 100% transparent.
151 False otherwise.
154 logging.log(SCRIPT_DEBUG, "action: IsDisplayed %s" % selector)
155 return self._ReturnElementIfDisplayed(selector) is not None
157 def Wait(self, duration):
158 """Wait for |duration| in seconds.
160 To avoid deadlocks, the accummulated waiting time for the whole object does
161 not exceed MAX_WAIT_TIME_IN_SECONDS.
163 Args:
164 duration: The time to wait in seconds.
166 Raises:
167 Exception: In case the accummulated waiting limit is exceeded.
170 logging.log(SCRIPT_DEBUG, "action: Wait %s" % duration)
171 self.remaining_seconds_to_wait -= duration
172 if self.remaining_seconds_to_wait < 0:
173 raise Exception("Waiting limit exceeded for website: %s" % self.name)
174 time.sleep(duration)
176 # TODO(vabr): Pull this out into some website-utils and use in Environment
177 # also?
178 def WaitUntilDisplayed(self, selector):
179 """Waits until the element described by |selector| is displayed.
181 Args:
182 selector: The CSS selector of the element to wait for.
184 Returns:
185 The displayed element.
188 element = self._ReturnElementIfDisplayed(selector)
189 while not element:
190 self.Wait(1)
191 element = self._ReturnElementIfDisplayed(selector)
192 return element
194 # Form actions.
196 def FillPasswordInto(self, selector):
197 """Ensures that the selected element's value is the saved password.
199 Depending on self.autofill_expectation, this either checks that the
200 element already has the password autofilled, or checks that the value
201 is empty and replaces it with the password. If self.password_not_auto
202 is true, it skips the checks and just overwrites the value with the
203 password.
205 Args:
206 selector: The CSS selector for the filled element.
208 Raises:
209 Exception: An exception is raised if the element's value is different
210 from the expectation.
213 logging.log(SCRIPT_DEBUG, "action: FillPasswordInto %s" % selector)
214 password_element = self.WaitUntilDisplayed(selector)
216 # Chrome protects the password inputs and doesn't fill them until
217 # the user interacts with the page. To be sure that such thing has
218 # happened we perform |Keys.CONTROL| keypress.
219 action_chains = ActionChains(self.driver)
220 action_chains.key_down(Keys.CONTROL).key_up(Keys.CONTROL).perform()
222 self.Wait(2) # TODO(vabr): Detect when autofill finished.
223 if not self.password_not_auto:
224 if self.autofill_expectation == WebsiteTest.AUTOFILLED:
225 if password_element.get_attribute("value") != self.password:
226 raise Exception("Error: autofilled password is different from the"
227 "saved one on website: %s" % self.name)
228 elif self.autofill_expectation == WebsiteTest.NOT_AUTOFILLED:
229 if password_element.get_attribute("value"):
230 raise Exception("Error: password value unexpectedly not empty on"
231 "website: %s" % self.name)
233 password_element.clear()
234 password_element.send_keys(self.password)
236 def FillUsernameInto(self, selector):
237 """Ensures that the selected element's value is the saved username.
239 Depending on self.autofill_expectation, this either checks that the
240 element already has the username autofilled, or checks that the value
241 is empty and replaces it with the password. If self.username_not_auto
242 is true, it skips the checks and just overwrites the value with the
243 username.
245 Args:
246 selector: The CSS selector for the filled element.
248 Raises:
249 Exception: An exception is raised if the element's value is different
250 from the expectation.
253 logging.log(SCRIPT_DEBUG, "action: FillUsernameInto %s" % selector)
254 username_element = self.WaitUntilDisplayed(selector)
256 self.Wait(2) # TODO(vabr): Detect when autofill finished.
257 if not self.username_not_auto:
258 if self.autofill_expectation == WebsiteTest.AUTOFILLED:
259 if username_element.get_attribute("value") != self.username:
260 raise Exception("Error: filled username different from the saved"
261 " one on website: %s" % self.name)
262 return
263 if self.autofill_expectation == WebsiteTest.NOT_AUTOFILLED:
264 if username_element.get_attribute("value"):
265 raise Exception("Error: username value unexpectedly not empty on"
266 "website: %s" % self.name)
268 username_element.clear()
269 username_element.send_keys(self.username)
271 def Submit(self, selector):
272 """Finds an element using CSS |selector| and calls its submit() handler.
274 Args:
275 selector: The CSS selector for the element to call submit() on.
278 logging.log(SCRIPT_DEBUG, "action: Submit %s" % selector)
279 element = self.WaitUntilDisplayed(selector)
280 element.submit()
282 # Login/Logout methods
284 def Login(self):
285 """Login Method. Has to be overridden by the WebsiteTest test."""
287 raise NotImplementedError("Login is not implemented.")
289 def LoginWhenAutofilled(self):
290 """Logs in and checks that the password is autofilled."""
292 self.autofill_expectation = WebsiteTest.AUTOFILLED
293 self.Login()
295 def LoginWhenNotAutofilled(self):
296 """Logs in and checks that the password is not autofilled."""
298 self.autofill_expectation = WebsiteTest.NOT_AUTOFILLED
299 self.Login()
301 def Logout(self):
302 self.environment.DeleteCookies()
304 # Test scenarios
306 def PromptFailTest(self):
307 """Checks that prompt is not shown on a failed login attempt.
309 Tries to login with a wrong password and checks that the password
310 is not offered for saving.
312 Raises:
313 Exception: An exception is raised if the test fails.
316 logging.log(SCRIPT_DEBUG, "PromptFailTest for %s" % self.name)
317 correct_password = self.password
318 # Hardcoded random wrong password. Chosen by fair `pwgen` call.
319 # For details, see: http://xkcd.com/221/.
320 self.password = "ChieF2ae"
321 self.LoginWhenNotAutofilled()
322 self.password = correct_password
323 self.environment.CheckForNewString(
324 [environment.MESSAGE_ASK, environment.MESSAGE_SAVE],
325 False,
326 "Error: did not detect wrong login on website: %s" % self.name)
328 def PromptSuccessTest(self):
329 """Checks that prompt is shown on a successful login attempt.
331 Tries to login with a correct password and checks that the password
332 is offered for saving. Chrome cannot have the auto-save option on
333 when running this test.
335 Raises:
336 Exception: An exception is raised if the test fails.
339 logging.log(SCRIPT_DEBUG, "PromptSuccessTest for %s" % self.name)
340 if not self.environment.show_prompt:
341 raise Exception("Switch off auto-save during PromptSuccessTest.")
342 self.LoginWhenNotAutofilled()
343 self.environment.CheckForNewString(
344 [environment.MESSAGE_ASK],
345 True,
346 "Error: did not detect login success on website: %s" % self.name)
348 def SaveAndAutofillTest(self):
349 """Checks that a correct password is saved and autofilled.
351 Tries to login with a correct password and checks that the password
352 is saved and autofilled on next visit. Chrome must have the auto-save
353 option on when running this test.
355 Raises:
356 Exception: An exception is raised if the test fails.
359 logging.log(SCRIPT_DEBUG, "SaveAndAutofillTest for %s" % self.name)
360 if self.environment.show_prompt:
361 raise Exception("Switch off auto-save during PromptSuccessTest.")
362 self.LoginWhenNotAutofilled()
363 self.environment.CheckForNewString(
364 [environment.MESSAGE_SAVE],
365 True,
366 "Error: did not detect login success on website: %s" % self.name)
367 self.Logout()
368 self.LoginWhenAutofilled()
369 self.environment.CheckForNewString(
370 [environment.MESSAGE_SAVE],
371 True,
372 "Error: failed autofilled login on website: %s" % self.name)