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 7import logging 8import time 9 10from selenium.webdriver.common.action_chains import ActionChains 11from selenium.webdriver.common.keys import Keys 12 13import environment 14 15 16def _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 30class 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() 407