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."""
10 from selenium
.webdriver
.common
.action_chains
import ActionChains
11 from selenium
.webdriver
.common
.keys
import Keys
15 SCRIPT_DEBUG
= 9 # TODO(vabr) -- make this consistent with run_tests.py.
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.
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
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.
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.
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.
57 # Mouse/Keyboard actions.
59 def Click(self
, selector
):
60 """Clicks on the element described by |selector|.
63 selector: The clicked element's CSS selector.
66 logging
.log(SCRIPT_DEBUG
, "action: Click %s" % selector
)
67 element
= self
.WaitUntilDisplayed(selector
)
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,
77 * be not covered by another element
78 * be inside the visible area.
79 Note that transparency does not influence clickability.
82 selector: The clicked element's CSS selector.
85 True if the element is clickable (and was clicked on).
89 logging
.log(SCRIPT_DEBUG
, "action: ClickIfVisible %s" % selector
)
90 element
= self
.WaitUntilDisplayed(selector
)
98 """Navigates the main frame to |url|.
101 url: The URL of where to go to.
104 logging
.log(SCRIPT_DEBUG
, "action: GoTo %s" % self
.name
)
107 def HoverOver(self
, selector
):
108 """Hovers over the element described by |selector|.
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
)
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.
128 selector: The CSS selector of the checked element.
131 The element if displayed, None otherwise.
135 element
= self
.driver
.find_element_by_css_selector(selector
)
136 return element
if element
.is_displayed() else 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.
147 selector: The CSS selector of the checked element.
150 True if the element is in the DOM and less than 100% transparent.
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.
164 duration: The time to wait in seconds.
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
)
176 # TODO(vabr): Pull this out into some website-utils and use in Environment
178 def WaitUntilDisplayed(self
, selector
):
179 """Waits until the element described by |selector| is displayed.
182 selector: The CSS selector of the element to wait for.
185 The displayed element.
188 element
= self
._ReturnElementIfDisplayed
(selector
)
191 element
= self
._ReturnElementIfDisplayed
(selector
)
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
206 selector: The CSS selector for the filled element.
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
246 selector: The CSS selector for the filled element.
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
)
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.
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
)
282 # Login/Logout methods
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
295 def LoginWhenNotAutofilled(self
):
296 """Logs in and checks that the password is not autofilled."""
298 self
.autofill_expectation
= WebsiteTest
.NOT_AUTOFILLED
302 self
.environment
.DeleteCookies()
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.
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
],
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.
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
],
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.
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
],
366 "Error: did not detect login success on website: %s" % self
.name
)
368 self
.LoginWhenAutofilled()
369 self
.environment
.CheckForNewString(
370 [environment
.MESSAGE_SAVE
],
372 "Error: failed autofilled login on website: %s" % self
.name
)