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

# Source Code for Module tlib.base.SeleniumTester

      1  import os
      2  import re
      3  import inspect
      4  # noinspection PyPackageRequirements
      5  import pytest
      6  import collections
      7  from time import sleep
      8  # noinspection PyPackageRequirements
      9  from selenium import webdriver
     10  from _pytest.python import FixtureRequest
     11  # noinspection PyPackageRequirements
     12  import jinja2
     13  from tlib.base import TestHelper
     14  from tlib.base import FileHelper
     15  from tlib.base.PytestTester import PytestTester
     16  from selenium.webdriver.common.by import By
     17  from selenium.webdriver.support.ui import WebDriverWait
     18  from selenium.webdriver.support import expected_conditions
     19  from selenium.common.exceptions import TimeoutException, NoAlertPresentException
     20  from selenium.webdriver.remote.webelement import WebElement
     21  import logging
     22
     23
     24  # noinspection PyMethodParameters
     25  # noinspection PyUnresolvedReferences
     26 -class SeleniumTester(PytestTester):
     27
     28      _homepage = None
     29      _test_logger = None # Reference to test logger
     30      _tlib_logger = None # Reference to tlib logger
     31      _browser = None
     32
     33      _folder_dest = None # destination of report for the current class
     34      _test_case_id = None
     35      _test_case_name = None
     36      _test_params = None
     37
     38      _jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(TestHelper.tlib_template_folder()), trim_blocks = True)
     39
     40      _screenshot_report = None # List of tests for which there are screenshots
     41      _screenshot_report_template = _jinja_env.get_template("screenshot_report.html") # Template for generating screenshot report
     42
     43      _screenshots = {} # Screenshots generated during a test case
     44      _tc_screenshot_template = _jinja_env.get_template("testcase_screenshots.html") # Template for generating screenshots for a test case
     45
     46
     47 -    def _get_browser(self):
     48          """
     49          Returns Instance of the WebDriver browser
     50          @rtype: webdriver.Remote
     51          """
     52          return self.__class__._browser
     53
     54
     55 -    def _set_browser(self, browser):
     56          """
     57          Instance of the WebDriver browser
     58          @type browser: webdriver.Remote
     59          """
     60          self.__class__._browser = browser
     61
     62      browser = property(_get_browser, _set_browser)
     63
     64 -    def _get_homepage(self):
     65          """
     66          Instance of the WebDriver homepage
     67          """
     68          return self.__class__._homepage
     69
     70
     71 -    def _set_homepage(self, homepage):
     72          """
     73          Instance of the WebDriver homepage
     74          @type homepage: webdriver.Remote
     75          """
     76          self.__class__._homepage = homepage
     77
     78      homepage = property(_get_homepage, _set_homepage)
     79
     80
     81      @property
     82 -    def test_logger(self):
     83          """
     84          Logger object to log test information
     85          """
     86          return self.__class__._test_logger
     87
     88
     89      @staticmethod
     90 -    def screenshot_folder():
     91          """
     92          Returns project's screenshot folder
     93          This method must be defined in subclasses
     94          """
     95          raise NotImplementedError("Subclasses should implement this!")
     96
     97
     98      @pytest.fixture(scope='class', autouse=True)
     99 -    def initialize_class(self, test_logger, tlib_logger, browser, base_url):
    100          """
    101          @type test_logger: logging
    102          @type browser: webdriver.Remote
    103          Stores an instance of browser and test_logger
    104          """
    105          self.__class__._test_logger = test_logger
    106          self.__class__._tlib_logger = tlib_logger
    107          self.browser = browser
    108          # Only initialize homepage if it wasn't already initialized
    109          if self.homepage is None:
    110              self.homepage = base_url
    111
    112
    113 -    def get_unused_report_name(self, tc_id, tc_name):
    114          """
    115          Gets a string based on test case id and name, taking into account if test case has already been run or not
    116          @param tc_id: Test case id
    117          @type tc_id: str
    118          @param tc_name: Test case name
    119          @type tc_name: str
    120          """
    121          i = 0
    122          while True:
    123              if i == 0:
    124                  # filename = "%(tc_id)s - %(tc_name)s" % { "tc_id": tc_id, "tc_name": tc_name}
    125                  filename = "%(tc_id)s" % { "tc_id": tc_id}
    126              else:
    127                  # filename = "%(tc_id)s - %(tc_name)s[retry %(cnt)d]" % { "tc_id": tc_id, "tc_name": tc_name, "cnt": i}
    128                  filename = "%(tc_id)s [retry %(cnt)d]" % { "tc_id": tc_id, "cnt": i}
    129
    130              if not filename in self.__class__._screenshot_report:
    131                  return filename
    132
    133              i += 1
    134
    135
    136      @pytest.fixture(scope='class', autouse=True)
    137 -    def generate_class_report(self, request):
    138          """
    139          Generates HTML page with all test case results for the class
    140          @type request: FixtureRequest
    141          """
    142          self.__class__._screenshot_report = collections.OrderedDict()
    143          self.__class__._folder_dest = re.sub('\.py$', '', os.path.relpath(inspect.getfile(request.cls)))
    144
    145          def generate_report():
    146              if len(self.__class__._screenshot_report) > 0:
    147                  #Generate HTML based on template
    148                  html = self.__class__._screenshot_report_template.render(test_class=self.__class__.__name__,
    149                              files=self.__class__._screenshot_report)
    150
    151                  htm_file = "%s.htm" % self.__class__._folder_dest
    152                  full_filename = os.path.join(self.screenshot_folder(), htm_file)
    153
    154                  f = open(full_filename, "w")
    155                  try:
    156                      f.write(html)
    157                  finally:
    158                      f.close()
    159
    160          request.addfinalizer(generate_report)
    161
    162
    163      @pytest.fixture(scope='function', autouse=True)
    164 -    def generate_test_report(self, request):
    165          """
    166          Generates HTML page with all the screenshots for a test case
    167          Supports generating different When test cases are run multiple times with the same arguments
    168          @type request: FixtureRequest
    169          """
    170          # noinspection PyProtectedMember
    171          self.__class__._test_params = request.keywords.node._genid
    172          self.__class__._test_case_name = request.keywords.node.name
    173          self.__class__._screenshots = {}
    174
    175          #Get marker test case name or use a default if there is none
    176          marker = request.node.get_marker("testcasename")
    177          if marker is None:
    178              self._test_logger.warn("Test case doesn't have marker testcasename")
    179              self.__class__._test_case_id = "UNKNOWN_TEST_CASE_ID"
    180          else:
    181              self.__class__._test_case_id = marker.args[0]
    182
    183
    184          def generate_report():
    185
    186              if len(self.__class__._screenshots) > 0:
    187                  name = self.get_unused_report_name(self.__class__._test_case_id, self.__class__._test_case_name)
    188
    189                  #Generate HTML based on template
    190                  html = self.__class__._tc_screenshot_template.render(test_case_id=self.__class__._test_case_id,
    191                              test_case_name=self.__class__._test_case_name,
    192                              screenshots=self.__class__._screenshots)
    193
    194                  #Filename includes a counter in case test case is being run more than once
    195                  htm_file = FileHelper.get_filename_from_string(name) + '.htm'
    196                  relative_filename = os.path.join(self.__class__._folder_dest, htm_file)
    197                  full_filename = os.path.join(self.screenshot_folder(), relative_filename)
    198
    199                  self.__class__._screenshot_report[name] = {"tc_id": self.__class__._test_case_id,
    200                                                             "tc_name": self.__class__._test_case_name,
    201                                                             "filename": relative_filename}
    202
    203                  f = open(full_filename, "w")
    204                  try:
    205                      f.write(html)
    206                  finally:
    207                      f.close()
    208
    209
    210          request.addfinalizer(generate_report)
    211
    212
    213 -    def navigate(self, page, save_screenshot=True):
    214          """
    215          Navigate to "page"
    216          """
    217          self.test_logger.info(u"Navigating to %s" % page)
    218          self.browser.get(page)
    219          if save_screenshot:
    220              self.save_screenshot(u"Navigate to %s" % page)
    221
    222
    223 -    def go_home(self, save_screenshot=True):
    224          """
    225          Navigate to home page
    226          """
    227          self.navigate(self.homepage, save_screenshot=False)
    228          if save_screenshot:
    229              self.save_screenshot("Homepage")
    230
    231
    232 -    def save_screenshot(self, description):
    233          """
    234          Saves screen shot for the current page
    235          """
    236
    237          def get_params():
    238              if self.__class__._test_params is None:
    239                  return ""
    240              else:
    241                  return self.__class__._test_params
    242
    243          #Waits until page is loaded
    244          self._tlib_logger.debug("Saving screenshot")
    245          self.wait_for_page_loaded()
    246
    247          #Folder containing images for test case
    248          name = self.get_unused_report_name(self.__class__._test_case_id, self.__class__._test_case_name)
    249          name = FileHelper.get_filename_from_string(name)
    250
    251          test_folder = os.path.join(self.screenshot_folder(), self.__class__._folder_dest, name)
    252
    253          try:
    254              os.makedirs(test_folder)
    255          except WindowsError:
    256              pass
    257
    258          #Get number of existing images
    259          i = len(os.listdir(test_folder)) + 1
    260
    261          #Get filename
    262          img_filename = "%02d.png" % i
    263          full_path = os.path.join(test_folder, img_filename)
    264
    265          self.__class__._screenshots[i] = {"filename": "%s/%s" % (name, img_filename),
    266                                            u"description": description}
    267
    268          self.browser.get_screenshot_as_file(full_path)
    269
    270
    271 -    def wait_for_page_loaded(self, timeout=10):
    272          """
    273          Waist until document.readyState is equal to complete
    274          @type timeout: Integer
    275          @param timeout: Number of seconds before timing out
    276          """
    277          if self.browser.execute_script("return document.readyState") == "complete":
    278              return
    279          else:
    280              self._tlib_logger.debug("Waiting for '%s' to load" % self.browser.current_url)
    281
    282          condition = lambda *args: self.browser.execute_script("return document.readyState") == "complete"
    283          try:
    284              WebDriverWait(self.browser, timeout).until(condition)
    285          except TimeoutException:
    286              self.test_logger.error('Timeout while waiting for page to load')
    287              pytest.fail('Timeout while waiting for page to load')
    288
    289          self._tlib_logger.debug("Page '%s' finished loading" % self.browser.current_url)
    290
    291 -    def wait_for_alert(self, timeout=10):
    292          """
    293          Waist until an alert is visible
    294          @type timeout: Integer
    295          @param timeout: Number of seconds before timing out
    296          @rtype: bool
    297          """
    298          def is_alert_visible():
    299              try:
    300                  #Try to get alert text to trigger exception if alert is not visible
    301                  alert = self.browser.switch_to_alert().text
    302                  return True
    303              except NoAlertPresentException as e:
    304                  return False
    305
    306          condition = lambda *args: is_alert_visible()
    307          try:
    308              WebDriverWait(self.browser, timeout).until(condition)
    309              return self.browser.switch_to_alert()
    310          except TimeoutException:
    311              self.test_logger.error('Timeout while waiting for alert to appear')
    312              pytest.fail('Timeout while waiting for alert to appear')
    313
    314      @pytest.fixture(scope='function', autouse=True)
    315 -    def setup_test(self, request, test_logger, browser):
    316          """
    317          Goes to homepage before each test, unless marker skipsetup is given
    318          """
    319          #Don't go to home is skipsetup marker was given
    320          if not request.node.function.__dict__.has_key('skipsetup'):
    321              #Before clearing cookies, need to go to home screen, to ensure it's the current domain
    322              self.go_home(save_screenshot=False)
    323              browser.delete_all_cookies()
    324              self.go_home()
    325          else:
    326              test_logger.info("Skipping setup")
    327
    328
    329 -    def wait_for_element_to_be_visible(self, locator_strategy, locator_string, error_msg=None, timeout=10):
    330          """
    331          Wait until an element becomes visible
    332          @param locator_strategy: Location strategy to use
    333          @type locator_strategy: By
    334          @param locator_string: String used to locate element
    335          @type locator_string: str
    336          @param error_msg: Error string to show if element is not found
    337          @type error_msg: str
    338          @param timeout: Maximum time in seconds to wait for the element to be visible
    339          @type timeout: int
    340          @rtype: WebElement
    341          """
    342          try:
    343              element = WebDriverWait(self.browser, timeout).until(expected_conditions.visibility_of_element_located((locator_strategy, locator_string)))
    344              return element
    345          except TimeoutException:
    346              if error_msg is None:
    347                  error_msg = "Timeout while waiting for element '%s' to be visible" % locator_string
    348              self.save_screenshot("[ERROR] %s" % error_msg)
    349              pytest.fail(error_msg)
    350
    351
    352 -    def wait_for_element_to_be_clickable(self, locator_strategy, locator_string, error_msg=None, timeout=10):
    353          """
    354          Wait until an element cna be clicked
    355          @param locator_strategy: Location strategy to use
    356          @type locator_strategy: By
    357          @param locator_string: String used to locate element
    358          @type locator_string: str
    359          @param error_msg: Error string to show if element is not found
    360          @type error_msg: str
    361          @param timeout: Maximum time in seconds to wait for the element to be clickable
    362          @type timeout: int
    363          @rtype: WebElement
    364          """
    365          try:
    366              element = WebDriverWait(self.browser, timeout).until(expected_conditions.element_to_be_clickable((locator_strategy, locator_string)))
    367              return element
    368          except TimeoutException:
    369              if error_msg is None:
    370                  error_msg = "Timeout while waiting for element '%s' to be clickable" % locator_string
    371              self.save_screenshot("[ERROR] %s" % error_msg)
    372              pytest.fail(error_msg)
    373
    374
    375 -    def wait_for_element_to_be_present(self, locator_strategy, locator_string, error_msg=None, timeout=10):
    376          """
    377          Wait until an element is present
    378          @param locator_strategy: Location strategy to use
    379          @type locator_strategy: By
    380          @param locator_string: String used to locate element
    381          @type locator_string: str
    382          @param error_msg: Error string to show if element is not found
    383          @type error_msg: str
    384          @param timeout: Maximum time in seconds to wait for the element to be present
    385          @type timeout: int
    386          @rtype: WebElement
    387          """
    388          try:
    389              element = WebDriverWait(self.browser, timeout).until(expected_conditions.alert_is_present((locator_strategy, locator_string)))
    390              return element
    391          except TimeoutException:
    392              if error_msg is None:
    393                  error_msg = "Timeout while waiting for element '%s' to be present" % locator_string
    394              self.save_screenshot("[ERROR] %s" % error_msg)
    395              pytest.fail(error_msg)
    396
    397
    398 -    def wait_for_element_to_be_selected(self, locator_strategy, locator_string, error_msg=None, timeout=10):
    399          """
    400          Wait until an element is selected
    401          @param locator_strategy: Location strategy to use
    402          @type locator_strategy: By
    403          @param locator_string: String used to locate element
    404          @type locator_string: str
    405          @param error_msg: Error string to show if element is not found
    406          @type error_msg: str
    407          @param timeout: Maximum time in seconds to wait for the element to be selected
    408          @type timeout: int
    409          @rtype: WebElement
    410          """
    411          try:
    412              element = WebDriverWait(self.browser, timeout).until(expected_conditions.element_located_to_be_selected((locator_strategy, locator_string)))
    413              return element
    414          except TimeoutException:
    415              if error_msg is None:
    416                  error_msg = "Timeout while waiting for element '%s' to be selected" % locator_string
    417              self.save_screenshot("[ERROR] %s" % error_msg)
    418              pytest.fail(error_msg)
    419
    420
    421 -    def wait_for_element_to_be_invisible(self, locator_strategy, locator_string, error_msg=None, timeout=10):
    422          """
    423          Wait until an element becomes invisible
    424          @param locator_strategy: Location strategy to use
    425          @type locator_strategy: By
    426          @param locator_string: String used to locate element
    427          @type locator_string: str
    428          @param error_msg: Error string to show if element is not found
    429          @type error_msg: str
    430          @param timeout: Maximum time in seconds to wait for the element to be hidden
    431          @type timeout: int
    432          @rtype: WebElement
    433          """
    434          try:
    435              element = WebDriverWait(self.browser, timeout).until(expected_conditions.invisibility_of_element_located((locator_strategy, locator_string)))
    436              return element
    437          except TimeoutException:
    438              if error_msg is None:
    439                  error_msg = "Timeout while waiting for element '%s' to be invisible" % locator_string
    440              self.save_screenshot("[ERROR] %s" % error_msg)
    441              pytest.fail(error_msg)
    442
    443
    444 -    def wait_for_element_to_be_static(self, locator_strategy, locator_string, error_msg=None, timeout=10):
    445          """
    446          Wait until an element that moves on the screen stops moving
    447          @param locator_strategy: Location strategy to use
    448          @type locator_strategy: By
    449          @param locator_string: String used to locate element
    450          @type locator_string: str
    451          @param error_msg: Error string to show if element is not found
    452          @type error_msg: str
    453          @param timeout: Maximum time in seconds to wait for the element to be visible
    454          @type timeout: int
    455          @rtype: WebElement
    456          """
    457          try:
    458              element = WebDriverWait(self.browser, timeout).until(expected_conditions.visibility_of_element_located((locator_strategy, locator_string)))
    459
    460              #wait until element is not moving or click will fail
    461              old_location = {'x':0, 'y':0}
    462              while old_location != element.location:
    463                  self._tlib_logger.debug("Pop-up is still moving. Previous position: %s, current position: %s" % (old_location, element.location))
    464                  old_location = element.location
    465                  sleep(0.1)
    466                  element = self.browser.find_element(locator_strategy, locator_string)
    467
    468              return element
    469          except TimeoutException:
    470              if error_msg is None:
    471                  error_msg = "Timeout while waiting for element '%s' to be visible" % locator_string
    472              self.save_screenshot("[ERROR] %s" % error_msg)
    473              pytest.fail(error_msg)
    474
    475
    476 -    def wait_for_text_to_be_present_in_element(self, locator_strategy, locator_string, text, error_msg=None, timeout=10):
    477          """
    478          Wait for an element that contains specified text
    479          @param locator_strategy: Location strategy to use
    480          @type locator_strategy: By
    481          @param locator_string: String used to locate element
    482          @type locator_string: str
    483          @param error_msg: Error string to show if element is not found
    484          @type error_msg: str
    485          @param timeout: Maximum time in seconds to wait
    486          @type timeout: int
    487          """
    488          try:
    489              WebDriverWait(self.browser, timeout).until(expected_conditions.text_to_be_present_in_element((locator_strategy, locator_string), text))
    490          except TimeoutException:
    491              if error_msg is None:
    492                  error_msg = "Timeout while waiting for text %(text)s to be present in element '%(element)s'" % {"text": text, "element":locator_string}
    493              self.save_screenshot("[ERROR] %s" % error_msg)
    494              pytest.fail(error_msg)
    495
    496
    497 -    def wait_for_text_to_be_present_in_element_value(self, locator_strategy, locator_string, text, error_msg=None, timeout=10):
    498          """
    499          Wait for an element's value to contain some test
    500          @param locator_strategy: Location strategy to use
    501          @type locator_strategy: By
    502          @param locator_string: String used to locate element
    503          @type locator_string: str
    504          @param error_msg: Error string to show if element is not found
    505          @type error_msg: str
    506          @param timeout: Maximum time in seconds to wait
    507          @type timeout: int
    508          """
    509          try:
    510              WebDriverWait(self.browser, timeout).until(expected_conditions.text_to_be_present_in_element_value((locator_strategy, locator_string), text))
    511          except TimeoutException:
    512              if error_msg is None:
    513                  error_msg = "Timeout while waiting for text %(text)s to be present in the value of element '%(element)s'" % {"text": text, "element":locator_string}
    514              self.save_screenshot("[ERROR] %s" % error_msg)
    515              pytest.fail(error_msg)
    516