blob: 565e3d4e43be2aef53a0f41b4a5488610f73d8bd [file] [log] [blame]
import abc
import errno
import os
import platform
import socket
import time
import traceback
from typing import ClassVar, Type
import mozprocess
from .browsers.base import OutputHandler
__all__ = ["SeleniumServer", "ChromeDriverServer", "CWTChromeDriverServer",
"EdgeChromiumDriverServer", "OperaDriverServer",
"InternetExplorerDriverServer", "EdgeDriverServer",
"ServoDriverServer", "WebKitDriverServer", "WebDriverServer"]
class WebDriverServer(object):
__metaclass__ = abc.ABCMeta
default_base_path = "/"
output_handler_cls = OutputHandler # type: ClassVar[Type[OutputHandler]]
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._output_handler = None
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,
output_handler_kwargs=None,
output_handler_start_kwargs=None):
try:
self._run(block, output_handler_kwargs, output_handler_start_kwargs)
except KeyboardInterrupt:
self.stop()
def _run(self, block, output_handler_kwargs, output_handler_start_kwargs):
if output_handler_kwargs is None:
output_handler_kwargs = {}
if output_handler_start_kwargs is None:
output_handler_start_kwargs = {}
self._cmd = self.make_command()
self._output_handler = self.output_handler_cls(self.logger,
self._cmd,
**output_handler_kwargs)
self._proc = mozprocess.ProcessHandler(
self._cmd,
processOutputLine=self._output_handler,
env=self.env,
storeOutput=False)
self.logger.debug("Starting WebDriver: %s" % ' '.join(self._cmd))
try:
self._proc.run()
except OSError as e:
if e.errno == errno.ENOENT:
raise IOError(
"WebDriver executable not found: %s" % self.binary)
raise
self._output_handler.after_process_start(self._proc.pid)
self.logger.debug(
"Waiting for WebDriver to become accessible: %s" % self.url)
try:
wait_for_service((self.host, self.port))
except Exception:
self.logger.error(
"WebDriver was not accessible "
"within the timeout:\n%s" % traceback.format_exc())
raise
self._output_handler.start(**output_handler_start_kwargs)
if block:
self._proc.wait()
def stop(self, force=False):
self.logger.debug("Stopping WebDriver")
clean = True
if self.is_alive():
kill_result = self._proc.kill()
if force and kill_result != 0:
clean = False
self._proc.kill(9)
success = not self.is_alive()
if success and self._output_handler is not None:
# Only try to do output post-processing if we managed to shut down
self._output_handler.after_process_stop(clean)
self._output_handler = None
return success
def is_alive(self):
return hasattr(self._proc, "proc") and self._proc.poll() is None
@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 = get_free_port()
return self._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):
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 "",
cmd_arg("enable-chrome-logs")] + self._args
class CWTChromeDriverServer(WebDriverServer):
def make_command(self):
return [self.binary,
"--port=%s" % str(self.port)] + self._args
class EdgeChromiumDriverServer(WebDriverServer):
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 make_command(self):
return [self.binary,
"--port=%s" % str(self.port)] + self._args
class OperaDriverServer(ChromeDriverServer):
pass
class InternetExplorerDriverServer(WebDriverServer):
def make_command(self):
return [self.binary,
"--port=%s" % str(self.port)] + self._args
class SafariDriverServer(WebDriverServer):
def make_command(self):
return [self.binary,
"--port=%s" % 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=None, args=None):
env = env if env is not None else os.environ.copy()
env["RUST_BACKTRACE"] = "1"
WebDriverServer.__init__(self, logger, binary,
host=host,
port=port,
env=env,
args=args)
self.binary_args = binary_args
def make_command(self):
command = [self.binary,
"--webdriver=%s" % self.port,
"--hard-fail",
"--headless"] + self._args
if self.binary_args:
command += self.binary_args
return command
class WebKitDriverServer(WebDriverServer):
def make_command(self):
return [self.binary, "--port=%s" % str(self.port)] + self._args
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():
"""Get a random unbound port"""
while True:
s = socket.socket()
try:
s.bind(("127.0.0.1", 0))
except OSError:
continue
else:
return s.getsockname()[1]
finally:
s.close()
def wait_for_service(addr, timeout=60):
"""Waits until network service given as a tuple of (host, port) becomes
available or the `timeout` duration is reached, at which point
``socket.timeout`` is raised."""
end = time.time() + timeout
while end > time.time():
so = socket.socket()
try:
so.connect(addr)
except socket.timeout:
pass
except OSError as e:
if e.errno != errno.ECONNREFUSED:
raise
else:
return True
finally:
so.close()
time.sleep(0.5)
raise socket.timeout("Service is unavailable: %s:%i" % addr)