| import abc |
| import errno |
| import os |
| import platform |
| import socket |
| import threading |
| import time |
| import traceback |
| import urlparse |
| |
| import mozprocess |
| |
| |
| __all__ = ["SeleniumServer", "ChromeDriverServer", |
| "GeckoDriverServer", "ServoDriverServer", |
| "WebDriverServer"] |
| |
| |
| class WebDriverServer(object): |
| __metaclass__ = abc.ABCMeta |
| |
| default_base_path = "/" |
| _used_ports = set() |
| |
| def __init__(self, logger, binary, host="127.0.0.1", port=None, |
| base_path="", env=None, args=None): |
| if binary is None: |
| raise ValueError("WebDriver server binary must be given " |
| "to --webdriver-binary argument") |
| |
| self.logger = logger |
| self.binary = binary |
| self.host = host |
| if base_path == "": |
| self.base_path = self.default_base_path |
| else: |
| self.base_path = base_path |
| self.env = os.environ.copy() if env is None else env |
| |
| self._port = port |
| self._cmd = None |
| self._args = args if args is not None else [] |
| self._proc = None |
| |
| @abc.abstractmethod |
| def make_command(self): |
| """Returns the full command for starting the server process as a list.""" |
| |
| def start(self, block=False): |
| try: |
| self._run(block) |
| except KeyboardInterrupt: |
| self.stop() |
| |
| def _run(self, block): |
| self._cmd = self.make_command() |
| self._proc = mozprocess.ProcessHandler( |
| self._cmd, |
| processOutputLine=self.on_output, |
| env=self.env, |
| storeOutput=False) |
| |
| try: |
| self._proc.run() |
| except OSError as e: |
| if e.errno == errno.ENOENT: |
| raise IOError( |
| "WebDriver HTTP server executable not found: %s" % self.binary) |
| raise |
| |
| self.logger.debug( |
| "Waiting for server to become accessible: %s" % self.url) |
| try: |
| wait_for_service((self.host, self.port)) |
| except: |
| self.logger.error( |
| "WebDriver HTTP server was not accessible " |
| "within the timeout:\n%s" % traceback.format_exc()) |
| raise |
| |
| if block: |
| self._proc.wait() |
| |
| def stop(self, force=False): |
| if self.is_alive: |
| return self._proc.kill() |
| return not self.is_alive |
| |
| @property |
| def is_alive(self): |
| return hasattr(self._proc, "proc") and self._proc.poll() is None |
| |
| def on_output(self, line): |
| self.logger.process_output(self.pid, |
| line.decode("utf8", "replace"), |
| command=" ".join(self._cmd)) |
| |
| @property |
| def pid(self): |
| if self._proc is not None: |
| return self._proc.pid |
| |
| @property |
| def url(self): |
| return "http://%s:%i%s" % (self.host, self.port, self.base_path) |
| |
| @property |
| def port(self): |
| if self._port is None: |
| self._port = self._find_next_free_port() |
| return self._port |
| |
| @staticmethod |
| def _find_next_free_port(): |
| port = get_free_port(4444, exclude=WebDriverServer._used_ports) |
| WebDriverServer._used_ports.add(port) |
| return port |
| |
| |
| class SeleniumServer(WebDriverServer): |
| default_base_path = "/wd/hub" |
| |
| def make_command(self): |
| return ["java", "-jar", self.binary, "-port", str(self.port)] + self._args |
| |
| |
| class ChromeDriverServer(WebDriverServer): |
| default_base_path = "/wd/hub" |
| |
| def __init__(self, logger, binary="chromedriver", port=None, |
| base_path="", args=None): |
| WebDriverServer.__init__( |
| self, logger, binary, port=port, base_path=base_path, args=args) |
| |
| def make_command(self): |
| return [self.binary, |
| cmd_arg("port", str(self.port)), |
| cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args |
| |
| |
| class EdgeDriverServer(WebDriverServer): |
| def __init__(self, logger, binary="MicrosoftWebDriver.exe", port=None, |
| base_path="", host="localhost", args=None): |
| WebDriverServer.__init__( |
| self, logger, binary, host=host, port=port, args=args) |
| |
| def make_command(self): |
| return [self.binary, |
| "--port=%s" % str(self.port)] + self._args |
| |
| |
| class GeckoDriverServer(WebDriverServer): |
| def __init__(self, logger, marionette_port=2828, binary="geckodriver", |
| host="127.0.0.1", port=None, args=None): |
| env = os.environ.copy() |
| env["RUST_BACKTRACE"] = "1" |
| WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env, args=args) |
| self.marionette_port = marionette_port |
| |
| def make_command(self): |
| return [self.binary, |
| "--marionette-port", str(self.marionette_port), |
| "--host", self.host, |
| "--port", str(self.port)] + self._args |
| |
| |
| class ServoDriverServer(WebDriverServer): |
| def __init__(self, logger, binary="servo", binary_args=None, host="127.0.0.1", port=None): |
| env = os.environ.copy() |
| env["RUST_BACKTRACE"] = "1" |
| WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env) |
| self.binary_args = binary_args |
| |
| def make_command(self): |
| command = [self.binary, |
| "--webdriver", str(self.port), |
| "--hard-fail", |
| "--headless"] + self._args |
| if self.binary_args: |
| command += self.binary_args |
| return command |
| |
| |
| def cmd_arg(name, value=None): |
| prefix = "-" if platform.system() == "Windows" else "--" |
| rv = prefix + name |
| if value is not None: |
| rv += "=" + value |
| return rv |
| |
| |
| def get_free_port(start_port, exclude=None): |
| """Get the first port number after start_port (inclusive) that is |
| not currently bound. |
| |
| :param start_port: Integer port number at which to start testing. |
| :param exclude: Set of port numbers to skip""" |
| port = start_port |
| while True: |
| if exclude and port in exclude: |
| port += 1 |
| continue |
| s = socket.socket() |
| try: |
| s.bind(("127.0.0.1", port)) |
| except socket.error: |
| port += 1 |
| else: |
| return port |
| finally: |
| s.close() |
| |
| |
| def wait_for_service(addr, timeout=15): |
| """Waits until network service given as a tuple of (host, port) becomes |
| available or the `timeout` duration is reached, at which point |
| ``socket.error`` is raised.""" |
| end = time.time() + timeout |
| while end > time.time(): |
| so = socket.socket() |
| try: |
| so.connect(addr) |
| except socket.timeout: |
| pass |
| except socket.error as e: |
| if e[0] != errno.ECONNREFUSED: |
| raise |
| else: |
| return True |
| finally: |
| so.close() |
| time.sleep(0.5) |
| raise socket.error("Service is unavailable: %s:%i" % addr) |