diff --git a/tests/wpt/config.ini b/tests/wpt/config.ini index 3dec291038a..abd24963da4 100644 --- a/tests/wpt/config.ini +++ b/tests/wpt/config.ini @@ -1,5 +1,6 @@ [products] servo = +servodriver = [web-platform-tests] remote_url = https://github.com/w3c/web-platform-tests.git diff --git a/tests/wpt/config_css.ini b/tests/wpt/config_css.ini index 14158a31a2d..a3c6a29a895 100644 --- a/tests/wpt/config_css.ini +++ b/tests/wpt/config_css.ini @@ -1,5 +1,6 @@ [products] servo = +servodriver = [web-platform-tests] name = CSS tests diff --git a/tests/wpt/harness/wptrunner/browsers/__init__.py b/tests/wpt/harness/wptrunner/browsers/__init__.py index 60167201d01..ffc5aedc830 100644 --- a/tests/wpt/harness/wptrunner/browsers/__init__.py +++ b/tests/wpt/harness/wptrunner/browsers/__init__.py @@ -29,4 +29,5 @@ module global scope. product_list = ["b2g", "chrome", "firefox", - "servo"] + "servo", + "servodriver"] diff --git a/tests/wpt/harness/wptrunner/browsers/firefox.py b/tests/wpt/harness/wptrunner/browsers/firefox.py index 0f009494e92..e0b79786969 100644 --- a/tests/wpt/harness/wptrunner/browsers/firefox.py +++ b/tests/wpt/harness/wptrunner/browsers/firefox.py @@ -4,6 +4,7 @@ import os import subprocess +import sys import mozinfo from mozprocess import ProcessHandler @@ -70,7 +71,6 @@ class FirefoxBrowser(Browser): self.binary = binary self.prefs_root = prefs_root self.marionette_port = None - self.used_ports.add(self.marionette_port) self.runner = None self.debug_info = debug_info self.profile = None @@ -81,6 +81,7 @@ class FirefoxBrowser(Browser): def start(self): self.marionette_port = get_free_port(2828, exclude=self.used_ports) + self.used_ports.add(self.marionette_port) env = os.environ.copy() env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" @@ -191,7 +192,8 @@ class FirefoxBrowser(Browser): env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) - if env_var in env else certutil_dir) + if env_var in env else certutil_dir).encode( + sys.getfilesystemencoding() or 'utf-8', 'replace') def certutil(*args): cmd = [self.certutil_binary] + list(args) diff --git a/tests/wpt/harness/wptrunner/browsers/servodriver.py b/tests/wpt/harness/wptrunner/browsers/servodriver.py new file mode 100644 index 00000000000..ceb1625237a --- /dev/null +++ b/tests/wpt/harness/wptrunner/browsers/servodriver.py @@ -0,0 +1,141 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import subprocess +import tempfile + +from mozprocess import ProcessHandler + +from .base import Browser, require_arg, get_free_port, browser_command, ExecutorBrowser +from ..executors import executor_kwargs as base_executor_kwargs +from ..executors.executorservodriver import (ServoWebDriverTestharnessExecutor, + ServoWebDriverRefTestExecutor) + +here = os.path.join(os.path.split(__file__)[0]) + +__wptrunner__ = {"product": "servodriver", + "check_args": "check_args", + "browser": "ServoWebDriverBrowser", + "executor": {"testharness": "ServoWebDriverTestharnessExecutor", + "reftest": "ServoWebDriverRefTestExecutor"}, + "browser_kwargs": "browser_kwargs", + "executor_kwargs": "executor_kwargs", + "env_options": "env_options"} + +hosts_text = """127.0.0.1 web-platform.test +127.0.0.1 www.web-platform.test +127.0.0.1 www1.web-platform.test +127.0.0.1 www2.web-platform.test +127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test +127.0.0.1 xn--lve-6lad.web-platform.test +""" + + +def check_args(**kwargs): + require_arg(kwargs, "binary") + + +def browser_kwargs(**kwargs): + return {"binary": kwargs["binary"], + "debug_info": kwargs["debug_info"]} + + +def executor_kwargs(test_type, server_config, cache_manager, **kwargs): + rv = base_executor_kwargs(test_type, server_config, + cache_manager, **kwargs) + return rv + + +def env_options(): + return {"host": "web-platform.test", + "bind_hostname": "true", + "testharnessreport": "testharnessreport-servodriver.js", + "supports_debugger": True} + + +def make_hosts_file(): + hosts_fd, hosts_path = tempfile.mkstemp() + with os.fdopen(hosts_fd, "w") as f: + f.write(hosts_text) + return hosts_path + + +class ServoWebDriverBrowser(Browser): + used_ports = set() + + def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1"): + Browser.__init__(self, logger) + self.binary = binary + self.webdriver_host = webdriver_host + self.webdriver_port = None + self.proc = None + self.debug_info = debug_info + self.hosts_path = make_hosts_file() + self.command = None + + def start(self): + self.webdriver_port = get_free_port(4444, exclude=self.used_ports) + self.used_ports.add(self.webdriver_port) + + env = os.environ.copy() + env["HOST_FILE"] = self.hosts_path + + debug_args, command = browser_command(self.binary, + ["--cpu", "--hard-fail", + "--webdriver", str(self.webdriver_port), + "about:blank"], + self.debug_info) + + self.command = command + + self.command = debug_args + self.command + + if not self.debug_info or not self.debug_info.interactive: + self.proc = ProcessHandler(self.command, + processOutputLine=[self.on_output], + env=env, + storeOutput=False) + self.proc.run() + else: + self.proc = subprocess.Popen(self.command, env=env) + + self.logger.debug("Servo Started") + + def stop(self): + self.logger.debug("Stopping browser") + if self.proc is not None: + try: + self.proc.kill() + except OSError: + # This can happen on Windows if the process is already dead + pass + + def pid(self): + if self.proc is None: + return None + + try: + return self.proc.pid + except AttributeError: + return None + + def on_output(self, line): + """Write a line of output from the process to the log""" + self.logger.process_output(self.pid(), + line.decode("utf8", "replace"), + command=" ".join(self.command)) + + def is_alive(self): + if self.runner: + return self.runner.is_running() + return False + + def cleanup(self): + self.stop() + + def executor_browser(self): + assert self.webdriver_port is not None + return ExecutorBrowser, {"webdriver_host": self.webdriver_host, + "webdriver_port": self.webdriver_port} diff --git a/tests/wpt/harness/wptrunner/executors/executormarionette.py b/tests/wpt/harness/wptrunner/executors/executormarionette.py index aadb51c2776..0e9da816b7f 100644 --- a/tests/wpt/harness/wptrunner/executors/executormarionette.py +++ b/tests/wpt/harness/wptrunner/executors/executormarionette.py @@ -157,20 +157,19 @@ class MarionetteProtocol(Protocol): let prefInterface = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); let pref = '%s'; - let value = '%s'; let type = prefInterface.getPrefType(pref); switch(type) { case prefInterface.PREF_STRING: - prefInterface.setCharPref(pref, value); + prefInterface.setCharPref(pref, '%s'); break; case prefInterface.PREF_BOOL: - prefInterface.setBoolPref(pref, value); + prefInterface.setBoolPref(pref, %s); break; case prefInterface.PREF_INT: - prefInterface.setIntPref(pref, value); + prefInterface.setIntPref(pref, %s); break; } - """ % (name, value) + """ % (name, value, value, value) self.marionette.execute_script(script) self.marionette.set_context(self.marionette.CONTEXT_CONTENT) diff --git a/tests/wpt/harness/wptrunner/executors/executorservodriver.py b/tests/wpt/harness/wptrunner/executors/executorservodriver.py new file mode 100644 index 00000000000..d03e0889cb1 --- /dev/null +++ b/tests/wpt/harness/wptrunner/executors/executorservodriver.py @@ -0,0 +1,229 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import socket +import threading +import time +import traceback + +from .base import (Protocol, + RefTestExecutor, + RefTestImplementation, + TestharnessExecutor, + strip_server) +import webdriver +from ..testrunner import Stop + +here = os.path.join(os.path.split(__file__)[0]) + +extra_timeout = 5 + + +class ServoWebDriverProtocol(Protocol): + def __init__(self, executor, browser, capabilities, **kwargs): + Protocol.__init__(self, executor, browser) + self.capabilities = capabilities + self.host = browser.webdriver_host + self.port = browser.webdriver_port + self.session = None + + def setup(self, runner): + """Connect to browser via WebDriver.""" + self.runner = runner + + session_started = False + try: + self.session = webdriver.Session(self.host, self.port) + self.session.start() + except: + self.logger.warning( + "Connecting with WebDriver failed:\n%s" % traceback.format_exc()) + else: + self.logger.debug("session started") + session_started = True + + if not session_started: + self.logger.warning("Failed to connect via WebDriver") + self.executor.runner.send_message("init_failed") + else: + self.executor.runner.send_message("init_succeeded") + + def teardown(self): + self.logger.debug("Hanging up on WebDriver session") + try: + self.session.end() + except: + pass + + def is_alive(self): + try: + # Get a simple property over the connection + self.session.handle + # TODO what exception? + except Exception: + return False + return True + + def after_connect(self): + pass + + def wait(self): + while True: + try: + self.session.execute_async_script("") + except webdriver.TimeoutException: + pass + except (socket.timeout, IOError): + break + except Exception as e: + self.logger.error(traceback.format_exc(e)) + break + + +class ServoWebDriverRun(object): + def __init__(self, func, session, url, timeout): + self.func = func + self.result = None + self.session = session + self.url = url + self.timeout = timeout + self.result_flag = threading.Event() + + def run(self): + timeout = self.timeout + + try: + self.session.timeouts.script = timeout + extra_timeout + except IOError: + self.logger.error("Lost webdriver connection") + return Stop + + executor = threading.Thread(target=self._run) + executor.start() + + flag = self.result_flag.wait(timeout + 2 * extra_timeout) + if self.result is None: + assert not flag + self.result = False, ("EXTERNAL-TIMEOUT", None) + + return self.result + + def _run(self): + try: + self.result = True, self.func(self.session, self.url, self.timeout) + except webdriver.TimeoutException: + self.result = False, ("EXTERNAL-TIMEOUT", None) + except (socket.timeout, IOError): + self.result = False, ("CRASH", None) + except Exception as e: + message = getattr(e, "message", "") + if message: + message += "\n" + message += traceback.format_exc(e) + self.result = False, ("ERROR", e) + finally: + self.result_flag.set() + + +def timeout_func(timeout): + if timeout: + t0 = time.time() + return lambda: time.time() - t0 > timeout + extra_timeout + else: + return lambda: False + + +class ServoWebDriverTestharnessExecutor(TestharnessExecutor): + def __init__(self, browser, server_config, timeout_multiplier=1, + close_after_done=True, capabilities=None, debug_info=None): + TestharnessExecutor.__init__(self, browser, server_config, timeout_multiplier=1, + debug_info=None) + self.protocol = ServoWebDriverProtocol(self, browser, capabilities=capabilities) + with open(os.path.join(here, "testharness_servodriver.js")) as f: + self.script = f.read() + + def on_protocol_change(self, new_protocol): + pass + + def is_alive(self): + return self.protocol.is_alive() + + def do_test(self, test): + url = self.test_url(test) + + success, data = ServoWebDriverRun(self.do_testharness, + self.protocol.session, + url, + test.timeout * self.timeout_multiplier).run() + + if success: + return self.convert_result(test, data) + + return (test.result_cls(*data), []) + + def do_testharness(self, session, url, timeout): + session.url = url + result = json.loads( + session.execute_async_script( + self.script % {"abs_url": url, + "url": strip_server(url), + "timeout_multiplier": self.timeout_multiplier, + "timeout": timeout * 1000})) + if "test" not in result: + result["test"] = strip_server(url) + return result + + +class TimeoutError(Exception): + pass + + +class ServoWebDriverRefTestExecutor(RefTestExecutor): + def __init__(self, browser, server_config, timeout_multiplier=1, + screenshot_cache=None, capabilities=None, debug_info=None): + """Selenium WebDriver-based executor for reftests""" + RefTestExecutor.__init__(self, + browser, + server_config, + screenshot_cache=screenshot_cache, + timeout_multiplier=timeout_multiplier, + debug_info=debug_info) + self.protocol = ServoWebDriverProtocol(self, browser, + capabilities=capabilities) + self.implementation = RefTestImplementation(self) + + with open(os.path.join(here, "reftest-wait_servodriver.js")) as f: + self.wait_script = f.read() + + def is_alive(self): + return self.protocol.is_alive() + + def do_test(self, test): + try: + result = self.implementation.run_test(test) + return self.convert_result(test, result) + except IOError: + return test.result_cls("CRASH", None), [] + except TimeoutError: + return test.result_cls("TIMEOUT", None), [] + except Exception as e: + message = getattr(e, "message", "") + if message: + message += "\n" + message += traceback.format_exc(e) + return test.result_cls("ERROR", message), [] + + def screenshot(self, test): + timeout = test.timeout * self.timeout_multiplier if self.debug_info is None else None + return ServoWebDriverRun(self._screenshot, + self.protocol.session, + self.test_url(test), + timeout).run() + + def _screenshot(self, session, url, timeout): + session.url = url + session.execute_async_script(self.wait_script) + return session.screenshot() diff --git a/tests/wpt/harness/wptrunner/executors/reftest-wait_servodriver.js b/tests/wpt/harness/wptrunner/executors/reftest-wait_servodriver.js new file mode 100644 index 00000000000..6040b4336cd --- /dev/null +++ b/tests/wpt/harness/wptrunner/executors/reftest-wait_servodriver.js @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +callback = arguments[arguments.length - 1]; + +function check_done() { + if (!document.body.classList.contains('reftest-wait')) { + callback(); + } else { + setTimeout(check_done, 50); + } +} + +if (document.readyState === 'complete') { + check_done(); +} else { + addEventListener("load", check_done); +} diff --git a/tests/wpt/harness/wptrunner/executors/testharness_servodriver.js b/tests/wpt/harness/wptrunner/executors/testharness_servodriver.js new file mode 100644 index 00000000000..20aeb34b9ee --- /dev/null +++ b/tests/wpt/harness/wptrunner/executors/testharness_servodriver.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +window.__wd_results_callback__ = arguments[arguments.length - 1]; +window.__wd_results_timer__ = setTimeout(timeout, %(timeout)s); diff --git a/tests/wpt/harness/wptrunner/executors/webdriver.py b/tests/wpt/harness/wptrunner/executors/webdriver.py new file mode 100644 index 00000000000..cabb0d66637 --- /dev/null +++ b/tests/wpt/harness/wptrunner/executors/webdriver.py @@ -0,0 +1,587 @@ +import errno +import httplib +import json +import socket +import time +import urlparse +from collections import defaultdict + +element_key = "element-6066-11e4-a52e-4f735466cecf" + + +class WebDriverException(Exception): + http_status = None + status_code = None + + def __init__(self, message): + self.message = message + + +class ElementNotSelectableException(WebDriverException): + http_status = 400 + status_code = "element not selectable" + + +class ElementNotVisibleException(WebDriverException): + http_status = 400 + status_code = "element not visible" + + +class InvalidArgumentException(WebDriverException): + http_status = 400 + status_code = "invalid argument" + + +class InvalidCookieDomainException(WebDriverException): + http_status = 400 + status_code = "invalid cookie domain" + + +class InvalidElementCoordinatesException(WebDriverException): + http_status = 400 + status_code = "invalid element coordinates" + + +class InvalidElementStateException(WebDriverException): + http_status = 400 + status_code = "invalid cookie domain" + + +class InvalidSelectorException(WebDriverException): + http_status = 400 + status_code = "invalid selector" + + +class InvalidSessionIdException(WebDriverException): + http_status = 404 + status_code = "invalid session id" + + +class JavascriptErrorException(WebDriverException): + http_status = 500 + status_code = "javascript error" + + +class MoveTargetOutOfBoundsException(WebDriverException): + http_status = 500 + status_code = "move target out of bounds" + + +class NoSuchAlertException(WebDriverException): + http_status = 400 + status_code = "no such alert" + + +class NoSuchElementException(WebDriverException): + http_status = 404 + status_code = "no such element" + + +class NoSuchFrameException(WebDriverException): + http_status = 400 + status_code = "no such frame" + + +class NoSuchWindowException(WebDriverException): + http_status = 400 + status_code = "no such window" + + +class ScriptTimeoutException(WebDriverException): + http_status = 408 + status_code = "script timeout" + + +class SessionNotCreatedException(WebDriverException): + http_status = 500 + status_code = "session not created" + + +class StaleElementReferenceException(WebDriverException): + http_status = 400 + status_code = "stale element reference" + + +class TimeoutException(WebDriverException): + http_status = 408 + status_code = "timeout" + + +class UnableToSetCookieException(WebDriverException): + http_status = 500 + status_code = "unable to set cookie" + + +class UnexpectedAlertOpenException(WebDriverException): + http_status = 500 + status_code = "unexpected alert open" + + +class UnknownErrorException(WebDriverException): + http_status = 500 + status_code = "unknown error" + + +class UnknownCommandException(WebDriverException): + http_status = (404, 405) + status_code = "unknown command" + + +class UnsupportedOperationException(WebDriverException): + http_status = 500 + status_code = "unsupported operation" + + +def group_exceptions(): + exceptions = defaultdict(dict) + for item in _objs: + if type(item) == type and issubclass(item, WebDriverException): + if not isinstance(item.http_status, tuple): + statuses = (item.http_status,) + else: + statuses = item.http_status + + for status in statuses: + exceptions[status][item.status_code] = item + return exceptions + + +_objs = locals().values() +_exceptions = group_exceptions() +del _objs +del group_exceptions + + +def wait_for_port(host, port, timeout=60): + """ Wait for the specified Marionette host/port to be available.""" + starttime = time.time() + poll_interval = 0.1 + while time.time() - starttime < timeout: + sock = None + try: + sock = socket.socket() + sock.connect((host, port)) + return True + except socket.error as e: + if e[0] != errno.ECONNREFUSED: + raise + finally: + if sock: + sock.close() + time.sleep(poll_interval) + return False + + +class Transport(object): + def __init__(self, host, port, url_prefix="", port_timeout=60): + self.host = host + self.port = port + self.port_timeout = port_timeout + if url_prefix == "": + self.path_prefix = "/" + else: + self.path_prefix = "/%s/" % url_prefix.strip("/") + self._connection = None + + def connect(self): + wait_for_port(self.host, self.port, self.port_timeout) + self._connection = httplib.HTTPConnection(self.host, self.port) + + def close_connection(self): + if self._connection: + self._connection.close() + self._connection = None + + def url(self, suffix): + return urlparse.urljoin(self.url_prefix, suffix) + + def send(self, method, url, body=None, headers=None, key=None): + if not self._connection: + self.connect() + + if body is None and method == "POST": + body = {} + + if isinstance(body, dict): + body = json.dumps(body) + + if isinstance(body, unicode): + body = body.encode("utf-8") + + if headers is None: + headers = {} + + url = self.path_prefix + url + + self._connection.request(method, url, body, headers) + + try: + resp = self._connection.getresponse() + except Exception: + # This should probably be more specific + raise IOError + body = resp.read() + + try: + data = json.loads(body) + except: + raise + raise WebDriverException("Could not parse response body as JSON: %s" % body) + + if resp.status != 200: + cls = _exceptions.get(resp.status, {}).get(data.get("status", None), WebDriverException) + raise cls(data.get("message", "")) + + if key is not None: + data = data[key] + + if not data: + data = None + + return data + + +def command(func): + def inner(self, *args, **kwargs): + if hasattr(self, "session"): + session_id = self.session.session_id + else: + session_id = self.session_id + + if session_id is None: + raise SessionNotCreatedException("Session not created") + return func(self, *args, **kwargs) + + inner.__name__ = func.__name__ + inner.__doc__ = func.__doc__ + + return inner + + +class Timeouts(object): + def __init__(self, session): + self.session = session + self._script = 30 + self._load = 0 + self._implicit_wait = 0 + + def _set_timeouts(self, name, value): + body = {"type": name, + "ms": value * 1000} + return self.session.send_command("POST", "timeouts", body) + + @property + def script(self): + return self._script + + @script.setter + def script(self, value): + self._set_timeouts("script", value) + self._script = value + + @property + def load(self): + return self._load + + @load.setter + def set_load(self, value): + self._set_timeouts("page load", value) + self._script = value + + @property + def implicit_wait(self): + return self._implicit_wait + + @implicit_wait.setter + def implicit_wait(self, value): + self._set_timeouts("implicit wait", value) + self._implicit_wait = value + + +class Window(object): + def __init__(self, session): + self.session = session + + @property + @command + def size(self): + return self.session.send_command("GET", "window/size") + + @size.setter + @command + def size(self, (height, width)): + body = {"width": width, + "height": height} + + return self.session.send_command("POST", "window/size", body) + + @property + @command + def maximize(self): + return self.session.send_command("POST", "window/maximize") + + +class Find(object): + def __init__(self, session): + self.session = session + + @command + def css(self, selector, all=True): + return self._find_element("css selector", selector, all) + + def _find_element(self, strategy, selector, all): + route = "elements" if all else "element" + + body = {"using": strategy, + "value": selector} + + data = self.session.send_command("POST", route, body, key="value") + + if all: + rv = [self.session._element(item) for item in data] + else: + rv = self.session._element(data) + + return rv + + +class Session(object): + def __init__(self, host, port, url_prefix="", desired_capabilities=None, port_timeout=60): + self.transport = Transport(host, port, url_prefix, port_timeout) + self.desired_capabilities = desired_capabilities + self.session_id = None + self.timeouts = None + self.window = None + self.find = None + self._element_cache = {} + + def start(self): + desired_capabilities = self.desired_capabilities if self.desired_capabilities else {} + body = {"capabilities": {"desiredCapabilites": desired_capabilities}} + + rv = self.transport.send("POST", "session", body=body) + self.session_id = rv["sessionId"] + + self.timeouts = Timeouts(self) + self.window = Window(self) + self.find = Find(self) + + return rv["value"] + + @command + def end(self): + url = "session/%s" % self.session_id + self.transport.send("DELETE", url) + self.session_id = None + self.timeouts = None + self.window = None + self.find = None + self.transport.close_connection() + + def __enter__(self): + resp = self.start() + if resp.error: + raise Exception(resp) + return self + + def __exit__(self, *args, **kwargs): + resp = self.end() + if resp.error: + raise Exception(resp) + + def send_command(self, method, url, body=None, key=None): + url = urlparse.urljoin("session/%s/" % self.session_id, url) + return self.transport.send(method, url, body, key=key) + + @property + @command + def url(self): + return self.send_command("GET", "url", key="value") + + @url.setter + @command + def url(self, url): + if urlparse.urlsplit(url).netloc is None: + return self.url(url) + body = {"url": url} + return self.send_command("POST", "url", body) + + @command + def back(self): + return self.send_command("POST", "back") + + @command + def forward(self): + return self.send_command("POST", "forward") + + @command + def refresh(self): + return self.send_command("POST", "refresh") + + @property + @command + def title(self): + return self.send_command("GET", "title", key="value") + + @property + @command + def handle(self): + return self.send_command("GET", "window_handle", key="value") + + @handle.setter + @command + def handle(self, handle): + body = {"handle": handle} + return self.send_command("POST", "window", body=body) + + def switch_frame(self, frame): + if frame == "parent": + url = "frame/parent" + body = None + else: + url = "frame" + if isinstance(frame, Element): + body = {"id": frame.json()} + else: + body = {"id": frame} + print body + return self.send_command("POST", url, body) + + @command + def close(self): + return self.send_command("DELETE", "window_handle") + + @property + @command + def handles(self): + return self.send_command("GET", "window_handles", key="value") + + @property + @command + def active_element(self): + data = self.send_command("GET", "element/active", key="value") + if data is not None: + return self._element(data) + + def _element(self, data): + elem_id = data[element_key] + assert elem_id + if elem_id in self._element_cache: + return self._element_cache[elem_id] + return Element(self, elem_id) + + @command + def cookies(self, name=None): + if name is None: + url = "cookie" + else: + url = "cookie/%s" % name + return self.send_command("GET", url, {}, key="value") + + @command + def set_cookie(self, name, value, path=None, domain=None, secure=None, expiry=None): + body = {"name": name, + "value": value} + if path is not None: + body["path"] = path + if domain is not None: + body["domain"] = domain + if secure is not None: + body["secure"] = secure + if expiry is not None: + body["expiry"] = expiry + self.send_command("POST", "cookie", body) + + def delete_cookie(self, name=None): + if name is None: + url = "cookie" + else: + url = "cookie/%s" % name + self.send_command("DELETE", url, {}, key="value") + + #[...] + + @command + def execute_script(self, script, args=None): + if args is None: + args = [] + + body = { + "script": script, + "args": args + } + return self.send_command("POST", "execute", body, key="value") + + @command + def execute_async_script(self, script, args=None): + if args is None: + args = [] + + body = { + "script": script, + "args": args + } + return self.send_command("POST", "execute_async", body, key="value") + + #[...] + + @command + def screenshot(self): + return self.send_command("GET", "screenshot", key="value") + + +class Element(object): + def __init__(self, session, id): + self.session = session + self.id = id + assert id not in self.session._element_cache + self.session._element_cache[self.id] = self + + def json(self): + return {element_key: self.id} + + @property + def session_id(self): + return self.session.session_id + + def url(self, suffix): + return "element/%s/%s" % (self.id, suffix) + + @command + def find_element(self, strategy, selector): + body = {"using": strategy, + "value": selector} + + elem = self.session.send_command("POST", self.url("element"), body, key="value") + return self.session.element(elem) + + @command + def click(self): + self.session.send_command("POST", self.url("click"), {}) + + @command + def tap(self): + self.session.send_command("POST", self.url("tap"), {}) + + @command + def clear(self): + self.session.send_command("POST", self.url("clear"), {}) + + @command + def send_keys(self, keys): + if isinstance(keys, (str, unicode)): + keys = [char for char in keys] + + body = {"value": keys} + + return self.session.send_command("POST", self.url("value"), body) + + @property + @command + def text(self): + return self.session.send_command("GET", self.url("text"), key="value") + + @property + @command + def name(self): + return self.session.send_command("GET", self.url("name"), key="value") diff --git a/tests/wpt/harness/wptrunner/testharnessreport-servodriver.js b/tests/wpt/harness/wptrunner/testharnessreport-servodriver.js new file mode 100644 index 00000000000..2209af8b061 --- /dev/null +++ b/tests/wpt/harness/wptrunner/testharnessreport-servodriver.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +setup({output:%(output)d}); + +add_completion_callback(function() { + add_completion_callback(function (tests, status) { + var test_results = tests.map(function(x) { + return {name:x.name, status:x.status, message:x.message, stack:x.stack} + }); + var results = JSON.stringify({tests:test_results, + status: status.status, + message: status.message, + stack: status.stack}); + (function done() { + if (window.__wd_results_callback__) { + clearTimeout(__wd_results_timer__); + __wd_results_callback__(results) + } else { + setTimeout(done, 20); + } + })() + }) +});