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