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 __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)
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)
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
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]
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 __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]
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))
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), )
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])
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))