Package tlib :: Package base :: Module WebDriverTester
[hide private]
[frames] | no frames]

Source Code for Module tlib.base.WebDriverTester

  1  import os 
  2  import inspect 
  3  # noinspection PyPackageRequirements 
  4  import pytest 
  5  import collections 
  6  from time import sleep 
  7  # noinspection PyPackageRequirements 
  8  from _pytest.python import FixtureRequest 
  9  # noinspection PyPackageRequirements 
 10  import jinja2 
 11  from tlib.base import TestHelper 
 12  from tlib.base import FileHelper 
 13  from tlib.base.PytestTester import PytestTester 
 14  from selenium.webdriver.common.by import By 
 15  from selenium.webdriver.support.ui import WebDriverWait 
 16  from selenium.webdriver.support import expected_conditions 
 17  from selenium.common.exceptions import TimeoutException, NoAlertPresentException, NoSuchElementException 
 18  from selenium.webdriver.remote.webelement import WebElement 
 19  from TestHelper import Singleton 
20 21 22 # noinspection PyMethodParameters 23 # noinspection PyUnresolvedReferences 24 -class WebDriverTester(PytestTester):
25 26 __metaclass__ = Singleton 27 28 _driver = None 29 30 _folder_dest = None # destination of report for the current class 31 _test_case_id = None 32 _test_case_name = None 33 _test_params = None 34 35 _jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(TestHelper.tlib_template_folder()), 36 trim_blocks=True) 37 38 _screenshot_report = None # List of tests for which there are screenshots 39 40 # Template for generating screenshot report 41 _screenshot_report_template = _jinja_env.get_template("screenshot_report.html") 42 43 _screenshots = {} # Screenshots generated during a test case 44 45 # Template for generating screenshots for a test case 46 _tc_screenshot_template = _jinja_env.get_template("testcase_screenshots.html") 47 48 _request = None 49
50 - def screenshot_folder(self):
51 """ 52 Returns location of screenshot folder 53 54 @return: str 55 """ 56 screenshot_folder = self._find_screenshot_folder() 57 if screenshot_folder is None: 58 #Couldn't find screenshot folder 59 raise NotImplementedError("Could not find screenshot folder. " 60 "Class should implement method screenshot_folder") 61 else: 62 return screenshot_folder
63
64 - def _find_screenshot_folder(self):
65 """ 66 Will try to find a folder named "screenshot" starting from the file being executed and up to 67 three levels up 68 69 @return: str 70 """ 71 #Get current folder 72 curr_folder = os.path.dirname(self._request.fspath.strpath) 73 74 #Go up to three levels to find screenshot folder 75 for i in range(1, 4): 76 curr_folder = os.path.abspath(os.path.join(curr_folder, os.pardir)) 77 78 #Check if there is a folder 'screenshots' 79 screenshot_folder = os.path.join(curr_folder, 'screenshots') 80 if os.path.exists(screenshot_folder): 81 return screenshot_folder
82
83 - def _get_unused_report_name(self, tc_id, tc_params):
84 """ 85 Gets a string based on test case id and name, taking into account if test case has already been run or not 86 @param tc_id: Test case id 87 @type tc_id: str 88 @type tc_name: str 89 """ 90 i = 0 91 while True: 92 if i == 0: 93 filename = "%(tc_id)s_%(tc_params)s" % {"tc_id": tc_id, "tc_params": tc_params} 94 else: 95 filename = "%(tc_id)s_%(tc_params)s [retry %(cnt)d]" % \ 96 {"tc_id": tc_id, "tc_params": tc_params, "cnt": i} 97 98 if not filename in self._screenshot_report: 99 return filename 100 101 i += 1
102 103 @pytest.fixture(scope='class', autouse=True)
104 - def initialize_webdriver_class(self, request):
105 """ 106 @type request: FixtureRequest 107 """ 108 109 #Generates initial HTML page with all test case results for the class 110 self._screenshot_report = collections.OrderedDict() 111 self._folder_dest = os.path.basename(os.path.dirname(os.path.abspath(inspect.getfile(request.cls)))) 112 113 def generate_report(): 114 if len(self._screenshot_report) > 0: 115 #Generate HTML based on template 116 html = self._screenshot_report_template.render(test_class=request.cls.__name__, 117 files=self._screenshot_report) 118 119 htm_file = "%s.htm" % self._folder_dest 120 full_filename = os.path.join(self.screenshot_folder(), htm_file) 121 122 f = open(full_filename, "w") 123 try: 124 f.write(html) 125 finally: 126 f.close()
127 128 request.addfinalizer(generate_report)
129 130 @pytest.fixture(scope='function', autouse=True)
131 - def setup_webdriver_test(self, request):
132 """ 133 Goes to homepage before each test, unless marker skipsetup is given 134 135 @type request: FixtureRequest 136 """ 137 #Store an instance of the request object. Required for the generation of screenshots 138 self._request = request 139 140 #Initialization required for screenshot generation 141 # noinspection PyProtectedMember 142 self._test_params = request.keywords.node._genid 143 self._test_case_name = request.keywords.node.name 144 self._screenshots = {} 145 146 #Get marker test case name or use a default if there is none 147 marker = request.node.get_marker("testcasename") 148 if marker is None: 149 self.test_logger.warn("Test case doesn't have marker testcasename") 150 self._test_case_id = "UNKNOWN_TEST_CASE_ID" 151 else: 152 self._test_case_id = marker.args[0] 153 154 def generate_report(): 155 """ 156 Generates HTML page with all the screenshots for a test case 157 Supports generating different files when test cases are run multiple times with the same arguments 158 """ 159 160 if len(self._screenshots) > 0: 161 name = self._get_unused_report_name(self._test_case_id, self._test_params) 162 name = self.remove_non_ascii_chars(name) 163 164 test_case_name=self._test_case_name 165 test_case_name = self.remove_non_ascii_chars(test_case_name) 166 167 #Generate HTML based on template 168 html = self._tc_screenshot_template.render(test_case_id=self._test_case_id, 169 test_case_name=test_case_name, 170 screenshots=self._screenshots) 171 172 #Filename includes a counter in case test case is being run more than once 173 htm_file = FileHelper.get_filename_from_string(name) + '.htm' 174 relative_filename = os.path.join(self._folder_dest, htm_file) 175 full_filename = os.path.join(self.screenshot_folder(), relative_filename) 176 full_filename = self.remove_non_ascii_chars(full_filename) 177 178 self._screenshot_report[name] = {"tc_id": self._test_case_id, 179 "tc_name": test_case_name, 180 "filename": relative_filename} 181 182 f = open(full_filename, "w") 183 try: 184 f.write(html) 185 finally: 186 f.close()
187 188 request.addfinalizer(generate_report) 189
190 - def save_screenshot(self, description):
191 """ 192 Saves screen shot for the current page 193 """ 194 #Waits until page is loaded 195 self.tlib_logger.debug("Saving screenshot") 196 self.wait_for_page_loaded() 197 198 #Folder containing images for test case 199 name = self._get_unused_report_name(self._test_case_id, self._test_params) 200 name = FileHelper.get_filename_from_string(name) 201 name = self.remove_non_ascii_chars(name) 202 203 test_folder = os.path.join(self.screenshot_folder(), self._folder_dest, name) 204 205 try: 206 os.makedirs(test_folder) 207 except WindowsError: 208 pass 209 210 #Get number of existing images 211 i = len(os.listdir(test_folder)) + 1 212 213 #Get filename 214 img_filename = "%02d.png" % i 215 full_path = os.path.join(test_folder, img_filename) 216 217 self._screenshots[i] = {"filename": "%s/%s" % (name, img_filename), 218 u"description": description} 219 220 self._driver.get_screenshot_as_file(full_path)
221
222 - def remove_non_ascii_chars(self, str):
223 """ 224 Removes non-ascii characters from a string 225 @type str: string 226 @param str: String with non-ascii characters 227 @rtype: string 228 """ 229 return ''.join([i if ord(i) < 128 else '' for i in str])
230
231 - def compare_images(self, ref, new):
232 """ 233 Compare two images, if they are equal return True else False, if empty then None 234 @type ref: ImageFile 235 @type new: ImageFile 236 @param ref: Reference Image 237 @param new: Image captured at run time 238 @rtype: bool 239 """ 240 sleep(0.2) 241 return ImageChops.difference(ref, new).getbbox() is None
242 243
244 - def take_screenshot(self):
245 """ 246 Takes a screenshot of the entire view as a PIL.Image 247 @rtype: Image 248 """ 249 data = self._driver.get_screenshot_as_base64() 250 return Image.open(StringIO(base64.decodestring(data)))
251
252 - def _get_dimensions(self, element):
253 """ 254 Get the dimensions of the element 255 @type element: WebElement 256 @param element: Element whose dimensions need to calculated 257 @rtype: tuple (element dimensions) 258 """ 259 location = element.location 260 size = element.size 261 return (location['x'], 262 location['y'], 263 location['x'] + size['width'], 264 location['y'] + size['height'] 265 )
266
267 - def get_screenshot_of_specific_element_on_webpage(self, element):
268 """ 269 Get page screenshot cropped to only include the given element 270 @type element: WebElement 271 @param element: WebElement to be extracted 272 @rtype: Extracted/Cropped WebElement 273 """ 274 dimensions = self._get_dimensions(element) 275 original_image = self.take_screenshot() 276 cropped_image = original_image.crop(dimensions) 277 278 test_folder = os.path.join(self.screenshot_folder(), self._folder_dest) 279 cropped_image.save("%s\Captured-%s.png" % (test_folder, self._test_params)) 280 281 return cropped_image
282
283 - def wait_for_page_loaded(self, timeout=10):
284 raise NotImplementedError("This method should be implemented by derived classes")
285
286 - def wait_for_alert(self, timeout=10):
287 """ 288 Waist until an alert is visible 289 @type timeout: Integer 290 @param timeout: Number of seconds before timing out 291 @rtype: bool 292 """ 293 def is_alert_visible(): 294 try: 295 #Try to get alert text to trigger exception if alert is not visible 296 alert = self._driver.switch_to_alert().text 297 return True 298 except NoAlertPresentException as e: 299 return False
300 301 condition = lambda *args: is_alert_visible() 302 try: 303 WebDriverWait(self._driver, timeout).until(condition) 304 return self._driver.switch_to_alert() 305 except TimeoutException: 306 self.test_logger.error('Timeout while waiting for alert to appear') 307 raise RuntimeError('Timeout while waiting for alert to appear') 308
309 - def wait_for_element_to_be_visible(self, locator_strategy, locator_string, error_msg=None, timeout=10):
310 """ 311 Wait until an element becomes visible 312 @param locator_strategy: Location strategy to use 313 @type locator_strategy: By 314 @param locator_string: String used to locate element 315 @type locator_string: str 316 @param error_msg: Error string to show if element is not found 317 @type error_msg: str 318 @param timeout: Maximum time in seconds to wait for the element to be visible 319 @type timeout: int 320 @rtype: WebElement 321 """ 322 try: 323 element = WebDriverWait(self._driver, timeout).\ 324 until(expected_conditions.visibility_of_element_located((locator_strategy, locator_string))) 325 return element 326 except TimeoutException: 327 if error_msg is None: 328 error_msg = "Timeout while waiting for element '%s' to be visible" % locator_string 329 self.save_screenshot("[ERROR] %s" % error_msg) 330 raise RuntimeError(error_msg)
331
332 - def wait_for_element_to_be_clickable(self, locator_strategy, locator_string, error_msg=None, timeout=10):
333 """ 334 Wait until an element cna be clicked 335 @param locator_strategy: Location strategy to use 336 @type locator_strategy: By 337 @param locator_string: String used to locate element 338 @type locator_string: str 339 @param error_msg: Error string to show if element is not found 340 @type error_msg: str 341 @param timeout: Maximum time in seconds to wait for the element to be clickable 342 @type timeout: int 343 @rtype: WebElement 344 """ 345 try: 346 element = WebDriverWait(self._driver, timeout).\ 347 until(expected_conditions.element_to_be_clickable((locator_strategy, locator_string))) 348 return element 349 except TimeoutException: 350 if error_msg is None: 351 error_msg = "Timeout while waiting for element '%s' to be clickable" % locator_string 352 self.save_screenshot("[ERROR] %s" % error_msg) 353 raise RuntimeError(error_msg)
354
355 - def wait_for_element_to_be_present(self, locator_strategy, locator_string, error_msg=None, timeout=10):
356 """ 357 Wait until an element is present 358 @param locator_strategy: Location strategy to use 359 @type locator_strategy: By 360 @param locator_string: String used to locate element 361 @type locator_string: str 362 @param error_msg: Error string to show if element is not found 363 @type error_msg: str 364 @param timeout: Maximum time in seconds to wait for the element to be present 365 @type timeout: int 366 @rtype: WebElement 367 """ 368 try: 369 element = WebDriverWait(self._driver, timeout).\ 370 until(expected_conditions.presence_of_element_located((locator_strategy, locator_string))) 371 return element 372 except TimeoutException: 373 if error_msg is None: 374 error_msg = "Timeout while waiting for element '%s' to be present" % locator_string 375 self.save_screenshot("[ERROR] %s" % error_msg) 376 raise RuntimeError(error_msg)
377
378 - def wait_for_element_to_be_selected(self, locator_strategy, locator_string, error_msg=None, timeout=10):
379 """ 380 Wait until an element is selected 381 @param locator_strategy: Location strategy to use 382 @type locator_strategy: By 383 @param locator_string: String used to locate element 384 @type locator_string: str 385 @param error_msg: Error string to show if element is not found 386 @type error_msg: str 387 @param timeout: Maximum time in seconds to wait for the element to be selected 388 @type timeout: int 389 @rtype: WebElement 390 """ 391 try: 392 element = WebDriverWait(self._driver, timeout).\ 393 until(expected_conditions.element_located_to_be_selected((locator_strategy, locator_string))) 394 return element 395 except TimeoutException: 396 if error_msg is None: 397 error_msg = "Timeout while waiting for element '%s' to be selected" % locator_string 398 self.save_screenshot("[ERROR] %s" % error_msg) 399 raise RuntimeError(error_msg)
400
401 - def wait_for_element_to_be_invisible(self, locator_strategy, locator_string, error_msg=None, timeout=10):
402 """ 403 Wait until an element becomes invisible 404 @param locator_strategy: Location strategy to use 405 @type locator_strategy: By 406 @param locator_string: String used to locate element 407 @type locator_string: str 408 @param error_msg: Error string to show if element is not found 409 @type error_msg: str 410 @param timeout: Maximum time in seconds to wait for the element to be hidden 411 @type timeout: int 412 @rtype: WebElement 413 """ 414 try: 415 element = WebDriverWait(self._driver, timeout).\ 416 until(expected_conditions.invisibility_of_element_located((locator_strategy, locator_string))) 417 return element 418 except TimeoutException: 419 if error_msg is None: 420 error_msg = "Timeout while waiting for element '%s' to be invisible" % locator_string 421 self.save_screenshot("[ERROR] %s" % error_msg) 422 raise RuntimeError(error_msg)
423
424 - def wait_for_element_to_be_static(self, locator_strategy, locator_string, error_msg=None, timeout=10):
425 """ 426 Wait until an element that moves on the screen stops moving 427 @param locator_strategy: Location strategy to use 428 @type locator_strategy: By 429 @param locator_string: String used to locate element 430 @type locator_string: str 431 @param error_msg: Error string to show if element is not found 432 @type error_msg: str 433 @param timeout: Maximum time in seconds to wait for the element to be visible 434 @type timeout: int 435 @rtype: WebElement 436 """ 437 try: 438 element = WebDriverWait(self._driver, timeout).\ 439 until(expected_conditions.visibility_of_element_located((locator_strategy, locator_string))) 440 441 #wait until element is not moving or click will fail 442 old_location = {'x': 0, 'y': 0} 443 while old_location != element.location: 444 self.tlib_logger.debug("Pop-up is still moving. Previous position: %s, current position: %s" % 445 (old_location, element.location)) 446 old_location = element.location 447 sleep(0.1) 448 element = self._driver.find_element(locator_strategy, locator_string) 449 450 return element 451 except TimeoutException: 452 if error_msg is None: 453 error_msg = "Timeout while waiting for element '%s' to be visible" % locator_string 454 self.save_screenshot("[ERROR] %s" % error_msg) 455 raise RuntimeError(error_msg)
456
457 - def wait_for_text_to_be_present_in_element(self, locator_strategy, locator_string, text, 458 error_msg=None, timeout=10):
459 """ 460 Wait for an element that contains specified text 461 @param locator_strategy: Location strategy to use 462 @type locator_strategy: By 463 @param locator_string: String used to locate element 464 @type locator_string: str 465 @param error_msg: Error string to show if element is not found 466 @type error_msg: str 467 @param timeout: Maximum time in seconds to wait 468 @type timeout: int 469 """ 470 try: 471 WebDriverWait(self._driver, timeout).\ 472 until(expected_conditions.text_to_be_present_in_element((locator_strategy, locator_string), text)) 473 except TimeoutException: 474 if error_msg is None: 475 error_msg = "Timeout while waiting for text %(text)s to be present in element '%(element)s'" % \ 476 {"text": text, "element": locator_string} 477 self.save_screenshot("[ERROR] %s" % error_msg) 478 raise RuntimeError(error_msg)
479
480 - def wait_for_text_to_be_present_in_element_value(self, locator_strategy, locator_string, text, 481 error_msg=None, timeout=10):
482 """ 483 Wait for an element's value to contain some test 484 @param locator_strategy: Location strategy to use 485 @type locator_strategy: By 486 @param locator_string: String used to locate element 487 @type locator_string: str 488 @param error_msg: Error string to show if element is not found 489 @type error_msg: str 490 @param timeout: Maximum time in seconds to wait 491 @type timeout: int 492 """ 493 try: 494 WebDriverWait(self._driver, timeout).\ 495 until(expected_conditions.text_to_be_present_in_element_value((locator_strategy, locator_string), text)) 496 except TimeoutException: 497 if error_msg is None: 498 error_msg = "Timeout while waiting for text %(text)s to be present " \ 499 "in the value of element '%(element)s'" % {"text": text, "element": locator_string} 500 self.save_screenshot("[ERROR] %s" % error_msg) 501 raise RuntimeError(error_msg)
502 503 518
519 - def get_webelement_by_xpath(self, locator_string):
520 """ 521 Get the webelement by xpath 522 @param locator_string: String used to locate element 523 @type locator_string: str 524 @param error_msg: Error string to show if element is not found 525 @type error_msg: str 526 """ 527 try: 528 self.wait_for_element_to_be_visible(By.XPATH, locator_string) 529 return self.browser.find_element_by_xpath(locator_string) 530 except NoSuchElementException: 531 error_msg="Could not find the xpath: '%s'" 532 self.save_screenshot(error_msg % locator_string) 533 raise RuntimeError(error_msg % locator_string)
534
535 - def get_webelement_by_css(self, locator_string):
536 """ 537 Get the webelement by CSS 538 @param locator_string: String used to locate element 539 @type locator_string: str 540 @param error_msg: Error string to show if element is not found 541 @type error_msg: str 542 """ 543 try: 544 self.wait_for_element_to_be_visible(By.CSS_SELECTOR, locator_string) 545 return self.browser.find_element_by_css_selector(locator_string) 546 except NoSuchElementException: 547 error_msg="Could not find css: '%s'" 548 self.save_screenshot(error_msg % locator_string) 549 raise RuntimeError(error_msg %locator_string)
550
551 - def get_webelements_by_xpath(self, locator_string):
552 """ 553 Get the webelement list by xpath 554 @param locator_string: String used to locate element 555 @type locator_string: str 556 @param error_msg: Error string to show if element is not found 557 @type error_msg: str 558 """ 559 try: 560 return self.browser.find_elements_by_xpath(locator_string) 561 except NoSuchElementException: 562 error_msg="Could not find the link: '%s'" 563 self.save_screenshot(error_msg % locator_string) 564 raise RuntimeError(error_msg+ " '%s'" % locator_string)
565
566 - def get_webelements_by_css(self, locator_string):
567 """ 568 Get the webelement list by CSS 569 @param locator_string: String used to locate element 570 @type locator_string: str 571 @param error_msg: Error string to show if element is not found 572 @type error_msg: str 573 """ 574 try: 575 return self.browser.find_elements_by_css_selector(locator_string) 576 except NoSuchElementException: 577 error_msg="Could not find css: '%s'" 578 self.save_screenshot(error_msg % locator_string) 579 raise RuntimeError(error_msg % locator_string)
580