Exemple #1
0
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
Exemple #2
0
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