1# Copyright (c) 2012 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 5import time 6 7from selenium.common.exceptions import NoSuchElementException 8from selenium.common.exceptions import TimeoutException as \ 9 SeleniumTimeoutException 10from selenium.common.exceptions import WebDriverException 11from selenium.webdriver.support.ui import WebDriverWait 12 13class WebDriverCoreHelpers(object): 14 """Base class for manipulating web pages using webdriver.""" 15 16 def __init__(self): 17 super(WebDriverCoreHelpers, self).__init__() 18 self.driver = None 19 self.wait = WebDriverWait(self.driver, timeout=5) 20 21 22 def _check_for_alert_in_message(self, message, alert_handler): 23 """Check for an alert in error message and handle it. 24 25 @param message: The error message. 26 @param alert_handler: The handler method to call. 27 28 """ 29 if (message.find('An open modal dialog blocked') != -1 and 30 message.find('unexpected alert open') != -1): 31 alert = self.driver.switch_to_alert() 32 alert_handler(alert) 33 else: 34 raise RuntimeError(message) 35 36 37 def _handle_alert(self, xpath, alert_handler): 38 """Calls the alert handler if there is an alert. 39 40 @param xpath: The xpath that could raise the alert. 41 @param alert_handler: the handler method to call. 42 43 """ 44 try: 45 self.driver.find_element_by_xpath(xpath) 46 return 47 except WebDriverException, e: 48 message = str(e) 49 # The messages differ based on the webdriver version 50 if (message.find('An open modal dialog blocked') == -1 and 51 message.find('unexpected alert open') == -1): 52 return 53 self._handler(alert_handler) 54 # Sometimes routers put out multiple alert statements on the same page. 55 self._handle_alert(xpath, alert_handler) 56 57 58 def _handler(self, alert_handler): 59 """Handles the alert. 60 61 @param alert_handler: The custom handler method to call. 62 63 """ 64 alert = self.driver.switch_to_alert() 65 if not alert_handler: 66 # The caller did not provide us with a handler, dismiss and raise. 67 try: 68 alert_text = alert.text 69 except WebDriverException: 70 # There is a bug in selenium where the alert object will exist 71 # but you can't get to the text object right away. 72 time.sleep(1) 73 alert_text = alert.text 74 alert.accept() 75 raise RuntimeError('An alert was encountered and no handler was ' 76 'specified. The text from the alert was: %s' 77 % alert_text) 78 alert_handler(alert) 79 80 81 def set_wait_time(self, time): 82 """Sets the wait time of webdriver commands. 83 84 @param time: the time to wait in seconds. 85 86 """ 87 self.wait = WebDriverWait(self.driver, timeout=time) 88 89 90 def restore_default_wait_time(self): 91 """Restores the default webdriver wait time.""" 92 self.wait = WebDriverWait(self.driver, timeout=5) 93 94 95 def wait_for_objects_by_id(self, element_ids, wait_time=5): 96 """Wait for one of the element_ids to show up. 97 98 @param element_ids: A list of all the element ids to find. 99 @param wait_time: The time to wait before giving up. 100 101 @return The id that was found first. 102 103 """ 104 xpaths = [] 105 for element_id in element_ids: 106 xpaths.append('id("%s")' % element_id) 107 xpath_found = self.wait_for_objects_by_xpath(xpaths, wait_time) 108 for element_id in element_ids: 109 if element_id in xpath_found: 110 return element_id 111 112 113 def wait_for_objects_by_xpath(self, xpaths, wait_time=5): 114 """Wait for one of the items in the xpath to show up. 115 116 @param xpaths: A list of all the xpath's of elements to find. 117 @param wait_time: The time to wait before giving up. 118 119 @return The xpath that was found first. 120 121 """ 122 excpetion = None 123 if wait_time < len(xpaths): 124 wait_time = len(xpaths) 125 start_time = int(time.time()) 126 while (int(time.time()) - start_time) < wait_time: 127 for xpath in xpaths: 128 try: 129 element = self.wait_for_object_by_xpath(xpath, 130 wait_time=0.25) 131 if element and element.is_displayed(): 132 return xpath 133 except SeleniumTimeoutException, e: 134 exception = str(e) 135 pass 136 raise SeleniumTimeoutException(exception) 137 138 139 def click_button_by_id(self, element_id, alert_handler=None): 140 """Clicks a button by id. 141 142 @param element_id: the id of the button 143 @param alert_handler: method invoked if an alert is detected. The method 144 must take one parameter, a webdriver alert object 145 146 """ 147 xpath = 'id("%s")' % element_id 148 return self.click_button_by_xpath(xpath, alert_handler) 149 150 151 def click_button_by_xpath(self, xpath, alert_handler=None): 152 """Clicks a button by xpath. 153 154 @param xpath: the xpath of the button 155 @param alert_handler: method invoked if an alert is detected. The method 156 must take one parameter, a webdriver alert object 157 158 """ 159 button = self.wait_for_object_by_xpath(xpath) 160 button.click() 161 self._handle_alert(xpath, alert_handler) 162 163 164 def get_url(self, page_url, page_title=None, element_xpath=None): 165 """Load page and check if the page loads completely, if not, reload. 166 167 @param page_url: The url to load. 168 @param page_title: The complete/partial title of the page after loaded. 169 @param element_xpath: The element that we search for to confirm that 170 the page loaded. 171 172 """ 173 self.driver.get(page_url) 174 if page_title: 175 try: 176 self.wait.until(lambda _: page_title in self.driver.title) 177 except SeleniumTimeoutException, e: 178 self.driver.get(page_url) 179 self.wait.until(lambda _: self.driver.title) 180 finally: 181 if not page_title in self.driver.title: 182 raise WebDriverException('Page did not load. Expected %s in' 183 'title, but got %s as title.' % 184 (page_title, self.driver.title)) 185 if element_xpath: 186 self.wait_for_object_by_xpath(element_xpath) 187 188 189 def wait_for_object_by_id(self, element_id, wait_time=5): 190 """Waits for an element to become available; returns a reference to it. 191 192 @param element_id: the id of the element to wait for 193 @param wait_time: the time to wait for the object 194 195 @returns a reference to the element if found before a timeout. 196 197 """ 198 xpath = 'id("%s")' % element_id 199 return self.wait_for_object_by_xpath(xpath, wait_time=wait_time) 200 201 202 def wait_for_object_by_xpath_to_vanish(self, xpath, wait_time=5): 203 """Wait for the item in xpath to disappear from page. 204 205 @param xpath: The xpath of the object to wait on. 206 @param wait_time: The time to wait before giving up. 207 208 @return void or raise exception if object does not vanish. 209 210 """ 211 start_time = int(time.time()) 212 while (int(time.time()) - start_time) < wait_time: 213 if self.object_by_xpath_exist(xpath): 214 time.sleep(0.5) 215 else: 216 return 217 raise SeleniumTimeoutException('The object with xpath %s failed to' 218 ' vanish.' % xpath) 219 220 221 def wait_for_object_by_id_to_vanish(self, element_id, wait_time=5): 222 """Wait for the item in xpath to disappear from page. 223 224 @param element_id: The id of the object to wait on. 225 @param wait_time: The time to wait before giving up. 226 227 @return void or raise exception if object does not vanish. 228 229 """ 230 xpath = 'id("%s")' % element_id 231 return self.wait_for_object_by_xpath_to_vanish(xpath, 232 wait_time=wait_time) 233 234 235 def object_by_id_exist(self, element_id): 236 """Finds if an object exist in this particular page. 237 238 @param element_id: the id of the element to find 239 240 @returns True if the element exists. False if the element does not. 241 242 """ 243 xpath = 'id("%s")' % element_id 244 return self.object_by_xpath_exist(xpath) 245 246 247 def object_by_xpath_exist(self, xpath): 248 """Finds if an object exist in this particular page. 249 250 @param xpath: the xpath of the element to find 251 252 @returns True if the xpath exists. False if the xpath does not. 253 254 """ 255 try: 256 self.wait_for_object_by_xpath(xpath) 257 except SeleniumTimeoutException: 258 return False 259 return True 260 261 262 def wait_for_object_by_xpath(self, xpath, wait_time=5): 263 """Waits for an element to become available; returns a reference to it. 264 265 @param xpath: the xpath of the element to wait for 266 @param wait_time: the time to wait for the object. 267 268 @returns reference to the element if found before a timeout. 269 270 """ 271 self.set_wait_time(wait_time) 272 try: 273 self.wait.until(lambda _: self.driver.find_element_by_xpath(xpath)) 274 element = self.driver.find_element_by_xpath(xpath) 275 except (SeleniumTimeoutException, NoSuchElementException) as e: 276 self.restore_default_wait_time() 277 raise SeleniumTimeoutException('Unable to find the object by ' 278 'xpath: %s\n WebDriver exception: ' 279 '%s' % (xpath, str(e))) 280 self.restore_default_wait_time() 281 return element 282 283 284 def item_in_popup_by_id_exist(self, item, element_id): 285 """Returns if an item exists in a popup given a id 286 287 @param item: name of the item 288 @param element_id: the id of the popup 289 290 @returns True if the item exists; False otherwise. 291 292 """ 293 xpath = 'id("%s")' % element_id 294 return self.item_in_popup_by_xpath_exist(item, xpath) 295 296 297 def item_in_popup_by_xpath_exist(self, item, xpath): 298 """Returns if an item exists in a popup given an xpath 299 300 @param item: name of the item 301 @param xpath: the xpath of the popup 302 303 @returns True if the item exists; False otherwise. 304 305 """ 306 if self.number_of_items_in_popup_by_xpath(xpath) == 0: 307 raise SeleniumTimeoutException('The popup at xpath %s has no items.' 308 % xpath) 309 popup = self.driver.find_element_by_xpath(xpath) 310 for option in popup.find_elements_by_tag_name('option'): 311 if option.text == item: 312 return True 313 return False 314 315 316 def number_of_items_in_popup_by_id(self, element_id, alert_handler=None): 317 """Returns the number of items in a popup given the element ID. 318 319 @param element_id: the html ID of the item 320 @param alert_handler: method invoked if an alert is detected. The method 321 must take one parameter, a webdriver alert object 322 323 @returns the number of items in the popup. 324 325 """ 326 xpath = 'id("%s")' % element_id 327 return self.number_of_items_in_popup_by_xpath(xpath, alert_handler) 328 329 330 def number_of_items_in_popup_by_xpath(self, xpath, alert_handler=None): 331 """Returns the number of items in a popup given a xpath 332 333 @param xpath: the xpath of the popup 334 @param alert_handler: method invoked if an alert is detected. The method 335 must take one parameter, a webdriver alert object 336 337 @returns the number of items in the popup. 338 339 """ 340 popup = self.driver.find_element_by_xpath(xpath) 341 try: 342 self.wait.until(lambda _: 343 len(popup.find_elements_by_tag_name('option'))) 344 except SeleniumTimeoutException, e: 345 return 0 346 return len(popup.find_elements_by_tag_name('option')) 347 348 349 def select_item_from_popup_by_id(self, item, element_id, 350 wait_for_xpath=None, alert_handler=None): 351 """Selects an item from a popup, by passing the element ID. 352 353 @param item: the string of the item to select from the popup 354 @param element_id: the html ID of the item 355 @param wait_for_xpath: an item to wait for before returning, if not 356 specified the method does not wait. 357 @param alert_handler: method invoked if an alert is detected. The method 358 must take one parameter, a webdriver alert object 359 360 """ 361 xpath = 'id("%s")' % element_id 362 self.select_item_from_popup_by_xpath(item, xpath, wait_for_xpath, 363 alert_handler) 364 365 366 def select_item_from_popup_by_xpath(self, item, xpath, wait_for_xpath=None, 367 alert_handler=None): 368 """Selects an item from a popup, by passing the xpath of the popup. 369 370 @param item: the string of the item to select from the popup 371 @param xpath: the xpath of the popup 372 @param wait_for_xpath: an item to wait for before returning, if not 373 specified the method does not wait. 374 @param alert_handler: method invoked if an alert is detected. The method 375 must take one parameter, a webdriver alert object 376 377 """ 378 if self.number_of_items_in_popup_by_xpath(xpath) == 0: 379 raise SeleniumTimeoutException('The popup at xpath %s has no items.' 380 % xpath) 381 if not self.item_in_popup_by_xpath_exist(item, xpath): 382 raise SeleniumTimeoutException('The popup at xpath %s does not ' 383 'contain the item %s.' % (xpath, 384 item)) 385 popup = self.driver.find_element_by_xpath(xpath) 386 for option in popup.find_elements_by_tag_name('option'): 387 if option.text == item: 388 option.click() 389 break 390 self._handle_alert(xpath, alert_handler) 391 if wait_for_xpath: 392 self.wait_for_object_by_xpath(wait_for_xpath) 393 394 395 def set_content_of_text_field_by_id(self, content, text_field_id, 396 wait_for_xpath=None, 397 abort_check=False): 398 """Sets the content of a textfield, by passing the element ID. 399 400 @param content: the content to apply to the textfield 401 @param text_field_id: the html ID of the textfield 402 @param wait_for_xpath: an item to wait for before returning, if not 403 specified the method does not wait. 404 405 """ 406 xpath = 'id("%s")' % text_field_id 407 self.set_content_of_text_field_by_xpath(content, xpath, 408 wait_for_xpath=wait_for_xpath, 409 abort_check=abort_check) 410 411 412 def set_content_of_text_field_by_xpath(self, content, xpath, 413 wait_for_xpath=None, 414 abort_check=False): 415 """Sets the content of a textfield, by passing the xpath. 416 417 @param content: the content to apply to the textfield 418 @param xpath: the xpath of the textfield 419 @param wait_for_xpath: an item to wait for before returning, if not 420 specified the method does not wait. 421 @param abort_check: do not get the current value before setting 422 423 """ 424 # When we can get the value we know the text field is ready. 425 text_field = self.driver.find_element_by_xpath(xpath) 426 if text_field.get_attribute('type') != 'password' and not abort_check: 427 try: 428 self.wait.until(lambda _: text_field.get_attribute('value')) 429 except SeleniumTimeoutException, e: 430 raise SeleniumTimeoutException('Unable to obtain the value of ' 431 'the text field %s.\nWebDriver ' 432 'exception:%s' % (xpath, str(e))) 433 text_field.clear() 434 text_field.send_keys(content) 435 if wait_for_xpath: self.wait_for_object_by_xpath(wait_for_xpath) 436 437 438 def set_check_box_selected_by_id(self, check_box_id, selected=True, 439 wait_for_xpath=None, alert_handler=None): 440 """Sets the state of a checkbox, by passing the ID. 441 442 @param check_box_id: the html id of the checkbox 443 @param selected: True to check the checkbox; False to uncheck it 444 @param wait_for_xpath: an item to wait for before returning, if not 445 specified the method does not wait. 446 @param alert_handler: method invoked if an alert is detected. The method 447 must take one parameter, a webdriver alert object 448 449 """ 450 xpath = 'id("%s")' % check_box_id 451 self.set_check_box_selected_by_xpath(xpath, selected, wait_for_xpath, 452 alert_handler) 453 454 455 def set_check_box_selected_by_xpath(self, xpath, selected=True, 456 wait_for_xpath=None, 457 alert_handler=None): 458 """Sets the state of a checkbox, by passing the xpath. 459 460 @param xpath: the xpath of the checkbox 461 @param selected: True to check the checkbox; False to uncheck it 462 @param wait_for_xpath: an item to wait for before returning, if not 463 specified the method does not wait. 464 @param alert_handler: method invoked if an alert is detected. The method 465 must take one parameter, a webdriver alert object 466 """ 467 check_box = self.wait_for_object_by_xpath(xpath) 468 value = check_box.get_attribute('value') 469 if (value == '1' and not selected) or (value == '0' and selected): 470 check_box.click() 471 self._handle_alert(xpath, alert_handler) 472 if wait_for_xpath: 473 self.wait_for_object_by_xpath(wait_for_xpath) 474