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