Unreviewed. Update W3C WebDriver imported tests.
[WebKit-https.git] / WebDriverTests / imported / w3c / tools / wptrunner / wptrunner / webdriver_server.py
1 import abc
2 import errno
3 import os
4 import platform
5 import socket
6 import threading
7 import time
8 import traceback
9 import urlparse
10
11 import mozprocess
12
13
14 __all__ = ["SeleniumServer", "ChromeDriverServer", "OperaDriverServer",
15            "GeckoDriverServer", "InternetExplorerDriverServer", "EdgeDriverServer",
16            "ServoDriverServer", "WebDriverServer"]
17
18
19 class WebDriverServer(object):
20     __metaclass__ = abc.ABCMeta
21
22     default_base_path = "/"
23     _used_ports = set()
24
25     def __init__(self, logger, binary, host="127.0.0.1", port=None,
26                  base_path="", env=None, args=None):
27         if binary is None:
28             raise ValueError("WebDriver server binary must be given "
29                              "to --webdriver-binary argument")
30
31         self.logger = logger
32         self.binary = binary
33         self.host = host
34         if base_path == "":
35             self.base_path = self.default_base_path
36         else:
37             self.base_path = base_path
38         self.env = os.environ.copy() if env is None else env
39
40         self._port = port
41         self._cmd = None
42         self._args = args if args is not None else []
43         self._proc = None
44
45     @abc.abstractmethod
46     def make_command(self):
47         """Returns the full command for starting the server process as a list."""
48
49     def start(self, block=False):
50         try:
51             self._run(block)
52         except KeyboardInterrupt:
53             self.stop()
54
55     def _run(self, block):
56         self._cmd = self.make_command()
57         self._proc = mozprocess.ProcessHandler(
58             self._cmd,
59             processOutputLine=self.on_output,
60             env=self.env,
61             storeOutput=False)
62
63         try:
64             self._proc.run()
65         except OSError as e:
66             if e.errno == errno.ENOENT:
67                 raise IOError(
68                     "WebDriver HTTP server executable not found: %s" % self.binary)
69             raise
70
71         self.logger.debug(
72             "Waiting for server to become accessible: %s" % self.url)
73         try:
74             wait_for_service((self.host, self.port))
75         except:
76             self.logger.error(
77                 "WebDriver HTTP server was not accessible "
78                 "within the timeout:\n%s" % traceback.format_exc())
79             raise
80
81         if block:
82             self._proc.wait()
83
84     def stop(self, force=False):
85         if self.is_alive:
86             return self._proc.kill()
87         return not self.is_alive
88
89     @property
90     def is_alive(self):
91         return hasattr(self._proc, "proc") and self._proc.poll() is None
92
93     def on_output(self, line):
94         self.logger.process_output(self.pid,
95                                    line.decode("utf8", "replace"),
96                                    command=" ".join(self._cmd))
97
98     @property
99     def pid(self):
100         if self._proc is not None:
101             return self._proc.pid
102
103     @property
104     def url(self):
105         return "http://%s:%i%s" % (self.host, self.port, self.base_path)
106
107     @property
108     def port(self):
109         if self._port is None:
110             self._port = self._find_next_free_port()
111         return self._port
112
113     @staticmethod
114     def _find_next_free_port():
115         port = get_free_port(4444, exclude=WebDriverServer._used_ports)
116         WebDriverServer._used_ports.add(port)
117         return port
118
119
120 class SeleniumServer(WebDriverServer):
121     default_base_path = "/wd/hub"
122
123     def make_command(self):
124         return ["java", "-jar", self.binary, "-port", str(self.port)] + self._args
125
126
127 class ChromeDriverServer(WebDriverServer):
128     default_base_path = "/"
129
130     def __init__(self, logger, binary="chromedriver", port=None,
131                  base_path="", args=None):
132         WebDriverServer.__init__(
133             self, logger, binary, port=port, base_path=base_path, args=args)
134
135     def make_command(self):
136         return [self.binary,
137                 cmd_arg("port", str(self.port)),
138                 cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
139
140 class EdgeDriverServer(WebDriverServer):
141     default_base_path = "/"
142
143     def __init__(self, logger, binary="microsoftwebdriver.exe", port=None,
144                  base_path="", args=None):
145         WebDriverServer.__init__(
146             self, logger, binary, port=port, base_path=base_path, args=args)
147
148     def make_command(self):
149         return [self.binary,
150                 cmd_arg("port", str(self.port)),
151                 cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
152
153 class OperaDriverServer(ChromeDriverServer):
154     def __init__(self, logger, binary="operadriver", port=None,
155                  base_path="", args=None):
156         ChromeDriverServer.__init__(
157             self, logger, binary, port=port, base_path=base_path, args=args)
158
159
160 class EdgeDriverServer(WebDriverServer):
161     def __init__(self, logger, binary="MicrosoftWebDriver.exe", port=None,
162                  base_path="", host="localhost", args=None):
163         WebDriverServer.__init__(
164             self, logger, binary, host=host, port=port, args=args)
165
166     def make_command(self):
167         return [self.binary,
168                 "--port=%s" % str(self.port)] + self._args
169
170
171 class InternetExplorerDriverServer(WebDriverServer):
172     def __init__(self, logger, binary="IEDriverServer.exe", port=None,
173                  base_path="", host="localhost", args=None):
174         WebDriverServer.__init__(
175             self, logger, binary, host=host, port=port, args=args)
176
177     def make_command(self):
178         return [self.binary,
179                 "--port=%s" % str(self.port)] + self._args
180
181
182 class GeckoDriverServer(WebDriverServer):
183     def __init__(self, logger, marionette_port=2828, binary="geckodriver",
184                  host="127.0.0.1", port=None, args=None):
185         env = os.environ.copy()
186         env["RUST_BACKTRACE"] = "1"
187         WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env, args=args)
188         self.marionette_port = marionette_port
189
190     def make_command(self):
191         return [self.binary,
192                 "--marionette-port", str(self.marionette_port),
193                 "--host", self.host,
194                 "--port", str(self.port)] + self._args
195
196
197 class ServoDriverServer(WebDriverServer):
198     def __init__(self, logger, binary="servo", binary_args=None, host="127.0.0.1", port=None):
199         env = os.environ.copy()
200         env["RUST_BACKTRACE"] = "1"
201         WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env)
202         self.binary_args = binary_args
203
204     def make_command(self):
205         command = [self.binary,
206                    "--webdriver", str(self.port),
207                    "--hard-fail",
208                    "--headless"] + self._args
209         if self.binary_args:
210             command += self.binary_args
211         return command
212
213
214 def cmd_arg(name, value=None):
215     prefix = "-" if platform.system() == "Windows" else "--"
216     rv = prefix + name
217     if value is not None:
218         rv += "=" + value
219     return rv
220
221
222 def get_free_port(start_port, exclude=None):
223     """Get the first port number after start_port (inclusive) that is
224     not currently bound.
225
226     :param start_port: Integer port number at which to start testing.
227     :param exclude: Set of port numbers to skip"""
228     port = start_port
229     while True:
230         if exclude and port in exclude:
231             port += 1
232             continue
233         s = socket.socket()
234         try:
235             s.bind(("127.0.0.1", port))
236         except socket.error:
237             port += 1
238         else:
239             return port
240         finally:
241             s.close()
242
243
244 def wait_for_service(addr, timeout=15):
245     """Waits until network service given as a tuple of (host, port) becomes
246     available or the `timeout` duration is reached, at which point
247     ``socket.error`` is raised."""
248     end = time.time() + timeout
249     while end > time.time():
250         so = socket.socket()
251         try:
252             so.connect(addr)
253         except socket.timeout:
254             pass
255         except socket.error as e:
256             if e[0] != errno.ECONNREFUSED:
257                 raise
258         else:
259             return True
260         finally:
261             so.close()
262         time.sleep(0.5)
263     raise socket.error("Service is unavailable: %s:%i" % addr)