Exemple #1
0
    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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid('driver')
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, 'results',
                                          self.timestamp)

        # load configuration files
        self.bs_config_file = os.path.join(self.project_root, 'config',
                                           'browserstack.properties')
        self.bs_config_file_smoke = os.path.join(
            self.project_root, 'config', 'browserstack_smoke.properties')
        if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(
                self.bs_config_file_smoke)):
            sys.exit(
                'One of browserstack properties files not found! Session terminated.'
            )
        self.bs_config = ConfigParser.RawConfigParser()

        # load cmd arguments and set default values if not specified
        cmd_args = self.get_runner_args()
        self.env_type = cmd_args[0]
        self.test_type = cmd_args[1]
Exemple #2
0
    def __init__(self):
        self.test_control = ControlTest()
        self.email_address = self.test_control.gid('email_address')
        self.email_imap = self.test_control.gid('email_imap')
        self.email_password = self.test_control.gid('email_password')
        self.email_mailbox = self.test_control.gid('email_mailbox')

        self.mail = imaplib.IMAP4_SSL(self.email_imap)
        self.mail.login(self.email_address, self.email_password)
Exemple #3
0
class CircleAPI():
    """ Handles communication with Circle CI via REST API """

    def __init__(self):
        self.test_control = ControlTest()
        self.api_token = self.test_control.gid('circleci_api_token')
        self.circle_username = self.test_control.gid('circleci_username')
        self.circle_project = self.test_control.gid('circleci_project')
        self.circle_branch = self.test_control.gid('circleci_branch')

    def collect_artifacts(self, destination_folder):
        """ downloads build artifacts from CircleCI for latest build from specific branch """
        if not os.path.exists(destination_folder):
            os.makedirs(destination_folder)
        artifact_data = self.get_artifact_data()
        for artifact in artifact_data:
            self.save_artifact(artifact, destination_folder)
        if len([name for name in os.listdir(destination_folder)]) == 0:
            return False
        else:
            return True

    def save_artifact(self, artifact, destination_folder):
        """ saves artifact into specified folder """
        file_name = artifact['url'].split('/')[-1]
        file_request_url = artifact['url'] + '?circle-token=' + self.api_token
        extension = open(os.path.join(destination_folder, file_name), 'wb')
        response = requests.get(file_request_url, stream=True)

        if not response.ok:
            # Something went wrong
            print 'error'

        for block in response.iter_content(1024):
            if not block:
                break

            extension.write(block)
        extension.close()

    def get_artifact_data(self):
        """ returns json with artifact urls """
        latest_dev_build_url = 'https://circleci.com/api/v1/project/' + self.circle_username + '/' \
                               + self.circle_project + '/tree/' + self.circle_branch + '?circle-token=' \
                               + self.api_token + '&limit=1'

        headers = {'Accept': 'application/json'}
        response = requests.get(latest_dev_build_url, headers=headers)
        builds_json = json.loads(response.text)
        build_number = int(builds_json[0]['build_num'])

        artifacts_url = 'https://circleci.com/api/v1/project/' + self.circle_username + '/' \
                        + self.circle_project + '/' + str(build_number) + '/artifacts?circle-token=' + self.api_token
        response = requests.get(artifacts_url, headers=headers)
        return json.loads(response.text)
Exemple #4
0
class EmailIMAP():
    def __init__(self):
        self.test_control = ControlTest()
        self.email_address = self.test_control.gid('email_address')
        self.email_imap = self.test_control.gid('email_imap')
        self.email_password = self.test_control.gid('email_password')
        self.email_mailbox = self.test_control.gid('email_mailbox')

        self.mail = imaplib.IMAP4_SSL(self.email_imap)
        self.mail.login(self.email_address, self.email_password)

    def retrieve_latest_email(self, timeout=36):
        """ tries to retrieve latest email. If there are no emails,
        it waits for 5 seconds and then tries again, until total (default 36*5 = 180s) timeout is reached """
        count = 0
        while count < timeout:
            id_list = self.get_all_email_ids()
            print id_list
            if len(id_list) > 0:
                return self.get_latest_message()
            else:
                time.sleep(5)
                count += 1
        return None

    def cleanup_emails(self):
        """ cleans up email folder """
        for num in self.get_all_email_ids():
            self.mail.store(num, '+FLAGS', '\\Deleted')
        self.mail.expunge()

    def is_pattern_in_message(self, pattern, message):
        """ return Boolean if pattern is to be found in message """
        return bool(re.match(pattern, message))

    # SUPPORT METHODS

    def get_latest_message(self):
        """ gets last email message """
        id_list = self.get_all_email_ids()
        latest_email_id = id_list[-1]  # get the latest email

        result, data = self.mail.fetch(latest_email_id, "(RFC822)")  # fetch the email body (RFC822) for the given ID
        raw_email = data[0][1]  # here's the body, which is raw text of the whole email
        return email.message_from_string(raw_email)

    def get_all_email_ids(self):
        """ connects to gmail account and return list of all email ids """
        self.mail.select(self.email_mailbox)
        result, data = self.mail.search(None, "ALL")

        ids = data[0]
        id_list = ids.split()
        return id_list
Exemple #5
0
class CircleAPI(object):
    """Handles communication with Circle CI via REST API"""

    def __init__(self):
        self.test_control = ControlTest()
        self.api_token = self.test_control.gid('circleci_api_token')
        self.circle_username = self.test_control.gid('circleci_username')
        self.circle_project = self.test_control.gid('circleci_project')
        self.circle_branch = self.test_control.gid('circleci_branch')

    def collect_artifacts(self, destination_folder):
        """downloads build artifacts from CircleCI for latest build from specific branch"""
        if not os.path.exists(destination_folder):
            os.makedirs(destination_folder)
        artifact_data = self.get_artifact_data()
        for artifact in artifact_data:
            self.save_artifact(artifact, destination_folder)
        return bool(os.listdir(destination_folder))

    def save_artifact(self, artifact, destination_folder):
        """ saves artifact into specified folder """
        file_name = artifact['url'].split('/')[-1]
        file_request_url = artifact['url'] + '?circle-token=' + self.api_token

        response = requests.get(file_request_url, stream=True)
        response.raise_for_status()

        with open(os.path.join(destination_folder, file_name), 'wb') as extension:
            for block in response.iter_content(1024):
                extension.write(block)

    def get_artifact_data(self):
        """returns json with artifact urls"""
        latest_dev_build_url = (
            'https://circleci.com/api/v1/project/{circle_username}/{circle_project}/tree/'
            '{circle_branch}?circle-token={api_token}&limit=1').format(**self.__dict__)

        headers = {'Accept': 'application/json'}
        response = requests.get(latest_dev_build_url, headers=headers)
        builds_json = json.loads(response.text)
        build_number = builds_json[0]['build_num']

        artifacts_url = (
            'https://circleci.com/api/v1/project/{circle_username}/'
            '{circle_project}/{build_number}/artifacts?circle-token={api_token}'
        ).format(build_number=build_number, **self.__dict__)

        response = requests.get(artifacts_url, headers=headers)
        return json.loads(response.text)
    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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid('driver')
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, 'results', self.timestamp)

        # load configuration files
        self.bs_config_file = os.path.join(self.project_root, 'config', 'browserstack.properties')
        self.bs_config_file_smoke = os.path.join(self.project_root, 'config', 'browserstack_smoke.properties')
        if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(self.bs_config_file_smoke)):
            sys.exit('One of browserstack properties files not found! Session terminated.')
        self.bs_config = ConfigParser.RawConfigParser()

        # load cmd arguments and set default values if not specified
        cmd_args = self.get_runner_args()
        self.env_type = cmd_args[0]
        self.test_type = cmd_args[1]

        # load browserstack credentials from cmd argument, if available
        if cmd_args[2] != 'none':
            browserstack_auth = cmd_args[2].split(':')
            os.environ['bs_username'] = browserstack_auth[0]
            os.environ['bs_password'] = browserstack_auth[1]
Exemple #7
0
    def __init__(self):
        self.test_control = ControlTest()
        self.email_address = self.test_control.gid('email_address')
        self.email_imap = self.test_control.gid('email_imap')
        self.email_password = self.test_control.gid('email_password')
        self.email_mailbox = self.test_control.gid('email_mailbox')

        self.mail = imaplib.IMAP4_SSL(self.email_imap)
        self.mail.login(self.email_address, self.email_password)
Exemple #8
0
    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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid('driver')
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, 'results', self.timestamp)

        # load configuration files
        if self.driver_name is not None:
            self.bs_config_file = os.path.join(self.project_root, 'config', 'browserstack.properties')
            self.bs_config_file_smoke = os.path.join(self.project_root, 'config', 'browserstack_smoke.properties')
            self.bs_config_file_mobile = os.path.join(self.project_root, 'config', 'browserstack_mobile.properties')
            self.bs_config = ConfigParser.RawConfigParser()
        self.bs_username = None
        self.bs_password = None
        self.jira_username = None
        self.jira_password = None
        # load credentials
        credentials = self.load_services_credentials()
        self.reporting = credentials['reporting']
        self.env_type = credentials['env_type']
        self.test_type = credentials['test_type']
        self.test_mobile = credentials['test_mobile']
        self.jira_username = credentials['jira_username']
        self.jira_password = credentials['jira_password']
        self.cycle_id = credentials['cycle_id']
        self.bs_username = credentials['bs_username']
        self.bs_password = credentials['bs_password']
        # check if configuration files are present
        if self.reporting != "simple" and self.driver_name is not None:
            if self.test_mobile == 'yes':
                if not (os.path.exists(self.bs_config_file_mobile)):
                    sys.exit('Browserstack mobile properties file not found! Session terminated.')
            else:
                if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(self.bs_config_file_smoke)):
                    sys.exit('One of browserstack properties files not found! Session terminated.')
Exemple #9
0
    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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid("driver")
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, "results", self.timestamp)

        # load configuration files
        self.bs_config_file = os.path.join(self.project_root, "config", "browserstack.properties")
        self.bs_config_file_smoke = os.path.join(self.project_root, "config", "browserstack_smoke.properties")
        if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(self.bs_config_file_smoke)):
            sys.exit("One of browserstack properties files not found! Session terminated.")
        self.bs_config = ConfigParser.RawConfigParser()

        # load cmd arguments and set default values if not specified
        cmd_args = self.get_runner_args()
        self.env_type = cmd_args[0]
        self.test_type = cmd_args[1]
Exemple #10
0
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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid('driver')
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, 'results',
                                          self.timestamp)

        # load configuration files
        self.bs_config_file = os.path.join(self.project_root, 'config',
                                           'browserstack.properties')
        self.bs_config_file_smoke = os.path.join(
            self.project_root, 'config', 'browserstack_smoke.properties')
        if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(
                self.bs_config_file_smoke)):
            sys.exit(
                'One of browserstack properties files not found! Session terminated.'
            )
        self.bs_config = ConfigParser.RawConfigParser()

        # load cmd arguments and set default values if not specified
        cmd_args = self.get_runner_args()
        self.env_type = cmd_args[0]
        self.test_type = cmd_args[1]

    def set_project_root(self):
        """ Sets tested project root folder into OS environment variable """
        if os.path.exists(self.project_root) and os.path.isdir(
                self.project_root):
            os.environ['SALSA_WEBQA_PROJECT'] = self.project_root
        else:
            sys.exit('Project directory "' + self.project_root +
                     '" does not exist! Session terminated.')

    def run_tests(self):
        """ Triggers PyTest runner locally or on BrowserStack.
        It runs PyTest for each BS combination, taken from either versioned .properties file or environment variable """
        # check that runner is not run directly
        if __name__ == "__main__":
            sys.exit(
                'The runner cannot be executed directly.'
                ' You need to import it within project specific runner. Session terminated.'
            )
        else:
            if self.driver_name.lower() == 'browserstack':
                test_status = self.run_on_browserstack()
            else:
                test_status = self.run_locally()
            self.archive_results()
            self.generate_combined_report()
            return test_status

    def run_on_browserstack(self):
        """ Runs tests on BrowserStack """
        test_status = 0
        if bs_api.wait_for_free_sessions(
            (self.tc.gid('bs_username'), self.tc.gid('bs_password')),
                self.tc.gid('session_waiting_time'),
                self.tc.gid('session_waiting_delay')):
            self.cleanup_results()

            # load browserstack variables from configuration files
            if self.env_type == 'versioned':
                if self.test_type == 'smoke':
                    self.bs_config.read(self.bs_config_file_smoke)
                else:
                    self.bs_config.read(self.bs_config_file)
                for config_section in self.bs_config.sections():
                    print('Running combination: ' + config_section)
                    test_status = self.trigger_pytest(config_section)

            # load browserstack variables from OS environment variable
            elif self.env_type == 'direct':
                config_list = json.loads(str(os.environ['BROWSERSTACK']))
                for config_section in config_list['test_suite']:
                    print('Running combination: ' + str(config_section))
                    test_status = self.trigger_pytest(config_section)
        return test_status

    def run_locally(self):
        """ Runs tests on local browser """
        self.cleanup_results()
        print('Running for browser: ' + self.driver_name)
        return self.trigger_pytest(self.driver_name)

    def trigger_pytest(self, config_section):
        """ Runs PyTest runner on specific configuration """
        pytest_arguments = [os.path.join(self.project_root, 'tests')]

        # set pytest parallel execution argument
        parallel_tests = int(self.tc.gid('parallel_tests'))
        if parallel_tests > 1:
            pytest_arguments.extend(['-n', str(parallel_tests)])

        # set pytest smoke test argument
        if self.test_type == 'smoke':
            pytest_arguments.extend(['-m', self.test_type])

        # setup pytest arguments for browserstack
        if self.driver_name.lower() == 'browserstack':

            # set pytest arguments values from OS environment variable
            if self.env_type == 'direct':
                browser = config_section['browser']
                browser_version = config_section['browser_version']
                os_type = config_section['os']
                os_version = config_section['os_version']
                resolution = config_section['resolution']
                junitxml_path = os.path.join(
                    self.result_folder, browser + browser_version + os_type +
                    os_version + resolution + '.xml')
                html_path = os.path.join(
                    self.result_folder, browser + browser_version + os_type +
                    os_version + resolution + '.html')

            # set pytest arguments values from configuration files
            else:
                if self.test_type == 'smoke':
                    self.bs_config.read(self.bs_config_file_smoke)
                else:
                    self.bs_config.read(self.bs_config_file)

                browser = self.bs_config.get(config_section, 'browser')
                browser_version = self.bs_config.get(config_section,
                                                     'browser_version')
                os_type = self.bs_config.get(config_section, 'os')
                os_version = self.bs_config.get(config_section, 'os_version')
                resolution = self.bs_config.get(config_section, 'resolution')
                junitxml_path = os.path.join(self.result_folder,
                                             config_section + '.xml')
                html_path = os.path.join(self.result_folder,
                                         config_section + '.html')
            test_result_prefix = '[' + browser + ', ' + browser_version + ', ' + os_type \
                                 + ', ' + os_version + ', ' + resolution + ']'

            # prepare pytest arguments into execution list
            pytest_arguments.extend([
                '--junitxml=' + junitxml_path,
                '--junit-prefix=' + test_result_prefix, '--html=' + html_path,
                '--html-prefix=' + test_result_prefix, '--xbrowser=' + browser,
                '--xbrowserversion=' + browser_version, '--xos=' + os_type,
                '--xosversion=' + os_version, '--xresolution=' + resolution,
                '--instafail'
            ])

        # setup pytest arguments for local browser
        else:
            pytest_arguments.append(
                '--junitxml=' +
                os.path.join(self.result_folder, self.driver_name + '.xml'))
            pytest_arguments.append(
                '--html=' +
                os.path.join(self.result_folder, self.driver_name + '.html'))

        # run pytest and return its exit code
        return pytest.main(pytest_arguments)

    def get_runner_args(self):
        """ Retrieves the command line arguments passed to the script """
        parser = argparse.ArgumentParser(
            description='Selenium Python test runner execution arguments.')
        parser.add_argument(
            '--env',
            help='BrowserStack environments; '
            'options: "direct" - passed as OS environment variable '
            '(JSON format), "versioned" (default) - loaded from *.properties configuration files',
            default='versioned')
        parser.add_argument(
            '--tests',
            help='Tests to run; options: "smoke", "all" (default)',
            default='all')
        args = parser.parse_args()
        return [args.env, args.tests]

    def cleanup_results(self):
        """ Cleans up test result folder """
        if os.path.exists(os.path.join(self.project_root, 'results')):
            shutil.rmtree(os.path.join(self.project_root, 'results'))
        os.makedirs(self.result_folder)

    def archive_results(self):
        """ Archives test results in zip package """
        archive_folder = os.path.join(self.project_root, 'results_archive')
        if not (os.path.exists(archive_folder)):
            os.makedirs(archive_folder)
        shutil.make_archive(os.path.join(archive_folder, self.timestamp),
                            "zip", os.path.join(self.project_root, 'results'))

    def generate_combined_report(self):
        data = os.listdir(
            os.path.join(self.project_root, 'results', self.timestamp))
        result_reports = [item[:-5] for item in data if item.endswith('.html')]
        if len(result_reports) > 0:
            env = Environment(loader=FileSystemLoader(
                os.path.join(self.current_folder, 'library', 'report',
                             'resources')))
            template = env.get_template('CombinedReportTemplate.html')
            template_vars = {'data': result_reports}
            output = template.render(template_vars)
            formatted_output = output.encode('utf8').strip()
            final_report = open(
                os.path.join(self.project_root, 'results', self.timestamp,
                             'CombinedReport.html'), 'w')
            final_report.write(formatted_output)
            final_report.close()
            shutil.copy(
                os.path.join(self.current_folder, 'library', 'report',
                             'resources', 'combined_report.js'),
                os.path.join(self.project_root, 'results', self.timestamp))
Exemple #11
0
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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid("driver")
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, "results", self.timestamp)

        # load configuration files
        self.bs_config_file = os.path.join(self.project_root, "config", "browserstack.properties")
        self.bs_config_file_smoke = os.path.join(self.project_root, "config", "browserstack_smoke.properties")
        if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(self.bs_config_file_smoke)):
            sys.exit("One of browserstack properties files not found! Session terminated.")
        self.bs_config = ConfigParser.RawConfigParser()

        # load cmd arguments and set default values if not specified
        cmd_args = self.get_runner_args()
        self.env_type = cmd_args[0]
        self.test_type = cmd_args[1]

    def set_project_root(self):
        """ Sets tested project root folder into OS environment variable """
        if os.path.exists(self.project_root) and os.path.isdir(self.project_root):
            os.environ["SALSA_WEBQA_PROJECT"] = self.project_root
        else:
            sys.exit('Project directory "' + self.project_root + '" does not exist! Session terminated.')

    def run_tests(self):
        """ Triggers PyTest runner locally or on BrowserStack.
        It runs PyTest for each BS combination, taken from either versioned .properties file or environment variable """
        # check that runner is not run directly
        if __name__ == "__main__":
            sys.exit(
                "The runner cannot be executed directly."
                " You need to import it within project specific runner. Session terminated."
            )
        else:
            if self.driver_name.lower() == "browserstack":
                test_status = self.run_on_browserstack()
            else:
                test_status = self.run_locally()
            self.archive_results()
            self.generate_combined_report()
            return test_status

    def run_on_browserstack(self):
        """ Runs tests on BrowserStack """
        test_status = 0
        if bs_api.wait_for_free_sessions(
            (self.tc.gid("bs_username"), self.tc.gid("bs_password")),
            self.tc.gid("session_waiting_time"),
            self.tc.gid("session_waiting_delay"),
        ):
            self.cleanup_results()

            # load browserstack variables from configuration files
            if self.env_type == "versioned":
                if self.test_type == "smoke":
                    self.bs_config.read(self.bs_config_file_smoke)
                else:
                    self.bs_config.read(self.bs_config_file)
                for config_section in self.bs_config.sections():
                    print("Running combination: " + config_section)
                    test_status = self.trigger_pytest(config_section)

            # load browserstack variables from OS environment variable
            elif self.env_type == "direct":
                config_list = json.loads(str(os.environ["BROWSERSTACK"]))
                for config_section in config_list["test_suite"]:
                    print("Running combination: " + str(config_section))
                    test_status = self.trigger_pytest(config_section)
        return test_status

    def run_locally(self):
        """ Runs tests on local browser """
        self.cleanup_results()
        print("Running for browser: " + self.driver_name)
        return self.trigger_pytest(self.driver_name)

    def trigger_pytest(self, config_section):
        """ Runs PyTest runner on specific configuration """
        pytest_arguments = [os.path.join(self.project_root, "tests")]

        # set pytest parallel execution argument
        parallel_tests = int(self.tc.gid("parallel_tests"))
        if parallel_tests > 1:
            pytest_arguments.extend(["-n", str(parallel_tests)])

        # set pytest smoke test argument
        if self.test_type == "smoke":
            pytest_arguments.extend(["-m", self.test_type])

        # setup pytest arguments for browserstack
        if self.driver_name.lower() == "browserstack":

            # set pytest arguments values from OS environment variable
            if self.env_type == "direct":
                browser = config_section["browser"]
                browser_version = config_section["browser_version"]
                os_type = config_section["os"]
                os_version = config_section["os_version"]
                resolution = config_section["resolution"]
                junitxml_path = os.path.join(
                    self.result_folder, browser + browser_version + os_type + os_version + resolution + ".xml"
                )
                html_path = os.path.join(
                    self.result_folder, browser + browser_version + os_type + os_version + resolution + ".html"
                )

            # set pytest arguments values from configuration files
            else:
                if self.test_type == "smoke":
                    self.bs_config.read(self.bs_config_file_smoke)
                else:
                    self.bs_config.read(self.bs_config_file)

                browser = self.bs_config.get(config_section, "browser")
                browser_version = self.bs_config.get(config_section, "browser_version")
                os_type = self.bs_config.get(config_section, "os")
                os_version = self.bs_config.get(config_section, "os_version")
                resolution = self.bs_config.get(config_section, "resolution")
                junitxml_path = os.path.join(self.result_folder, config_section + ".xml")
                html_path = os.path.join(self.result_folder, config_section + ".html")
            test_result_prefix = (
                "[" + browser + ", " + browser_version + ", " + os_type + ", " + os_version + ", " + resolution + "]"
            )

            # prepare pytest arguments into execution list
            pytest_arguments.extend(
                [
                    "--junitxml=" + junitxml_path,
                    "--junit-prefix=" + test_result_prefix,
                    "--html=" + html_path,
                    "--html-prefix=" + test_result_prefix,
                    "--xbrowser=" + browser,
                    "--xbrowserversion=" + browser_version,
                    "--xos=" + os_type,
                    "--xosversion=" + os_version,
                    "--xresolution=" + resolution,
                    "--instafail",
                ]
            )

        # setup pytest arguments for local browser
        else:
            pytest_arguments.append("--junitxml=" + os.path.join(self.result_folder, self.driver_name + ".xml"))
            pytest_arguments.append("--html=" + os.path.join(self.result_folder, self.driver_name + ".html"))

        # run pytest and return its exit code
        return pytest.main(pytest_arguments)

    def get_runner_args(self):
        """ Retrieves the command line arguments passed to the script """
        parser = argparse.ArgumentParser(description="Selenium Python test runner execution arguments.")
        parser.add_argument(
            "--env",
            help="BrowserStack environments; "
            'options: "direct" - passed as OS environment variable '
            '(JSON format), "versioned" (default) - loaded from *.properties configuration files',
            default="versioned",
        )
        parser.add_argument("--tests", help='Tests to run; options: "smoke", "all" (default)', default="all")
        args = parser.parse_args()
        return [args.env, args.tests]

    def cleanup_results(self):
        """ Cleans up test result folder """
        if os.path.exists(os.path.join(self.project_root, "results")):
            shutil.rmtree(os.path.join(self.project_root, "results"))
        os.makedirs(self.result_folder)

    def archive_results(self):
        """ Archives test results in zip package """
        archive_folder = os.path.join(self.project_root, "results_archive")
        if not (os.path.exists(archive_folder)):
            os.makedirs(archive_folder)
        shutil.make_archive(
            os.path.join(archive_folder, self.timestamp), "zip", os.path.join(self.project_root, "results")
        )

    def generate_combined_report(self):
        data = os.listdir(os.path.join(self.project_root, "results", self.timestamp))
        result_reports = [item[:-5] for item in data if item.endswith(".html")]
        if len(result_reports) > 0:
            env = Environment(
                loader=FileSystemLoader(os.path.join(self.current_folder, "library", "report", "resources"))
            )
            template = env.get_template("CombinedReportTemplate.html")
            template_vars = {"data": result_reports}
            output = template.render(template_vars)
            formatted_output = output.encode("utf8").strip()
            final_report = open(os.path.join(self.project_root, "results", self.timestamp, "CombinedReport.html"), "w")
            final_report.write(formatted_output)
            final_report.close()
            shutil.copy(
                os.path.join(self.current_folder, "library", "report", "resources", "combined_report.js"),
                os.path.join(self.project_root, "results", self.timestamp),
            )
Exemple #12
0
 def __init__(self):
     self.test_control = ControlTest()
     self.api_token = self.test_control.gid('circleci_api_token')
     self.circle_username = self.test_control.gid('circleci_username')
     self.circle_project = self.test_control.gid('circleci_project')
     self.circle_branch = self.test_control.gid('circleci_branch')
 def __init__(self, driver):
     self.driver = driver
     self.tc = ControlTest()
     self.base_url = self.tc.gid('base_url')
     self.default_implicit_wait = int(self.tc.gid('default_implicit_wait'))
     self.timeout = int(self.tc.gid('timeout'))
class SeleniumTest():
    def __init__(self, driver):
        self.driver = driver
        self.tc = ControlTest()
        self.base_url = self.tc.gid('base_url')
        self.default_implicit_wait = int(self.tc.gid('default_implicit_wait'))
        self.timeout = int(self.tc.gid('timeout'))

    def save_screenshot(self, name, project_root):
        """ Saves application screenshot """
        screenshot_folder = os.path.join(project_root, 'screenshots')
        if not os.path.exists(screenshot_folder):
            os.makedirs(screenshot_folder)
        self.driver.save_screenshot(os.path.join(screenshot_folder, name + '.png'))

    def save_file_from_url(self, file_path, url):
        """ Saves file from url """
        if not os.path.isfile(file_path):
            save_file = open(file_path, 'wb')
            response = requests.get(url, stream=True)

            if not response.ok:
                print('Something went wrong when requesting file from url "' + str(url) + '".')

            for block in response.iter_content(1024):
                if not block:
                    break

                save_file.write(block)
            save_file.close()
        else:
            print('File "' + str(file_path) + '" already exists.')

    def get_base_url(self):
        """ Return the url for the current page."""
        return self.base_url

    def get_current_url(self):
        """ Return the url for the current page."""
        return self.driver.current_url

    def hover_on(self, element):
        """ Mouse over specific element """
        mouse_over = ActionChains(self.driver).move_to_element(element)
        mouse_over.perform()

    def go_to_page(self, url):
        """ Opens url in currently active window """
        self.driver.get(url)
        self.driver.implicitly_wait(self.default_implicit_wait)

    def click_and_wait(self, element, locator=None):
        """ clicks on a element and then waits for specific element to be present or simply waits implicitly """
        element.click()
        if locator is None:
            self.driver.implicitly_wait(10)
        else:
            self.wait_for_element_ready(locator)

    def check_images_are_loaded(self):
        """ checks all images on the pages and verifies if they are properly loaded """
        images_not_loaded = []
        for image in self.driver.find_elements_by_tag_name('img'):
            script = 'return arguments[0].complete && typeof arguments[0].naturalWidth' \
                     ' != "undefined" && arguments[0].naturalWidth > 0'
            image_loaded = bool(self.driver.execute_script(script, image))
            if not image_loaded:
                if image.get_attribute('src') is not None:
                    images_not_loaded.append(self.driver.title + ': ' + str(image.get_attribute('src')))
        return images_not_loaded

    def is_element_present(self, locator):
        """
        True if the element at the specified locator is present in the DOM.
        Note: It returns false immediately if the element is not found.
        """
        self.driver.implicitly_wait(0)
        try:
            self.driver.find_element(*locator)
            return True
        except NoSuchElementException:
            return False
        finally:
            # set the implicit wait back
            self.driver.implicitly_wait(self.default_implicit_wait)

    def is_element_visible(self, locator):
        """
        True if the element at the specified locator is visible in the browser.
        Note: It uses an implicit wait if element is not immediately found.
        """
        try:
            return self.driver.find_element(*locator).is_displayed()
        except (NoSuchElementException, ElementNotVisibleException):
            return False

    def is_element_not_visible(self, locator):
        """
        True if the element at the specified locator is not visible.
        Note: It returns true immediately if the element is not found.
        """
        self.driver.implicitly_wait(0)
        try:
            return not self.driver.find_element(*locator).is_displayed()
        except (NoSuchElementException, ElementNotVisibleException):
            return True
        finally:
            # set the implicit wait back
            self.driver.implicitly_wait(self.default_implicit_wait)

    def wait_for_element_present(self, locator, timeout=None):
        """ Wait for the element at the specified locator
        to be present in the DOM. """
        if timeout is None:
            timeout = self.timeout
        count = 0
        while not self.is_element_present(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception(str(locator) + ' has not loaded')

    def wait_for_element_visible(self, locator, timeout=None):
        """
        Wait for the element at the specified locator to be visible.
        """
        if timeout is None:
            timeout = self.timeout
        count = 0
        while not self.is_element_visible(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception(str(locator) + " is not visible")

    def wait_for_element_not_visible(self, locator, timeout=None):
        """
        Wait for the element at the specified locator not to be visible anymore.
        """
        if timeout is None:
            timeout = self.timeout
        count = 0
        while self.is_element_visible(locator):
            time.sleep(1)
            count += 1
            if count == timeout:
                raise Exception(str(locator) + " is still visible")

    def wait_for_element_not_present(self, locator, timeout=None):
        """ Wait for the element at the specified locator
         not to be present in the DOM. """
        if timeout is None:
            timeout = self.timeout
        self.driver.implicitly_wait(0)
        try:
            WebDriverWait(self.driver, timeout).until(
                lambda s: len(self.find_elements(*locator)) < 1)
            return True
        except TimeoutException:
            Assert.fail(TimeoutException)
        finally:
            self.driver.implicitly_wait(self.default_implicit_wait)

    def wait_for_text_to_match(self, text, locator, max_count=20, delay=0.25):
        """ Waits for element text to match specified text, until certain deadline """
        element = self.driver.find_element(*locator)
        counter = 0
        while element.text != text:
            if counter < max_count:
                time.sleep(delay)
                counter += 1
            else:
                Assert.fail('"' + text + '" text did not match "' + element.text
                            + '" after ' + str(counter * delay) + ' seconds')
                break

    def wait_for_attribute_value(self, attribute, attribute_text, locator, max_count=20, delay=0.25):
        """ Waits for element attribute value to match specified text, until certain deadline """
        element = self.driver.find_element(*locator)
        counter = 0
        while element.get_attribute(attribute) != attribute_text:
            if counter < max_count:
                time.sleep(delay)
                counter += 1
            else:
                Assert.fail('"' + attribute_text + '" text did not match "' + element.get_attribute(attribute)
                            + '" after ' + str(counter * delay) + ' seconds')
                break

    def wait_for_element_ready(self, locator, timeout=None):
        """ Waits until certain element is present and clickable """
        if timeout is None:
            timeout = self.timeout
        WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator),
                                                  'Element specified by ' + str(locator) + ' was not present!')
        WebDriverWait(self.driver, timeout).until(EC.element_to_be_clickable(locator),
                                                  'Element specified by ' + str(
                                                      locator) + ' did not become clickable!')

    def find_element(self, locator):
        """ Return the element at the specified locator."""
        return self.driver.find_element(*locator)

    def find_elements(self, locator):
        """ Return a list of elements at the specified locator."""
        return self.driver.find_elements(*locator)

    def find_elements_with_text(self, text, locator):
        """ Find elements that have specified text """
        elements = self.driver.find_elements(*locator)
        selected = [item for item in elements if item.text == text]
        if len(selected) == 1:
            return selected[0]
        else:
            return selected

    def link_destination(self, locator):
        """ Return the href attribute of the element at the specified locator."""
        link = self.driver.find_element(*locator)
        return link.get_attribute('href')

    def image_source(self, locator):
        """ Return the src attribute of the element at the specified locator."""
        link = self.driver.find_element(*locator)
        return link.get_attribute('src')

    def select_dropdown_value(self, select, value):
        """ Set 'select' dropdown value """
        select = Select(select)
        option = [option for option in select.options if option.text == value][0]
        option.click()

    def upload_file(self, file_path, input_field_locator, delay=5):
        """ uploads file through the file input field
            @file_path: path to file (including the file name) relative to test project root
            @input_field_locator: locator of input element with type="file"
            @delay: seconds to wait for file to upload
        """
        file_path = os.path.join(self.tc.project_root, file_path)
        self.driver.find_element(*input_field_locator).send_keys(file_path)
        time.sleep(delay)

    def execute_js_script(self, script, arguments=None):
        """execute any js command with arguments or without it"""
        script_value = self.driver.execute_script(script, arguments)
        return script_value

    def open_new_tab(self, url):
        """Open new tab using keyboard, for now work only in Firefox and IE, in Chrome use js script to open tab """
        ActionChains(self.driver).send_keys(Keys.CONTROL, "t").perform()
        windows = self.driver.window_handles
        self.driver.switch_to_window(windows[-1])
        self.driver.get(url)

    def switch_new_tab(self):
        """switch to new tab/window"""
        windows = self.driver.window_handles
        self.driver.switch_to_window(windows[-1])

    def switch_first_tab(self):
        """Close current tab, switch to first tab/window"""
        windows = self.driver.window_handles
        self.driver.close()
        self.driver.switch_to_window(windows[0])
Exemple #15
0
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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid('driver')
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, 'results', self.timestamp)

        # load configuration files
        if self.driver_name is not None:
            self.bs_config_file = os.path.join(self.project_root, 'config', 'browserstack.properties')
            self.bs_config_file_smoke = os.path.join(self.project_root, 'config', 'browserstack_smoke.properties')
            self.bs_config_file_mobile = os.path.join(self.project_root, 'config', 'browserstack_mobile.properties')
            self.bs_config = ConfigParser.RawConfigParser()
        self.bs_username = None
        self.bs_password = None
        self.jira_username = None
        self.jira_password = None
        # load credentials
        credentials = self.load_services_credentials()
        self.reporting = credentials['reporting']
        self.env_type = credentials['env_type']
        self.test_type = credentials['test_type']
        self.test_mobile = credentials['test_mobile']
        self.jira_username = credentials['jira_username']
        self.jira_password = credentials['jira_password']
        self.cycle_id = credentials['cycle_id']
        self.bs_username = credentials['bs_username']
        self.bs_password = credentials['bs_password']
        # check if configuration files are present
        if self.reporting != "simple" and self.driver_name is not None:
            if self.test_mobile == 'yes':
                if not (os.path.exists(self.bs_config_file_mobile)):
                    sys.exit('Browserstack mobile properties file not found! Session terminated.')
            else:
                if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(self.bs_config_file_smoke)):
                    sys.exit('One of browserstack properties files not found! Session terminated.')

    def load_services_credentials(self):
        # load cmd arguments and set default values if not specified
        credentials = {}
        cmd_args = self.get_runner_args()
        credentials['reporting'] = cmd_args[0]
        credentials['env_type'] = cmd_args[1]
        credentials['test_type'] = cmd_args[2]
        credentials['test_mobile'] = cmd_args[3]
        credentials['jira_username'] = None
        credentials['jira_password'] = None
        credentials['cycle_id'] = None
        credentials['bs_username'] = None
        credentials['bs_password'] = None

        # load Jira credentials from cmd argument, if available
        if cmd_args[4] != 'none':
            # Jira support activated
            jira_auth = cmd_args[4].split(':')
            auth = (jira_auth[0], jira_auth[1])
            credentials['jira_username'] = jira_auth[0]
            credentials['jira_password'] = jira_auth[1]
            # create test cycle and remember it's id in env variable
            credentials['cycle_id'] = self.tc.create_cycle(self.tc.gid('jira_cycle_name'), auth)

        # load browserstack credentials from cmd argument, if available
        if cmd_args[5] != 'none':
            browserstack_auth = cmd_args[5].split(':')
            credentials['bs_username'] = browserstack_auth[0]
            credentials['bs_password'] = browserstack_auth[1]
        return credentials

    def set_project_root(self):
        """ Sets tested project root folder into OS environment variable """
        if os.path.exists(self.project_root) and os.path.isdir(self.project_root):
            os.environ['SALSA_WEBQA_PROJECT'] = self.project_root
        else:
            sys.exit('Project directory "' + self.project_root + '" does not exist! Session terminated.')

    def run_tests(self):
        """ Triggers PyTest runner locally or on BrowserStack.
        It runs PyTest for each BS combination, taken from either versioned .properties file or environment variable """
        # check that runner is not run directly
        if __name__ == "__main__":
            sys.exit('The runner cannot be executed directly.'
                     ' You need to import it within project specific runner. Session terminated.')
        else:
            test_status = 0
            self.cleanup_results()
            if self.reporting == 'simple' or self.driver_name is None:
                test_status = self.trigger_pytest()
            elif self.reporting == 'all':
                if self.driver_name.lower() == 'browserstack':
                    test_status_selenium = self.run_on_browserstack()
                else:
                    test_status_selenium = self.run_locally()
                test_status_simple = self.trigger_pytest()
                test_status = max(test_status_selenium, test_status_simple)

            elif self.reporting == 'selenium':
                if self.driver_name.lower() == 'browserstack':
                    test_status = self.run_on_browserstack()
                else:
                    test_status = self.run_locally()
            self.archive_results()
            self.generate_combined_report()
            return test_status

    def run_on_browserstack(self):
        """ Runs tests on BrowserStack """
        test_status = 0
        # If password not provided in command line look ad server configuration file
        if self.bs_username is None:
            credentials = self.tc.gid('browserstack').split(':')
            self.bs_username = credentials[0]
            self.bs_password = credentials[1]
        if bs_api.wait_for_free_sessions((self.bs_username, self.bs_password),
                                         self.tc.gid('session_waiting_time'), self.tc.gid('session_waiting_delay')):
            # load browserstack variables from configuration files
            if self.env_type == 'versioned':
                if self.test_type == 'smoke':
                    self.bs_config.read(self.bs_config_file_smoke)
                elif self.test_mobile == 'yes':
                    self.bs_config.read(self.bs_config_file_mobile)
                else:
                    self.bs_config.read(self.bs_config_file)
                for config_section in self.bs_config.sections():
                    print('Running combination: ' + config_section)
                    test_status = self.trigger_pytest(config_section)

            # load browserstack variables from OS environment variable
            elif self.env_type == 'direct':
                config_list = json.loads(str(os.environ['BROWSERSTACK']))
                for config_section in config_list['test_suite']:
                    print('Running combination: ' + str(config_section))
                    test_status = self.trigger_pytest(config_section)
        return test_status

    def run_locally(self):
        """ Runs tests on local browser """
        print('Running for browser: ' + self.driver_name)
        return self.trigger_pytest(self.driver_name)

    def trigger_pytest(self, config_section=None):
        """ Runs PyTest runner on specific configuration """
        if config_section is None or self.reporting == 'simple':
            path = os.path.join(self.project_root, 'non_selenium_tests')
            print('Running non selenium tests')
            if not os.path.exists(path):
                print("Can't run non selenium tests files are not found")
                return None
            pytest_arguments = [os.path.join(self.project_root, 'non_selenium_tests'),
                                '--junitxml=' + os.path.join(self.result_folder, "Non_selenium_report" + '.xml'),
                                '--html=' + os.path.join(self.result_folder, "Non_selenium_report" + '.html')]
            return pytest.main(pytest_arguments)
        pytest_arguments = [os.path.join(self.project_root, 'tests')]
        # set pytest parallel execution argument
        parallel_tests = int(self.tc.gid('parallel_tests'))
        if parallel_tests > 1:
            pytest_arguments.extend(['-n', str(parallel_tests)])

        # set pytest smoke test argument
        if self.test_type == 'smoke':
            pytest_arguments.extend(['-m', self.test_type])

        if self.cycle_id:
            pytest_arguments.extend(['--jira_cycle_id', self.cycle_id])

        # setup pytest arguments for browserstack
        if self.driver_name.lower() == 'browserstack':
            # get arguments for running tests on mobile browsers
            if self.test_mobile == 'yes':
                pytest_arguments = self.get_pytest_arguments_mobile(config_section, pytest_arguments)
            # get arguments for running tests on desktop browsers
            else:
                pytest_arguments = self.get_pytest_arguments_desktop(config_section, pytest_arguments)

        # setup pytest arguments for local browser
        else:
            pytest_arguments.append('--junitxml=' + os.path.join(self.result_folder, self.driver_name + '.xml'))
            pytest_arguments.append('--html=' + os.path.join(self.result_folder, self.driver_name + '.html'))

        # check if jira credentials are setup and add it to pytest
        if self.jira_username and self.jira_password:
            pytest_arguments.append('--jira_support=' + self.jira_username + ":" + self.jira_password)
        if self.bs_username and self.bs_password:
            pytest_arguments.append('--browserstack=' + self.bs_username + ":" + self.bs_password)

        # run pytest and return its exit code
        return pytest.main(pytest_arguments)

    def get_pytest_arguments_desktop(self, config_section, pytest_arguments):
        """ get pytest arguments to run tests on browserstack desktop browsers """
        # arguments passed through environment variable
        if self.env_type == 'direct':
            browser = config_section['browser']
            browser_version = config_section['browser_version']
            os_type = config_section['os']
            os_version = config_section['os_version']
            resolution = config_section['resolution']
            junit_xml_path = os.path.join(self.result_folder, browser + browser_version
                                          + os_type + os_version + resolution + '.xml')
            html_path = os.path.join(self.result_folder, browser + browser_version
                                     + os_type + os_version + resolution + '.html')
        # arguments passed through config file
        else:
            if self.test_type == 'smoke':
                self.bs_config.read(self.bs_config_file_smoke)
            else:
                self.bs_config.read(self.bs_config_file)
            browser = self.bs_config.get(config_section, 'browser')
            browser_version = self.bs_config.get(config_section, 'browser_version')
            os_type = self.bs_config.get(config_section, 'os')
            os_version = self.bs_config.get(config_section, 'os_version')
            resolution = self.bs_config.get(config_section, 'resolution')
            junit_xml_path = os.path.join(self.result_folder, config_section + '.xml')
            html_path = os.path.join(self.result_folder, config_section + '.html')

        test_result_prefix = '[' + browser + ', ' + browser_version + ', ' + os_type \
                             + ', ' + os_version + ', ' + resolution + ']'

        # prepare pytest arguments into execution list
        pytest_arguments.extend([
            '--junitxml=' + junit_xml_path,
            '--junit-prefix=' + test_result_prefix,
            '--html=' + html_path,
            '--html-prefix=' + test_result_prefix,
            '--xbrowser=' + browser,
            '--xbrowserversion=' + browser_version,
            '--xos=' + os_type,
            '--xosversion=' + os_version,
            '--xresolution=' + resolution,
            '--instafail',
            '--test_mobile=no'])

        return pytest_arguments

    def get_pytest_arguments_mobile(self, config_section, pytest_arguments):
        """ get pytest arguments to run tests on browserstack mobile browsers """
        # arguments passed through environment variable
        if self.env_type == 'direct':
            browser_name = config_section['browserName']
            platform = config_section['platform']
            device = config_section['device']
            orientation = config_section['deviceOrientation']
            junit_xml_path = os.path.join(self.result_folder, browser_name + platform + device + orientation + '.xml')
            html_path = os.path.join(self.result_folder, browser_name + platform + device + orientation + '.html')
        # arguments passed through config file
        else:
            self.bs_config.read(self.bs_config_file_mobile)
            browser_name = self.bs_config.get(config_section, 'browserName')
            platform = self.bs_config.get(config_section, 'platform')
            device = self.bs_config.get(config_section, 'device')
            orientation = self.bs_config.get(config_section, 'deviceOrientation')
            junit_xml_path = os.path.join(self.result_folder, config_section + '.xml')
            html_path = os.path.join(self.result_folder, config_section + '.html')

        test_result_prefix = '[' + device + ', ' + platform + ', ' + browser_name + ']'

        pytest_arguments.extend([
            '--junitxml=' + junit_xml_path,
            '--junit-prefix=' + test_result_prefix,
            '--html=' + html_path,
            '--html-prefix=' + test_result_prefix,
            '--xbrowserName=' + browser_name,
            '--xplatform=' + platform,
            '--xdevice=' + device,
            '--xdeviceOrientation=' + orientation,
            '--test_mobile=yes'])

        return pytest_arguments

    def get_runner_args(self):
        """ Retrieves the command line arguments passed to the script """
        parser = argparse.ArgumentParser(description='Selenium Python test runner execution arguments.')
        parser.add_argument('--reporting',
                            help='Generate reports for non selenium non_selenium_tests;'
                                 'options: "all, selenium, simple"',
                            default='all')
        parser.add_argument('--env',
                            help='BrowserStack environments; '
                                 'options: "direct" - passed as OS environment variable '
                                 '(JSON format), "versioned" (default) - loaded from *.properties configuration files',
                            default='versioned')
        parser.add_argument('--tests',
                            help='Tests to run; options: "smoke", "all" (default)',
                            default='all')
        parser.add_argument('--mobile',
                            help='Run tests on mobile/tablets, "default:none"'
                                 'for running use "yes"',
                            default='none')
        parser.add_argument('--jira_support',
                            help='Jira credentials; format: "username:password',
                            default='none')
        parser.add_argument('--browserstack',
                            help='BrowserStack credentials; format: "username:token"',
                            default='none')

        args = parser.parse_args()
        return [args.reporting, args.env, args.tests, args.mobile, args.jira_support, args.browserstack]

    def cleanup_results(self):
        """ Cleans up test result folder """
        if os.path.exists(os.path.join(self.project_root, 'results')):
            shutil.rmtree(os.path.join(self.project_root, 'results'))
        os.makedirs(self.result_folder)

    def archive_results(self):
        """ Archives test results in zip package """
        archive_folder = os.path.join(self.project_root, 'results_archive')
        if not (os.path.exists(archive_folder)):
            os.makedirs(archive_folder)
        shutil.make_archive(os.path.join(archive_folder, self.timestamp), "zip",
                            os.path.join(self.project_root, 'results'))

    def generate_combined_report(self):
        data = os.listdir(os.path.join(self.project_root, 'results', self.timestamp))
        result_reports = [item[:-5] for item in data if item.endswith('.html')]
        if len(result_reports) > 0:
            env = Environment(
                loader=FileSystemLoader(os.path.join(self.current_folder, 'library', 'report', 'resources')))
            template = env.get_template('CombinedReportTemplate.html')
            template_vars = {'data': result_reports}
            output = template.render(template_vars)
            formatted_output = output.encode('utf8').strip()
            final_report = open(os.path.join(self.project_root, 'results', self.timestamp, 'CombinedReport.html'), 'w')
            final_report.write(formatted_output)
            final_report.close()
            shutil.copy(os.path.join(self.current_folder, 'library', 'report', 'resources', 'combined_report.js'),
                        os.path.join(self.project_root, 'results', self.timestamp))
 def __init__(self):
     ControlTest.__init__(self)
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
        self.tc = ControlTest()
        self.driver_name = self.tc.gid('driver')
        self.timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        self.result_folder = os.path.join(self.project_root, 'results', self.timestamp)

        # load configuration files
        self.bs_config_file = os.path.join(self.project_root, 'config', 'browserstack.properties')
        self.bs_config_file_smoke = os.path.join(self.project_root, 'config', 'browserstack_smoke.properties')
        if not (os.path.exists(self.bs_config_file)) or not (os.path.exists(self.bs_config_file_smoke)):
            sys.exit('One of browserstack properties files not found! Session terminated.')
        self.bs_config = ConfigParser.RawConfigParser()

        # load cmd arguments and set default values if not specified
        cmd_args = self.get_runner_args()
        self.env_type = cmd_args[0]
        self.test_type = cmd_args[1]

        # load browserstack credentials from cmd argument, if available
        if cmd_args[2] != 'none':
            browserstack_auth = cmd_args[2].split(':')
            os.environ['bs_username'] = browserstack_auth[0]
            os.environ['bs_password'] = browserstack_auth[1]

    def set_project_root(self):
        """ Sets tested project root folder into OS environment variable """
        if os.path.exists(self.project_root) and os.path.isdir(self.project_root):
            os.environ['SALSA_WEBQA_PROJECT'] = self.project_root
        else:
            sys.exit('Project directory "' + self.project_root + '" does not exist! Session terminated.')

    def run_tests(self):
        """ Triggers PyTest runner locally or on BrowserStack.
        It runs PyTest for each BS combination, taken from either versioned .properties file or environment variable """
        # check that runner is not run directly
        if __name__ == "__main__":
            sys.exit('The runner cannot be executed directly.'
                     ' You need to import it within project specific runner. Session terminated.')
        else:
            if self.driver_name.lower() == 'browserstack':
                test_status = self.run_on_browserstack()
            else:
                test_status = self.run_locally()
            self.archive_results()
            self.generate_combined_report()
            return test_status

    def run_on_browserstack(self):
        """ Runs tests on BrowserStack """
        test_status = 0
        if bs_api.wait_for_free_sessions((self.tc.gid('bs_username'), self.tc.gid('bs_password')),
                                         self.tc.gid('session_waiting_time'), self.tc.gid('session_waiting_delay')):
            self.cleanup_results()

            # load browserstack variables from configuration files
            if self.env_type == 'versioned':
                if self.test_type == 'smoke':
                    self.bs_config.read(self.bs_config_file_smoke)
                else:
                    self.bs_config.read(self.bs_config_file)
                for config_section in self.bs_config.sections():
                    print('Running combination: ' + config_section)
                    test_status = self.trigger_pytest(config_section)

            # load browserstack variables from OS environment variable
            elif self.env_type == 'direct':
                config_list = json.loads(str(os.environ['BROWSERSTACK']))
                for config_section in config_list['test_suite']:
                    print('Running combination: ' + str(config_section))
                    test_status = self.trigger_pytest(config_section)
        return test_status

    def run_locally(self):
        """ Runs tests on local browser """
        self.cleanup_results()
        print('Running for browser: ' + self.driver_name)
        return self.trigger_pytest(self.driver_name)

    def trigger_pytest(self, config_section):
        """ Runs PyTest runner on specific configuration """
        pytest_arguments = [os.path.join(self.project_root, 'tests')]

        # set pytest parallel execution argument
        parallel_tests = int(self.tc.gid('parallel_tests'))
        if parallel_tests > 1:
            pytest_arguments.extend(['-n', str(parallel_tests)])

        # set pytest smoke test argument
        if self.test_type == 'smoke':
            pytest_arguments.extend(['-m', self.test_type])

        # setup pytest arguments for browserstack
        if self.driver_name.lower() == 'browserstack':

            # set pytest arguments values from OS environment variable
            if self.env_type == 'direct':
                browser = config_section['browser']
                browser_version = config_section['browser_version']
                os_type = config_section['os']
                os_version = config_section['os_version']
                resolution = config_section['resolution']
                junitxml_path = os.path.join(self.result_folder, browser + browser_version
                                             + os_type + os_version + resolution + '.xml')
                html_path = os.path.join(self.result_folder, browser + browser_version
                                         + os_type + os_version + resolution + '.html')

            # set pytest arguments values from configuration files
            else:
                if self.test_type == 'smoke':
                    self.bs_config.read(self.bs_config_file_smoke)
                else:
                    self.bs_config.read(self.bs_config_file)

                browser = self.bs_config.get(config_section, 'browser')
                browser_version = self.bs_config.get(config_section, 'browser_version')
                os_type = self.bs_config.get(config_section, 'os')
                os_version = self.bs_config.get(config_section, 'os_version')
                resolution = self.bs_config.get(config_section, 'resolution')
                junitxml_path = os.path.join(self.result_folder, config_section + '.xml')
                html_path = os.path.join(self.result_folder, config_section + '.html')
            test_result_prefix = '[' + browser + ', ' + browser_version + ', ' + os_type \
                                 + ', ' + os_version + ', ' + resolution + ']'

            # prepare pytest arguments into execution list
            pytest_arguments.extend([
                '--junitxml=' + junitxml_path,
                '--junit-prefix=' + test_result_prefix,
                '--html=' + html_path,
                '--html-prefix=' + test_result_prefix,
                '--xbrowser=' + browser,
                '--xbrowserversion=' + browser_version,
                '--xos=' + os_type,
                '--xosversion=' + os_version,
                '--xresolution=' + resolution,
                '--instafail'])

        # setup pytest arguments for local browser
        else:
            pytest_arguments.append('--junitxml=' + os.path.join(self.result_folder, self.driver_name + '.xml'))
            pytest_arguments.append('--html=' + os.path.join(self.result_folder, self.driver_name + '.html'))

        # run pytest and return its exit code
        return pytest.main(pytest_arguments)

    def get_runner_args(self):
        """ Retrieves the command line arguments passed to the script """
        parser = argparse.ArgumentParser(description='Selenium Python test runner execution arguments.')
        parser.add_argument('--env',
                            help='BrowserStack environments; '
                                 'options: "direct" - passed as OS environment variable '
                                 '(JSON format), "versioned" (default) - loaded from *.properties configuration files',
                            default='versioned')
        parser.add_argument('--tests',
                            help='Tests to run; options: "smoke", "all" (default)',
                            default='all')
        parser.add_argument('--browserstack',
                            help='BrowserStack credentials; format: "username:token"',
                            default='none')
        args = parser.parse_args()
        return [args.env, args.tests, args.browserstack]

    def cleanup_results(self):
        """ Cleans up test result folder """
        if os.path.exists(os.path.join(self.project_root, 'results')):
            shutil.rmtree(os.path.join(self.project_root, 'results'))
        os.makedirs(self.result_folder)

    def archive_results(self):
        """ Archives test results in zip package """
        archive_folder = os.path.join(self.project_root, 'results_archive')
        if not (os.path.exists(archive_folder)):
            os.makedirs(archive_folder)
        shutil.make_archive(os.path.join(archive_folder, self.timestamp), "zip",
                            os.path.join(self.project_root, 'results'))

    def generate_combined_report(self):
        data = os.listdir(os.path.join(self.project_root, 'results', self.timestamp))
        result_reports = [item[:-5] for item in data if item.endswith('.html')]
        if len(result_reports) > 0:
            env = Environment(
                loader=FileSystemLoader(os.path.join(self.current_folder, 'library', 'report', 'resources')))
            template = env.get_template('CombinedReportTemplate.html')
            template_vars = {'data': result_reports}
            output = template.render(template_vars)
            formatted_output = output.encode('utf8').strip()
            final_report = open(os.path.join(self.project_root, 'results', self.timestamp, 'CombinedReport.html'), 'w')
            final_report.write(formatted_output)
            final_report.close()
            shutil.copy(os.path.join(self.current_folder, 'library', 'report', 'resources', 'combined_report.js'),
                        os.path.join(self.project_root, 'results', self.timestamp))