Пример #1
0
 def __init__(self):
     self.bs_api = BrowserStackAPI()
     self.project_root = self.get_project_root()
     self.configs = self.load_configs()
     self.session_link = None
     self.session_id = None
     self.driver = None
Пример #2
0
 def __init__(self):
     self.bs_api = BrowserStackAPI()
     self.project_root = self.get_project_root()
     self.configs = self.load_configs()
     self.session_link = None
     self.session_id = None
     self.driver = None
Пример #3
0
"""
import os
import ConfigParser
import sys
import time
import json
import shutil
import argparse

import pytest
from jinja2 import Environment, FileSystemLoader

from salsa_webqa.library.support.browserstack import BrowserStackAPI
from salsa_webqa.library.control_test import ControlTest

bs_api = BrowserStackAPI()


class SalsaRunner():
    """ Selenium Webdriver Python test runner.
    - runs python selenium tests on customizable configurations, locally or on BrowserStack using PyTest
    - checks for available BrowserStack sessions and wait if necessary
    - archive the test results in .zip file """
    def __init__(self, project_root):

        # set project root folder and current folder
        self.current_folder = os.path.dirname(os.path.abspath(__file__))
        self.project_root = project_root
        self.set_project_root()

        # set support variables
Пример #4
0
class ControlTest():
    def __init__(self):
        self.bs_api = BrowserStackAPI()
        self.project_root = self.get_project_root()
        self.configs = self.load_configs()
        self.session_link = None
        self.session_id = None
        self.driver = None

    def get_project_root(self):
        project_root = os.environ.get('SALSA_WEBQA_PROJECT')
        # if OS environment variable is not defined it means that test is being run directly (without runner).
        # In this casse call-stack is used to determine the correct project folder.
        if project_root is None:
            project_root = os.path.abspath(
                os.path.join(
                    os.path.dirname(inspect.getsourcefile(sys._getframe(2))),
                    os.pardir))
            os.environ['SALSA_WEBQA_PROJECT'] = project_root
        return project_root

    def load_configs(self):
        """ Loads variables from .properties configuration files """
        config_path = os.path.join(self.project_root, 'config')
        config = ConfigParser.ConfigParser()

        # load server config variables
        server_config = os.path.join(config_path, 'server_config.properties')
        config.read(server_config)
        server_config_vars = dict(config.defaults())

        # load local config variables
        local_config = os.path.join(config_path, 'local_config.properties')
        config.read(local_config)
        local_config_vars = dict(config.defaults())

        return_configs = [server_config_vars, local_config_vars]
        return return_configs

    def gid(self, searched_id):
        """ Gets value from config variables based on provided key.
         If local execution parameter is "True", function will try to search for parameter in local configuration file.
         If such parameter is not found or there is an error while reading the file, server (default) configuration
         file will be used instead. """
        server_config = self.configs[0]
        local_config = self.configs[1]
        local_execution = local_config.get('local_execution')
        use_server = True

        if local_execution.lower() == 'true':
            try:
                searched_value = local_config.get(searched_id)
                if searched_value != '':
                    use_server = False
            except:
                print('There was an error while retrieving value "' +
                      searched_id + '" from local config!.' +
                      '\nUsing server value instead.')
        if use_server:
            value_to_return = server_config.get(searched_id)
        else:
            value_to_return = local_config.get(searched_id)

        return value_to_return

    def get_browserstack_capabilities(self, build_name=None):
        """ Returns dictionary of capabilities for specific Browserstack browser/os combination """
        if build_name is not None:
            build_name = build_name
        else:
            build_name = self.gid('build_name')
        desired_cap = {
            'os': pytest.config.getoption('xos'),
            'os_version': pytest.config.getoption('xosversion'),
            'browser': pytest.config.getoption('xbrowser'),
            'browser_version': pytest.config.getoption('xbrowserversion'),
            'resolution': pytest.config.getoption('xresolution'),
            'browserstack.debug': self.gid('browserstack_debug').lower(),
            'project': self.gid('project_name'),
            'build': build_name,
            'name': self.get_test_name() + time.strftime('_%Y-%m-%d')
        }
        return desired_cap

    def get_capabilities(self, build_name=None, browserstack=False):
        """ Returns dictionary of browser capabilities """
        desired_cap = {}
        if bool(self.gid('accept_ssl_cert').lower() == 'false'):
            desired_cap['acceptSslCerts'] = False
        else:
            desired_cap['acceptSslCerts'] = True
        if browserstack:
            desired_cap.update(self.get_browserstack_capabilities(build_name))
        return desired_cap

    def get_test_name(self):
        """ Returns test name from the call stack, assuming there can be only
         one 'test_' file in the stack. If there are more it means two PyTest
        tests ran when calling get_test_name, which is invalid use case. """
        test_name = None
        frames = inspect.getouterframes(inspect.currentframe())
        for frame in frames:
            if re.match('test_.*', ntpath.basename(frame[1])):
                test_name = ntpath.basename(frame[1])[:-3]
                break
        if test_name is None:
            test_name = self.gid('project_name')
        return test_name

    def get_default_browser_attributes(self, browser, height, url, width):
        """ Returns default browser values if not initially set """
        if url is None:
            url = self.gid('base_url')
        if browser is None:
            browser = self.gid('driver')
        if width is None:
            width = self.gid('window_width')
        if height is None:
            height = self.gid('window_height')
        return browser, height, url, width

    def get_browser_profile(self, browser_type):
        """ returns ChromeOptions or FirefoxProfile with default settings, based on browser """
        profile = None
        if browser_type.lower() == 'chrome':
            profile = webdriver.ChromeOptions()
            profile.add_experimental_option("excludeSwitches",
                                            ["ignore-certificate-errors"])
        elif browser_type.lower() == 'firefox':
            profile = webdriver.FirefoxProfile()
        return profile

    def add_extension_to_browser(self, browser_type, browser_profile):
        """ returns browser profile updated with one or more extensions """
        if browser_type == 'chrome':
            all_extensions = self.get_extension_file_names('crx')
            for chr_extension in all_extensions:
                browser_profile.add_extension(
                    os.path.join(self.project_root, 'extension',
                                 chr_extension + '.crx'))
        elif browser_type == 'firefox':
            all_extensions = self.get_extension_file_names('xpi')
            for ff_extension in all_extensions:
                browser_profile.add_extension(
                    os.path.join(self.project_root, 'extension',
                                 ff_extension + '.xpi'))
                # TODO set extension version for Firefox
                # browser_profile.set_preference("extensions." + self.gid('extension_name') + ".currentVersion",
                # self.gid('extension_version'))
        return browser_profile

    def call_browserstack_browser(self, build_name):
        """ Starts browser on BrowserStack """
        bs_username = self.gid('bs_username')
        bs_password = self.gid('bs_password')

        # wait until free browserstack session is available
        self.bs_api.wait_for_free_sessions(
            (bs_username, bs_password), int(self.gid('session_waiting_time')),
            int(self.gid('session_waiting_delay')))

        # get browser capabilities and profile
        capabilities = self.get_capabilities(build_name, browserstack=True)
        browser_type = capabilities['browser'].lower()
        browser_profile = self.get_browser_profile(browser_type)

        # add extensions to remote driver
        if bool(self.gid('with_extension')):
            self.add_extension_to_browser(browser_type, browser_profile)

        # add Chrome options to desired capabilities
        if browser_type == 'chrome':
            chrome_capabilities = browser_profile.to_capabilities()
            capabilities.update(chrome_capabilities)
            browser_profile = None

        # start remote driver
        command_executor = 'http://' + bs_username + ':' + bs_password + '@hub.browserstack.com:80/wd/hub'
        self.driver = webdriver.Remote(command_executor=command_executor,
                                       desired_capabilities=capabilities,
                                       browser_profile=browser_profile)
        auth = (bs_username, bs_password)
        session = self.bs_api.get_session(auth, capabilities['build'],
                                          'running')
        self.session_link = self.bs_api.get_session_link(session)
        self.session_id = self.bs_api.get_session_hashed_id(session)

    def call_local_browser(self, browser_type):
        """ Starts local browser """
        # get browser capabilities and profile
        capabilities = self.get_capabilities()
        browser_profile = self.get_browser_profile(browser_type)

        # add extensions to browser
        if bool(self.gid('with_extension')):
            browser_profile = self.add_extension_to_browser(
                browser_type, browser_profile)

        # starts local browser
        if browser_type == "firefox":
            self.driver = webdriver.Firefox(browser_profile,
                                            capabilities=capabilities)
        elif browser_type == "chrome":
            self.driver = webdriver.Chrome(desired_capabilities=capabilities,
                                           chrome_options=browser_profile)
        elif browser_type == "ie":
            self.driver = webdriver.Ie(capabilities=capabilities)
        elif browser_type == "phantomjs":
            self.driver = webdriver.PhantomJS(
                desired_capabilities=capabilities)
        elif browser_type == "opera":
            self.driver = webdriver.Opera(desired_capabilities=capabilities)
            # SafariDriver bindings for Python not yet implemented
            # elif browser == "Safari":
            # self.driver = webdriver.SafariDriver()

    def start_browser(self,
                      build_name=None,
                      url=None,
                      browser=None,
                      width=None,
                      height=None):
        """ Browser startup function.
         Initialize session over Browserstack or local browser. """
        # get default parameter values
        browser, height, url, width = self.get_default_browser_attributes(
            browser, height, url, width)

        if browser.lower() == "browserstack":
            self.call_browserstack_browser(build_name)
        else:
            self.call_local_browser(browser.lower())
            self.driver.set_window_size(width, height)

        self.test_init(url, browser)
        return self.driver

    def stop_browser(self, delete_cookies=True):
        """ Browser termination function """
        if delete_cookies:
            self.driver.delete_all_cookies()
        self.driver.quit()

    def test_init(self, url, browser):
        """ Executed only once after browser starts.
         Suitable for general pre-test logic that do not need to run before every individual test-case. """
        self.driver.get(url)
        if browser.lower() == "browserstack":
            self.driver.maximize_window()
        self.driver.implicitly_wait(int(self.gid('default_implicit_wait')))

    def start_test(self, reload=None):
        """ To be executed before every test-case (test function) """
        if self.session_link is not None:
            print("Link to Browserstack report: %s " % self.session_link)
        if reload is not None:
            self.driver.get(self.gid('base_url'))
            self.driver.implicitly_wait(self.gid('default_implicit_wait'))
            time.sleep(5)

    def stop_test(self, test_info):
        """ To be executed after every test-case (test function) """
        if test_info.test_status not in ('passed', None):
            # save screenshot in case test fails
            screenshot_folder = os.path.join(self.project_root, 'screenshots')
            if not os.path.exists(screenshot_folder):
                os.makedirs(screenshot_folder)
            file_name = re.sub('[^A-Za-z0-9_. ]+', '', test_info.test_name)
            self.driver.save_screenshot(
                os.path.join(screenshot_folder, file_name + '.png'))

    def get_extension_file_names(self, extension_type):
        """ Method reads extension folder and gets extension file name based on provided extension type"""
        extension_location = os.path.join(self.project_root, 'extension')
        extension_file_names = []
        extension_code = self.gid('path_to_extension_code')

        # build Chrome extension from sources if required
        if extension_type == 'crx' and extension_code:
            extension_path = os.path.abspath(
                os.path.join(self.project_root,
                             self.gid('path_to_extension_code')))
            if not os.path.exists(extension_location):
                os.makedirs(extension_location)
            self.build_extension()
            shutil.copy(extension_path + '.' + extension_type,
                        extension_location)

        # find all extensions in folder (build from sources in previous step + already existing)
        if os.path.exists(extension_location):
            extension_files = glob.glob(
                os.path.join(extension_location, '*.' + extension_type))
            for extension in extension_files:
                extension_file_name = self.path_leaf(extension).replace(
                    '.' + extension_type, '', 1)
                extension_file_names.append(extension_file_name)
        return extension_file_names

    def collect_extensions_from_build(self, build_folder, destination_folder):

        # test whether *.xpi and *.crx are present
        ff_extensions = glob.glob(os.path.join(build_folder, '*.xpi'))
        crx_extensions = glob.glob(os.path.join(build_folder, '*.crx'))
        if len(ff_extensions) == 0 or len(crx_extensions) == 0:
            return False

        # copy extensions to destination_folder
        for path in ff_extensions:
            shutil.copy(path, destination_folder)

        for path in crx_extensions:
            shutil.copy(path, destination_folder)

        return True

    def path_leaf(self, path):
        """ Method reads provided path and extract last part of path. Method will return empty string if path ends
        with / (backslash)"""
        head, tail = ntpath.split(path)
        return tail or ntpath.basename(head)

    def build_extension(self):
        """ Method build Chrome extension from code provided in path in local config file."""
        # build Chrome extension
        extension_path = os.path.abspath(
            os.path.join(self.project_root,
                         self.gid('path_to_extension_code')))
        shell_path = os.path.abspath(os.path.join(extension_path, os.pardir))
        extension_name = self.path_leaf(extension_path)
        os.chdir(shell_path)
        shell_command = 'crxmake ' + extension_name
        try:
            cmd_shell = subprocess.check_call(shell_command, shell=True)
        except subprocess.CalledProcessError:
            print('There was an issue with building extension!')
Пример #5
0
class ControlTest():
    def __init__(self):
        self.bs_api = BrowserStackAPI()
        self.project_root = self.get_project_root()
        self.configs = self.load_configs()
        self.session_link = None
        self.session_id = None
        self.driver = None

    def get_project_root(self):
        project_root = os.environ.get('SALSA_WEBQA_PROJECT')
        # if OS environment variable is not defined it means that test is being run directly (without runner).
        # In this casse call-stack is used to determine the correct project folder.
        if project_root is None:
            project_root = os.path.abspath(
                os.path.join(os.path.dirname(inspect.getsourcefile(sys._getframe(2))), os.pardir))
            os.environ['SALSA_WEBQA_PROJECT'] = project_root
        return project_root

    def load_configs(self):
        """ Loads variables from .properties configuration files """
        config_path = os.path.join(self.project_root, 'config')
        config = ConfigParser.ConfigParser()

        # load server config variables
        server_config = os.path.join(config_path, 'server_config.properties')
        config.read(server_config)
        server_config_vars = dict(config.defaults())

        # load local config variables
        local_config = os.path.join(config_path, 'local_config.properties')
        config.read(local_config)
        local_config_vars = dict(config.defaults())

        return_configs = [server_config_vars, local_config_vars]
        return return_configs

    def gid(self, searched_id):
        """ Gets value from config variables based on provided key.
         If local execution parameter is "True", function will try to search for parameter in local configuration file.
         If such parameter is not found or there is an error while reading the file, server (default) configuration
         file will be used instead. """
        server_config = self.configs[0]
        local_config = self.configs[1]
        local_execution = local_config.get('local_execution')
        use_server = True

        if local_execution.lower() == 'true':
            try:
                searched_value = local_config.get(searched_id)
                if searched_value != '':
                    use_server = False
            except:
                print('There was an error while retrieving value "' + searched_id + '" from local config!.'
                      + '\nUsing server value instead.')
        if use_server:
            value_to_return = server_config.get(searched_id)
        else:
            value_to_return = local_config.get(searched_id)

        return value_to_return

    def get_browserstack_capabilities(self, build_name=None):
        """ Returns dictionary of capabilities for specific Browserstack browser/os combination """
        if build_name is not None:
            build_name = build_name
        else:
            build_name = self.gid('build_name')
        desired_cap = {'os': pytest.config.getoption('xos'),
                       'os_version': pytest.config.getoption('xosversion'),
                       'browser': pytest.config.getoption('xbrowser'),
                       'browser_version': pytest.config.getoption('xbrowserversion'),
                       'resolution': pytest.config.getoption('xresolution'),
                       'browserstack.debug': self.gid('browserstack_debug').lower(),
                       'project': self.gid('project_name'),
                       'build': build_name,
                       'name': self.get_test_name() + time.strftime('_%Y-%m-%d')}
        return desired_cap

    def get_capabilities(self, build_name=None, browserstack=False):
        """ Returns dictionary of browser capabilities """
        desired_cap = {}
        if bool(self.gid('accept_ssl_cert').lower() == 'false'):
            desired_cap['acceptSslCerts'] = False
        else:
            desired_cap['acceptSslCerts'] = True
        if browserstack:
            desired_cap.update(self.get_browserstack_capabilities(build_name))
        return desired_cap

    def get_test_name(self):
        """ Returns test name from the call stack, assuming there can be only
         one 'test_' file in the stack. If there are more it means two PyTest
        tests ran when calling get_test_name, which is invalid use case. """
        test_name = None
        frames = inspect.getouterframes(inspect.currentframe())
        for frame in frames:
            if re.match('test_.*', ntpath.basename(frame[1])):
                test_name = ntpath.basename(frame[1])[:-3]
                break
        if test_name is None:
            test_name = self.gid('project_name')
        return test_name

    def get_default_browser_attributes(self, browser, height, url, width):
        """ Returns default browser values if not initially set """
        if url is None:
            url = self.gid('base_url')
        if browser is None:
            browser = self.gid('driver')
        if width is None:
            width = self.gid('window_width')
        if height is None:
            height = self.gid('window_height')
        return browser, height, url, width

    def get_browser_profile(self, browser_type):
        """ returns ChromeOptions or FirefoxProfile with default settings, based on browser """
        profile = None
        if browser_type.lower() == 'chrome':
            profile = webdriver.ChromeOptions()
            profile.add_experimental_option("excludeSwitches", ["ignore-certificate-errors"])
        elif browser_type.lower() == 'firefox':
            profile = webdriver.FirefoxProfile()
        return profile

    def add_extension_to_browser(self, browser_type, browser_profile):
        """ returns browser profile updated with one or more extensions """
        if browser_type == 'chrome':
            all_extensions = self.get_extension_file_names('crx')
            for chr_extension in all_extensions:
                browser_profile.add_extension(os.path.join(self.project_root, 'extension', chr_extension + '.crx'))
        elif browser_type == 'firefox':
            all_extensions = self.get_extension_file_names('xpi')
            for ff_extension in all_extensions:
                browser_profile.add_extension(os.path.join(self.project_root, 'extension', ff_extension + '.xpi'))
                # TODO set extension version for Firefox
                # browser_profile.set_preference("extensions." + self.gid('extension_name') + ".currentVersion",
                # self.gid('extension_version'))
        return browser_profile

    def call_browserstack_browser(self, build_name):
        """ Starts browser on BrowserStack """
        bs_username = self.gid('bs_username')
        bs_password = self.gid('bs_password')

        # wait until free browserstack session is available
        self.bs_api.wait_for_free_sessions((bs_username, bs_password),
                                           int(self.gid('session_waiting_time')),
                                           int(self.gid('session_waiting_delay')))

        # get browser capabilities and profile
        capabilities = self.get_capabilities(build_name, browserstack=True)
        browser_type = capabilities['browser'].lower()
        browser_profile = self.get_browser_profile(browser_type)

        # add extensions to remote driver
        if bool(self.gid('with_extension')):
            self.add_extension_to_browser(browser_type, browser_profile)

        # add Chrome options to desired capabilities
        if browser_type == 'chrome':
            chrome_capabilities = browser_profile.to_capabilities()
            capabilities.update(chrome_capabilities)
            browser_profile = None

        # start remote driver
        command_executor = 'http://' + bs_username + ':' + bs_password + '@hub.browserstack.com:80/wd/hub'
        self.driver = webdriver.Remote(
            command_executor=command_executor,
            desired_capabilities=capabilities,
            browser_profile=browser_profile)
        auth = (bs_username, bs_password)
        session = self.bs_api.get_session(auth, capabilities['build'], 'running')
        self.session_link = self.bs_api.get_session_link(session)
        self.session_id = self.bs_api.get_session_hashed_id(session)

    def call_local_browser(self, browser_type):
        """ Starts local browser """
        # get browser capabilities and profile
        capabilities = self.get_capabilities()
        browser_profile = self.get_browser_profile(browser_type)

        # add extensions to browser
        if bool(self.gid('with_extension')):
            browser_profile = self.add_extension_to_browser(browser_type, browser_profile)

        # starts local browser
        if browser_type == "firefox":
            self.driver = webdriver.Firefox(browser_profile, capabilities=capabilities)
        elif browser_type == "chrome":
            self.driver = webdriver.Chrome(desired_capabilities=capabilities, chrome_options=browser_profile)
        elif browser_type == "ie":
            self.driver = webdriver.Ie(capabilities=capabilities)
        elif browser_type == "phantomjs":
            self.driver = webdriver.PhantomJS(desired_capabilities=capabilities)
        elif browser_type == "opera":
            self.driver = webdriver.Opera(desired_capabilities=capabilities)
            # SafariDriver bindings for Python not yet implemented
            # elif browser == "Safari":
            # self.driver = webdriver.SafariDriver()

    def start_browser(self, build_name=None, url=None, browser=None, width=None, height=None):
        """ Browser startup function.
         Initialize session over Browserstack or local browser. """
        # get default parameter values
        browser, height, url, width = self.get_default_browser_attributes(browser, height, url, width)

        if browser.lower() == "browserstack":
            self.call_browserstack_browser(build_name)
        else:
            self.call_local_browser(browser.lower())
            self.driver.set_window_size(width, height)

        self.test_init(url, browser)
        return self.driver

    def stop_browser(self, delete_cookies=True):
        """ Browser termination function """
        if delete_cookies:
            self.driver.delete_all_cookies()
        self.driver.quit()

    def test_init(self, url, browser):
        """ Executed only once after browser starts.
         Suitable for general pre-test logic that do not need to run before every individual test-case. """
        self.driver.get(url)
        if browser.lower() == "browserstack":
            self.driver.maximize_window()
        self.driver.implicitly_wait(int(self.gid('default_implicit_wait')))

    def start_test(self, reload=None):
        """ To be executed before every test-case (test function) """
        if self.session_link is not None:
            print ("Link to Browserstack report: %s " % self.session_link)
        if reload is not None:
            self.driver.get(self.gid('base_url'))
            self.driver.implicitly_wait(self.gid('default_implicit_wait'))
            time.sleep(5)

    def stop_test(self, test_info):
        """ To be executed after every test-case (test function) """
        if test_info.test_status not in ('passed', None):
            # save screenshot in case test fails
            screenshot_folder = os.path.join(self.project_root, 'screenshots')
            if not os.path.exists(screenshot_folder):
                os.makedirs(screenshot_folder)
            file_name = re.sub('[^A-Za-z0-9_. ]+', '', test_info.test_name)
            self.driver.save_screenshot(os.path.join(screenshot_folder, file_name + '.png'))

    def get_extension_file_names(self, extension_type):
        """ Method reads extension folder and gets extension file name based on provided extension type"""
        extension_location = os.path.join(self.project_root, 'extension')
        extension_file_names = []
        extension_code = self.gid('path_to_extension_code')

        # build Chrome extension from sources if required
        if extension_type == 'crx' and extension_code:
            extension_path = os.path.abspath(os.path.join(self.project_root, self.gid('path_to_extension_code')))
            if not os.path.exists(extension_location):
                os.makedirs(extension_location)
            self.build_extension()
            shutil.copy(extension_path + '.' + extension_type, extension_location)

        # find all extensions in folder (build from sources in previous step + already existing)
        if os.path.exists(extension_location):
            extension_files = glob.glob(os.path.join(extension_location, '*.' + extension_type))
            for extension in extension_files:
                extension_file_name = self.path_leaf(extension).replace('.' + extension_type, '', 1)
                extension_file_names.append(extension_file_name)
        return extension_file_names

    def collect_extensions_from_build(self, build_folder, destination_folder):

        # test whether *.xpi and *.crx are present
        ff_extensions = glob.glob(os.path.join(build_folder, '*.xpi'))
        crx_extensions = glob.glob(os.path.join(build_folder, '*.crx'))
        if len(ff_extensions) == 0 or len(crx_extensions) == 0:
            return False

        # copy extensions to destination_folder
        for path in ff_extensions:
            shutil.copy(path, destination_folder)

        for path in crx_extensions:
            shutil.copy(path, destination_folder)

        return True

    def path_leaf(self, path):
        """ Method reads provided path and extract last part of path. Method will return empty string if path ends
        with / (backslash)"""
        head, tail = ntpath.split(path)
        return tail or ntpath.basename(head)

    def build_extension(self):
        """ Method build Chrome extension from code provided in path in local config file."""
        # build Chrome extension
        extension_path = os.path.abspath(os.path.join(self.project_root, self.gid('path_to_extension_code')))
        shell_path = os.path.abspath(os.path.join(extension_path, os.pardir))
        extension_name = self.path_leaf(extension_path)
        os.chdir(shell_path)
        shell_command = 'crxmake ' + extension_name
        try:
            cmd_shell = subprocess.check_call(shell_command, shell=True)
        except subprocess.CalledProcessError:
            print('There was an issue with building extension!')
Пример #6
0
class ControlTest(object):
    def __init__(self):
        self.bs_api = BrowserStackAPI()
        self.zapi = ZAPI()
        self.project_root = self.get_project_root()
        self.configs = self.load_configs()
        self.session_link = None
        self.session_id = None
        self.driver = None

    def get_project_root(self):
        project_root = os.environ.get('SALSA_WEBQA_PROJECT')
        # if OS environment variable is not defined it means that test is being run directly (without runner).
        # In this casse call-stack is used to determine the correct project folder.
        if project_root is None:
            project_root = os.path.abspath(
                os.path.join(os.path.dirname(inspect.getsourcefile(sys._getframe(2))), os.pardir))
            os.environ['SALSA_WEBQA_PROJECT'] = project_root
        return project_root

    def load_configs(self):
        """ Loads variables from .properties configuration files,  check if project didn't contain such folder
        (for non selenium projects) """
        config_path = os.path.join(self.project_root, 'config')
        config = ConfigParser.ConfigParser()
        if os.path.exists(config_path):
            # load server config variables
            server_config = os.path.join(config_path, 'server_config.properties')
            config.read(server_config)
            server_config_vars = dict(config.defaults())
            # load local config variables
            local_config = os.path.join(config_path, 'local_config.properties')
            config.read(local_config)
            local_config_vars = dict(config.defaults())
            # load non selenium config variables
            non_selenium_config = os.path.join(config_path, 'non_selenium_config.properties')
            config.read(non_selenium_config)
            non_selenium_config = dict(config.defaults())
            return_configs = [server_config_vars, local_config_vars, non_selenium_config]
            return return_configs

    def gid(self, key):
        """ Gets value from config variables based on provided key.
         If local execution parameter is "True", function will try to search for parameter in local configuration file.
         If such parameter is not found or there is an error while reading the file, server (default) configuration
         file will be used instead. """
        if not self.configs:
            return None

        # first try to lookup pytest config
        try:
            value = pytest.config.getoption(key)
            if value:
                return value
            else:
                use_logs = True
        except (ValueError, AttributeError):
            use_logs = True
        # lookup value from config files
        if use_logs:
            server_config = self.configs[0]
            local_config = self.configs[1]
            configs = []
            if local_config.get('local_execution').lower() == 'true':
                configs.append((local_config, 'local config'))
            configs.extend([(server_config, 'server config'), (os.environ, 'env variables')])
            for idx, cfg in enumerate(configs):
                if key in cfg[0] and cfg[0][key] != '':
                    # if idx:
                    # print "%s not found in %s, using value from %s" % (key, configs[0][1], cfg[1])
                    return cfg[0][key]
                    # print "%s not found in any config" % key

    def get_browserstack_capabilities(self, build_name=None):
        """Returns dictionary of capabilities for specific Browserstack browser/os combination """
        build_name = build_name or self.gid('build_name')
        cfg = pytest.config
        test_mobile = self.gid('test_mobile')
        capabilities = {
            'browserstack.debug': self.gid('browserstack_debug').lower(),
            'project': self.gid('project_name'),
            'build': build_name,
            'name': self.get_test_name() + time.strftime('_%Y-%m-%d')
        }
        if test_mobile == 'yes':
            capabilities.update({
                'device': cfg.getoption('xdevice'),
                'platform': cfg.getoption('xplatform'),
                'deviceOrientation': cfg.getoption('xdeviceOrientation'),
                'browserName': cfg.getoption('xbrowserName'),
            })
        else:
            capabilities.update({
                'os': cfg.getoption('xos'),
                'os_version': cfg.getoption('xosversion'),
                'browser': cfg.getoption('xbrowser'),
                'browser_version': cfg.getoption('xbrowserversion'),
                'resolution': cfg.getoption('xresolution'),
            })
        return capabilities

    def get_capabilities(self, build_name=None, browserstack=False):
        """ Returns dictionary of browser capabilities """
        desired_cap = {
            'acceptSslCerts': self.gid('accept_ssl_cert').lower() == 'false'
        }
        if browserstack:
            desired_cap.update(self.get_browserstack_capabilities(build_name))
        return desired_cap

    def get_test_name(self):
        """ Returns test name from the call stack, assuming there can be only
         one 'test_' file in the stack. If there are more it means two PyTest
        tests ran when calling get_test_name, which is invalid use case. """
        frames = inspect.getouterframes(inspect.currentframe())
        for frame in frames:
            if re.match('test_.*', ntpath.basename(frame[1])):
                return ntpath.basename(frame[1])[:-3]

        return self.gid('project_name')

    def get_default_browser_attributes(self, browser, height, url, width):
        """ Returns default browser values if not initially set """
        return (
            browser or self.gid('driver'),
            height or self.gid('window_height'),
            url or self.gid('base_url'),
            width or self.gid('window_width')
        )

    def get_browser_profile(self, browser_type):
        """ returns ChromeOptions or FirefoxProfile with default settings, based on browser """
        if browser_type.lower() == 'chrome':
            profile = webdriver.ChromeOptions()
            profile.add_experimental_option("excludeSwitches", ["ignore-certificate-errors"])
            # set mobile device emulation
            mobile_browser_emulation = self.gid('mobile_browser_emulation')
            if mobile_browser_emulation:
                profile.add_experimental_option("mobileEmulation", {"deviceName": mobile_browser_emulation})
            return profile
        elif browser_type.lower() == 'firefox':
            return webdriver.FirefoxProfile()

    def update_browser_profile(self, capabilities, browser_type=None):
        """ Returns updated browser profile ready to be passed to driver """
        browser_profile = None
        test_mobile = self.gid('test_mobile')
        if test_mobile != 'yes':
            if browser_type is None:
                browser_type = capabilities['browser'].lower()
            browser_profile = self.get_browser_profile(browser_type)

            # add extensions to remote driver
            if self.gid('with_extension'):
                browser_profile = self.add_extension_to_browser(browser_type, browser_profile)

            # add Chrome options to desired capabilities
            if browser_type == 'chrome':
                chrome_capabilities = browser_profile.to_capabilities()
                capabilities.update(chrome_capabilities)
                browser_profile = None
        return browser_profile

    def add_extension_to_browser(self, browser_type, browser_profile):
        """ returns browser profile updated with one or more extensions """
        if browser_type == 'chrome':
            all_extensions = self.get_extension_file_names('crx')
            for chr_extension in all_extensions:
                browser_profile.add_extension(os.path.join(self.project_root, 'extension', chr_extension + '.crx'))
        elif browser_type == 'firefox':
            all_extensions = self.get_extension_file_names('xpi')
            for ff_extension in all_extensions:
                browser_profile.add_extension(os.path.join(self.project_root, 'extension', ff_extension + '.xpi'))
                # TODO set extension version for Firefox
                # browser_profile.set_preference("extensions." + self.gid('extension_name') + ".currentVersion",
                # self.gid('extension_version'))
        return browser_profile

    def call_browserstack_browser(self, build_name):
        """ Starts browser on BrowserStack """
        bs_auth = self.get_auth("browserstack")
        if bs_auth:
            # wait until free browserstack session is available
            self.bs_api.wait_for_free_sessions(bs_auth,
                                               int(self.gid('session_waiting_time')),
                                               int(self.gid('session_waiting_delay')))

            # get browser capabilities and profile
            capabilities = self.get_capabilities(build_name, browserstack=True)
            hub_url = 'http://{0}:{1}@hub.browserstack.com:80/wd/hub'.format(*bs_auth)

            # call remote driver
            self.start_remote_driver(hub_url, capabilities)

            session = self.bs_api.get_session(bs_auth, capabilities['build'], 'running')
            self.session_link = self.bs_api.get_session_link(session)
            self.session_id = self.bs_api.get_session_hashed_id(session)
        else:
            sys.exit('Browserstack credentials were not specified! Unable to start browser.')

    def call_browser(self, browser_type):
        """ Starts local browser """
        # get browser capabilities and profile
        capabilities = self.get_capabilities()
        remote_hub = self.gid('remote_hub')

        if remote_hub:
            self.start_remote_driver(remote_hub, capabilities, browser_type)
        else:
            self.start_local_driver(capabilities, browser_type)

    def start_remote_driver(self, remote_driver_url, capabilities, browser_type=None):
        """ Call remote browser (driver) """
        browser_profile = self.update_browser_profile(capabilities, browser_type)

        # browser type is specified (test not run on BrowserStack)
        if browser_type:
            attr = 'INTERNETEXPLORER' if browser_type == 'ie' else browser_type.upper()
            capabilities.update(getattr(DesiredCapabilities, attr, {}))
            browser_version = self.gid('browser_version')
            platform = self.gid('platform')
            if browser_version:
                capabilities['version'] = browser_version
            if platform:
                capabilities['platform'] = platform

        self.driver = webdriver.Remote(
            command_executor=remote_driver_url,
            desired_capabilities=capabilities,
            browser_profile=browser_profile)

    def start_local_driver(self, capabilities, browser_type=None):
        # add extensions to browser
        browser_profile = self.update_browser_profile(capabilities, browser_type)

        # starts local browser
        if browser_type == "firefox":
            self.driver = webdriver.Firefox(browser_profile, capabilities=capabilities)
        elif browser_type == "chrome":
            self.driver = webdriver.Chrome(desired_capabilities=capabilities, chrome_options=browser_profile)
        elif browser_type == "ie":
            self.driver = webdriver.Ie(capabilities=capabilities)
        elif browser_type == "phantomjs":
            self.driver = webdriver.PhantomJS(desired_capabilities=capabilities)
        elif browser_type == "opera":
            self.driver = webdriver.Opera(desired_capabilities=capabilities)
            # SafariDriver bindings for Python not yet implemented
            # elif browser == "Safari":
            # self.driver = webdriver.SafariDriver()

    def start_browser(self, build_name=None, url=None, browser=None, width=None, height=None):
        """ Browser startup function.
         Initialize session over Browserstack or local browser. """
        # get default parameter values
        browser, height, url, width = self.get_default_browser_attributes(browser, height, url, width)

        if browser.lower() == "browserstack":
            self.call_browserstack_browser(build_name)
        else:
            self.call_browser(browser.lower())
            self.driver.set_window_size(width, height)
        self.test_init(url)
        return self.driver

    def stop_browser(self, delete_cookies=True):
        """ Browser termination function """
        if delete_cookies:
            self.driver.delete_all_cookies()
        self.driver.quit()

    def test_init(self, url):
        """ Executed only once after browser starts.
         Suitable for general pre-test logic that do not need to run before every individual test-case. """
        self.driver.get(url)
        self.driver.implicitly_wait(int(self.gid('default_implicit_wait')))

    def start_test(self, reload_page=None):
        """ To be executed before every test-case (test function) """
        if self.session_link:
            print "Link to Browserstack report: %s " % self.session_link
        if reload_page:
            self.driver.get(self.gid('base_url'))
            self.driver.implicitly_wait(self.gid('default_implicit_wait'))
            time.sleep(5)

    def stop_test(self, test_info, execution_id=None):
        """ To be executed after every test-case (test function) """
        if test_info.test_status not in ('passed', None):
            # save screenshot in case test fails
            screenshot_folder = os.path.join(self.project_root, 'screenshots')
            if not os.path.exists(screenshot_folder):
                os.makedirs(screenshot_folder)
            file_name = re.sub('[^A-Za-z0-9_. ]+', '', test_info.test_name)
            self.driver.save_screenshot(os.path.join(screenshot_folder, file_name + '.png'))
        if execution_id:
            print "change status in Jira execution"
            auth = self.get_auth("jira")
            if test_info.test_status == "failed_execution":
                self.zapi.update_execution_status(execution_id, "FAIL", auth)
            elif test_info.test_status == "passed":
                self.zapi.update_execution_status(execution_id, "PASS", auth)

    def get_extension_file_names(self, extension_type):
        """ Method reads extension folder and gets extension file name based on provided extension type"""
        extension_location = os.path.join(self.project_root, 'extension')
        extension_file_names = []
        extension_code = self.gid('path_to_extension_code')

        # build Chrome extension from sources if required
        if extension_type == 'crx' and extension_code:
            extension_path = os.path.abspath(os.path.join(self.project_root, self.gid('path_to_extension_code')))
            if not os.path.exists(extension_location):
                os.makedirs(extension_location)
            self.build_extension()
            shutil.copy(extension_path + '.' + extension_type, extension_location)

        # find all extensions in folder (build from sources in previous step + already existing)
        if os.path.exists(extension_location):
            extension_files = glob.glob(os.path.join(extension_location, '*.' + extension_type))
            for extension in extension_files:
                extension_file_name = self.path_leaf(extension).replace('.' + extension_type, '', 1)
                extension_file_names.append(extension_file_name)
        return extension_file_names

    def path_leaf(self, path):
        """ Method reads provided path and extract last part of path. Method will return empty string if path ends
        with / (backslash)"""
        head, tail = ntpath.split(path)
        return tail or ntpath.basename(head)

    def build_extension(self):
        """ Method build Chrome extension from code provided in path in local config file."""
        # build Chrome extension
        extension_path = os.path.abspath(os.path.join(self.project_root, self.gid('path_to_extension_code')))
        shell_path = os.path.abspath(os.path.join(extension_path, os.pardir))
        extension_name = self.path_leaf(extension_path)
        os.chdir(shell_path)
        shell_command = 'crxmake ' + extension_name
        try:
            subprocess.check_call(shell_command, shell=True)
        except subprocess.CalledProcessError:
            print 'There was an issue with building extension!'

    def get_auth(self, parameter):
        auth = None
        if parameter.lower() == 'jira':
            auth = self.gid('jira_support')
        elif parameter.lower() == 'browserstack':
            auth = self.gid('browserstack')
        if auth:
            return tuple(auth.split(":"))
        return None

    def create_cycle(self, cycle_name, auth):
        cycle_id = self.zapi.create_new_test_cycle("%s %S" % (cycle_name, datetime.today().strftime("%d-%m-%y")),
                                                   self.gid('jira_project'), self.gid('jira_project_version'), auth)
        return cycle_id

    def get_execution_id(self, jira_id):
        jira_auth = self.get_auth("jira")
        if jira_auth:
            cycle_base = self.gid('jira_base_cycle')
            cycle_id = self.gid('jira_cycle_id')
            issue_id = self.zapi.get_issueid(cycle_base, jira_id, jira_auth)
            execution_id = self.zapi.add_new_execution(self.gid('jira_project'), self.gid('jira_project_version'),
                                                       cycle_id, issue_id, jira_auth)
            return execution_id