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