mirror of
https://github.com/servo/servo.git
synced 2025-08-28 00:28:20 +01:00
Update web-platform-tests to revision e87f38097902e16348d4e17f4fe3bc2d0112bff1
This commit is contained in:
parent
2f8fa32e91
commit
db5631a086
381 changed files with 11610 additions and 4232 deletions
|
@ -179,7 +179,7 @@ Simple key-value pairs are of the form::
|
|||
|
||||
key: value
|
||||
|
||||
Note that unlike ini files, only `:` is a valid seperator; `=` will
|
||||
Note that unlike ini files, only `:` is a valid separator; `=` will
|
||||
not work as expected. Key-value pairs may also have conditional
|
||||
values of the form::
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ def env_options():
|
|||
# https://github.com/w3c/web-platform-tests/pull/9480
|
||||
return {"host_ip": "127.0.0.1",
|
||||
"host": "web-platform.test",
|
||||
"bind_hostname": False,
|
||||
"bind_address": False,
|
||||
"certificate_domain": "web-platform.test",
|
||||
"supports_debugger": True}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ def env_extras(**kwargs):
|
|||
def env_options():
|
||||
return {"host": "web-platform.test",
|
||||
"host_ip": "127.0.0.1",
|
||||
"bind_hostname": False,
|
||||
"bind_address": False,
|
||||
"testharnessreport": "testharnessreport-servo.js",
|
||||
"supports_debugger": True}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ def env_extras(**kwargs):
|
|||
|
||||
|
||||
def env_options():
|
||||
return {"bind_hostname": "true"}
|
||||
return {}
|
||||
|
||||
|
||||
class WebKitBrowser(Browser):
|
||||
|
|
|
@ -150,8 +150,8 @@ class TestEnvironment(object):
|
|||
if "host" in self.options:
|
||||
local_config["host"] = self.options["host"]
|
||||
|
||||
if "bind_hostname" in self.options:
|
||||
local_config["bind_hostname"] = self.options["bind_hostname"]
|
||||
if "bind_address" in self.options:
|
||||
local_config["bind_address"] = self.options["bind_address"]
|
||||
|
||||
with open(default_config_path) as f:
|
||||
default_config = json.load(f)
|
||||
|
|
|
@ -8,6 +8,7 @@ import urlparse
|
|||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from ..testrunner import Stop
|
||||
from protocol import Protocol
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
|
@ -199,6 +200,9 @@ class TestExecutor(object):
|
|||
message += traceback.format_exc(e)
|
||||
return test.result_cls(status, message), []
|
||||
|
||||
def wait(self):
|
||||
self.protocol.base.wait()
|
||||
|
||||
|
||||
class TestharnessExecutor(TestExecutor):
|
||||
convert_result = testharness_result_converter
|
||||
|
@ -367,25 +371,6 @@ class WdspecExecutor(TestExecutor):
|
|||
from . import pytestrunner
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
def __init__(self, executor, browser):
|
||||
self.executor = executor
|
||||
self.browser = browser
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
return self.executor.logger
|
||||
|
||||
def setup(self, runner):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
def wait(self):
|
||||
pass
|
||||
|
||||
|
||||
class WdspecRun(object):
|
||||
def __init__(self, func, session, path, timeout):
|
||||
self.func = func
|
||||
|
@ -426,6 +411,14 @@ class WdspecRun(object):
|
|||
self.result_flag.set()
|
||||
|
||||
|
||||
class ConnectionlessProtocol(Protocol):
|
||||
def connect(self):
|
||||
pass
|
||||
|
||||
def after_connect(self):
|
||||
pass
|
||||
|
||||
|
||||
class WebDriverProtocol(Protocol):
|
||||
server_cls = None
|
||||
|
||||
|
@ -437,24 +430,21 @@ class WebDriverProtocol(Protocol):
|
|||
self.session_config = None
|
||||
self.server = None
|
||||
|
||||
def setup(self, runner):
|
||||
def connect(self):
|
||||
"""Connect to browser via the HTTP server."""
|
||||
try:
|
||||
self.server = self.server_cls(
|
||||
self.logger,
|
||||
binary=self.webdriver_binary,
|
||||
args=self.webdriver_args)
|
||||
self.server.start(block=False)
|
||||
self.logger.info(
|
||||
"WebDriver HTTP server listening at %s" % self.server.url)
|
||||
self.session_config = {"host": self.server.host,
|
||||
"port": self.server.port,
|
||||
"capabilities": self.capabilities}
|
||||
except Exception:
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
self.server = self.server_cls(
|
||||
self.logger,
|
||||
binary=self.webdriver_binary,
|
||||
args=self.webdriver_args)
|
||||
self.server.start(block=False)
|
||||
self.logger.info(
|
||||
"WebDriver HTTP server listening at %s" % self.server.url)
|
||||
self.session_config = {"host": self.server.host,
|
||||
"port": self.server.port,
|
||||
"capabilities": self.capabilities}
|
||||
|
||||
def after_connect(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
if self.server is not None and self.server.is_alive:
|
||||
|
@ -476,3 +466,80 @@ class WebDriverProtocol(Protocol):
|
|||
conn.request("HEAD", self.server.base_path + "invalid")
|
||||
res = conn.getresponse()
|
||||
return res.status == 404
|
||||
|
||||
|
||||
class CallbackHandler(object):
|
||||
"""Handle callbacks from testdriver-using tests.
|
||||
|
||||
The default implementation here makes sense for things that are roughly like
|
||||
WebDriver. Things that are more different to WebDriver may need to create a
|
||||
fully custom implementation."""
|
||||
|
||||
def __init__(self, logger, protocol, test_window):
|
||||
self.protocol = protocol
|
||||
self.test_window = test_window
|
||||
self.logger = logger
|
||||
self.callbacks = {
|
||||
"action": self.process_action,
|
||||
"complete": self.process_complete
|
||||
}
|
||||
|
||||
self.actions = {
|
||||
"click": ClickAction(self.logger, self.protocol)
|
||||
}
|
||||
|
||||
def __call__(self, result):
|
||||
url, command, payload = result
|
||||
self.logger.debug("Got async callback: %s" % result[1])
|
||||
try:
|
||||
callback = self.callbacks[command]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown callback type %r" % result[1])
|
||||
return callback(url, payload)
|
||||
|
||||
def process_complete(self, url, payload):
|
||||
rv = [url] + payload
|
||||
return True, rv
|
||||
|
||||
def process_action(self, url, payload):
|
||||
parent = self.protocol.base.current_window
|
||||
try:
|
||||
self.protocol.base.set_window(self.test_window)
|
||||
action = payload["action"]
|
||||
self.logger.debug("Got action: %s" % action)
|
||||
try:
|
||||
action_handler = self.actions[action]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown action %s" % action)
|
||||
try:
|
||||
action_handler(payload)
|
||||
except Exception as e:
|
||||
self.logger.warning("Action %s failed" % action)
|
||||
self.logger.warning(traceback.format_exc())
|
||||
self._send_message("complete", "failure")
|
||||
else:
|
||||
self.logger.debug("Action %s completed" % action)
|
||||
self._send_message("complete", "success")
|
||||
finally:
|
||||
self.protocol.base.set_window(parent)
|
||||
|
||||
return False, None
|
||||
|
||||
def _send_message(self, message_type, status, message=None):
|
||||
self.protocol.testdriver.send_message(message_type, status, message=message)
|
||||
|
||||
|
||||
class ClickAction(object):
|
||||
def __init__(self, logger, protocol):
|
||||
self.logger = logger
|
||||
self.protocol = protocol
|
||||
|
||||
def __call__(self, payload):
|
||||
selector = payload["selector"]
|
||||
elements = self.protocol.select.elements_by_selector(selector)
|
||||
if len(elements) == 0:
|
||||
raise ValueError("Selector matches no elements")
|
||||
elif len(elements) > 1:
|
||||
raise ValueError("Selector matches multiple elements")
|
||||
self.logger.debug("Clicking element: %s" % selector)
|
||||
self.protocol.click.element(elements[0])
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
|
@ -11,8 +12,8 @@ pytestrunner = None
|
|||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
from .base import (ExecutorException,
|
||||
Protocol,
|
||||
from .base import (CallbackHandler,
|
||||
ExecutorException,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestExecutor,
|
||||
|
@ -24,12 +25,18 @@ from .base import (ExecutorException,
|
|||
testharness_result_converter,
|
||||
reftest_result_converter,
|
||||
strip_server)
|
||||
|
||||
from .protocol import (BaseProtocolPart,
|
||||
TestharnessProtocolPart,
|
||||
PrefsProtocolPart,
|
||||
Protocol,
|
||||
StorageProtocolPart,
|
||||
SelectorProtocolPart,
|
||||
ClickProtocolPart,
|
||||
TestDriverProtocolPart)
|
||||
from ..testrunner import Stop
|
||||
from ..webdriver_server import GeckoDriverServer
|
||||
|
||||
|
||||
|
||||
def do_delayed_imports():
|
||||
global errors, marionette
|
||||
|
||||
|
@ -42,78 +49,17 @@ def do_delayed_imports():
|
|||
from marionette_driver import marionette, errors
|
||||
|
||||
|
||||
class MarionetteProtocol(Protocol):
|
||||
def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1):
|
||||
do_delayed_imports()
|
||||
|
||||
Protocol.__init__(self, executor, browser)
|
||||
self.marionette = None
|
||||
self.marionette_port = browser.marionette_port
|
||||
self.capabilities = capabilities
|
||||
self.timeout_multiplier = timeout_multiplier
|
||||
class MarionetteBaseProtocolPart(BaseProtocolPart):
|
||||
def __init__(self, parent):
|
||||
super(MarionetteBaseProtocolPart, self).__init__(parent)
|
||||
self.timeout = None
|
||||
self.runner_handle = None
|
||||
|
||||
def setup(self, runner):
|
||||
"""Connect to browser via Marionette."""
|
||||
Protocol.setup(self, runner)
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
|
||||
startup_timeout = marionette.Marionette.DEFAULT_STARTUP_TIMEOUT * self.timeout_multiplier
|
||||
self.marionette = marionette.Marionette(host='localhost',
|
||||
port=self.marionette_port,
|
||||
socket_timeout=None,
|
||||
startup_timeout=startup_timeout)
|
||||
try:
|
||||
self.logger.debug("Waiting for Marionette connection")
|
||||
while True:
|
||||
try:
|
||||
self.marionette.raise_for_port()
|
||||
break
|
||||
except IOError:
|
||||
# When running in a debugger wait indefinitely for Firefox to start
|
||||
if self.executor.debug_info is None:
|
||||
raise
|
||||
|
||||
self.logger.debug("Starting Marionette session")
|
||||
self.marionette.start_session()
|
||||
self.logger.debug("Marionette session started")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning("Failed to start a Marionette session: %s" % e)
|
||||
self.executor.runner.send_message("init_failed")
|
||||
|
||||
else:
|
||||
try:
|
||||
self.after_connect()
|
||||
except Exception:
|
||||
self.logger.warning("Post-connection steps failed")
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
self.marionette._request_in_app_shutdown()
|
||||
self.marionette.delete_session(send_request=False)
|
||||
except Exception:
|
||||
# This is typically because the session never started
|
||||
pass
|
||||
if self.marionette is not None:
|
||||
del self.marionette
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""Check if the Marionette connection is still active."""
|
||||
try:
|
||||
self.marionette.current_window_handle
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def after_connect(self):
|
||||
self.load_runner(self.executor.last_environment["protocol"])
|
||||
def execute_script(self, script, async=False):
|
||||
method = self.marionette.execute_async_script if async else self.marionette.execute_script
|
||||
return method(script, new_sandbox=False)
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
"""Set the Marionette script timeout.
|
||||
|
@ -121,13 +67,60 @@ class MarionetteProtocol(Protocol):
|
|||
:param timeout: Script timeout in seconds
|
||||
|
||||
"""
|
||||
self.marionette.timeout.script = timeout
|
||||
self.timeout = timeout
|
||||
if timeout != self.timeout:
|
||||
self.marionette.timeout.script = timeout
|
||||
self.timeout = timeout
|
||||
|
||||
def load_runner(self, protocol):
|
||||
@property
|
||||
def current_window(self):
|
||||
return self.marionette.current_window_handle
|
||||
|
||||
def set_window(self, handle):
|
||||
self.marionette.switch_to_window(handle)
|
||||
|
||||
def wait(self):
|
||||
try:
|
||||
socket_timeout = self.marionette.client.socket_timeout
|
||||
except AttributeError:
|
||||
# This can happen if there was a crash
|
||||
return
|
||||
if socket_timeout:
|
||||
try:
|
||||
self.marionette.timeout.script = socket_timeout / 2
|
||||
except (socket.error, IOError):
|
||||
self.logger.debug("Socket closed")
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.marionette.execute_async_script("")
|
||||
except errors.NoSuchWindowException:
|
||||
# The window closed
|
||||
break
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Script timed out")
|
||||
pass
|
||||
except (socket.timeout, IOError):
|
||||
self.logger.debug("Socket closed")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.warning(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
|
||||
class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
|
||||
def __init__(self, parent):
|
||||
super(MarionetteTestharnessProtocolPart, self).__init__(parent)
|
||||
self.runner_handle = None
|
||||
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def load_runner(self, url_protocol):
|
||||
# Check if we previously had a test window open, and if we did make sure it's closed
|
||||
self.marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||||
url = urlparse.urljoin(self.executor.server_url(protocol), "/testharness_runner.html")
|
||||
url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
|
||||
"/testharness_runner.html")
|
||||
self.logger.debug("Loading %s" % url)
|
||||
self.runner_handle = self.marionette.current_window_handle
|
||||
try:
|
||||
|
@ -142,7 +135,7 @@ class MarionetteProtocol(Protocol):
|
|||
self.marionette.execute_script(
|
||||
"document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
|
||||
|
||||
def close_old_windows(self, protocol):
|
||||
def close_old_windows(self, url_protocol):
|
||||
handles = self.marionette.window_handles
|
||||
runner_handle = None
|
||||
try:
|
||||
|
@ -168,7 +161,8 @@ class MarionetteProtocol(Protocol):
|
|||
|
||||
self.marionette.switch_to_window(runner_handle)
|
||||
if runner_handle != self.runner_handle:
|
||||
self.load_runner(protocol)
|
||||
self.load_runner(url_protocol)
|
||||
return self.runner_handle
|
||||
|
||||
def dismiss_alert(self, f):
|
||||
while True:
|
||||
|
@ -183,50 +177,36 @@ class MarionetteProtocol(Protocol):
|
|||
else:
|
||||
break
|
||||
|
||||
def wait(self):
|
||||
try:
|
||||
socket_timeout = self.marionette.client.socket_timeout
|
||||
except AttributeError:
|
||||
# This can happen if there was a crash
|
||||
return
|
||||
if socket_timeout:
|
||||
def get_test_window(self, window_id, parent):
|
||||
test_window = None
|
||||
if window_id:
|
||||
try:
|
||||
self.marionette.timeout.script = socket_timeout / 2
|
||||
except (socket.error, IOError):
|
||||
self.logger.debug("Socket closed")
|
||||
return
|
||||
|
||||
self.marionette.switch_to_window(self.runner_handle)
|
||||
while True:
|
||||
try:
|
||||
self.marionette.execute_async_script("")
|
||||
except errors.NoSuchWindowException:
|
||||
# The window closed
|
||||
break
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Script timed out")
|
||||
# Try this, it's in Level 1 but nothing supports it yet
|
||||
win_s = self.marionette.execute_script("return window['%s'];" % self.window_id)
|
||||
win_obj = json.loads(win_s)
|
||||
test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
|
||||
except Exception:
|
||||
pass
|
||||
except (socket.timeout, IOError):
|
||||
self.logger.debug("Socket closed")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.warning(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
def on_environment_change(self, old_environment, new_environment):
|
||||
#Unset all the old prefs
|
||||
for name in old_environment.get("prefs", {}).iterkeys():
|
||||
value = self.executor.original_pref_values[name]
|
||||
if value is None:
|
||||
self.clear_user_pref(name)
|
||||
if test_window is None:
|
||||
after = self.marionette.window_handles
|
||||
if len(after) == 2:
|
||||
test_window = next(iter(set(after) - set([parent])))
|
||||
elif after[0] == parent and len(after) > 2:
|
||||
# Hope the first one here is the test window
|
||||
test_window = after[1]
|
||||
else:
|
||||
self.set_pref(name, value)
|
||||
raise Exception("unable to find test window")
|
||||
|
||||
for name, value in new_environment.get("prefs", {}).iteritems():
|
||||
self.executor.original_pref_values[name] = self.get_pref(name)
|
||||
self.set_pref(name, value)
|
||||
assert test_window != parent
|
||||
return test_window
|
||||
|
||||
def set_pref(self, name, value):
|
||||
|
||||
class MarionettePrefsProtocolPart(PrefsProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def set(self, name, value):
|
||||
if value.lower() not in ("true", "false"):
|
||||
try:
|
||||
int(value)
|
||||
|
@ -258,7 +238,7 @@ class MarionetteProtocol(Protocol):
|
|||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
def clear_user_pref(self, name):
|
||||
def clear(self, name):
|
||||
self.logger.info("Clearing pref %s" % (name))
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
|
@ -269,7 +249,7 @@ class MarionetteProtocol(Protocol):
|
|||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
def get_pref(self, name):
|
||||
def get(self, name):
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
|
@ -289,6 +269,11 @@ class MarionetteProtocol(Protocol):
|
|||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
|
||||
class MarionetteStorageProtocolPart(StorageProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def clear_origin(self, url):
|
||||
self.logger.info("Clearing origin %s" % (url))
|
||||
script = """
|
||||
|
@ -307,12 +292,118 @@ class MarionetteProtocol(Protocol):
|
|||
self.marionette.execute_script(script)
|
||||
|
||||
|
||||
class MarionetteSelectorProtocolPart(SelectorProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def elements_by_selector(self, selector):
|
||||
return self.marionette.find_elements("css selector", selector)
|
||||
|
||||
|
||||
class MarionetteClickProtocolPart(ClickProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def element(self, element):
|
||||
return element.click()
|
||||
|
||||
|
||||
class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def send_message(self, message_type, status, message=None):
|
||||
obj = {
|
||||
"type": "testdriver-%s" % str(message_type),
|
||||
"status": str(status)
|
||||
}
|
||||
if message:
|
||||
obj["message"] = str(message)
|
||||
self.marionette.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
|
||||
|
||||
|
||||
class MarionetteProtocol(Protocol):
|
||||
implements = [MarionetteBaseProtocolPart,
|
||||
MarionetteTestharnessProtocolPart,
|
||||
MarionettePrefsProtocolPart,
|
||||
MarionetteStorageProtocolPart,
|
||||
MarionetteSelectorProtocolPart,
|
||||
MarionetteClickProtocolPart,
|
||||
MarionetteTestDriverProtocolPart]
|
||||
|
||||
def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1):
|
||||
do_delayed_imports()
|
||||
|
||||
super(MarionetteProtocol, self).__init__(executor, browser)
|
||||
self.marionette = None
|
||||
self.marionette_port = browser.marionette_port
|
||||
self.capabilities = capabilities
|
||||
self.timeout_multiplier = timeout_multiplier
|
||||
self.runner_handle = None
|
||||
|
||||
def connect(self):
|
||||
self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
|
||||
startup_timeout = marionette.Marionette.DEFAULT_STARTUP_TIMEOUT * self.timeout_multiplier
|
||||
self.marionette = marionette.Marionette(host='localhost',
|
||||
port=self.marionette_port,
|
||||
socket_timeout=None,
|
||||
startup_timeout=startup_timeout)
|
||||
|
||||
self.logger.debug("Waiting for Marionette connection")
|
||||
while True:
|
||||
try:
|
||||
self.marionette.raise_for_port()
|
||||
break
|
||||
except IOError:
|
||||
# When running in a debugger wait indefinitely for Firefox to start
|
||||
if self.executor.debug_info is None:
|
||||
raise
|
||||
|
||||
self.logger.debug("Starting Marionette session")
|
||||
self.marionette.start_session()
|
||||
self.logger.debug("Marionette session started")
|
||||
|
||||
def after_connect(self):
|
||||
self.testharness.load_runner(self.executor.last_environment["protocol"])
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
self.marionette._request_in_app_shutdown()
|
||||
self.marionette.delete_session(send_request=False)
|
||||
except Exception:
|
||||
# This is typically because the session never started
|
||||
pass
|
||||
if self.marionette is not None:
|
||||
del self.marionette
|
||||
super(MarionetteProtocol, self).teardown()
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
try:
|
||||
self.marionette.current_window_handle
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def on_environment_change(self, old_environment, new_environment):
|
||||
#Unset all the old prefs
|
||||
for name in old_environment.get("prefs", {}).iterkeys():
|
||||
value = self.executor.original_pref_values[name]
|
||||
if value is None:
|
||||
self.prefs.clear(name)
|
||||
else:
|
||||
self.prefs.set(name, value)
|
||||
|
||||
for name, value in new_environment.get("prefs", {}).iteritems():
|
||||
self.executor.original_pref_values[name] = self.get_pref(name)
|
||||
self.prefs.set(name, value)
|
||||
|
||||
|
||||
class ExecuteAsyncScriptRun(object):
|
||||
def __init__(self, logger, func, protocol, url, timeout):
|
||||
self.logger = logger
|
||||
self.result = (None, None)
|
||||
self.protocol = protocol
|
||||
self.marionette = protocol.marionette
|
||||
self.func = func
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
|
@ -322,19 +413,18 @@ class ExecuteAsyncScriptRun(object):
|
|||
index = self.url.rfind("/storage/")
|
||||
if index != -1:
|
||||
# Clear storage
|
||||
self.protocol.clear_origin(self.url)
|
||||
self.protocol.storage.clear_origin(self.url)
|
||||
|
||||
timeout = self.timeout
|
||||
|
||||
try:
|
||||
if timeout is not None:
|
||||
if timeout + extra_timeout != self.protocol.timeout:
|
||||
self.protocol.set_timeout(timeout + extra_timeout)
|
||||
self.protocol.base.set_timeout(timeout + extra_timeout)
|
||||
else:
|
||||
# We just want it to never time out, really, but marionette doesn't
|
||||
# make that possible. It also seems to time out immediately if the
|
||||
# timeout is set too high. This works at least.
|
||||
self.protocol.set_timeout(2**28 - 1)
|
||||
self.protocol.base.set_timeout(2**28 - 1)
|
||||
except IOError:
|
||||
self.logger.error("Lost marionette connection before starting test")
|
||||
return Stop
|
||||
|
@ -363,7 +453,7 @@ class ExecuteAsyncScriptRun(object):
|
|||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.marionette, self.url, self.timeout)
|
||||
self.result = True, self.func(self.protocol, self.url, self.timeout)
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Got a marionette timeout")
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
|
@ -384,6 +474,8 @@ class ExecuteAsyncScriptRun(object):
|
|||
|
||||
|
||||
class MarionetteTestharnessExecutor(TestharnessExecutor):
|
||||
supports_testdriver = True
|
||||
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
close_after_done=True, debug_info=None, capabilities=None,
|
||||
**kwargs):
|
||||
|
@ -394,6 +486,7 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
|
|||
|
||||
self.protocol = MarionetteProtocol(self, browser, capabilities, timeout_multiplier)
|
||||
self.script = open(os.path.join(here, "testharness_marionette.js")).read()
|
||||
self.script_resume = open(os.path.join(here, "testharness_marionette_resume.js")).read()
|
||||
self.close_after_done = close_after_done
|
||||
self.window_id = str(uuid.uuid4())
|
||||
|
||||
|
@ -409,7 +502,7 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
|
|||
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||
|
||||
if new_environment["protocol"] != self.last_environment["protocol"]:
|
||||
self.protocol.load_runner(new_environment["protocol"])
|
||||
self.protocol.testharness.load_runner(new_environment["protocol"])
|
||||
|
||||
def do_test(self, test):
|
||||
timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None
|
||||
|
@ -425,24 +518,34 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
|
|||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_testharness(self, marionette, url, timeout):
|
||||
if self.close_after_done:
|
||||
marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||||
self.protocol.close_old_windows(self.protocol)
|
||||
def do_testharness(self, protocol, url, timeout):
|
||||
protocol.base.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||||
parent_window = protocol.testharness.close_old_windows(protocol)
|
||||
|
||||
if timeout is not None:
|
||||
timeout_ms = str(timeout * 1000)
|
||||
else:
|
||||
timeout_ms = "null"
|
||||
|
||||
script = self.script % {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout_ms,
|
||||
"explicit_timeout": timeout is None}
|
||||
format_map = {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout_ms,
|
||||
"explicit_timeout": timeout is None}
|
||||
|
||||
rv = marionette.execute_async_script(script, new_sandbox=False)
|
||||
script = self.script % format_map
|
||||
|
||||
rv = protocol.base.execute_script(script)
|
||||
test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
|
||||
|
||||
handler = CallbackHandler(self.logger, protocol, test_window)
|
||||
while True:
|
||||
result = protocol.base.execute_script(
|
||||
self.script_resume % format_map, async=True)
|
||||
done, rv = handler(result)
|
||||
if done:
|
||||
break
|
||||
return rv
|
||||
|
||||
|
||||
|
@ -506,8 +609,8 @@ class MarionetteRefTestExecutor(RefTestExecutor):
|
|||
self.has_window = False
|
||||
|
||||
if not self.has_window:
|
||||
self.protocol.marionette.execute_script(self.script)
|
||||
self.protocol.marionette.switch_to_window(self.protocol.marionette.window_handles[-1])
|
||||
self.protocol.base.execute_script(self.script)
|
||||
self.protocol.base.set_window(self.protocol.marionette.window_handles[-1])
|
||||
self.has_window = True
|
||||
|
||||
result = self.implementation.run_test(test)
|
||||
|
|
|
@ -8,12 +8,18 @@ import traceback
|
|||
import urlparse
|
||||
import uuid
|
||||
|
||||
from .base import (Protocol,
|
||||
from .base import (CallbackHandler,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestharnessExecutor,
|
||||
extra_timeout,
|
||||
strip_server)
|
||||
from .protocol import (BaseProtocolPart,
|
||||
TestharnessProtocolPart,
|
||||
Protocol,
|
||||
SelectorProtocolPart,
|
||||
ClickProtocolPart,
|
||||
TestDriverProtocolPart)
|
||||
from ..testrunner import Stop
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
@ -32,45 +38,142 @@ def do_delayed_imports():
|
|||
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||
|
||||
|
||||
class SeleniumBaseProtocolPart(BaseProtocolPart):
|
||||
def setup(self):
|
||||
self.webdriver = self.parent.webdriver
|
||||
|
||||
def execute_script(self, script, async=False):
|
||||
method = self.webdriver.execute_async_script if async else self.webdriver.execute_script
|
||||
return method(script)
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
self.webdriver.set_script_timeout(timeout * 1000)
|
||||
|
||||
@property
|
||||
def current_window(self):
|
||||
return self.webdriver.current_window_handle
|
||||
|
||||
def set_window(self, handle):
|
||||
self.webdriver.switch_to_window(handle)
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
self.webdriver.execute_async_script("")
|
||||
except exceptions.TimeoutException:
|
||||
pass
|
||||
except (socket.timeout, exceptions.NoSuchWindowException,
|
||||
exceptions.ErrorInResponseException, IOError):
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
|
||||
class SeleniumTestharnessProtocolPart(TestharnessProtocolPart):
|
||||
def setup(self):
|
||||
self.webdriver = self.parent.webdriver
|
||||
|
||||
def load_runner(self, url_protocol):
|
||||
url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
|
||||
"/testharness_runner.html")
|
||||
self.logger.debug("Loading %s" % url)
|
||||
self.webdriver.get(url)
|
||||
self.webdriver.execute_script("document.title = '%s'" %
|
||||
threading.current_thread().name.replace("'", '"'))
|
||||
|
||||
def close_old_windows(self):
|
||||
exclude = self.webdriver.current_window_handle
|
||||
handles = [item for item in self.webdriver.window_handles if item != exclude]
|
||||
for handle in handles:
|
||||
try:
|
||||
self.webdriver.switch_to_window(handle)
|
||||
self.webdriver.close()
|
||||
except exceptions.NoSuchWindowException:
|
||||
pass
|
||||
self.webdriver.switch_to_window(exclude)
|
||||
return exclude
|
||||
|
||||
def get_test_window(self, window_id, parent):
|
||||
test_window = None
|
||||
if window_id:
|
||||
try:
|
||||
# Try this, it's in Level 1 but nothing supports it yet
|
||||
win_s = self.webdriver.execute_script("return window['%s'];" % self.window_id)
|
||||
win_obj = json.loads(win_s)
|
||||
test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if test_window is None:
|
||||
after = self.webdriver.window_handles
|
||||
if len(after) == 2:
|
||||
test_window = next(iter(set(after) - set([parent])))
|
||||
elif after[0] == parent and len(after) > 2:
|
||||
# Hope the first one here is the test window
|
||||
test_window = after[1]
|
||||
else:
|
||||
raise Exception("unable to find test window")
|
||||
|
||||
assert test_window != parent
|
||||
return test_window
|
||||
|
||||
|
||||
class SeleniumSelectorProtocolPart(SelectorProtocolPart):
|
||||
def setup(self):
|
||||
self.webdriver = self.parent.webdriver
|
||||
|
||||
def elements_by_selector(self, selector):
|
||||
return self.webdriver.find_elements_by_css_selector(selector)
|
||||
|
||||
|
||||
class SeleniumClickProtocolPart(ClickProtocolPart):
|
||||
def setup(self):
|
||||
self.webdriver = self.parent.webdriver
|
||||
|
||||
def element(self, element):
|
||||
return element.click()
|
||||
|
||||
|
||||
class SeleniumTestDriverProtocolPart(TestDriverProtocolPart):
|
||||
def setup(self):
|
||||
self.webdriver = self.parent.webdriver
|
||||
|
||||
def send_message(self, message_type, status, message=None):
|
||||
obj = {
|
||||
"type": "testdriver-%s" % str(message_type),
|
||||
"status": str(status)
|
||||
}
|
||||
if message:
|
||||
obj["message"] = str(message)
|
||||
self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
|
||||
|
||||
|
||||
class SeleniumProtocol(Protocol):
|
||||
implements = [SeleniumBaseProtocolPart,
|
||||
SeleniumTestharnessProtocolPart,
|
||||
SeleniumSelectorProtocolPart,
|
||||
SeleniumClickProtocolPart,
|
||||
SeleniumTestDriverProtocolPart]
|
||||
|
||||
def __init__(self, executor, browser, capabilities, **kwargs):
|
||||
do_delayed_imports()
|
||||
|
||||
Protocol.__init__(self, executor, browser)
|
||||
super(SeleniumProtocol, self).__init__(executor, browser)
|
||||
self.capabilities = capabilities
|
||||
self.url = browser.webdriver_url
|
||||
self.webdriver = None
|
||||
|
||||
def setup(self, runner):
|
||||
def connect(self):
|
||||
"""Connect to browser via Selenium's WebDriver implementation."""
|
||||
self.runner = runner
|
||||
self.logger.debug("Connecting to Selenium on URL: %s" % self.url)
|
||||
|
||||
session_started = False
|
||||
try:
|
||||
self.webdriver = webdriver.Remote(command_executor=RemoteConnection(self.url.strip("/"),
|
||||
resolve_ip=False),
|
||||
desired_capabilities=self.capabilities)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
"Connecting to Selenium failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
self.logger.debug("Selenium session started")
|
||||
session_started = True
|
||||
self.webdriver = webdriver.Remote(command_executor=RemoteConnection(self.url.strip("/"),
|
||||
resolve_ip=False),
|
||||
desired_capabilities=self.capabilities)
|
||||
|
||||
if not session_started:
|
||||
self.logger.warning("Failed to connect to Selenium")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
try:
|
||||
self.after_connect()
|
||||
except Exception:
|
||||
print >> sys.stderr, traceback.format_exc()
|
||||
self.logger.warning(
|
||||
"Failed to connect to navigate initial page")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
def after_conect(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
self.logger.debug("Hanging up on Selenium session")
|
||||
|
@ -90,35 +193,14 @@ class SeleniumProtocol(Protocol):
|
|||
return True
|
||||
|
||||
def after_connect(self):
|
||||
self.load_runner("http")
|
||||
|
||||
def load_runner(self, protocol):
|
||||
url = urlparse.urljoin(self.executor.server_url(protocol),
|
||||
"/testharness_runner.html")
|
||||
self.logger.debug("Loading %s" % url)
|
||||
self.webdriver.get(url)
|
||||
self.webdriver.execute_script("document.title = '%s'" %
|
||||
threading.current_thread().name.replace("'", '"'))
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
self.webdriver.execute_async_script("")
|
||||
except exceptions.TimeoutException:
|
||||
pass
|
||||
except (socket.timeout, exceptions.NoSuchWindowException,
|
||||
exceptions.ErrorInResponseException, IOError):
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(traceback.format_exc(e))
|
||||
break
|
||||
self.testharness.load_runner(self.executor.last_environment["protocol"])
|
||||
|
||||
|
||||
class SeleniumRun(object):
|
||||
def __init__(self, func, webdriver, url, timeout):
|
||||
def __init__(self, func, protocol, url, timeout):
|
||||
self.func = func
|
||||
self.result = None
|
||||
self.webdriver = webdriver
|
||||
self.protocol = protocol
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
@ -127,7 +209,7 @@ class SeleniumRun(object):
|
|||
timeout = self.timeout
|
||||
|
||||
try:
|
||||
self.webdriver.set_script_timeout((timeout + extra_timeout) * 1000)
|
||||
self.protocol.base.set_timeout((timeout + extra_timeout))
|
||||
except exceptions.ErrorInResponseException:
|
||||
self.logger.error("Lost WebDriver connection")
|
||||
return Stop
|
||||
|
@ -144,7 +226,7 @@ class SeleniumRun(object):
|
|||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.webdriver, self.url, self.timeout)
|
||||
self.result = True, self.func(self.protocol, self.url, self.timeout)
|
||||
except exceptions.TimeoutException:
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
except (socket.timeout, exceptions.ErrorInResponseException):
|
||||
|
@ -182,13 +264,13 @@ class SeleniumTestharnessExecutor(TestharnessExecutor):
|
|||
|
||||
def on_environment_change(self, new_environment):
|
||||
if new_environment["protocol"] != self.last_environment["protocol"]:
|
||||
self.protocol.load_runner(new_environment["protocol"])
|
||||
self.protocol.testharness.load_runner(new_environment["protocol"])
|
||||
|
||||
def do_test(self, test):
|
||||
url = self.test_url(test)
|
||||
|
||||
success, data = SeleniumRun(self.do_testharness,
|
||||
self.protocol.webdriver,
|
||||
self.protocol,
|
||||
url,
|
||||
test.timeout * self.timeout_multiplier).run()
|
||||
|
||||
|
@ -197,111 +279,28 @@ class SeleniumTestharnessExecutor(TestharnessExecutor):
|
|||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_testharness(self, webdriver, url, timeout):
|
||||
def do_testharness(self, protocol, url, timeout):
|
||||
format_map = {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout * 1000}
|
||||
|
||||
parent = webdriver.current_window_handle
|
||||
handles = [item for item in webdriver.window_handles if item != parent]
|
||||
for handle in handles:
|
||||
try:
|
||||
webdriver.switch_to_window(handle)
|
||||
webdriver.close()
|
||||
except exceptions.NoSuchWindowException:
|
||||
pass
|
||||
webdriver.switch_to_window(parent)
|
||||
parent_window = protocol.testharness.close_old_windows()
|
||||
# Now start the test harness
|
||||
protocol.base.execute_script(self.script % format_map)
|
||||
test_window = protocol.testharness.get_test_window(webdriver, parent_window)
|
||||
|
||||
webdriver.execute_script(self.script % format_map)
|
||||
try:
|
||||
# Try this, it's in Level 1 but nothing supports it yet
|
||||
win_s = webdriver.execute_script("return window['%s'];" % self.window_id)
|
||||
win_obj = json.loads(win_s)
|
||||
test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
|
||||
except Exception:
|
||||
after = webdriver.window_handles
|
||||
if len(after) == 2:
|
||||
test_window = next(iter(set(after) - set([parent])))
|
||||
elif after[0] == parent and len(after) > 2:
|
||||
# Hope the first one here is the test window
|
||||
test_window = after[1]
|
||||
else:
|
||||
raise Exception("unable to find test window")
|
||||
assert test_window != parent
|
||||
|
||||
handler = CallbackHandler(webdriver, test_window, self.logger)
|
||||
handler = CallbackHandler(self.logger, protocol, test_window)
|
||||
while True:
|
||||
result = webdriver.execute_async_script(
|
||||
self.script_resume % format_map)
|
||||
result = protocol.base.execute_script(
|
||||
self.script_resume % format_map, async=True)
|
||||
done, rv = handler(result)
|
||||
if done:
|
||||
break
|
||||
return rv
|
||||
|
||||
|
||||
class CallbackHandler(object):
|
||||
def __init__(self, webdriver, test_window, logger):
|
||||
self.webdriver = webdriver
|
||||
self.test_window = test_window
|
||||
self.logger = logger
|
||||
|
||||
def __call__(self, result):
|
||||
self.logger.debug("Got async callback: %s" % result[1])
|
||||
try:
|
||||
attr = getattr(self, "process_%s" % result[1])
|
||||
except AttributeError:
|
||||
raise ValueError("Unknown callback type %r" % result[1])
|
||||
else:
|
||||
return attr(result)
|
||||
|
||||
def process_complete(self, result):
|
||||
rv = [result[0]] + result[2]
|
||||
return True, rv
|
||||
|
||||
def process_action(self, result):
|
||||
parent = self.webdriver.current_window_handle
|
||||
try:
|
||||
self.webdriver.switch_to.window(self.test_window)
|
||||
action = result[2]["action"]
|
||||
self.logger.debug("Got action: %s" % action)
|
||||
if action == "click":
|
||||
selector = result[2]["selector"]
|
||||
elements = self.webdriver.find_elements_by_css_selector(selector)
|
||||
if len(elements) == 0:
|
||||
raise ValueError("Selector matches no elements")
|
||||
elif len(elements) > 1:
|
||||
raise ValueError("Selector matches multiple elements")
|
||||
self.logger.debug("Clicking element: %s" % selector)
|
||||
try:
|
||||
elements[0].click()
|
||||
except (exceptions.ElementNotInteractableException,
|
||||
exceptions.ElementNotVisibleException) as e:
|
||||
self._send_message("complete",
|
||||
"failure",
|
||||
e)
|
||||
self.logger.debug("Clicking element failed: %s" % str(e))
|
||||
else:
|
||||
self._send_message("complete",
|
||||
"success")
|
||||
self.logger.debug("Clicking element succeeded")
|
||||
finally:
|
||||
self.webdriver.switch_to.window(parent)
|
||||
|
||||
return False, None
|
||||
|
||||
def _send_message(self, message_type, status, message=None):
|
||||
obj = {
|
||||
"type": "testdriver-%s" % str(message_type),
|
||||
"status": str(status)
|
||||
}
|
||||
if message:
|
||||
obj["message"] = str(message)
|
||||
self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
|
||||
|
||||
|
||||
|
||||
class SeleniumRefTestExecutor(RefTestExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
screenshot_cache=None, close_after_done=True,
|
||||
|
@ -342,11 +341,12 @@ class SeleniumRefTestExecutor(RefTestExecutor):
|
|||
assert dpi is None
|
||||
|
||||
return SeleniumRun(self._screenshot,
|
||||
self.protocol.webdriver,
|
||||
self.protocol,
|
||||
self.test_url(test),
|
||||
test.timeout).run()
|
||||
|
||||
def _screenshot(self, webdriver, url, timeout):
|
||||
def _screenshot(self, protocol, url, timeout):
|
||||
webdriver = protocol.webdriver
|
||||
webdriver.get(url)
|
||||
|
||||
webdriver.execute_async_script(self.wait_script)
|
||||
|
|
|
@ -16,11 +16,12 @@ from mozprocess import ProcessHandler
|
|||
from serve.serve import make_hosts_file
|
||||
|
||||
from .base import (ExecutorException,
|
||||
Protocol,
|
||||
ConnectionlessProtocol,
|
||||
RefTestImplementation,
|
||||
testharness_result_converter,
|
||||
reftest_result_converter,
|
||||
WdspecExecutor, WebDriverProtocol)
|
||||
WdspecExecutor,
|
||||
WebDriverProtocol)
|
||||
from .process import ProcessTestExecutor
|
||||
from ..browsers.base import browser_command
|
||||
from ..wpttest import WdspecResult, WdspecSubtestResult
|
||||
|
@ -50,7 +51,7 @@ class ServoTestharnessExecutor(ProcessTestExecutor):
|
|||
self.pause_after_test = pause_after_test
|
||||
self.result_data = None
|
||||
self.result_flag = None
|
||||
self.protocol = Protocol(self, browser)
|
||||
self.protocol = ConnectionlessProtocol(self, browser)
|
||||
self.hosts_path = write_hosts_file(server_config)
|
||||
|
||||
def teardown(self):
|
||||
|
@ -181,7 +182,7 @@ class ServoRefTestExecutor(ProcessTestExecutor):
|
|||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
|
||||
self.protocol = Protocol(self, browser)
|
||||
self.protocol = ConnectionlessProtocol(self, browser)
|
||||
self.screenshot_cache = screenshot_cache
|
||||
self.implementation = RefTestImplementation(self)
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
|
@ -284,5 +285,6 @@ class ServoRefTestExecutor(ProcessTestExecutor):
|
|||
class ServoDriverProtocol(WebDriverProtocol):
|
||||
server_cls = ServoDriverServer
|
||||
|
||||
|
||||
class ServoWdspecExecutor(WdspecExecutor):
|
||||
protocol_cls = ServoDriverProtocol
|
||||
|
|
|
@ -33,28 +33,15 @@ class ServoWebDriverProtocol(Protocol):
|
|||
self.port = browser.webdriver_port
|
||||
self.session = None
|
||||
|
||||
def setup(self, runner):
|
||||
def connect(self):
|
||||
"""Connect to browser via WebDriver."""
|
||||
self.runner = runner
|
||||
|
||||
url = "http://%s:%d" % (self.host, self.port)
|
||||
session_started = False
|
||||
try:
|
||||
self.session = webdriver.Session(self.host, self.port,
|
||||
extension=webdriver.servo.ServoCommandExtensions)
|
||||
self.session.start()
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
"Connecting with WebDriver failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
self.logger.debug("session started")
|
||||
session_started = True
|
||||
self.session = webdriver.Session(self.host, self.port,
|
||||
extension=webdriver.servo.ServoCommandExtensions)
|
||||
self.session.start()
|
||||
|
||||
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 after_connect(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
self.logger.debug("Hanging up on WebDriver session")
|
||||
|
@ -72,9 +59,6 @@ class ServoWebDriverProtocol(Protocol):
|
|||
return False
|
||||
return True
|
||||
|
||||
def after_connect(self):
|
||||
pass
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
import traceback
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
"""Backend for a specific browser-control protocol.
|
||||
|
||||
Each Protocol is composed of a set of ProtocolParts that implement
|
||||
the APIs required for specific interactions. This reflects the fact
|
||||
that not all implementaions will support exactly the same feature set.
|
||||
Each ProtocolPart is exposed directly on the protocol through an accessor
|
||||
attribute with a name given by its `name` property.
|
||||
|
||||
:param Executor executor: The Executor instance that's using this Protocol
|
||||
:param Browser browser: The Browser using this protocol"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
implements = []
|
||||
|
||||
def __init__(self, executor, browser):
|
||||
self.executor = executor
|
||||
self.browser = browser
|
||||
|
||||
for cls in self.implements:
|
||||
name = cls.name
|
||||
assert not hasattr(self, name)
|
||||
setattr(self, name, cls(self))
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
""":returns: Current logger"""
|
||||
return self.executor.logger
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""Is the browser connection still active
|
||||
|
||||
:returns: A boolean indicating whether the connection is still active."""
|
||||
return True
|
||||
|
||||
def setup(self, runner):
|
||||
"""Handle protocol setup, and send a message to the runner to indicate
|
||||
success or failure."""
|
||||
msg = None
|
||||
try:
|
||||
msg = "Failed to start protocol connection"
|
||||
self.connect()
|
||||
|
||||
msg = None
|
||||
|
||||
for cls in self.implements:
|
||||
getattr(self, cls.name).setup()
|
||||
|
||||
msg = "Post-connection steps failed"
|
||||
self.after_connect()
|
||||
except Exception:
|
||||
if msg is not None:
|
||||
self.logger.warning(msg)
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.executor.runner.send_message("init_failed")
|
||||
return
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
@abstractmethod
|
||||
def connect(self):
|
||||
"""Make a connection to the remote browser"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def after_connect(self):
|
||||
"""Run any post-connection steps. This happens after the ProtocolParts are
|
||||
initalized so can depend on a fully-populated object."""
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
"""Run cleanup steps after the tests are finished."""
|
||||
for cls in self.implements:
|
||||
getattr(self, cls.name).teardown()
|
||||
|
||||
|
||||
class ProtocolPart(object):
|
||||
"""Base class for all ProtocolParts.
|
||||
|
||||
:param Protocol parent: The parent protocol"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = None
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
""":returns: Current logger"""
|
||||
return self.parent.logger
|
||||
|
||||
def setup(self):
|
||||
"""Run any setup steps required for the ProtocolPart."""
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
"""Run any teardown steps required for the ProtocolPart."""
|
||||
pass
|
||||
|
||||
|
||||
class BaseProtocolPart(ProtocolPart):
|
||||
"""Generic bits of protocol that are required for multiple test types"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "base"
|
||||
|
||||
@abstractmethod
|
||||
def execute_script(self, script, async=False):
|
||||
"""Execute javascript in the current Window.
|
||||
|
||||
:param str script: The js source to execute. This is implicitly wrapped in a function.
|
||||
:param bool async: Whether the script is asynchronous in the webdriver
|
||||
sense i.e. whether the return value is the result of
|
||||
the initial function call or if it waits for some callback.
|
||||
:returns: The result of the script execution.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_timeout(self, timeout):
|
||||
"""Set the timeout for script execution.
|
||||
|
||||
:param timeout: Script timeout in seconds"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def wait(self):
|
||||
"""Wait indefinitely for the browser to close"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def current_window(self):
|
||||
"""Return a handle identifying the current top level browsing context
|
||||
|
||||
:returns: A protocol-specific handle"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_window(self, handle):
|
||||
"""Set the top level browsing context to one specified by a given handle.
|
||||
|
||||
:param handle: A protocol-specific handle identifying a top level browsing
|
||||
context."""
|
||||
pass
|
||||
|
||||
|
||||
class TestharnessProtocolPart(ProtocolPart):
|
||||
"""Protocol part required to run testharness tests."""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "testharness"
|
||||
|
||||
@abstractmethod
|
||||
def load_runner(self, url_protocol):
|
||||
"""Load the initial page used to control the tests.
|
||||
|
||||
:param str url_protocol: "https" or "http" depending on the test metadata.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close_old_windows(self, url_protocol):
|
||||
"""Close existing windows except for the initial runner window.
|
||||
After calling this method there must be exactly one open window that
|
||||
contains the initial runner page.
|
||||
|
||||
:param str url_protocol: "https" or "http" depending on the test metadata.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_test_window(self, window_id, parent):
|
||||
"""Get the window handle dorresponding to the window containing the
|
||||
currently active test.
|
||||
|
||||
:param window_id: A string containing the DOM name of the Window that
|
||||
contains the test, or None.
|
||||
:param parent: The handle of the runner window.
|
||||
:returns: A protocol-specific window handle.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PrefsProtocolPart(ProtocolPart):
|
||||
"""Protocol part that allows getting and setting browser prefs."""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "prefs"
|
||||
|
||||
@abstractmethod
|
||||
def set(self, name, value):
|
||||
"""Set the named pref to value.
|
||||
|
||||
:param name: A pref name of browser-specific type
|
||||
:param value: A pref value of browser-specific type"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get(self, name):
|
||||
"""Get the current value of a named pref
|
||||
|
||||
:param name: A pref name of browser-specific type
|
||||
:returns: A pref value of browser-specific type"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def clear(self, name):
|
||||
"""Reset the value of a named pref back to the default.
|
||||
|
||||
:param name: A pref name of browser-specific type"""
|
||||
pass
|
||||
|
||||
|
||||
class StorageProtocolPart(ProtocolPart):
|
||||
"""Protocol part for manipulating browser storage."""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "storage"
|
||||
|
||||
@abstractmethod
|
||||
def clear_origin(self, url):
|
||||
"""Clear all the storage for a specified origin.
|
||||
|
||||
:param url: A url belonging to the origin"""
|
||||
pass
|
||||
|
||||
|
||||
class SelectorProtocolPart(ProtocolPart):
|
||||
"""Protocol part for selecting elements on the page."""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "select"
|
||||
|
||||
@abstractmethod
|
||||
def elements_by_selector(self, selector):
|
||||
"""Select elements matching a CSS selector
|
||||
|
||||
:param str selector: The CSS selector
|
||||
:returns: A list of protocol-specific handles to elements"""
|
||||
pass
|
||||
|
||||
|
||||
class ClickProtocolPart(ProtocolPart):
|
||||
"""Protocol part for performing trusted clicks"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "click"
|
||||
|
||||
@abstractmethod
|
||||
def element(self, element):
|
||||
"""Perform a trusted click somewhere on a specific element.
|
||||
|
||||
:param element: A protocol-specific handle to an element."""
|
||||
pass
|
||||
|
||||
|
||||
class TestDriverProtocolPart(ProtocolPart):
|
||||
"""Protocol part that implements the basic functionality required for
|
||||
all testdriver-based tests."""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "testdriver"
|
||||
|
||||
@abstractmethod
|
||||
def send_message(self, message_type, status, message=None):
|
||||
"""Send a testdriver message to the browser.
|
||||
|
||||
:param str message_type: The kind of the message.
|
||||
:param str status: Either "failure" or "success" depending on whether the
|
||||
previous command succeeded.
|
||||
:param str message: Additional data to add to the message."""
|
||||
pass
|
|
@ -1,31 +1,24 @@
|
|||
window.wrappedJSObject.timeout_multiplier = %(timeout_multiplier)d;
|
||||
window.wrappedJSObject.explicit_timeout = %(explicit_timeout)d;
|
||||
|
||||
window.wrappedJSObject.addEventListener("message", function listener(event) {
|
||||
if (event.data.type != "complete") {
|
||||
return;
|
||||
}
|
||||
window.wrappedJSObject.removeEventListener("message", listener);
|
||||
clearTimeout(timer);
|
||||
var tests = event.data.tests;
|
||||
var status = event.data.status;
|
||||
window.wrappedJSObject.message_queue = [];
|
||||
|
||||
var subtest_results = tests.map(function (x) {
|
||||
return [x.name, x.status, x.message, x.stack]
|
||||
});
|
||||
window.wrappedJSObject.setMessageListener = function(func) {
|
||||
window.wrappedJSObject.current_listener = func;
|
||||
window.wrappedJSObject.addEventListener(
|
||||
"message",
|
||||
func,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
marionetteScriptFinished(["%(url)s",
|
||||
status.status,
|
||||
status.message,
|
||||
status.stack,
|
||||
subtest_results]);
|
||||
}, false);
|
||||
window.wrappedJSObject.setMessageListener(function(event) {
|
||||
window.wrappedJSObject.message_queue.push(event);
|
||||
});
|
||||
|
||||
window.wrappedJSObject.win = window.open("%(abs_url)s", "%(window_id)s");
|
||||
window.wrappedJSObject.win = window.wrappedJSObject.open("%(abs_url)s", "%(window_id)s");
|
||||
|
||||
var timer = null;
|
||||
if (%(timeout)s) {
|
||||
timer = setTimeout(function() {
|
||||
window.wrappedJSObject.win.timeout();
|
||||
}, %(timeout)s);
|
||||
}
|
||||
window.wrappedJSObject.timer = setTimeout(function() {
|
||||
window.wrappedJSObject.win.timeout();
|
||||
window.wrappedJSObject.win.close();
|
||||
}, %(timeout)s);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
var callback = arguments[arguments.length - 1];
|
||||
|
||||
function process_event(event) {
|
||||
var data = event.data;
|
||||
|
||||
var payload = undefined;
|
||||
|
||||
switch(data.type) {
|
||||
case "complete":
|
||||
var tests = event.data.tests;
|
||||
var status = event.data.status;
|
||||
|
||||
var subtest_results = tests.map(function(x) {
|
||||
return [x.name, x.status, x.message, x.stack];
|
||||
});
|
||||
payload = [status.status,
|
||||
status.message,
|
||||
status.stack,
|
||||
subtest_results];
|
||||
clearTimeout(window.wrappedJSObject.timer);
|
||||
break;
|
||||
|
||||
case "action":
|
||||
window.wrappedJSObject.setMessageListener(function(event) {
|
||||
window.wrappedJSObject.message_queue.push(event);
|
||||
});
|
||||
payload = data;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
callback(["%(url)s", data.type, payload]);
|
||||
}
|
||||
|
||||
window.wrappedJSObject.removeEventListener("message", window.wrappedJSObject.current_listener);
|
||||
if (window.wrappedJSObject.message_queue.length) {
|
||||
var next = window.wrappedJSObject.message_queue.shift();
|
||||
process_event(next);
|
||||
} else {
|
||||
window.wrappedJSObject.addEventListener(
|
||||
"message", function f(event) {
|
||||
window.wrappedJSObject.removeEventListener("message", f);
|
||||
process_event(event);
|
||||
}, false);
|
||||
}
|
|
@ -26,6 +26,8 @@ function process_event(event) {
|
|||
});
|
||||
payload = data;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
callback(["%(url)s", data.type, payload]);
|
||||
|
|
|
@ -108,7 +108,7 @@ class TestRunner(object):
|
|||
raise
|
||||
|
||||
def wait(self):
|
||||
self.executor.protocol.wait()
|
||||
self.executor.wait()
|
||||
self.send_message("wait_finished")
|
||||
|
||||
def send_message(self, command, *args):
|
||||
|
@ -304,6 +304,7 @@ class TestRunnerManager(threading.Thread):
|
|||
# This is started in the actual new thread
|
||||
self.logger = None
|
||||
|
||||
self.test_count = 0
|
||||
self.unexpected_count = 0
|
||||
|
||||
# This may not really be what we want
|
||||
|
@ -569,6 +570,7 @@ class TestRunnerManager(threading.Thread):
|
|||
if self.browser.check_for_crashes():
|
||||
status = "CRASH"
|
||||
|
||||
self.test_count += 1
|
||||
is_unexpected = expected != status
|
||||
if is_unexpected:
|
||||
self.unexpected_count += 1
|
||||
|
@ -789,5 +791,8 @@ class ManagerGroup(object):
|
|||
self.stop_flag.set()
|
||||
self.logger.debug("Stop flag set in ManagerGroup")
|
||||
|
||||
def test_count(self):
|
||||
return sum(item.test_count for item in self.pool)
|
||||
|
||||
def unexpected_count(self):
|
||||
return sum(item.unexpected_count for item in self.pool)
|
||||
|
|
|
@ -60,4 +60,4 @@ def test_server_start_config(product):
|
|||
config = args[0][0]
|
||||
if "host" in env_options:
|
||||
assert config["host"] == env_options["host"]
|
||||
assert isinstance(config["bind_hostname"], bool)
|
||||
assert isinstance(config["bind_address"], bool)
|
||||
|
|
|
@ -173,6 +173,7 @@ def run_tests(config, test_paths, product, **kwargs):
|
|||
|
||||
logger.info("Using %i client processes" % kwargs["processes"])
|
||||
|
||||
test_total = 0
|
||||
unexpected_total = 0
|
||||
|
||||
kwargs["pause_after_test"] = get_pause_after_test(test_loader, **kwargs)
|
||||
|
@ -200,6 +201,7 @@ def run_tests(config, test_paths, product, **kwargs):
|
|||
elif repeat > 1:
|
||||
logger.info("Repetition %i / %i" % (repeat_count, repeat))
|
||||
|
||||
test_count = 0
|
||||
unexpected_count = 0
|
||||
logger.suite_start(test_loader.test_ids, name='web-platform-test', run_info=run_info)
|
||||
for test_type in kwargs["test_types"]:
|
||||
|
@ -267,13 +269,20 @@ def run_tests(config, test_paths, product, **kwargs):
|
|||
logger.critical("Main thread got signal")
|
||||
manager_group.stop()
|
||||
raise
|
||||
test_count += manager_group.test_count()
|
||||
unexpected_count += manager_group.unexpected_count()
|
||||
|
||||
test_total += test_count
|
||||
unexpected_total += unexpected_count
|
||||
logger.info("Got %i unexpected results" % unexpected_count)
|
||||
if repeat_until_unexpected and unexpected_total > 0:
|
||||
break
|
||||
logger.suite_end()
|
||||
|
||||
if test_total == 0:
|
||||
logger.error("No tests ran")
|
||||
return False
|
||||
|
||||
return unexpected_total == 0
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue