Update web-platform-tests to revision e87f38097902e16348d4e17f4fe3bc2d0112bff1

This commit is contained in:
WPT Sync Bot 2018-03-17 21:12:30 -04:00
parent 2f8fa32e91
commit db5631a086
381 changed files with 11610 additions and 4232 deletions

View file

@ -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::

View file

@ -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}

View file

@ -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}

View file

@ -61,7 +61,7 @@ def env_extras(**kwargs):
def env_options():
return {"bind_hostname": "true"}
return {}
class WebKitBrowser(Browser):

View file

@ -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)

View file

@ -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])

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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);

View file

@ -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);
}

View file

@ -26,6 +26,8 @@ function process_event(event) {
});
payload = data;
break;
default:
return;
}
callback(["%(url)s", data.type, payload]);

View file

@ -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)

View file

@ -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)

View file

@ -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