Chromium Code Reviews| 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 |