OLD | NEW |
(Empty) | |
| 1 """Website testing class.""" |
| 2 |
| 3 |
| 4 import time |
| 5 |
| 6 from selenium.common.exceptions import ElementNotVisibleException |
| 7 from selenium.common.exceptions import NoSuchElementException |
| 8 from selenium.common.exceptions import StaleElementReferenceException |
| 9 from selenium.webdriver.common.action_chains import ActionChains |
| 10 from selenium.webdriver.common.keys import Keys |
| 11 |
| 12 |
| 13 def _IsOneStringPrefixOfAnother(s1, s2): |
| 14 l = min(len(s1), len(s2)) |
| 15 return s1[:l] == s2[:l] |
| 16 |
| 17 |
| 18 def _IsOneSubstringOfAnother(s1, s2): |
| 19 return s1 in s2 or s2 in s1 |
| 20 |
| 21 |
| 22 class Website: |
| 23 """Handles a tested Website.""" |
| 24 |
| 25 class Mode: |
| 26 """Test mode.""" |
| 27 # Password and username are expected to be autofilled. |
| 28 Autofilled = 1 |
| 29 # Password and username are not expected to be autofilled. |
| 30 NotAutofilled = 2 |
| 31 |
| 32 def __init__(self): |
| 33 pass |
| 34 |
| 35 def __init__( |
| 36 self, name, url, username=None, password=None, |
| 37 username_not_auto=False): |
| 38 """Creates a new Website. |
| 39 |
| 40 Args: |
| 41 name: The Website name. |
| 42 url: The Website URL. |
| 43 username: The Website username. If it's None, the username is going to be |
| 44 replaced by the value in the usernames and passwords file. |
| 45 password: The Website password. If it's None, the password is going to be |
| 46 replaced by the value in the usernames and passwords file. |
| 47 username_not_auto: Username inputs in some Websites (like wikipedia) are |
| 48 sometimes filled with some messages and thus, the usernames are not |
| 49 automatically autofilled. This flag handles that and disables us from |
| 50 checking if the state of the DOM is the same as the username of |
| 51 Website. |
| 52 """ |
| 53 # Name of the Website |
| 54 self.name = name |
| 55 # URL of the Website |
| 56 self.url = url |
| 57 # Username of the Website. |
| 58 self.username = username |
| 59 # Password of the Website. |
| 60 self.password = password |
| 61 # TODO: Remove this as soon as the password internals page is ready. |
| 62 self.internals_folder = ("file:///usr/local/google/home/rchtara/chrome" |
| 63 "/python/passwordinternals/internalmock/") |
| 64 self.internals_file = "" |
| 65 # Username is not automatically filled. |
| 66 self.username_not_auto = username_not_auto |
| 67 # Autofilling mode. |
| 68 self.mode = self.Mode.NotAutofilled |
| 69 # Waiting duration before stopping the test. |
| 70 self.max_duration = 200 |
| 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 print "action: Click %s" % selector |
| 81 element = self.driver.find_element_by_css_selector(selector) |
| 82 element.click() |
| 83 |
| 84 def ClickIfAvailable(self, selector): |
| 85 """Clicks on an element, if it's available. |
| 86 |
| 87 Args: |
| 88 selector: The element CSS selector. |
| 89 """ |
| 90 print "action: ClickIfAvailable %s" % selector |
| 91 try: |
| 92 element = self.driver.find_element_by_css_selector(selector) |
| 93 element.click() |
| 94 except NoSuchElementException: |
| 95 return False |
| 96 except StaleElementReferenceException: |
| 97 return False |
| 98 |
| 99 def Enter(self, selector): |
| 100 """Sends an enter key to an element. |
| 101 |
| 102 Args: |
| 103 selector: The element CSS selector. |
| 104 """ |
| 105 print "action: Enter %s" % selector |
| 106 body = self.driver.find_element_by_tag_name("body") |
| 107 body.send_keys(Keys.ENTER) |
| 108 |
| 109 def GoTo(self, url): |
| 110 """Goes to a URL. |
| 111 |
| 112 Args: |
| 113 url: The URL. |
| 114 """ |
| 115 print "action: GoTo %s" % self.url |
| 116 self.driver.get(url) |
| 117 |
| 118 def Hover(self, selector): |
| 119 """Hovers on an element, if it's available. |
| 120 |
| 121 Args: |
| 122 selector: The element CSS selector. |
| 123 """ |
| 124 print "action: Hover %s" % selector |
| 125 element = self.driver.find_element_by_css_selector(selector) |
| 126 hover = ActionChains(self.driver).move_to_element(element) |
| 127 hover.perform() |
| 128 |
| 129 # Waiting/Displaying actions. |
| 130 |
| 131 def IsDisplayed(self, selector): |
| 132 """Checks if an element is displayed. |
| 133 |
| 134 Args: |
| 135 selector: The element CSS selector. |
| 136 """ |
| 137 print "action: IsDisplayed %s" % selector |
| 138 try: |
| 139 element = self.driver.find_element_by_css_selector(selector) |
| 140 return element.is_displayed() |
| 141 except NoSuchElementException: |
| 142 return False |
| 143 except StaleElementReferenceException: |
| 144 return False |
| 145 |
| 146 def Wait(self, duration): |
| 147 """Wait for a duration. |
| 148 |
| 149 Args: |
| 150 duration: The element. |
| 151 """ |
| 152 print "action: Wait %s" % duration |
| 153 time.sleep(duration) |
| 154 self.max_duration -= 1 |
| 155 if self.max_duration < 0: |
| 156 raise Exception("Tests took more time than expected for the following " |
| 157 "website : %s \n" % self.name) |
| 158 |
| 159 def WaitUntilDisplayed(self, selector, timeout=10): |
| 160 """Waits until an element is displayed. |
| 161 |
| 162 Args: |
| 163 selector: The element CSS selector. |
| 164 """ |
| 165 if not self.IsDisplayed(selector): |
| 166 time.sleep(1) |
| 167 timeout = timeout - 1 |
| 168 if (timeout <= 0): |
| 169 raise Exception("Error: Element %s not shown before timeout is " |
| 170 "finished for the following website: %s" |
| 171 % (selector, self.name)) |
| 172 else: |
| 173 self.WaitUntilDisplayed(selector, timeout) |
| 174 self.max_duration -= 1 |
| 175 if self.max_duration < 0: |
| 176 raise Exception("Tests took more time than expected for the " |
| 177 "following website : %s \n" % self.name) |
| 178 |
| 179 # Form actions. |
| 180 |
| 181 def FillPassword(self, selector): |
| 182 """If the testing mode is the Autofilled mode, compares the Website |
| 183 password to the DOM state. |
| 184 If the testing mode is the NotAutofilled mode, checks that the DOM state |
| 185 is empty. |
| 186 Then, fills the input with the Website password. |
| 187 |
| 188 Args: |
| 189 selector: The password input CSS selector. |
| 190 |
| 191 Raises: |
| 192 Exception: An exception is raised if the DOM value of the password is |
| 193 different that the one we expected. |
| 194 """ |
| 195 print "action: FillPassword %s" % selector |
| 196 password_element = self.driver.find_element_by_css_selector(selector) |
| 197 if self.mode == self.Mode.Autofilled: |
| 198 # Chrome protects the password inputs and doesn't fill them until |
| 199 # the user interacts with the page. To guarantee that, we just |
| 200 # send a key to the password input. Clicking on the password input was |
| 201 # tried too, but because the password is sometimes hidden, this didn't |
| 202 # worked out. |
| 203 password_element.send_keys("a") |
| 204 ps = password_element.get_attribute("value")[:-1] |
| 205 password_element.clear() |
| 206 password_element.send_keys(ps) |
| 207 if password_element.get_attribute("value") != self.password: |
| 208 raise Exception("Error: autofilled password is different from the one " |
| 209 "we just saved for the following website : %s p1: %s " |
| 210 "p2:%s \n" % (self.name, |
| 211 password_element.get_attribute("value"), |
| 212 self.password)) |
| 213 elif self.mode == self.Mode.NotAutofilled: |
| 214 # Chrome protects the password inputs and doesn't fill them until |
| 215 # the user interacts with the page. To guarantee that, we just |
| 216 # send a key to the password input. Clicking on the password input was |
| 217 # tried too, but because the password is sometimes hidden, this didn't |
| 218 # worked out. |
| 219 password_element.send_keys("a") |
| 220 ps = password_element.get_attribute("value")[1:] |
| 221 password_element.clear() |
| 222 password_element.send_keys(ps) |
| 223 if ps: |
| 224 raise Exception("Error: password is autofilled when it shouldn't be " |
| 225 "for the following website : %s \n" |
| 226 % self.name) |
| 227 |
| 228 # Chrome protects the password inputs and doesn't fill them until |
| 229 # the user interacts with the page. To guarantee that, we just |
| 230 # send a key to the password. Clicking on the password input was tried |
| 231 # too, but because the password is sometime hidden, this didn't worked |
| 232 # out. |
| 233 password_element.send_keys("a") |
| 234 password_element.clear() |
| 235 password_element.send_keys(self.password) |
| 236 |
| 237 def FillUsername(self, selector): |
| 238 """If the testing mode is the Autofilled mode, compares the Website username |
| 239 to the input value. |
| 240 Then, fills the input with the Website username. |
| 241 |
| 242 Args: |
| 243 selector: The username input CSS selector. |
| 244 |
| 245 Raises: |
| 246 Exception: An exception is raised if the DOM value of the username is |
| 247 different that the one we expected. |
| 248 """ |
| 249 print "action: FillUsername %s" % selector |
| 250 username_element = self.driver.find_element_by_css_selector(selector) |
| 251 |
| 252 if (self.mode == self.Mode.Autofilled and not self.username_not_auto): |
| 253 if not (username_element.get_attribute("value") == self.username): |
| 254 raise Exception("Error: autofilled username is different form the one " |
| 255 "we just saved for the following website : %s \n" % |
| 256 self.name) |
| 257 |
| 258 else: |
| 259 username_element.clear() |
| 260 username_element.send_keys(self.username) |
| 261 |
| 262 def OptionalFillUsername(self, selector): |
| 263 """Fills the input with the website username, if the input exists. |
| 264 |
| 265 Args: |
| 266 selector: The username input CSS selector. |
| 267 """ |
| 268 print "action: OptionalFillUsername %s" % selector |
| 269 username_element = self.driver.find_element_by_css_selector(selector) |
| 270 try: |
| 271 username_element.clear() |
| 272 username_element.send_keys(self.username) |
| 273 except ElementNotVisibleException: |
| 274 pass |
| 275 |
| 276 def Submit(self, selector): |
| 277 """Submits an input. |
| 278 |
| 279 Args: |
| 280 selector: The input CSS selector. |
| 281 """ |
| 282 print "action: Submit %s" % selector |
| 283 element = self.driver.find_element_by_css_selector(selector) |
| 284 element.submit() |
| 285 |
| 286 # Login/Logout Methods |
| 287 |
| 288 def Login(self): |
| 289 """Login Method. Has to be overloaded by the Website test.""" |
| 290 raise NotImplementedError("Login is not implemented.") |
| 291 |
| 292 def LoginWhenAutofilled(self): |
| 293 """Logs in and checks that the password is autofilled.""" |
| 294 self.mode = self.Mode.Autofilled |
| 295 self.Login() |
| 296 |
| 297 def LoginWhenNotAutofilled(self): |
| 298 """Logs in and checks that the password is not autofilled.""" |
| 299 self.mode = self.Mode.NotAutofilled |
| 300 self.Login() |
| 301 |
| 302 def Logout(self): |
| 303 """Logout Method. Has to be overloaded by the Website test.""" |
| 304 raise NotImplementedError("Logout is not implemented.") |
| 305 |
| 306 # TestsTools |
| 307 |
| 308 def RemoveAllPasswords(self, urls): |
| 309 """Removes all the saved passwords for the current Website. |
| 310 |
| 311 Args: |
| 312 urls: All the available URLs in the saved passwords list. |
| 313 """ |
| 314 if (self.url != ""): |
| 315 i = 0 |
| 316 for current_url in urls: |
| 317 if _IsOneSubstringOfAnother(current_url, self.url): |
| 318 self.driver.execute_script( |
| 319 "document.querySelectorAll('#saved-passwords-list " |
| 320 ".row-delete-button')[%d].click()" % i) |
| 321 time.sleep(1) # Wait until command is executed. |
| 322 else: |
| 323 i = i + 1 |
| 324 |
| 325 # TODO(rchtara) Replace this when password internals is ready. |
| 326 def SwitchToInternals(self): |
| 327 """Switches from the Website window to internals tab.""" |
| 328 self.driver.switch_to_window(self.test_environment.internals_window) |
| 329 self.driver.get(self.internals_folder + self.internals_file) |
| 330 |
| 331 # TODO(rchtara) Replace this when password internals is ready. |
| 332 def SwitchFromInternals(self): |
| 333 """Switches from internals tab to the Website window.""" |
| 334 self.driver.switch_to_window(self.test_environment.websitewindow) |
| 335 |
| 336 # TODO(rchtara) Replace this when password internals is ready. |
| 337 def UrlExists(self, element_id, msg): |
| 338 elments = self.driver.find_element_by_id(element_id) |
| 339 urls = elments.find_elements_by_class_name("url") |
| 340 match = False |
| 341 for u in urls: |
| 342 if (_IsOneSubstringOfAnother(u.text, self.url) or |
| 343 _IsOneSubstringOfAnother(u.text, self.name)): |
| 344 match = True |
| 345 break |
| 346 if not match: |
| 347 raise Exception(msg) |
| 348 |
| 349 # TODO(rchtara) Replace this when password internals is ready. |
| 350 def UrlNotExists(self, element_id, msg): |
| 351 |
| 352 elments = self.driver.find_element_by_id(element_id) |
| 353 urls = elments.find_elements_by_class_name("url") |
| 354 match = False |
| 355 for u in urls: |
| 356 if _IsOneSubstringOfAnother(u.text, self.url): |
| 357 match = True |
| 358 break |
| 359 if match: |
| 360 raise Exception(msg) |
| 361 |
| 362 # Tests |
| 363 |
| 364 def WrongLoginTest(self): |
| 365 """Does the wrong login test: Tries to login with a wrong password and |
| 366 checks that the prompt is not shown. |
| 367 |
| 368 Raises: |
| 369 Exception: An exception is raised if the tests fail. |
| 370 """ |
| 371 print "\nWrong Login Test for %s \n" % self.name |
| 372 correct_password = self.password |
| 373 self.password = self.password + "1" |
| 374 self.LoginWhenNotAutofilled() |
| 375 self.password = correct_password |
| 376 self.internals_file = "wrongpass.html" |
| 377 self.SwitchToInternals() |
| 378 self.UrlExists("unsuccessfullogins", ("Error: password manager thinks that" |
| 379 " a login with wrong password was " |
| 380 "successful for the following " |
| 381 "website : %s \n" % self.name)) |
| 382 self.UrlNotExists( |
| 383 "showedprompts", |
| 384 "Error: prompt is shown for a wrong password for the following website" |
| 385 " : %s \n" % self.name) |
| 386 self.SwitchFromInternals() |
| 387 |
| 388 def SuccessfulLoginTest(self): |
| 389 """Does the successful login when the password is not expected to be |
| 390 autofilled test: Checks that the password is not autofilled, tries to login |
| 391 with a right password and checks if the that the prompt is shown. Then logs |
| 392 out. |
| 393 |
| 394 Raises: |
| 395 Exception: An exception is raised if the tests fail. |
| 396 """ |
| 397 print "\nSuccessful Login Test for %s \n" % self.name |
| 398 self.LoginWhenNotAutofilled() |
| 399 time.sleep(2) |
| 400 self.internals_file = "rightpass.html" |
| 401 self.SwitchToInternals() |
| 402 self.UrlExists( |
| 403 "successfullogins", |
| 404 "Error: password manager hasn't detected a successful login for the " |
| 405 "following website : %s \n" |
| 406 % self.name) |
| 407 self.UrlExists( |
| 408 "showedprompts", |
| 409 "Error: prompt is not shown for a right password for the following " |
| 410 "website : %s \n" % self.name) |
| 411 self.SwitchFromInternals() |
| 412 self.Logout() |
| 413 |
| 414 def SuccessfulLoginWithAutofilledPasswordTest(self): |
| 415 """Does the successful login when the password is expected to be autofilled |
| 416 test: Checks that the password is autofilled, tries to login with a right |
| 417 password and checks if the that the prompt is shown. Then logs out. |
| 418 |
| 419 Raises: |
| 420 Exception: An exception is raised if the tests fail. |
| 421 """ |
| 422 print "\nSuccessful Login With Autofilled Password Test %s \n" % self.name |
| 423 self.LoginWhenAutofilled() |
| 424 time.sleep(2) |
| 425 self.internals_file = "rightpass.html" |
| 426 self.SwitchToInternals() |
| 427 self.UrlExists( |
| 428 "successfullogins", |
| 429 "Error: password manager hasn't detected a successful login for the " |
| 430 "following website : %s \n" |
| 431 % self.name) |
| 432 self.UrlExists( |
| 433 "showedprompts", |
| 434 "Error: prompt is not shown for a right password for the following " |
| 435 "website : %s \n" % self.name) |
| 436 self.SwitchFromInternals() |
| 437 self.Logout() |
| 438 |
| 439 def SuccessfulLoginAfterDeletionTest(self): |
| 440 """Does the successful login after the deletion of the password test: Checks |
| 441 that the password is not autofilled, tries to login with a right password |
| 442 and checks if the that the prompt is shown. Then logs out. |
| 443 |
| 444 Raises: |
| 445 Exception: An exception is raised if the tests fail. |
| 446 """ |
| 447 print "\nSuccessful Login After Deletion Test for %s \n" % self.name |
| 448 self.LoginWhenNotAutofilled() |
| 449 self.internals_file = "rightpass.html" |
| 450 self.SwitchToInternals() |
| 451 self.UrlExists( |
| 452 "successfullogins", "Error: password manager hasn't detected a " |
| 453 "successful login for the following website : %s " |
| 454 "\n" |
| 455 % self.name) |
| 456 self.UrlExists( |
| 457 "showedprompts", |
| 458 "Error: prompt is not shown for a right password for the following " |
| 459 "website : %s \n" % self.name) |
| 460 self.SwitchFromInternals() |
| 461 self.Logout() |
OLD | NEW |