OLD | NEW |
---|---|
(Empty) | |
1 """WebsiteTest testing class.""" | |
2 | |
3 import logging | |
4 import time | |
5 | |
6 from selenium.webdriver.common.action_chains import ActionChains | |
7 from selenium.webdriver.common.keys import Keys | |
8 | |
9 | |
10 def _IsOneSubstringOfAnother(s1, s2): | |
11 """Checks if one of the string arguements is substring of the other. | |
12 | |
13 Args: | |
14 s1: The first string. | |
15 s2: The second string. | |
16 Returns: | |
17 | |
18 True if one of the string arguements is substring of the other. | |
19 False otherwise. | |
20 """ | |
21 return s1 in s2 or s2 in s1 | |
22 | |
23 | |
24 class WebsiteTest: | |
25 """Handles a tested WebsiteTest.""" | |
26 | |
27 class Mode: | |
28 """Test mode.""" | |
29 # Password and username are expected to be autofilled. | |
30 AUTOFILLED = 1 | |
31 # Password and username are not expected to be autofilled. | |
32 NOT_AUTOFILLED = 2 | |
33 | |
34 def __init__(self): | |
35 pass | |
36 | |
37 def __init__( | |
38 self, name, username=None, password=None, | |
39 username_not_auto=False): | |
40 """Creates a new WebsiteTest. | |
41 | |
42 Args: | |
43 name: The website name. | |
44 username: The website username. If it's None, the username is going to be | |
45 replaced with the value in the usernames and passwords file. | |
46 password: The website password. If it's None, the password is going to be | |
47 replaced with the value in the usernames and passwords file. | |
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 = username | |
58 # Password of the website. | |
59 self.password = password | |
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 spent in potentially | |
vabr (Chromium)
2014/05/22 10:07:02
nit: add units (seconds?)
rchtara
2014/05/22 12:42:01
Done.
| |
65 # 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 """Checks whether an element would be visible to the user: If it doesn't | |
vabr (Chromium)
2014/05/22 10:07:02
The "visible to the user" contradicts both "100% t
rchtara
2014/05/22 12:42:01
Done.
| |
144 exist in the DOM or is 100% transparent True is returned. Otherwise, even | |
vabr (Chromium)
2014/05/22 10:07:02
You mentioned True twice. Did you mean "False" in
rchtara
2014/05/22 12:42:01
Done.
| |
145 if it's covered by another element or it's out viewing area, True is | |
146 returned. | |
147 | |
148 Args: | |
149 selector: The element CSS selector. | |
150 | |
151 Returns: | |
152 True if the element is visible. | |
153 False otherwise. | |
154 """ | |
155 logging.info("action: IsDisplayed %s" % selector) | |
156 try: | |
157 element = self.driver.find_element_by_css_selector(selector) | |
158 return element.is_displayed() | |
159 except Exception: | |
160 return False | |
161 | |
162 def Wait(self, duration): | |
163 """Wait for a duration in seconds. This needs to be used in potentially | |
164 infinite loops, to limit their running time. | |
165 | |
166 Args: | |
167 duration: The time to wait in seconds. | |
168 """ | |
169 logging.info("action: Wait %s" % duration) | |
170 time.sleep(duration) | |
171 self.remaining_time_to_wait -= 1 | |
172 if self.remaining_time_to_wait < 0: | |
173 raise Exception("Tests took more time than expected for the following " | |
174 "website : %s \n" % self.name) | |
175 | |
176 def WaitUntilDisplayed(self, selector, timeout=10): | |
177 """Waits until an element is displayed. | |
178 | |
179 Args: | |
180 selector: The element CSS selector. | |
181 timeout: The maximum waiting time in seconds before failing. | |
182 """ | |
183 if not self.IsDisplayed(selector): | |
184 self.Wait(1) | |
185 timeout = timeout - 1 | |
186 if (timeout <= 0): | |
187 raise Exception("Error: Element %s not shown before timeout is " | |
188 "finished for the following website: %s" | |
189 % (selector, self.name)) | |
190 else: | |
191 self.WaitUntilDisplayed(selector, timeout) | |
192 | |
193 # Form actions. | |
194 | |
195 def FillPasswordInto(self, selector): | |
196 """If the testing mode is the Autofilled mode, compares the website | |
197 password to the DOM state. | |
198 If the testing mode is the NotAutofilled mode, checks that the DOM state | |
199 is empty. | |
200 Then, fills the input with the Website password. | |
201 | |
202 Args: | |
203 selector: The password input CSS selector. | |
204 | |
205 Raises: | |
206 Exception: An exception is raised if the DOM value of the password is | |
207 different than the one we expected. | |
208 """ | |
209 logging.info("action: FillPasswordInto %s" % selector) | |
210 | |
211 password_element = self.driver.find_element_by_css_selector(selector) | |
212 # Chrome protects the password inputs and doesn't fill them until | |
213 # the user interacts with the page. To be sure that such thing has | |
214 # happened we click on the password fields or one of its ancestors. | |
215 element = password_element | |
216 while True: | |
217 try: | |
218 element.click() | |
vabr (Chromium)
2014/05/22 10:07:02
Why don't you use ClickIfClickable?
That would be
rchtara
2014/05/22 12:42:01
I don't used because ClickIfClickable because I ne
vabr (Chromium)
2014/05/22 13:39:24
Fair enough, let's keep it this way then.
rchtara
2014/05/22 15:20:56
Done.
| |
219 break | |
220 except Exception: | |
221 try: | |
222 element = element.parent | |
223 except AttributeError: | |
224 raise Exception("Error: unable to find a clickable element to " | |
225 "release the password protection for the following website: %s \n" | |
226 % (self.name)) | |
227 | |
228 if self.mode == self.Mode.AUTOFILLED: | |
229 autofilled_password = password_element.get_attribute("value") | |
230 if autofilled_password != self.password: | |
231 raise Exception("Error: autofilled password is different from the one " | |
232 "we just saved for the following website : %s p1: %s " | |
233 "p2:%s \n" % (self.name, | |
234 password_element.get_attribute("value"), | |
235 self.password)) | |
236 | |
237 elif self.mode == self.Mode.NOT_AUTOFILLED: | |
238 autofilled_password = password_element.get_attribute("value") | |
239 if autofilled_password: | |
240 raise Exception("Error: password is autofilled when it shouldn't be " | |
241 "for the following website : %s \n" | |
242 % self.name) | |
243 | |
244 password_element.send_keys(self.password) | |
245 | |
246 def FillUsernameInto(self, selector): | |
247 """If the testing mode is the Autofilled mode, compares the website | |
248 username to the input value. Then, fills the input with the website | |
249 username. | |
250 | |
251 Args: | |
252 selector: The username input CSS selector. | |
253 | |
254 Raises: | |
255 Exception: An exception is raised if the DOM value of the username is | |
256 different that the one we expected. | |
257 """ | |
258 logging.info("action: FillUsernameInto %s" % selector) | |
259 username_element = self.driver.find_element_by_css_selector(selector) | |
260 | |
261 if (self.mode == self.Mode.AUTOFILLED and not self.username_not_auto): | |
262 if not (username_element.get_attribute("value") == self.username): | |
263 raise Exception("Error: autofilled username is different form the one " | |
264 "we just saved for the following website : %s \n" % | |
265 self.name) | |
266 else: | |
267 username_element.clear() | |
268 username_element.send_keys(self.username) | |
269 | |
270 def Submit(self, selector): | |
271 """Finds an element using CSS Selector and calls its submit() handler. | |
272 | |
273 Args: | |
274 selector: The input CSS selector. | |
275 """ | |
276 logging.info("action: Submit %s" % selector) | |
277 element = self.driver.find_element_by_css_selector(selector) | |
278 element.submit() | |
279 | |
280 # Login/Logout Methods | |
281 | |
282 def Login(self): | |
283 """Login Method. Has to be overloaded by the WebsiteTest test.""" | |
284 raise NotImplementedError("Login is not implemented.") | |
285 | |
286 def LoginWhenAutofilled(self): | |
287 """Logs in and checks that the password is autofilled.""" | |
288 self.mode = self.Mode.AUTOFILLED | |
289 self.Login() | |
290 | |
291 def LoginWhenNotAutofilled(self): | |
292 """Logs in and checks that the password is not autofilled.""" | |
293 self.mode = self.Mode.NOT_AUTOFILLED | |
294 self.Login() | |
295 | |
296 def Logout(self): | |
297 """Logout Method. Has to be overloaded by the Website test.""" | |
298 raise NotImplementedError("Logout is not implemented.") | |
299 | |
300 # Tests | |
301 | |
302 def WrongLoginTest(self): | |
303 """Does the wrong login test: Tries to login with a wrong password and | |
304 checks that the password is not saved. | |
305 | |
306 Raises: | |
307 Exception: An exception is raised if the test fails: If there is a | |
308 problem when performing the login (ex: the login button is not | |
309 available ...), if the state of the username and password fields is | |
310 not like we expected or if the password is saved. | |
311 """ | |
312 logging.info("\nWrong Login Test for %s \n" % self.name) | |
313 correct_password = self.password | |
314 self.password = self.password + "1" | |
315 self.LoginWhenNotAutofilled() | |
316 self.password = correct_password | |
317 self.Wait(2) | |
318 self.environment.SwitchToInternals() | |
319 self.environment.CheckPrompt( | |
320 "Message: Decision: SAVE the password", | |
321 False, | |
322 "Error: password manager thinks that a login with wrong password was " | |
323 "successful for the following website : %s \n" % self.name) | |
324 self.environment.SwitchFromInternals() | |
325 | |
326 def SuccessfulLoginTest(self): | |
327 """Does the successful login when the password is not expected to be | |
328 autofilled test: Checks that the password is not autofilled, tries to login | |
329 with a right password and checks if the password is saved. Then logs out. | |
330 | |
331 Raises: | |
332 Exception: An exception is raised if the test fails: If there is a | |
333 problem when performing the login and the logout (ex: the login | |
334 button is not available ...), if the state of the username and | |
335 password fields is not like we expected or if the password is not | |
336 saved. | |
337 """ | |
338 logging.info("\nSuccessful Login Test for %s \n" % self.name) | |
339 self.LoginWhenNotAutofilled() | |
340 self.Wait(2) | |
341 self.environment.SwitchToInternals() | |
342 self.environment.CheckPrompt( | |
343 "Message: Decision: SAVE the password", | |
344 True, | |
345 "Error: password manager hasn't detected a successful login for the " | |
346 "following website : %s \n" | |
347 % self.name) | |
348 self.environment.SwitchFromInternals() | |
349 self.Logout() | |
350 | |
351 def SuccessfulLoginWithAutofilledPasswordTest(self): | |
352 """Does the successful login when the password is expected to be autofilled | |
353 test: Checks that the password is autofilled, tries to login with a right | |
vabr (Chromium)
2014/05/22 10:07:02
nit: "a right" -> "the autofilled"
rchtara
2014/05/22 12:42:01
Done.
| |
354 password and checks if the password is saved. Then logs out. | |
355 | |
356 Raises: | |
357 Exception: An exception is raised if the test fails: If there is a | |
358 problem when performing the login and the logout (ex: the login | |
359 button is not available ...), if the state of the username and | |
360 password fields is not like we expected or if the password is not | |
361 saved. | |
362 """ | |
363 logging.info("\nSuccessful Login With Autofilled Password" | |
364 " Test %s \n" % self.name) | |
365 self.LoginWhenAutofilled() | |
366 self.Wait(2) | |
367 self.environment.SwitchToInternals() | |
368 self.environment.CheckPrompt( | |
369 "Message: Decision: SAVE the password", | |
370 True, | |
371 "Error: password manager hasn't detected a successful login for the " | |
372 "following website : %s \n" | |
373 % self.name) | |
374 self.environment.SwitchFromInternals() | |
375 self.Logout() | |
376 | |
377 def PromptTest(self): | |
378 """Does the prompt test: Tries to login with a wrong password and | |
379 checks that the prompt is not shown. Then tries to login with a right | |
380 password and checks that the prompt is not shown. | |
381 | |
382 Raises: | |
383 Exception: An exception is raised if the test fails: If there is a | |
384 problem when performing the login (ex: the login button is not | |
385 available ...), if the state of the username and password fields is | |
386 not like we expected or if the prompt is not shown for the right | |
387 password or is shown for a wrong one. | |
388 """ | |
389 logging.info("\nPrompt Test for %s \n" % self.name) | |
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.CheckPrompt( | |
397 "Message: Decision: ASK the user", | |
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.CheckPrompt( | |
407 "Message: Decision: ASK the user", | |
408 True, | |
409 "Error: password manager thinks that a login with wrong password was " | |
410 "successful for the following website : %s \n" % self.name) | |
411 self.environment.SwitchFromInternals() | |
OLD | NEW |