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() 413