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