Bug 1513880 [wpt PR 14496] - Revert "Make testharness tests run in a top-level browsing context": Remove .orig files from conflicts, a=testonly DONTBUILD

This commit is contained in:
Sebastian Hengst 2019-01-23 21:13:05 +02:00
Родитель a4eeeccf1d
Коммит 90d6431728
4 изменённых файлов: 0 добавлений и 1557 удалений

Просмотреть файл

@ -1,206 +0,0 @@
import os
import platform
import socket
from abc import ABCMeta, abstractmethod
from copy import deepcopy
from ..wptcommandline import require_arg # noqa: F401
here = os.path.split(__file__)[0]
def inherit(super_module, child_globals, product_name):
super_wptrunner = super_module.__wptrunner__
child_globals["__wptrunner__"] = child_wptrunner = deepcopy(super_wptrunner)
child_wptrunner["product"] = product_name
for k in ("check_args", "browser", "browser_kwargs", "executor_kwargs",
"env_extras", "env_options"):
attr = super_wptrunner[k]
child_globals[attr] = getattr(super_module, attr)
for v in super_module.__wptrunner__["executor"].values():
child_globals[v] = getattr(super_module, v)
if "run_info_extras" in super_wptrunner:
attr = super_wptrunner["run_info_extras"]
child_globals[attr] = getattr(super_module, attr)
def cmd_arg(name, value=None):
prefix = "-" if platform.system() == "Windows" else "--"
rv = prefix + name
def inherit(super_module, child_globals, product_name):
super_wptrunner = super_module.__wptrunner__
child_globals["__wptrunner__"] = child_wptrunner = deepcopy(super_wptrunner)
child_wptrunner["product"] = product_name
for k in ("check_args", "browser", "browser_kwargs", "executor_kwargs",
"env_extras", "env_options"):
attr = super_wptrunner[k]
child_globals[attr] = getattr(super_module, attr)
for v in super_module.__wptrunner__["executor"].values():
child_globals[v] = getattr(super_module, v)
if "run_info_extras" in super_wptrunner:
attr = super_wptrunner["run_info_extras"]
child_globals[attr] = getattr(super_module, attr)
def cmd_arg(name, value=None):
prefix = "-" if platform.system() == "Windows" else "--"
rv = prefix + name
if value is not None:
rv += "=" + value
return rv
def get_free_port(start_port, exclude=None):
"""Get the first port number after start_port (inclusive) that is
not currently bound.
:param start_port: Integer port number at which to start testing.
:param exclude: Set of port numbers to skip"""
port = start_port
while True:
if exclude and port in exclude:
port += 1
continue
s = socket.socket()
try:
s.bind(("127.0.0.1", port))
except socket.error:
port += 1
else:
return port
finally:
s.close()
def browser_command(binary, args, debug_info):
if debug_info:
if debug_info.requiresEscapedArgs:
args = [item.replace("&", "\\&") for item in args]
debug_args = [debug_info.path] + debug_info.args
else:
debug_args = []
command = [binary] + args
return debug_args, command
class BrowserError(Exception):
pass
class Browser(object):
__metaclass__ = ABCMeta
process_cls = None
init_timeout = 30
def __init__(self, logger):
"""Abstract class serving as the basis for Browser implementations.
The Browser is used in the TestRunnerManager to start and stop the browser
process, and to check the state of that process. This class also acts as a
context manager, enabling it to do browser-specific setup at the start of
the testrun and cleanup after the run is complete.
:param logger: Structured logger to use for output.
"""
self.logger = logger
def __enter__(self):
self.setup()
return self
def __exit__(self, *args, **kwargs):
self.cleanup()
def setup(self):
"""Used for browser-specific setup that happens at the start of a test run"""
pass
def settings(self, test):
return {}
@abstractmethod
def start(self, group_metadata, **kwargs):
"""Launch the browser object and get it into a state where is is ready to run tests"""
pass
@abstractmethod
def stop(self, force=False):
"""Stop the running browser process."""
pass
@abstractmethod
def pid(self):
"""pid of the browser process or None if there is no pid"""
pass
@abstractmethod
def is_alive(self):
"""Boolean indicating whether the browser process is still running"""
pass
def setup_ssl(self, hosts):
"""Return a certificate to use for tests requiring ssl that will be trusted by the browser"""
raise NotImplementedError("ssl testing not supported")
def cleanup(self):
"""Browser-specific cleanup that is run after the testrun is finished"""
pass
def executor_browser(self):
"""Returns the ExecutorBrowser subclass for this Browser subclass and the keyword arguments
with which it should be instantiated"""
return ExecutorBrowser, {}
def check_crash(self, process, test):
"""Check if a crash occured and output any useful information to the
log. Returns a boolean indicating whether a crash occured."""
return False
class NullBrowser(Browser):
def __init__(self, logger, **kwargs):
super(NullBrowser, self).__init__(logger)
def start(self, **kwargs):
"""No-op browser to use in scenarios where the TestRunnerManager shouldn't
actually own the browser process (e.g. Servo where we start one browser
per test)"""
pass
def stop(self, force=False):
pass
def pid(self):
return None
def is_alive(self):
return True
def on_output(self, line):
raise NotImplementedError
class ExecutorBrowser(object):
def __init__(self, **kwargs):
"""View of the Browser used by the Executor object.
This is needed because the Executor runs in a child process and
we can't ship Browser instances between processes on Windows.
Typically this will have a few product-specific properties set,
but in some cases it may have more elaborate methods for setting
up the browser from the runner process.
"""
for k, v in kwargs.iteritems():
setattr(self, k, v)

Просмотреть файл

@ -1,217 +0,0 @@
import os
import tempfile
import moznetwork
from mozprocess import ProcessHandler
from mozprofile import FirefoxProfile
from mozrunner import FennecEmulatorRunner
from tools.serve.serve import make_hosts_file
from .base import (get_free_port,
cmd_arg,
browser_command)
from ..executors.executormarionette import (MarionetteTestharnessExecutor, # noqa: F401
MarionetteRefTestExecutor) # noqa: F401
from .firefox import (get_timeout_multiplier,
update_properties,
executor_kwargs,
FirefoxBrowser)
__wptrunner__ = {"product": "fennec",
"check_args": "check_args",
"browser": "FennecBrowser",
"executor": {"testharness": "MarionetteTestharnessExecutor",
"reftest": "MarionetteRefTestExecutor"},
"browser_kwargs": "browser_kwargs",
"executor_kwargs": "executor_kwargs",
"env_extras": "env_extras",
"env_options": "env_options",
"run_info_extras": "run_info_extras",
"update_properties": "update_properties"}
def check_args(**kwargs):
pass
def browser_kwargs(test_type, run_info_data, config, **kwargs):
return {"package_name": kwargs["package_name"],
"device_serial": kwargs["device_serial"],
"prefs_root": kwargs["prefs_root"],
"extra_prefs": kwargs["extra_prefs"],
"test_type": test_type,
"debug_info": kwargs["debug_info"],
"symbols_path": kwargs["symbols_path"],
"stackwalk_binary": kwargs["stackwalk_binary"],
"certutil_binary": kwargs["certutil_binary"],
"ca_certificate_path": config.ssl_config["ca_cert_path"],
"stackfix_dir": kwargs["stackfix_dir"],
"binary_args": kwargs["binary_args"],
"timeout_multiplier": get_timeout_multiplier(test_type,
run_info_data,
**kwargs),
"leak_check": kwargs["leak_check"],
"stylo_threads": kwargs["stylo_threads"],
"chaos_mode_flags": kwargs["chaos_mode_flags"],
"config": config,
"install_fonts": kwargs["install_fonts"],
"tests_root": config.doc_root}
def env_extras(**kwargs):
return []
def run_info_extras(**kwargs):
return {"e10s": False,
"headless": False,
"sw-e10s": False}
def env_options():
# The server host is set to public localhost IP so that resources can be accessed
# from Android emulator
return {"server_host": moznetwork.get_ip(),
"bind_address": False,
"supports_debugger": True}
def write_hosts_file(config, device):
new_hosts = make_hosts_file(config, moznetwork.get_ip())
current_hosts = device.get_file("/etc/hosts")
if new_hosts == current_hosts:
return
hosts_fd, hosts_path = tempfile.mkstemp()
try:
with os.fdopen(hosts_fd, "w") as f:
f.write(new_hosts)
device.remount()
device.push(hosts_path, "/etc/hosts")
finally:
os.remove(hosts_path)
class FennecBrowser(FirefoxBrowser):
used_ports = set()
init_timeout = 300
shutdown_timeout = 60
def __init__(self, logger, prefs_root, test_type, package_name=None,
device_serial="emulator-5444", **kwargs):
FirefoxBrowser.__init__(self, logger, None, prefs_root, test_type, **kwargs)
self._package_name = package_name
self.device_serial = device_serial
self.tests_root = kwargs["tests_root"]
self.install_fonts = kwargs["install_fonts"]
@property
def package_name(self):
"""
Name of app to run on emulator.
"""
if self._package_name is None:
self._package_name = "org.mozilla.fennec"
user = os.getenv("USER")
if user:
self._package_name += "_" + user
return self._package_name
def start(self, **kwargs):
if self.marionette_port is None:
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
self.used_ports.add(self.marionette_port)
env = {}
env["MOZ_CRASHREPORTER"] = "1"
env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
env["STYLO_THREADS"] = str(self.stylo_threads)
if self.chaos_mode_flags is not None:
env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
preferences = self.load_prefs()
self.profile = FirefoxProfile(preferences=preferences)
self.profile.set_preferences({"marionette.port": self.marionette_port,
"dom.disable_open_during_load": False,
"places.history.enabled": False,
"dom.send_after_paint_to_content": True,
"network.preload": True})
if self.test_type == "reftest":
self.logger.info("Setting android reftest preferences")
self.profile.set_preferences({"browser.viewport.desktopWidth": 600,
# Disable high DPI
"layout.css.devPixelsPerPx": "1.0",
# Ensure that the full browser element
# appears in the screenshot
"apz.allow_zooming": False,
"android.widget_paints_background": False,
# Ensure that scrollbars are always painted
"ui.scrollbarFadeBeginDelay": 100000})
if self.install_fonts:
self.logger.debug("Copying Ahem font to profile")
font_dir = os.path.join(self.profile.profile, "fonts")
if not os.path.exists(font_dir):
os.makedirs(font_dir)
with open(os.path.join(self.tests_root, "fonts", "Ahem.ttf"), "rb") as src:
with open(os.path.join(font_dir, "Ahem.ttf"), "wb") as dest:
dest.write(src.read())
if self.leak_check and kwargs.get("check_leaks", True):
self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log")
if os.path.exists(self.leak_report_file):
os.remove(self.leak_report_file)
env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
else:
self.leak_report_file = None
if self.ca_certificate_path is not None:
self.setup_ssl()
debug_args, cmd = browser_command(self.package_name,
self.binary_args if self.binary_args else [] +
[cmd_arg("marionette"), "about:blank"],
self.debug_info)
self.runner = FennecEmulatorRunner(app=self.package_name,
profile=self.profile,
cmdargs=cmd[1:],
env=env,
symbols_path=self.symbols_path,
serial=self.device_serial,
# TODO - choose appropriate log dir
logdir=os.getcwd(),
process_class=ProcessHandler,
process_args={"processOutputLine": [self.on_output]})
self.logger.debug("Starting %s" % self.package_name)
# connect to a running emulator
self.runner.device.connect()
write_hosts_file(self.config, self.runner.device.device)
self.runner.stop()
self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
self.runner.device.device.forward(
local="tcp:{}".format(self.marionette_port),
remote="tcp:{}".format(self.marionette_port))
self.logger.debug("%s Started" % self.package_name)
def stop(self, force=False):
if self.runner is not None:
if (self.runner.device.connected and
len(self.runner.device.device.list_forwards()) > 0):
try:
self.runner.device.device.remove_forwards(
"tcp:{}".format(self.marionette_port))
except Exception:
self.logger.warning("Failed to remove port forwarding")
# We assume that stopping the runner prompts the
# browser to shut down. This allows the leak log to be written
self.runner.stop()
self.logger.debug("stopped")

Просмотреть файл

@ -1,252 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import glob
import os
import shutil
import subprocess
import tarfile
import tempfile
import time
from cStringIO import StringIO as CStringIO
import requests
from .base import Browser, ExecutorBrowser, require_arg
from ..executors import executor_kwargs as base_executor_kwargs
from ..executors.executorselenium import (SeleniumTestharnessExecutor,
SeleniumRefTestExecutor)
here = os.path.split(__file__)[0]
# Number of seconds to wait between polling operations when detecting status of
# Sauce Connect sub-process.
sc_poll_period = 1
__wptrunner__ = {"product": "sauce",
"check_args": "check_args",
"browser": "SauceBrowser",
"executor": {"testharness": "SeleniumTestharnessExecutor",
"reftest": "SeleniumRefTestExecutor"},
"browser_kwargs": "browser_kwargs",
"executor_kwargs": "executor_kwargs",
"env_extras": "env_extras",
"env_options": "env_options"}
def get_capabilities(**kwargs):
browser_name = kwargs["sauce_browser"]
platform = kwargs["sauce_platform"]
version = kwargs["sauce_version"]
build = kwargs["sauce_build"]
tags = kwargs["sauce_tags"]
tunnel_id = kwargs["sauce_tunnel_id"]
prerun_script = {
"MicrosoftEdge": {
"executable": "sauce-storage:edge-prerun.bat",
"background": False,
},
"safari": {
"executable": "sauce-storage:safari-prerun.sh",
"background": False,
}
}
capabilities = {
"browserName": browser_name,
"build": build,
"disablePopupHandler": True,
"name": "%s %s on %s" % (browser_name, version, platform),
"platform": platform,
"public": "public",
"selenium-version": "3.3.1",
"tags": tags,
"tunnel-identifier": tunnel_id,
"version": version,
"prerun": prerun_script.get(browser_name)
}
if browser_name == 'MicrosoftEdge':
capabilities['selenium-version'] = '2.4.8'
return capabilities
def get_sauce_config(**kwargs):
browser_name = kwargs["sauce_browser"]
sauce_user = kwargs["sauce_user"]
sauce_key = kwargs["sauce_key"]
hub_url = "%s:%s@localhost:4445" % (sauce_user, sauce_key)
data = {
"url": "http://%s/wd/hub" % hub_url,
"browserName": browser_name,
"capabilities": get_capabilities(**kwargs)
}
return data
def check_args(**kwargs):
require_arg(kwargs, "sauce_browser")
require_arg(kwargs, "sauce_platform")
require_arg(kwargs, "sauce_version")
require_arg(kwargs, "sauce_user")
require_arg(kwargs, "sauce_key")
def browser_kwargs(test_type, run_info_data, config, **kwargs):
sauce_config = get_sauce_config(**kwargs)
return {"sauce_config": sauce_config}
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
**kwargs):
executor_kwargs = base_executor_kwargs(test_type, server_config,
cache_manager, run_info_data, **kwargs)
executor_kwargs["capabilities"] = get_capabilities(**kwargs)
return executor_kwargs
def env_extras(**kwargs):
return [SauceConnect(**kwargs)]
def env_options():
return {"supports_debugger": False}
def get_tar(url, dest):
resp = requests.get(url, stream=True)
resp.raise_for_status()
with tarfile.open(fileobj=CStringIO(resp.raw.read())) as f:
f.extractall(path=dest)
class SauceConnect():
def __init__(self, **kwargs):
self.sauce_user = kwargs["sauce_user"]
self.sauce_key = kwargs["sauce_key"]
self.sauce_tunnel_id = kwargs["sauce_tunnel_id"]
self.sauce_connect_binary = kwargs.get("sauce_connect_binary")
self.sauce_connect_args = kwargs.get("sauce_connect_args")
self.sauce_init_timeout = kwargs.get("sauce_init_timeout")
self.sc_process = None
self.temp_dir = None
self.env_config = None
def __call__(self, env_options, env_config):
self.env_config = env_config
return self
def __enter__(self):
# Because this class implements the context manager protocol, it is
# possible for instances to be provided to the `with` statement
# directly. This class implements the callable protocol so that data
# which is not available during object initialization can be provided
# prior to this moment. Instances must be invoked in preparation for
# the context manager protocol, but this additional constraint is not
# itself part of the protocol.
assert self.env_config is not None, 'The instance has been invoked.'
if not self.sauce_connect_binary:
self.temp_dir = tempfile.mkdtemp()
get_tar("https://saucelabs.com/downloads/sc-4.4.9-linux.tar.gz", self.temp_dir)
self.sauce_connect_binary = glob.glob(os.path.join(self.temp_dir, "sc-*-linux/bin/sc"))[0]
self.upload_prerun_exec('edge-prerun.bat')
self.upload_prerun_exec('safari-prerun.sh')
self.sc_process = subprocess.Popen([
self.sauce_connect_binary,
"--user=%s" % self.sauce_user,
"--api-key=%s" % self.sauce_key,
"--no-remove-colliding-tunnels",
"--tunnel-identifier=%s" % self.sauce_tunnel_id,
"--metrics-address=0.0.0.0:9876",
"--readyfile=./sauce_is_ready",
"--tunnel-domains",
",".join(self.env_config.domains_set)
] + self.sauce_connect_args)
tot_wait = 0
while not os.path.exists('./sauce_is_ready') and self.sc_process.poll() is None:
if tot_wait >= self.sauce_init_timeout:
self.quit()
raise SauceException("Sauce Connect Proxy was not ready after %d seconds" % tot_wait)
time.sleep(sc_poll_period)
tot_wait += sc_poll_period
if self.sc_process.returncode is not None:
raise SauceException("Unable to start Sauce Connect Proxy. Process exited with code %s", self.sc_process.returncode)
def __exit__(self, exc_type, exc_val, exc_tb):
self.env_config = None
self.quit()
if self.temp_dir and os.path.exists(self.temp_dir):
try:
shutil.rmtree(self.temp_dir)
except OSError:
pass
def upload_prerun_exec(self, file_name):
auth = (self.sauce_user, self.sauce_key)
url = "https://saucelabs.com/rest/v1/storage/%s/%s?overwrite=true" % (self.sauce_user, file_name)
with open(os.path.join(here, 'sauce_setup', file_name), 'rb') as f:
requests.post(url, data=f, auth=auth)
def quit(self):
"""The Sauce Connect process may be managing an active "tunnel" to the
Sauce Labs service. Issue a request to the process to close any tunnels
and exit. If this does not occur within 5 seconds, force the process to
close."""
kill_wait = 5
tot_wait = 0
self.sc_process.terminate()
while self.sc_process.poll() is None:
time.sleep(sc_poll_period)
tot_wait += sc_poll_period
if tot_wait >= kill_wait:
self.sc_process.kill()
break
class SauceException(Exception):
pass
class SauceBrowser(Browser):
init_timeout = 300
def __init__(self, logger, sauce_config):
Browser.__init__(self, logger)
self.sauce_config = sauce_config
def start(self, **kwargs):
pass
def stop(self, force=False):
pass
def pid(self):
return None
def is_alive(self):
# TODO: Should this check something about the connection?
return True
def cleanup(self):
pass
def executor_browser(self):
return ExecutorBrowser, {"webdriver_url": self.sauce_config["url"]}

Просмотреть файл

@ -1,882 +0,0 @@
import json
import os
import threading
import traceback
import urlparse
import uuid
errors = None
marionette = None
pytestrunner = None
here = os.path.join(os.path.split(__file__)[0])
from .base import (CallbackHandler,
RefTestExecutor,
RefTestImplementation,
TestharnessExecutor,
WdspecExecutor,
WebDriverProtocol,
extra_timeout,
strip_server)
from .protocol import (ActionSequenceProtocolPart,
AssertsProtocolPart,
BaseProtocolPart,
TestharnessProtocolPart,
PrefsProtocolPart,
Protocol,
StorageProtocolPart,
SelectorProtocolPart,
ClickProtocolPart,
SendKeysProtocolPart,
TestDriverProtocolPart,
CoverageProtocolPart)
from ..testrunner import Stop
from ..webdriver_server import GeckoDriverServer
def do_delayed_imports():
global errors, marionette
# Marionette client used to be called marionette, recently it changed
# to marionette_driver for unfathomable reasons
try:
import marionette
from marionette import errors
except ImportError:
from marionette_driver import marionette, errors
class MarionetteBaseProtocolPart(BaseProtocolPart):
def __init__(self, parent):
super(MarionetteBaseProtocolPart, self).__init__(parent)
self.timeout = None
def setup(self):
self.marionette = self.parent.marionette
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, sandbox=None)
def set_timeout(self, timeout):
"""Set the Marionette script timeout.
:param timeout: Script timeout in seconds
"""
if timeout != self.timeout:
self.marionette.timeout.script = timeout
self.timeout = timeout
@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 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 errors.JavascriptException as e:
# This can happen if we navigate, but just keep going
self.logger.debug(e.message)
pass
except 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
with open(os.path.join(here, "runner.js")) as f:
self.runner_script = f.read()
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
if self.runner_handle:
self._close_windows()
url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
"/testharness_runner.html")
self.logger.debug("Loading %s" % url)
try:
self.dismiss_alert(lambda: self.marionette.navigate(url))
except Exception as e:
self.logger.critical(
"Loading initial page %s failed. Ensure that the "
"there are no other programs bound to this port and "
"that your firewall rules or network setup does not "
"prevent access.\e%s" % (url, traceback.format_exc(e)))
raise
self.runner_handle = self.marionette.current_window_handle
format_map = {"title": threading.current_thread().name.replace("'", '"')}
self.parent.base.execute_script(self.runner_script % format_map)
def _close_windows(self):
handles = self.marionette.window_handles
runner_handle = None
try:
handles.remove(self.runner_handle)
runner_handle = self.runner_handle
except ValueError:
# The runner window probably changed id but we can restore it
# This isn't supposed to happen, but marionette ids are not yet stable
# We assume that the first handle returned corresponds to the runner,
# but it hopefully doesn't matter too much if that assumption is
# wrong since we reload the runner in that tab anyway.
runner_handle = handles.pop(0)
self.logger.info("Changing harness_window to %s" % runner_handle)
for handle in handles:
try:
self.dismiss_alert(lambda: self.marionette.switch_to_window(handle))
self.marionette.switch_to_window(handle)
self.logger.info("Closing window %s" % handle)
self.marionette.close()
except errors.NoSuchWindowException:
# We might have raced with the previous test to close this
# window, skip it.
pass
self.marionette.switch_to_window(runner_handle)
return runner_handle
def close_old_windows(self, url_protocol):
runner_handle = self._close_windows()
if runner_handle != self.runner_handle:
self.load_runner(url_protocol)
return self.runner_handle
def dismiss_alert(self, f):
while True:
try:
f()
except errors.UnexpectedAlertOpen:
alert = self.marionette.switch_to_alert()
try:
alert.dismiss()
except errors.NoAlertPresentException:
pass
else:
break
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.parent.base.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.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:
raise Exception("unable to find test window")
assert test_window != parent
return test_window
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)
except ValueError:
value = "'%s'" % value
else:
value = value.lower()
self.logger.info("Setting pref %s (%s)" % (name, value))
script = """
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
let pref = '%s';
let type = prefInterface.getPrefType(pref);
let value = %s;
switch(type) {
case prefInterface.PREF_STRING:
prefInterface.setCharPref(pref, value);
break;
case prefInterface.PREF_BOOL:
prefInterface.setBoolPref(pref, value);
break;
case prefInterface.PREF_INT:
prefInterface.setIntPref(pref, value);
break;
}
""" % (name, value)
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
self.marionette.execute_script(script)
def clear(self, name):
self.logger.info("Clearing pref %s" % (name))
script = """
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
let pref = '%s';
prefInterface.clearUserPref(pref);
""" % name
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
self.marionette.execute_script(script)
def get(self, name):
script = """
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
let pref = '%s';
let type = prefInterface.getPrefType(pref);
switch(type) {
case prefInterface.PREF_STRING:
return prefInterface.getCharPref(pref);
case prefInterface.PREF_BOOL:
return prefInterface.getBoolPref(pref);
case prefInterface.PREF_INT:
return prefInterface.getIntPref(pref);
case prefInterface.PREF_INVALID:
return null;
}
""" % name
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 = """
let url = '%s';
let uri = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(url);
let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
let principal = ssm.createCodebasePrincipal(uri, {});
let qms = Components.classes["@mozilla.org/dom/quota-manager-service;1"]
.getService(Components.interfaces.nsIQuotaManagerService);
qms.clearStoragesForPrincipal(principal, "default", null, true);
""" % url
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
self.marionette.execute_script(script)
class MarionetteAssertsProtocolPart(AssertsProtocolPart):
def setup(self):
self.assert_count = {"chrome": 0, "content": 0}
self.chrome_assert_count = 0
self.marionette = self.parent.marionette
def get(self):
script = """
debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
if (debug.isDebugBuild) {
return debug.assertionCount;
}
return 0;
"""
def get_count(context, **kwargs):
try:
context_count = self.marionette.execute_script(script, **kwargs)
if context_count:
self.parent.logger.info("Got %s assert count %s" % (context, context_count))
test_count = context_count - self.assert_count[context]
self.assert_count[context] = context_count
return test_count
except errors.NoSuchWindowException:
# If the window was already closed
self.parent.logger.warning("Failed to get assertion count; window was closed")
except (errors.MarionetteException, IOError):
# This usually happens if the process crashed
pass
counts = []
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
counts.append(get_count("chrome"))
if self.parent.e10s:
counts.append(get_count("content", sandbox="system"))
counts = [item for item in counts if item is not None]
if not counts:
return None
return sum(counts)
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 MarionetteSendKeysProtocolPart(SendKeysProtocolPart):
def setup(self):
self.marionette = self.parent.marionette
def send_keys(self, element, keys):
return element.send_keys(keys)
class MarionetteActionSequenceProtocolPart(ActionSequenceProtocolPart):
def setup(self):
self.marionette = self.parent.marionette
def send_actions(self, actions):
actions = self.marionette._to_json(actions)
self.logger.info(actions)
self.marionette._send_message("WebDriver:PerformActions", actions)
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.parent.base.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
class MarionetteCoverageProtocolPart(CoverageProtocolPart):
def setup(self):
self.marionette = self.parent.marionette
if not self.parent.ccov:
self.is_enabled = False
return
script = """
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
return PerTestCoverageUtils.enabled;
"""
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
self.is_enabled = self.marionette.execute_script(script)
def reset(self):
script = """
var callback = arguments[arguments.length - 1];
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
PerTestCoverageUtils.beforeTest().then(callback, callback);
"""
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
try:
error = self.marionette.execute_async_script(script)
if error is not None:
raise Exception('Failure while resetting counters: %s' % json.dumps(error))
except (errors.MarionetteException, IOError):
# This usually happens if the process crashed
pass
def dump(self):
if len(self.marionette.window_handles):
handle = self.marionette.window_handles[0]
self.marionette.switch_to_window(handle)
script = """
var callback = arguments[arguments.length - 1];
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
PerTestCoverageUtils.afterTest().then(callback, callback);
"""
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
try:
error = self.marionette.execute_async_script(script)
if error is not None:
raise Exception('Failure while dumping counters: %s' % json.dumps(error))
except (errors.MarionetteException, IOError):
# This usually happens if the process crashed
pass
class MarionetteProtocol(Protocol):
implements = [MarionetteBaseProtocolPart,
MarionetteTestharnessProtocolPart,
MarionettePrefsProtocolPart,
MarionetteStorageProtocolPart,
MarionetteSelectorProtocolPart,
MarionetteClickProtocolPart,
MarionetteSendKeysProtocolPart,
MarionetteActionSequenceProtocolPart,
MarionetteTestDriverProtocolPart,
MarionetteAssertsProtocolPart,
MarionetteCoverageProtocolPart]
def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True, ccov=False):
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
self.e10s = e10s
self.ccov = ccov
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='127.0.0.1',
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.capabilities)
self.logger.debug("Marionette session started")
def after_connect(self):
pass
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.prefs.get(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.func = func
self.url = url
self.timeout = timeout
self.result_flag = threading.Event()
def run(self):
index = self.url.rfind("/storage/")
if index != -1:
# Clear storage
self.protocol.storage.clear_origin(self.url)
timeout = self.timeout
try:
if timeout is not None:
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.base.set_timeout(2**28 - 1)
except IOError:
self.logger.error("Lost marionette connection before starting test")
return Stop
if timeout is not None:
wait_timeout = timeout + 2 * extra_timeout
else:
wait_timeout = None
timer = threading.Timer(wait_timeout, self._timeout)
timer.start()
self._run()
self.result_flag.wait()
timer.cancel()
if self.result == (None, None):
self.logger.debug("Timed out waiting for a result")
self.result = False, ("EXTERNAL-TIMEOUT", None)
elif self.result[1] is None:
# We didn't get any data back from the test, so check if the
# browser is still responsive
if self.protocol.is_alive:
self.result = False, ("INTERNAL-ERROR", None)
else:
self.result = False, ("CRASH", None)
return self.result
def _run(self):
try:
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)
except IOError:
# This can happen on a crash
# Also, should check after the test if the firefox process is still running
# and otherwise ignore any other result and set it to crash
self.result = False, ("CRASH", None)
except Exception as e:
message = getattr(e, "message", "")
if message:
message += "\n"
message += traceback.format_exc(e)
self.logger.warning(traceback.format_exc())
self.result = False, ("INTERNAL-ERROR", e)
finally:
self.result_flag.set()
def _timeout(self):
self.result = False, ("EXTERNAL-TIMEOUT", None)
self.result_flag.set()
class MarionetteTestharnessExecutor(TestharnessExecutor):
supports_testdriver = True
def __init__(self, browser, server_config, timeout_multiplier=1,
close_after_done=True, debug_info=None, capabilities=None,
debug=False, ccov=False, **kwargs):
"""Marionette-based executor for testharness.js tests"""
TestharnessExecutor.__init__(self, browser, server_config,
timeout_multiplier=timeout_multiplier,
debug_info=debug_info)
self.protocol = MarionetteProtocol(self,
browser,
capabilities,
timeout_multiplier,
kwargs["e10s"],
ccov)
self.script = open(os.path.join(here, "testharness_webdriver.js")).read()
self.script_resume = open(os.path.join(here, "testharness_webdriver_resume.js")).read()
self.close_after_done = close_after_done
self.window_id = str(uuid.uuid4())
self.debug = debug
self.original_pref_values = {}
if marionette is None:
do_delayed_imports()
def setup(self, runner):
super(MarionetteTestharnessExecutor, self).setup(runner)
self.protocol.testharness.load_runner(self.last_environment["protocol"])
def is_alive(self):
return self.protocol.is_alive
def on_environment_change(self, new_environment):
self.protocol.on_environment_change(self.last_environment, new_environment)
if new_environment["protocol"] != self.last_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
else None)
success, data = ExecuteAsyncScriptRun(self.logger,
self.do_testharness,
self.protocol,
self.test_url(test),
timeout).run()
# The format of data depends on whether the test ran to completion or not
# For asserts we only care about the fact that if it didn't complete, the
# status is in the first field.
status = None
if not success:
status = data[0]
extra = None
if self.debug and (success or status not in ("CRASH", "INTERNAL-ERROR")):
assertion_count = self.protocol.asserts.get()
if assertion_count is not None:
extra = {"assertion_count": assertion_count}
if success:
return self.convert_result(test, data, extra=extra)
return (test.result_cls(extra=extra, *data), [])
def do_testharness(self, protocol, url, timeout):
parent_window = protocol.testharness.close_old_windows(protocol)
if timeout is not None:
timeout_ms = str(timeout * 1000)
else:
timeout_ms = "null"
if self.protocol.coverage.is_enabled:
self.protocol.coverage.reset()
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}
script = self.script % format_map
protocol.base.execute_script(script, async=True)
test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
handler = CallbackHandler(self.logger, protocol, test_window)
while True:
self.protocol.base.set_window(test_window)
result = protocol.base.execute_script(
self.script_resume % format_map, async=True)
if result is None:
# This can happen if we get an content process crash
return None
done, rv = handler(result)
if done:
break
if self.protocol.coverage.is_enabled:
self.protocol.coverage.dump()
return rv
class MarionetteRefTestExecutor(RefTestExecutor):
def __init__(self, browser, server_config, timeout_multiplier=1,
screenshot_cache=None, close_after_done=True,
debug_info=None, reftest_internal=False,
reftest_screenshot="unexpected", ccov=False,
group_metadata=None, capabilities=None, debug=False, **kwargs):
"""Marionette-based executor for reftests"""
RefTestExecutor.__init__(self,
browser,
server_config,
screenshot_cache=screenshot_cache,
timeout_multiplier=timeout_multiplier,
debug_info=debug_info)
self.protocol = MarionetteProtocol(self, browser, capabilities,
timeout_multiplier, kwargs["e10s"],
ccov)
self.implementation = (InternalRefTestImplementation
if reftest_internal
else RefTestImplementation)(self)
self.implementation_kwargs = ({"screenshot": reftest_screenshot} if
reftest_internal else {})
self.close_after_done = close_after_done
self.has_window = False
self.original_pref_values = {}
self.group_metadata = group_metadata
self.debug = debug
with open(os.path.join(here, "reftest.js")) as f:
self.script = f.read()
with open(os.path.join(here, "reftest-wait_marionette.js")) as f:
self.wait_script = f.read()
def setup(self, runner):
super(self.__class__, self).setup(runner)
self.implementation.setup(**self.implementation_kwargs)
def teardown(self):
try:
self.implementation.teardown()
handles = self.protocol.marionette.window_handles
if handles:
self.protocol.marionette.switch_to_window(handles[0])
super(self.__class__, self).teardown()
except Exception as e:
# Ignore errors during teardown
self.logger.warning("Exception during reftest teardown:\n%s" %
traceback.format_exc(e))
def is_alive(self):
return self.protocol.is_alive
def on_environment_change(self, new_environment):
self.protocol.on_environment_change(self.last_environment, new_environment)
def do_test(self, test):
if not isinstance(self.implementation, InternalRefTestImplementation):
if self.close_after_done and self.has_window:
self.protocol.marionette.close()
self.protocol.marionette.switch_to_window(
self.protocol.marionette.window_handles[-1])
self.has_window = False
if not self.has_window:
self.protocol.base.execute_script(self.script)
self.protocol.base.set_window(self.protocol.marionette.window_handles[-1])
self.has_window = True
if self.protocol.coverage.is_enabled:
self.protocol.coverage.reset()
result = self.implementation.run_test(test)
if self.protocol.coverage.is_enabled:
self.protocol.coverage.dump()
if self.debug:
assertion_count = self.protocol.asserts.get()
if "extra" not in result:
result["extra"] = {}
result["extra"]["assertion_count"] = assertion_count
return self.convert_result(test, result)
def screenshot(self, test, viewport_size, dpi):
# https://github.com/w3c/wptrunner/issues/166
assert viewport_size is None
assert dpi is None
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
test_url = self.test_url(test)
return ExecuteAsyncScriptRun(self.logger,
self._screenshot,
self.protocol,
test_url,
timeout).run()
def _screenshot(self, protocol, url, timeout):
protocol.marionette.navigate(url)
protocol.base.execute_script(self.wait_script, async=True)
screenshot = protocol.marionette.screenshot(full=False)
# strip off the data:img/png, part of the url
if screenshot.startswith("data:image/png;base64,"):
screenshot = screenshot.split(",", 1)[1]
return screenshot
class InternalRefTestImplementation(object):
def __init__(self, executor):
self.timeout_multiplier = executor.timeout_multiplier
self.executor = executor
@property
def logger(self):
return self.executor.logger
def setup(self, screenshot="unexpected"):
data = {"screenshot": screenshot}
if self.executor.group_metadata is not None:
data["urlCount"] = {urlparse.urljoin(self.executor.server_url(key[0]), key[1]):value
for key, value in self.executor.group_metadata.get("url_count", {}).iteritems()
if value > 1}
self.executor.protocol.marionette.set_context(self.executor.protocol.marionette.CONTEXT_CHROME)
self.executor.protocol.marionette._send_message("reftest:setup", data)
def run_test(self, test):
references = self.get_references(test)
timeout = (test.timeout * 1000) * self.timeout_multiplier
rv = self.executor.protocol.marionette._send_message("reftest:run",
{"test": self.executor.test_url(test),
"references": references,
"expected": test.expected(),
"timeout": timeout})["value"]
return rv
def get_references(self, node):
rv = []
for item, relation in node.references:
rv.append([self.executor.test_url(item), self.get_references(item), relation])
return rv
def teardown(self):
try:
self.executor.protocol.marionette._send_message("reftest:teardown", {})
self.executor.protocol.marionette.set_context(self.executor.protocol.marionette.CONTEXT_CONTENT)
except Exception as e:
# Ignore errors during teardown
self.logger.warning(traceback.format_exc(e))
class GeckoDriverProtocol(WebDriverProtocol):
server_cls = GeckoDriverServer
class MarionetteWdspecExecutor(WdspecExecutor):
protocol_cls = GeckoDriverProtocol