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__(self, name, username_not_auto=False): | |
38 """Creates a new WebsiteTest. | |
39 | |
40 Args: | |
41 name: The website name. | |
42 username_not_auto: Username inputs in some websites (like wikipedia) are | |
43 sometimes filled with some messages and thus, the usernames are not | |
44 automatically autofilled. This flag handles that and disables us from | |
45 checking if the state of the DOM is the same as the username of | |
46 website. | |
47 """ | |
48 # Name of the website | |
49 self.name = name | |
50 # Username of the website. | |
51 self.username = None | |
52 # Password of the website. | |
53 self.password = None | |
54 # Username is not automatically filled. | |
55 self.username_not_auto = username_not_auto | |
56 # Autofilling mode. | |
57 self.mode = self.Mode.NOT_AUTOFILLED | |
58 # The |remaining_time_to_wait| limits the total time in seconds spent in | |
59 # potentially infinite loops. | |
60 self.remaining_time_to_wait = 200 | |
61 # The testing Environment. | |
62 self.environment = None | |
63 # The webdriver. | |
64 self.driver = None | |
65 | |
66 # Mouse/Keyboard actions. | |
67 | |
68 def Click(self, selector): | |
69 """Clicks on an element. | |
70 | |
71 Args: | |
72 selector: The element CSS selector. | |
73 """ | |
74 logging.info("action: Click %s" % selector) | |
75 element = self.driver.find_element_by_css_selector(selector) | |
76 element.click() | |
77 | |
78 def ClickIfClickable(self, selector): | |
79 """Clicks on an element if it's clickable: If it doesn't exist in the DOM, | |
vabr (Chromium)
2014/05/22 13:39:24
This is still inconsistent with IsDisplayed:
rchtara
2014/05/22 15:20:56
This is what thought of before, but when I tried t
vabr (Chromium)
2014/05/22 15:46:24
OK, I can see how the .click() can throw more exce
rchtara
2014/05/22 16:22:25
You re welecome
| |
80 it's covered by another element or it's out viewing area, nothing is | |
81 done and False is returned. Otherwise, even if the element is 100% | |
82 transparent, the element is going to receive a click and a True is | |
83 returned. | |
84 | |
85 Args: | |
86 selector: The element CSS selector. | |
87 | |
88 Returns: | |
89 True if the click happens. | |
90 False otherwise. | |
91 """ | |
92 logging.info("action: ClickIfVisible %s" % selector) | |
93 try: | |
94 element = self.driver.find_element_by_css_selector(selector) | |
95 element.click() | |
96 return True | |
97 except Exception: | |
98 return False | |
99 | |
100 def GoTo(self, url): | |
101 """Navigates the main frame to the |url|. | |
102 | |
103 Args: | |
104 url: The URL. | |
105 """ | |
106 logging.info("action: GoTo %s" % self.name) | |
107 if self.environment.first_go_to: | |
108 self.environment.OpenTabAndGoToInternals(url) | |
109 self.environment.first_go_to = False | |
110 else: | |
111 self.driver.get(url) | |
112 | |
113 def HoverOver(self, selector): | |
114 """Hovers over an element. | |
115 | |
116 Args: | |
117 selector: The element CSS selector. | |
118 """ | |
119 logging.info("action: Hover %s" % selector) | |
120 element = self.driver.find_element_by_css_selector(selector) | |
121 hover = ActionChains(self.driver).move_to_element(element) | |
122 hover.perform() | |
123 | |
124 def SendEnterTo(self, selector): | |
125 """Sends an enter key to an element. | |
126 | |
127 Args: | |
128 selector: The element CSS selector. | |
129 """ | |
130 logging.info("action: SendEnterTo %s" % selector) | |
131 body = self.driver.find_element_by_tag_name("body") | |
132 body.send_keys(Keys.ENTER) | |
133 | |
134 # Waiting/Displaying actions. | |
135 | |
136 def IsDisplayed(self, selector): | |
137 """Returns False if an element doesn't exist in the DOM or is 100% | |
138 transparent. Otherwise, returns True even if it's covered by another | |
139 element or it's out viewing area. | |
140 | |
141 Args: | |
142 selector: The element CSS selector. | |
143 """ | |
144 logging.info("action: IsDisplayed %s" % selector) | |
145 try: | |
146 element = self.driver.find_element_by_css_selector(selector) | |
147 return element.is_displayed() | |
148 except Exception: | |
149 return False | |
150 | |
151 def Wait(self, duration): | |
152 """Wait for a duration in seconds. This needs to be used in potentially | |
153 infinite loops, to limit their running time. | |
154 | |
155 Args: | |
156 duration: The time to wait in seconds. | |
157 """ | |
158 logging.info("action: Wait %s" % duration) | |
159 time.sleep(duration) | |
160 self.remaining_time_to_wait -= 1 | |
161 if self.remaining_time_to_wait < 0: | |
162 raise Exception("Tests took more time than expected for the following " | |
163 "website : %s \n" % self.name) | |
164 | |
165 def WaitUntilDisplayed(self, selector, timeout=10): | |
166 """Waits until an element is displayed. | |
167 | |
168 Args: | |
169 selector: The element CSS selector. | |
170 timeout: The maximum waiting time in seconds before failing. | |
171 """ | |
172 if not self.IsDisplayed(selector): | |
173 self.Wait(1) | |
174 timeout = timeout - 1 | |
175 if (timeout <= 0): | |
176 raise Exception("Error: Element %s not shown before timeout is " | |
177 "finished for the following website: %s" | |
178 % (selector, self.name)) | |
179 else: | |
180 self.WaitUntilDisplayed(selector, timeout) | |
181 | |
182 # Form actions. | |
183 | |
184 def FillPasswordInto(self, selector): | |
185 """If the testing mode is the Autofilled mode, compares the website | |
186 password to the DOM state. | |
187 If the testing mode is the NotAutofilled mode, checks that the DOM state | |
188 is empty. | |
189 Then, fills the input with the Website password. | |
190 | |
191 Args: | |
192 selector: The password input CSS selector. | |
193 | |
194 Raises: | |
195 Exception: An exception is raised if the DOM value of the password is | |
196 different than the one we expected. | |
197 """ | |
198 logging.info("action: FillPasswordInto %s" % selector) | |
199 | |
200 password_element = self.driver.find_element_by_css_selector(selector) | |
201 # Chrome protects the password inputs and doesn't fill them until | |
202 # the user interacts with the page. To be sure that such thing has | |
203 # happened we click on the password fields or one of its ancestors. | |
204 element = password_element | |
205 while True: | |
206 try: | |
207 element.click() | |
208 break | |
209 except Exception: | |
210 try: | |
211 element = element.parent | |
212 except AttributeError: | |
213 raise Exception("Error: unable to find a clickable element to " | |
214 "release the password protection for the following website: %s \n" | |
215 % (self.name)) | |
216 | |
217 if self.mode == self.Mode.AUTOFILLED: | |
218 autofilled_password = password_element.get_attribute("value") | |
219 if autofilled_password != self.password: | |
220 raise Exception("Error: autofilled password is different from the one " | |
221 "we just saved for the following website : %s p1: %s " | |
222 "p2:%s \n" % (self.name, | |
223 password_element.get_attribute("value"), | |
224 self.password)) | |
225 | |
226 elif self.mode == self.Mode.NOT_AUTOFILLED: | |
227 autofilled_password = password_element.get_attribute("value") | |
228 if autofilled_password: | |
229 raise Exception("Error: password is autofilled when it shouldn't be " | |
230 "for the following website : %s \n" | |
231 % self.name) | |
232 | |
233 password_element.send_keys(self.password) | |
234 | |
235 def FillUsernameInto(self, selector): | |
236 """If the testing mode is the Autofilled mode, compares the website | |
237 username to the input value. Then, fills the input with the website | |
238 username. | |
239 | |
240 Args: | |
241 selector: The username input CSS selector. | |
242 | |
243 Raises: | |
244 Exception: An exception is raised if the DOM value of the username is | |
245 different that the one we expected. | |
246 """ | |
247 logging.info("action: FillUsernameInto %s" % selector) | |
248 username_element = self.driver.find_element_by_css_selector(selector) | |
249 | |
250 if (self.mode == self.Mode.AUTOFILLED and not self.username_not_auto): | |
251 if not (username_element.get_attribute("value") == self.username): | |
252 raise Exception("Error: autofilled username is different form the one " | |
253 "we just saved for the following website : %s \n" % | |
254 self.name) | |
255 else: | |
256 username_element.clear() | |
257 username_element.send_keys(self.username) | |
258 | |
259 def Submit(self, selector): | |
260 """Finds an element using CSS Selector and calls its submit() handler. | |
261 | |
262 Args: | |
263 selector: The input CSS selector. | |
264 """ | |
265 logging.info("action: Submit %s" % selector) | |
266 element = self.driver.find_element_by_css_selector(selector) | |
267 element.submit() | |
268 | |
269 # Login/Logout Methods | |
270 | |
271 def Login(self): | |
272 """Login Method. Has to be overloaded by the WebsiteTest test.""" | |
273 raise NotImplementedError("Login is not implemented.") | |
274 | |
275 def LoginWhenAutofilled(self): | |
276 """Logs in and checks that the password is autofilled.""" | |
277 self.mode = self.Mode.AUTOFILLED | |
278 self.Login() | |
279 | |
280 def LoginWhenNotAutofilled(self): | |
281 """Logs in and checks that the password is not autofilled.""" | |
282 self.mode = self.Mode.NOT_AUTOFILLED | |
283 self.Login() | |
284 | |
285 def Logout(self): | |
286 """Logout Method. Has to be overloaded by the Website test.""" | |
287 raise NotImplementedError("Logout is not implemented.") | |
288 | |
289 # Tests | |
290 | |
291 def WrongLoginTest(self): | |
292 """Does the wrong login test: Tries to login with a wrong password and | |
293 checks that the password is not saved. | |
294 | |
295 Raises: | |
296 Exception: An exception is raised if the test fails: If there is a | |
297 problem when performing the login (ex: the login button is not | |
298 available ...), if the state of the username and password fields is | |
299 not like we expected or if the password is saved. | |
300 """ | |
301 logging.info("\nWrong Login Test for %s \n" % self.name) | |
302 correct_password = self.password | |
303 self.password = self.password + "1" | |
304 self.LoginWhenNotAutofilled() | |
305 self.password = correct_password | |
306 self.Wait(2) | |
307 self.environment.SwitchToInternals() | |
308 self.environment.CheckPrompt( | |
309 "Message: Decision: SAVE the password", | |
310 False, | |
311 "Error: password manager thinks that a login with wrong password was " | |
312 "successful for the following website : %s \n" % self.name) | |
313 self.environment.SwitchFromInternals() | |
314 | |
315 def SuccessfulLoginTest(self): | |
316 """Does the successful login when the password is not expected to be | |
317 autofilled test: Checks that the password is not autofilled, tries to login | |
318 with a right password and checks if the password is saved. Then logs out. | |
319 | |
320 Raises: | |
321 Exception: An exception is raised if the test fails: If there is a | |
322 problem when performing the login and the logout (ex: the login | |
323 button is not available ...), if the state of the username and | |
324 password fields is not like we expected or if the password is not | |
325 saved. | |
326 """ | |
327 logging.info("\nSuccessful Login Test for %s \n" % self.name) | |
328 self.LoginWhenNotAutofilled() | |
329 self.Wait(2) | |
330 self.environment.SwitchToInternals() | |
331 self.environment.CheckPrompt( | |
332 "Message: Decision: SAVE the password", | |
333 True, | |
334 "Error: password manager hasn't detected a successful login for the " | |
335 "following website : %s \n" | |
336 % self.name) | |
337 self.environment.SwitchFromInternals() | |
338 self.Logout() | |
339 | |
340 def SuccessfulLoginWithAutofilledPasswordTest(self): | |
341 """Does the successful login when the password is expected to be autofilled | |
342 test: Checks that the password is autofilled, tries to login with the | |
343 autofilled password and checks if the password is saved. Then logs out. | |
344 | |
345 Raises: | |
346 Exception: An exception is raised if the test fails: If there is a | |
347 problem when performing the login and the logout (ex: the login | |
348 button is not available ...), if the state of the username and | |
349 password fields is not like we expected or if the password is not | |
350 saved. | |
351 """ | |
352 logging.info("\nSuccessful Login With Autofilled Password" | |
353 " Test %s \n" % self.name) | |
354 self.LoginWhenAutofilled() | |
355 self.Wait(2) | |
356 self.environment.SwitchToInternals() | |
357 self.environment.CheckPrompt( | |
358 "Message: Decision: SAVE the password", | |
359 True, | |
360 "Error: password manager hasn't detected a successful login for the " | |
361 "following website : %s \n" | |
362 % self.name) | |
363 self.environment.SwitchFromInternals() | |
364 self.Logout() | |
365 | |
366 def PromptTest(self): | |
367 """Does the prompt test: Tries to login with a wrong password and | |
368 checks that the prompt is not shown. Then tries to login with a right | |
369 password and checks that the prompt is not shown. | |
370 | |
371 Raises: | |
372 Exception: An exception is raised if the test fails: If there is a | |
373 problem when performing the login (ex: the login button is not | |
374 available ...), if the state of the username and password fields is | |
375 not like we expected or if the prompt is not shown for the right | |
376 password or is shown for a wrong one. | |
377 """ | |
378 logging.info("\nPrompt Test for %s \n" % self.name) | |
379 correct_password = self.password | |
380 self.password = self.password + "1" | |
381 self.LoginWhenNotAutofilled() | |
382 self.password = correct_password | |
383 self.Wait(2) | |
384 self.environment.SwitchToInternals() | |
385 self.environment.CheckPrompt( | |
386 "Message: Decision: ASK the user", | |
387 False, | |
388 "Error: password manager thinks that a login with wrong password was " | |
389 "successful for the following website : %s \n" % self.name) | |
390 self.environment.SwitchFromInternals() | |
391 | |
392 self.LoginWhenNotAutofilled() | |
393 self.Wait(2) | |
394 self.environment.SwitchToInternals() | |
395 self.environment.CheckPrompt( | |
396 "Message: Decision: ASK the user", | |
397 True, | |
398 "Error: password manager thinks that a login with wrong password was " | |
399 "successful for the following website : %s \n" % self.name) | |
400 self.environment.SwitchFromInternals() | |
OLD | NEW |