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, |
| 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.CheckForNewMessage( |
| 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.CheckForNewMessage( |
| 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.CheckForNewMessage( |
| 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.CheckForNewMessage( |
| 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.CheckForNewMessage( |
| 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 |