class SeleniumExecutor(AbstractSeleniumExecutor, WidgetProvider, FileLister, HavingInstallableTools, SelfDiagnosable): """ Selenium executor :type runner: bzt.modules.SubprocessedExecutor :type virtual_display_service: VirtualDisplay """ SUPPORTED_RUNNERS = ["nose", "junit", "testng", "rspec", "mocha", "nunit"] CHROMEDRIVER_DOWNLOAD_LINK = "https://chromedriver.storage.googleapis.com/{version}/chromedriver_{arch}.zip" CHROMEDRIVER_VERSION = "2.29" GECKODRIVER_DOWNLOAD_LINK = "https://github.com/mozilla/geckodriver/releases/download/v{version}/" \ "geckodriver-v{version}-{arch}.{ext}" GECKODRIVER_VERSION = "0.17.0" SELENIUM_TOOLS_DIR = get_full_path("~/.bzt/selenium-taurus/tools") def __init__(self): super(SeleniumExecutor, self).__init__() self.additional_env = {} self.end_time = None self.runner = None self.script = None self.runner_working_dir = None self.register_reader = True self.virtual_display_service = Service( ) # TODO: remove compatibility with deprecated virtual-display setting self.webdrivers = [] def add_env(self, env): if "PATH" in self.additional_env and "PATH" in env: old_path = self.additional_env["PATH"] new_path = env["PATH"] merged_path = [] for item in old_path.split(os.pathsep) + new_path.split( os.pathsep): if item in merged_path: continue else: merged_path.append(item) env["PATH"] = os.pathsep.join(merged_path) self.additional_env.update(env) def get_runner_working_dir(self): if self.runner_working_dir is None: self.runner_working_dir = self.engine.create_artifact( "classes", "") return self.runner_working_dir def create_runner(self): runner_type = self.get_runner_type() self.runner = self.engine.instantiate_module(runner_type) # todo: deprecated, remove it later self.runner.settings.merge( self.settings.get('selenium-tools').get(runner_type)) self.runner.parameters = self.parameters self.runner.provisioning = self.provisioning self.runner.execution = copy.deepcopy(self.execution) self.runner.execution['files'] = self.execution.get('files', []) self.runner.execution['executor'] = runner_type self.runner.register_reader = self.register_reader if runner_type == "nose": self.runner.execution["test-mode"] = "selenium" def get_virtual_display(self): if isinstance(self.virtual_display_service, VirtualDisplay): return self.virtual_display_service.get_virtual_display() def _get_chromedriver_link(self): settings = self.settings.get('chromedriver') link = settings.get('download-link', SeleniumExecutor.CHROMEDRIVER_DOWNLOAD_LINK) version = settings.get('version', SeleniumExecutor.CHROMEDRIVER_VERSION) if is_windows(): arch = 'win32' elif is_mac(): arch = 'mac64' else: arch = 'linux32' if platform_bitness() == 32 else 'linux64' return link.format(version=version, arch=arch) def _get_chromedriver_path(self): base_dir = get_full_path(SeleniumExecutor.SELENIUM_TOOLS_DIR) settings = self.settings.get('chromedriver') version = settings.get('version', SeleniumExecutor.CHROMEDRIVER_VERSION) filename = 'chromedriver.exe' if is_windows() else 'chromedriver' return os.path.join(base_dir, 'chromedriver', version, filename) def _get_geckodriver_link(self): settings = self.settings.get('geckodriver') link = settings.get('download-link', SeleniumExecutor.GECKODRIVER_DOWNLOAD_LINK) version = settings.get('version', SeleniumExecutor.GECKODRIVER_VERSION) if is_windows(): arch = 'win32' if platform_bitness() == 32 else 'win64' ext = 'zip' elif is_mac(): arch = 'macos' ext = 'tar.gz' else: arch = 'linux32' if platform_bitness() == 32 else 'linux64' ext = 'tar.gz' return link.format(version=version, arch=arch, ext=ext) def _get_geckodriver_path(self): base_dir = get_full_path(SeleniumExecutor.SELENIUM_TOOLS_DIR) settings = self.settings.get('geckodriver') version = settings.get('version', SeleniumExecutor.GECKODRIVER_VERSION) filename = 'geckodriver.exe' if is_windows() else 'geckodriver' return os.path.join(base_dir, 'geckodriver', version, filename) def install_required_tools(self): chromedriver_path = self._get_chromedriver_path() chromedriver_link = self._get_chromedriver_link() geckodriver_path = self._get_geckodriver_path() geckodriver_link = self._get_geckodriver_link() self.webdrivers = [ ChromeDriver(chromedriver_path, self.log, chromedriver_link), GeckoDriver(geckodriver_path, self.log, geckodriver_link) ] for tool in self.webdrivers: if not tool.check_if_installed(): self.log.info("Installing %s...", tool.tool_name) tool.install() def _add_webdrivers_to_path(self): path_var = os.getenv("PATH") paths = [driver.get_driver_dir() for driver in self.webdrivers] path = os.pathsep.join(paths) + os.pathsep + path_var self.add_env({"PATH": path}) def prepare(self): self.install_required_tools() self._add_webdrivers_to_path() if self.get_load().concurrency and self.get_load().concurrency > 1: msg = 'Selenium supports concurrency in cloud provisioning mode only\n' msg += 'For details look at http://gettaurus.org/docs/Cloud.md' self.log.warning(msg) # backwards-compatible virtual-display settings vd_conf = self.settings.get("virtual-display") if vd_conf: self.log.warning( "Configuring virtual-display in Selenium module settings is deprecated." " Use the service approach instead") service_conf = copy.deepcopy(vd_conf) service_conf["module"] = "virtual-display" self.virtual_display_service = VirtualDisplay() self.virtual_display_service.parameters.merge(service_conf) self.virtual_display_service.prepare() self.create_runner() self.runner.prepare() self.script = self.runner.script def get_runner_type(self): if "runner" in self.execution: runner = self.execution["runner"] if runner not in SeleniumExecutor.SUPPORTED_RUNNERS: msg = "Runner '%s' is not supported. Supported runners: %s" raise TaurusConfigError( msg % (runner, SeleniumExecutor.SUPPORTED_RUNNERS)) self.log.debug("Using script type: %s", runner) return runner script_name = self.get_script_path() if script_name: return self.detect_script_type(script_name) else: if "requests" in self.get_scenario(): return "nose" else: raise TaurusConfigError( "You must specify either script or list of requests to run Selenium" ) def resource_files(self): self.create_runner() return self.runner.resource_files() def detect_script_type(self, script_name): if not os.path.exists(script_name): raise TaurusConfigError("Script '%s' doesn't exist" % script_name) file_types = set() # gather file extensions and choose script_type according to priority if os.path.isfile(script_name): # regular file received file_types.add(os.path.splitext(script_name)[1].lower()) else: # dir received: check contained files for file_name in get_files_recursive(script_name): file_types.add(os.path.splitext(file_name)[1].lower()) if '.java' in file_types or '.jar' in file_types: # todo: next detection logic is duplicated in TestNGTester - can we avoid it? script_dir = get_full_path(self.get_script_path(), step_up=1) if os.path.exists(os.path.join( script_dir, 'testng.xml')) or self.execution.get( 'testng-xml', None): script_type = 'testng' else: script_type = 'junit' elif '.py' in file_types: script_type = 'nose' elif '.rb' in file_types: script_type = 'rspec' elif '.js' in file_types: script_type = 'mocha' elif '.dll' in file_types or '.exe' in file_types: script_type = 'nunit' else: if os.path.isfile(script_name): message = "Unsupported script type: %r" % script_name else: message = "Directory %r doesn't contain supported scripts" % script_name raise TaurusConfigError(message) self.log.debug("Detected script type: %s", script_type) return script_type def startup(self): """ Start runner :return: """ self.virtual_display_service.startup() self.start_time = time.time() self.runner.env.update(self.additional_env) self.runner.startup() def check(self): """ check if test completed :return: """ self.virtual_display_service.check() if self.widget: self.widget.update() return self.runner.check() def report_test_duration(self): if self.start_time: self.end_time = time.time() self.log.debug("Selenium tests ran for %s seconds", self.end_time - self.start_time) def shutdown(self): """ shutdown test_runner :return: """ self.virtual_display_service.shutdown() self.runner.shutdown() self.report_test_duration() def post_process(self): self.virtual_display_service.post_process() self.runner.post_process() if os.path.exists("geckodriver.log"): self.engine.existing_artifact("geckodriver.log", True) def has_results(self): return self.runner.has_results() def get_widget(self): if not self.widget: self.widget = SeleniumWidget(self.script, self.runner.stdout_file) return self.widget def get_error_diagnostics(self): diagnostics = [] if self.runner: diagnostics.extend(self.runner.get_error_diagnostics()) gecko_logs = [ "geckodriver.log", os.path.join(self.engine.artifacts_dir, "geckodriver.log") ] for possible_log in gecko_logs: if os.path.exists(possible_log): with open(possible_log) as fds: diagnostics.append("Geckodriver log:\n" + fds.read()) return diagnostics
class SeleniumExecutor(AbstractSeleniumExecutor, WidgetProvider, FileLister): """ Selenium executor :type runner: SubprocessedExecutor :type virtual_display_service: VirtualDisplay """ SUPPORTED_RUNNERS = ["nose", "junit", "testng", "rspec", "mocha"] def __init__(self): super(SeleniumExecutor, self).__init__() self.additional_env = {} self.end_time = None self.runner = None self.script = None self.generated_methods = BetterDict() self.runner_working_dir = None self.virtual_display_service = Service() # TODO: remove compatibility with deprecated virtual-display setting def add_env(self, env): self.additional_env.update(env) def get_runner_working_dir(self): if self.runner_working_dir is None: self.runner_working_dir = self.engine.create_artifact("classes", "") return self.runner_working_dir def create_runner(self): runner_type = self.get_runner_type() self.runner = self.engine.instantiate_module(runner_type) # todo: deprecated, remove it later self.runner.settings.merge(self.settings.get('selenium-tools').get(runner_type)) self.runner.parameters = self.parameters self.runner.provisioning = self.provisioning self.runner.execution = self.execution self.runner.execution['executor'] = runner_type if runner_type == "nose": self.runner.execution["test-mode"] = "selenium" def get_virtual_display(self): if isinstance(self.virtual_display_service, VirtualDisplay): return self.virtual_display_service.get_virtual_display() def prepare(self): if self.get_load().concurrency and self.get_load().concurrency > 1: msg = 'Selenium supports concurrency in cloud provisioning mode only\n' msg += 'For details look at http://gettaurus.org/docs/Cloud.md' self.log.warning(msg) # backwards-compatible virtual-display settings vd_conf = self.settings.get("virtual-display") if vd_conf: self.log.warning("Configuring virtual-display in Selenium module settings is deprecated." " Use the service approach instead") service_conf = copy.deepcopy(vd_conf) service_conf["module"] = "virtual-display" self.virtual_display_service = VirtualDisplay() self.virtual_display_service.parameters.merge(service_conf) self.virtual_display_service.prepare() self.create_runner() self.runner.prepare() self.script = self.runner.script def get_runner_type(self): if "runner" in self.execution: runner = self.execution["runner"] if runner not in SeleniumExecutor.SUPPORTED_RUNNERS: msg = "Runner '%s' is not supported. Supported runners: %s" raise TaurusConfigError(msg % (runner, SeleniumExecutor.SUPPORTED_RUNNERS)) self.log.debug("Using script type: %s", runner) return runner script_name = self.get_script_path() if script_name: return self.detect_script_type(script_name) else: if "requests" in self.get_scenario(): return "nose" else: raise TaurusConfigError("You must specify either script or list of requests to run Selenium") def resource_files(self): self.create_runner() return self.runner.resource_files() def detect_script_type(self, script_name): if not os.path.exists(script_name): raise TaurusConfigError("Script '%s' doesn't exist" % script_name) file_types = set() # gather file extensions and choose script_type according to priority if os.path.isfile(script_name): # regular file received file_types.add(os.path.splitext(script_name)[1].lower()) else: # dir received: check contained files for file_name in get_files_recursive(script_name): file_types.add(os.path.splitext(file_name)[1].lower()) if '.java' in file_types or '.jar' in file_types: # todo: next detection logic is duplicated in TestNGTester - can we avoid it? script_dir = get_full_path(self.get_script_path(), step_up=1) if os.path.exists(os.path.join(script_dir, 'testng.xml')) or self.execution.get('testng-xml', None): script_type = 'testng' else: script_type = 'junit' elif '.py' in file_types: script_type = 'nose' elif '.rb' in file_types: script_type = 'rspec' elif '.js' in file_types: script_type = 'mocha' else: raise TaurusConfigError("Supported script files not found, script detection is failed") self.log.debug("Detected script type: %s", script_type) return script_type def startup(self): """ Start runner :return: """ self.virtual_display_service.startup() self.start_time = time.time() self.runner.env.update(self.additional_env) self.runner.startup() def check(self): """ check if test completed :return: """ self.virtual_display_service.check() if self.widget: self.widget.update() return self.runner.check() def report_test_duration(self): if self.start_time: self.end_time = time.time() self.log.debug("Selenium tests ran for %s seconds", self.end_time - self.start_time) def shutdown(self): """ shutdown test_runner :return: """ self.virtual_display_service.shutdown() self.runner.shutdown() self.report_test_duration() def post_process(self): self.virtual_display_service.post_process() if os.path.exists("geckodriver.log"): self.engine.existing_artifact("geckodriver.log", True) def has_results(self): return self.runner.has_results() def get_widget(self): if not self.widget: self.widget = SeleniumWidget(self.script, self.runner.stdout_file) return self.widget