--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -5,17 +5,17 @@
import os
import sys
import tempfile
import time
from copy import deepcopy
from mozprofile import Profile
-from mozrunner import Runner
+from mozrunner import Runner, FennecEmulatorRunner
class GeckoInstance(object):
required_prefs = {
"browser.sessionstore.resume_from_crash": False,
"browser.shell.checkDefaultBrowser": False,
"browser.startup.page": 0,
"browser.tabs.remote.autostart.1": False,
@@ -42,51 +42,63 @@ class GeckoInstance(object):
# Until Bug 1238095 is fixed, we have to enable CPOWs in order
# for Marionette tests to work properly.
"dom.ipc.cpows.forbid-unsafe-from-browser": False,
}
def __init__(self, host, port, bin, profile=None, addons=None,
app_args=None, symbols_path=None, gecko_log=None, prefs=None,
workspace=None, verbose=0):
+ self.runner_class = Runner
+ self.app_args = app_args or []
+ self.runner = None
+ self.symbols_path = symbols_path
+ self.binary = bin
+
self.marionette_host = host
self.marionette_port = port
- self.bin = bin
# Alternative to default temporary directory
self.workspace = workspace
+ self.addons = addons
# Check if it is a Profile object or a path to profile
self.profile = None
- self.addons = addons
if isinstance(profile, Profile):
self.profile = profile
else:
self.profile_path = profile
self.prefs = prefs
- self.required_prefs = deepcopy(GeckoInstance.required_prefs)
+ self.required_prefs = deepcopy(self.required_prefs)
if prefs:
self.required_prefs.update(prefs)
- self.app_args = app_args or []
- self.runner = None
- self.symbols_path = symbols_path
- if gecko_log != '-':
- if gecko_log is None:
- gecko_log = 'gecko.log'
- elif os.path.isdir(gecko_log):
- fname = 'gecko-%d.log' % time.time()
- gecko_log = os.path.join(gecko_log, fname)
-
- gecko_log = os.path.realpath(gecko_log)
- if os.access(gecko_log, os.F_OK):
- os.remove(gecko_log)
-
- self.gecko_log = gecko_log
+ self._gecko_log_option = gecko_log
+ self._gecko_log = None
self.verbose = verbose
- def start(self):
+ @property
+ def gecko_log(self):
+ if self._gecko_log:
+ return self._gecko_log
+
+ path = self._gecko_log_option
+ if path != '-':
+ if path is None:
+ path = 'gecko.log'
+ elif os.path.isdir(path):
+ fname = 'gecko-%d.log' % time.time()
+ path = os.path.join(path, fname)
+
+ path = os.path.realpath(path)
+ if os.access(path, os.F_OK):
+ os.remove(path)
+
+ self._gecko_log = path
+ return self._gecko_log
+
+ def _update_profile(self):
profile_args = {"preferences": deepcopy(self.required_prefs)}
profile_args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port
if self.prefs:
profile_args["preferences"].update(self.prefs)
if self.verbose:
level = "TRACE" if self.verbose >= 2 else "DEBUG"
profile_args["preferences"]["marionette.logging"] = level
if '-jsdebugger' in self.app_args:
@@ -113,39 +125,46 @@ class GeckoInstance(object):
os.path.basename(self.profile_path),
time.time()
)
if self.workspace:
profile_args["path_to"] = os.path.join(self.workspace,
profile_name)
self.profile = Profile.clone(**profile_args)
+ def start(self):
+ self._update_profile()
+ self.runner = self.runner_class(**self._get_runner_args())
+ self.runner.start()
+
+ def _get_runner_args(self):
process_args = {
'processOutputLine': [NullOutput()],
}
if self.gecko_log == '-':
process_args['stream'] = sys.stdout
else:
process_args['logfile'] = self.gecko_log
env = os.environ.copy()
# environment variables needed for crashreporting
# https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
env.update({'MOZ_CRASHREPORTER': '1',
'MOZ_CRASHREPORTER_NO_REPORT': '1'})
- self.runner = Runner(
- binary=self.bin,
- profile=self.profile,
- cmdargs=['-no-remote', '-marionette'] + self.app_args,
- env=env,
- symbols_path=self.symbols_path,
- process_args=process_args)
- self.runner.start()
+
+ return {
+ 'binary': self.binary,
+ 'profile': self.profile,
+ 'cmdargs': ['-no-remote', '-marionette'] + self.app_args,
+ 'env': env,
+ 'symbols_path': self.symbols_path,
+ 'process_args': process_args
+ }
def close(self, restart=False):
if not restart:
self.profile = None
if self.runner:
self.runner.stop()
self.runner.cleanup()
@@ -159,16 +178,103 @@ class GeckoInstance(object):
if prefs:
self.prefs = prefs
else:
self.prefs = None
self.start()
+class FennecInstance(GeckoInstance):
+ def __init__(self, emulator_binary=None, avd_home=None, avd=None,
+ adb_path=None, serial=None, connect_to_running_emulator=False,
+ *args, **kwargs):
+ super(FennecInstance, self).__init__(*args, **kwargs)
+ self.runner_class = FennecEmulatorRunner
+ # runner args
+ self._package_name = None
+ self.emulator_binary = emulator_binary
+ self.avd_home = avd_home
+ self.adb_path = adb_path
+ self.avd = avd
+ self.serial = serial
+ self.connect_to_running_emulator = connect_to_running_emulator
+
+ @property
+ def package_name(self):
+ """
+ Name of app to run on emulator.
+
+ Note that FennecInstance does not use self.binary
+ """
+ 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):
+ self._update_profile()
+ self.runner = self.runner_class(**self._get_runner_args())
+ try:
+ if self.connect_to_running_emulator:
+ self.runner.device.connect()
+ self.runner.start()
+ except Exception as e:
+ message = 'Error possibly due to runner or device args.'
+ e.args += (message,)
+ if hasattr(e, 'strerror') and e.strerror:
+ e.strerror = ', '.join([e.strerror, message])
+ raise e
+ # gecko_log comes from logcat when running with device/emulator
+ logcat_args = {
+ 'filterspec': 'Gecko',
+ 'serial': self.runner.device.dm._deviceSerial
+ }
+ if self.gecko_log == '-':
+ logcat_args['stream'] = sys.stdout
+ else:
+ logcat_args['logfile'] = self.gecko_log
+ self.runner.device.start_logcat(**logcat_args)
+ self.runner.device.setup_port_forwarding(
+ local_port=self.marionette_port,
+ remote_port=self.marionette_port,
+ )
+
+ def _get_runner_args(self):
+ process_args = {
+ 'processOutputLine': [NullOutput()],
+ }
+
+ runner_args = {
+ 'app': self.package_name,
+ 'avd_home': self.avd_home,
+ 'adb_path': self.adb_path,
+ 'binary': self.emulator_binary,
+ 'profile': self.profile,
+ 'cmdargs': ['-marionette'] + self.app_args,
+ 'symbols_path': self.symbols_path,
+ 'process_args': process_args,
+ 'logdir': self.workspace or os.getcwd(),
+ 'serial': self.serial,
+ }
+ if self.avd:
+ runner_args['avd'] = self.avd
+
+ return runner_args
+
+ def close(self, restart=False):
+ super(FennecInstance, self).close(restart)
+ if self.runner and self.runner.device.connected:
+ self.runner.device.dm.remove_forward(
+ 'tcp:%d' % int(self.marionette_port)
+ )
+
+
class B2GDesktopInstance(GeckoInstance):
def __init__(self, host, port, bin, **kwargs):
# Pass a profile and change the binary to -bin so that
# the built-in gaia profile doesn't get touched.
if kwargs.get('profile', None) is None:
# GeckoInstance.start will clone the profile.
kwargs['profile'] = os.path.join(os.path.dirname(bin),
'gaia',
@@ -229,9 +335,10 @@ class NullOutput(object):
def __call__(self, line):
pass
apps = {
'b2g': B2GDesktopInstance,
'b2gdesktop': B2GDesktopInstance,
'fxdesktop': DesktopInstance,
+ 'fennec': FennecInstance,
}
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -538,73 +538,79 @@ class Marionette(object):
CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
TIMEOUT_SEARCH = 'implicit'
TIMEOUT_SCRIPT = 'script'
TIMEOUT_PAGE = 'page load'
DEFAULT_SOCKET_TIMEOUT = 360
DEFAULT_STARTUP_TIMEOUT = 120
- def __init__(self, host='localhost', port=2828, app=None, app_args=None,
- bin=None, profile=None, addons=None,
- gecko_log=None, baseurl=None,
- symbols_path=None, timeout=None,
- socket_timeout=DEFAULT_SOCKET_TIMEOUT,
- process_args=None, prefs=None,
- startup_timeout=None, workspace=None, verbose=0):
+ def __init__(self, host='localhost', port=2828, app=None, bin=None,
+ baseurl=None, timeout=None, socket_timeout=DEFAULT_SOCKET_TIMEOUT,
+ startup_timeout=None, **instance_args):
+ """
+ :param host: address for Marionette connection
+ :param port: integer port for Marionette connection
+ :param baseurl: where to look for files served from Marionette's www directory
+ :param startup_timeout: seconds to wait for a connection with binary
+ :param timeout: time to wait for page load, scripts, search
+ :param socket_timeout: timeout for Marionette socket operations
+ :param bin: path to app binary; if any truthy value is given this will
+ attempt to start a gecko instance with the specified `app`
+ :param app: type of instance_class to use for managing app instance.
+ See marionette_driver.geckoinstance
+ :param instance_args: args to pass to instance_class
+ """
self.host = host
- self.port = self.local_port = port
+ self.port = self.local_port = int(port)
self.bin = bin
- self.profile = profile
- self.addons = addons
self.instance = None
self.session = None
self.session_id = None
self.window = None
self.chrome_window = None
self.baseurl = baseurl
self._test_name = None
self.timeout = timeout
self.socket_timeout = socket_timeout
startup_timeout = startup_timeout or self.DEFAULT_STARTUP_TIMEOUT
-
- if bin:
- port = int(self.port)
- if not Marionette.is_port_available(port, host=self.host):
- ex_msg = "%s:%d is unavailable." % (self.host, port)
- raise errors.MarionetteException(message=ex_msg)
- if app:
- # select instance class for the given app
- try:
- instance_class = geckoinstance.apps[app]
- except KeyError:
- msg = 'Application "%s" unknown (should be one of %s)'
- raise NotImplementedError(msg % (app, geckoinstance.apps.keys()))
- else:
- try:
- config = ConfigParser.RawConfigParser()
- config.read(os.path.join(os.path.dirname(bin), 'application.ini'))
- app = config.get('App', 'Name')
- instance_class = geckoinstance.apps[app.lower()]
- except (ConfigParser.NoOptionError,
- ConfigParser.NoSectionError,
- KeyError):
- instance_class = geckoinstance.GeckoInstance
- self.instance = instance_class(host=self.host, port=self.port,
- bin=self.bin, profile=self.profile,
- app_args=app_args,
- symbols_path=symbols_path,
- gecko_log=gecko_log, prefs=prefs,
- addons=self.addons,
- workspace=workspace,
- verbose=verbose)
+ if self.bin:
+ self.instance = self._create_instance(app, instance_args)
self.instance.start()
self.raise_for_port(self.wait_for_port(timeout=startup_timeout))
+ def _create_instance(self, app, instance_args):
+ if not Marionette.is_port_available(self.port, host=self.host):
+ ex_msg = "%s:%d is unavailable." % (self.host, self.port)
+ raise errors.MarionetteException(message=ex_msg)
+ if app:
+ # select instance class for the given app
+ try:
+ instance_class = geckoinstance.apps[app]
+ except KeyError:
+ msg = 'Application "%s" unknown (should be one of %s)'
+ raise NotImplementedError(
+ msg % (app, geckoinstance.apps.keys()))
+ else:
+ try:
+ if not isinstance(self.bin, basestring):
+ raise TypeError("bin must be a string if app is not specified")
+ config = ConfigParser.RawConfigParser()
+ config.read(os.path.join(os.path.dirname(self.bin),
+ 'application.ini'))
+ app = config.get('App', 'Name')
+ instance_class = geckoinstance.apps[app.lower()]
+ except (ConfigParser.NoOptionError,
+ ConfigParser.NoSectionError,
+ KeyError):
+ instance_class = geckoinstance.GeckoInstance
+ return instance_class(host=self.host, port=self.port, bin=self.bin,
+ **instance_args)
+
@property
def profile_path(self):
if self.instance and self.instance.profile:
return self.instance.profile.profile
def cleanup(self):
if self.session:
try:
@@ -1016,17 +1022,16 @@ class Marionette(object):
profile.
: param in_app: If True, marionette will cause a restart from within the
browser. Otherwise the browser will be restarted immediately
by killing the process.
"""
if not self.instance:
raise errors.MarionetteException("restart can only be called "
"on gecko instances launched by Marionette")
-
if in_app:
if clean:
raise ValueError
# Values here correspond to constants in nsIAppStartup.
# See http://mzl.la/1X0JZsC
restart_flags = [
"eForceQuit",
"eRestart",
--- a/testing/marionette/harness/marionette/runner/base.py
+++ b/testing/marionette/harness/marionette/runner/base.py
@@ -1,14 +1,15 @@
# 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/.
from argparse import ArgumentParser
+from copy import deepcopy
import json
import mozinfo
import moznetwork
import os
import random
import re
import socket
import sys
@@ -253,36 +254,34 @@ class BaseMarionetteArguments(ArgumentPa
self.add_argument('tests',
nargs='*',
default=[],
help='Tests to run. '
'One or more paths to test files (Python or JS), '
'manifest files (.ini) or directories. '
'When a directory is specified, '
'all test files in the directory will be run.')
- self.add_argument('-v', '--verbose',
- action='count',
- help='Increase verbosity to include debug messages with -v, '
- 'and trace messages with -vv.')
+ self.add_argument('--binary',
+ help='path to gecko executable to launch before running the test')
self.add_argument('--address',
help='host:port of running Gecko instance to connect to')
- self.add_argument('--device',
- dest='device_serial',
- help='serial ID of a device to use for adb / fastboot')
+ self.add_argument('--emulator',
+ action='store_true',
+ help='If no --address is given, then the harness will launch an emulator. (See Remote options group.) '
+ 'If --address is given, then the harness assumes you are running an '
+ 'emulator already, and will launch gecko app on that emulator.')
self.add_argument('--app',
- help='application to use')
+ help='application to use. see marionette_driver.geckoinstance')
self.add_argument('--app-arg',
dest='app_args',
action='append',
default=[],
help='specify a command line argument to be passed onto the application')
- self.add_argument('--binary',
- help='gecko executable to launch before running the test')
self.add_argument('--profile',
- help='profile to use when launching the gecko process. if not passed, then a profile will be '
+ help='profile to use when launching the gecko process. If not passed, then a profile will be '
'constructed and used',
type=dir_path)
self.add_argument('--pref',
action='append',
dest='prefs_args',
help=(" A preference to set. Must be a key-value pair"
" separated by a ':'."))
self.add_argument('--preferences',
@@ -361,16 +360,21 @@ class BaseMarionetteArguments(ArgumentPa
"used multiple times in which case the test must contain "
"at least one of the given tags.")
self.add_argument('--workspace',
action='store',
default=None,
help="Path to directory for Marionette output. "
"(Default: .) (Default profile dest: TMP)",
type=dir_path)
+ self.add_argument('-v', '--verbose',
+ action='count',
+ help='Increase verbosity to include debug messages with -v, '
+ 'and trace messages with -vv.')
+ self.register_argument_container(RemoteMarionetteArguments())
def register_argument_container(self, container):
group = self.add_argument_group(container.name)
for cli, kwargs in container.args:
group.add_argument(*cli, **kwargs)
self.argument_containers.append(container)
@@ -414,18 +418,18 @@ class BaseMarionetteArguments(ArgumentPa
def verify_usage(self, args):
if not args.tests:
self.error('You must specify one or more test files, manifests, or directories.')
missing_tests = [path for path in args.tests if not os.path.exists(path)]
if missing_tests:
self.error("Test file(s) not found: " + " ".join([path for path in missing_tests]))
- if not args.address and not args.binary:
- self.error('You must specify --binary, or --address')
+ if not args.address and not args.binary and not args.emulator:
+ self.error('You must specify --binary, or --address, or --emulator')
if args.total_chunks is not None and args.this_chunk is None:
self.error('You must specify which chunk to run.')
if args.this_chunk is not None and args.total_chunks is None:
self.error('You must specify how many chunks to split the tests into.')
if args.total_chunks is not None:
@@ -448,55 +452,79 @@ class BaseMarionetteArguments(ArgumentPa
})
for container in self.argument_containers:
if hasattr(container, 'verify_usage_handler'):
container.verify_usage_handler(args)
return args
+class RemoteMarionetteArguments(object):
+ name = 'Remote (Emulator/Device)'
+ args = [
+ [['--emulator-binary'],
+ {'help': 'Path to emulator binary. By default mozrunner uses `which emulator`',
+ 'dest': 'emulator_bin',
+ }],
+ [['--adb'],
+ {'help': 'Path to the adb. By default mozrunner uses `which adb`',
+ 'dest': 'adb_path'
+ }],
+ [['--avd'],
+ {'help': ('Name of an AVD available in your environment.'
+ 'See mozrunner.FennecEmulatorRunner'),
+ }],
+ [['--avd-home'],
+ {'help': 'Path to avd parent directory',
+ }],
+ [['--device'],
+ {'help': ('Serial ID to connect to as seen in `adb devices`,'
+ 'e.g emulator-5444'),
+ 'dest': 'device_serial',
+ }],
+ ]
class BaseMarionetteTestRunner(object):
textrunnerclass = MarionetteTextTestRunner
driverclass = Marionette
def __init__(self, address=None,
app=None, app_args=None, binary=None, profile=None,
logger=None, logdir=None,
repeat=0, testvars=None,
symbols_path=None, timeout=None,
- shuffle=False, shuffle_seed=random.randint(0, sys.maxint),
- sdcard=None, this_chunk=1, total_chunks=1, sources=None,
+ shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1, total_chunks=1, sources=None,
server_root=None, gecko_log=None, result_callbacks=None,
prefs=None, test_tags=None,
socket_timeout=BaseMarionetteArguments.socket_timeout_default,
startup_timeout=None, addons=None, workspace=None,
- verbose=0, e10s=True, **kwargs):
+ verbose=0, e10s=True, emulator=False, **kwargs):
+ self.extra_kwargs = kwargs
+ self.test_kwargs = deepcopy(kwargs)
self.address = address
self.app = app
self.app_args = app_args or []
self.bin = binary
+ self.emulator = emulator
self.profile = profile
self.addons = addons
self.logger = logger
self.httpd = None
self.marionette = None
self.logdir = logdir
self.repeat = repeat
- self.test_kwargs = kwargs
self.symbols_path = symbols_path
self.timeout = timeout
self.socket_timeout = socket_timeout
self._capabilities = None
self._appinfo = None
self._appName = None
self.shuffle = shuffle
self.shuffle_seed = shuffle_seed
- self.sdcard = sdcard
self.sources = sources
self.server_root = server_root
self.this_chunk = this_chunk
self.total_chunks = total_chunks
self.mixin_run_tests = []
self.manifest_skipped_tests = []
self.tests = []
self.result_callbacks = result_callbacks or []
@@ -653,36 +681,52 @@ class BaseMarionetteTestRunner(object):
kwargs = {
'timeout': self.timeout,
'socket_timeout': self.socket_timeout,
'prefs': self.prefs,
'startup_timeout': self.startup_timeout,
'verbose': self.verbose,
}
- if self.bin:
+ if self.bin or self.emulator:
kwargs.update({
'host': 'localhost',
'port': 2828,
'app': self.app,
'app_args': self.app_args,
- 'bin': self.bin,
'profile': self.profile,
'addons': self.addons,
'gecko_log': self.gecko_log,
+ # ensure Marionette class takes care of starting gecko instance
+ 'bin': True,
+ })
+
+ if self.bin:
+ kwargs.update({
+ 'bin': self.bin,
+ })
+
+ if self.emulator:
+ kwargs.update({
+ 'avd_home': self.extra_kwargs.get('avd_home'),
+ 'adb_path': self.extra_kwargs.get('adb_path'),
+ 'emulator_binary': self.extra_kwargs.get('emulator_bin'),
})
if self.address:
host, port = self.address.split(':')
kwargs.update({
'host': host,
'port': int(port),
})
-
- if not self.bin:
+ if self.emulator:
+ kwargs.update({
+ 'connect_to_running_emulator': True,
+ })
+ if not self.bin and not self.emulator:
try:
#establish a socket connection so we can vertify the data come back
connection = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connection.connect((host,int(port)))
connection.close()
except Exception, e:
raise Exception("Connection attempt to %s:%s failed with error: %s" %(host,port,e))
if self.workspace:
@@ -802,24 +846,34 @@ setReq.onerror = function() {
start_time = time.time()
self._initialize_test_run(tests)
need_external_ip = self._start_marionette()
self._set_baseurl(need_external_ip)
self._add_tests(tests)
+ device_info = None
+ if self.marionette.instance and self.emulator:
+ try:
+ device_info = self.marionette.instance.runner.device.dm.getInfo()
+ except Exception:
+ self.logger.warning('Could not get device info.')
+
+ #TODO Get version_info in Fennec case
version_info = None
if self.bin:
version_info = mozversion.get_version(binary=self.bin,
sources=self.sources)
self.logger.info("running with e10s: {}".format(self.e10s))
- self.logger.suite_start(self.tests, version_info=version_info,)
+ self.logger.suite_start(self.tests,
+ version_info=version_info,
+ device_info=device_info)
self._log_skipped_tests()
interrupted = None
try:
counter = self.repeat
while counter >=0:
round_num = self.repeat - counter
--- a/testing/marionette/harness/marionette/tests/harness_unit/test_marionette_runner.py
+++ b/testing/marionette/harness/marionette/tests/harness_unit/test_marionette_runner.py
@@ -62,67 +62,71 @@ def logger():
@pytest.fixture()
def mach_parsed_kwargs(logger):
"""
Parsed and verified dictionary used during simplest
call to mach marionette-test
"""
return {
- 'addon': None,
- 'address': None,
- 'app': None,
- 'app_args': [],
- 'binary': u'/path/to/firefox',
- 'device_serial': None,
- 'e10s': False,
- 'gecko_log': None,
- 'homedir': None,
- 'jsdebugger': False,
- 'log_errorsummary': None,
- 'log_html': None,
- 'log_mach': None,
- 'log_mach_buffer': None,
- 'log_mach_level': None,
- 'log_mach_verbose': None,
- 'log_raw': None,
- 'log_raw_level': None,
- 'log_tbpl': None,
- 'log_tbpl_buffer': None,
- 'log_tbpl_level': None,
- 'log_unittest': None,
- 'log_xunit': None,
- 'logdir': None,
- 'logger_name': 'Marionette-based Tests',
- 'no_window': False,
- 'prefs': {},
- 'prefs_args': None,
- 'prefs_files': None,
- 'profile': None,
- 'pydebugger': None,
- 'repeat': 0,
- 'sdcard': None,
- 'server_root': None,
- 'shuffle': False,
- 'shuffle_seed': 2276870381009474531,
- 'socket_timeout': 360.0,
- 'sources': None,
- 'startup_timeout': 60,
- 'symbols_path': None,
- 'test_tags': None,
- 'tests': [u'/path/to/unit-tests.ini'],
- 'testvars': None,
- 'this_chunk': None,
- 'timeout': None,
- 'total_chunks': None,
- 'tree': 'b2g',
- 'type': None,
- 'verbose': None,
- 'workspace': None,
- 'logger': logger,
+ 'adb_path': None,
+ 'addon': None,
+ 'address': None,
+ 'app': None,
+ 'app_args': [],
+ 'avd': None,
+ 'avd_home': None,
+ 'binary': u'/path/to/firefox',
+ 'device_serial': None,
+ 'e10s': True,
+ 'emulator': False,
+ 'emulator_bin': None,
+ 'gecko_log': None,
+ 'jsdebugger': False,
+ 'log_errorsummary': None,
+ 'log_html': None,
+ 'log_mach': None,
+ 'log_mach_buffer': None,
+ 'log_mach_level': None,
+ 'log_mach_verbose': None,
+ 'log_raw': None,
+ 'log_raw_level': None,
+ 'log_tbpl': None,
+ 'log_tbpl_buffer': None,
+ 'log_tbpl_compact': None,
+ 'log_tbpl_level': None,
+ 'log_unittest': None,
+ 'log_xunit': None,
+ 'logger_name': 'Marionette-based Tests',
+ 'prefs': {
+ 'browser.tabs.remote.autostart': True,
+ 'browser.tabs.remote.force-enable': True,
+ 'extensions.e10sBlocksEnabling': False,
+ },
+ 'prefs_args': None,
+ 'prefs_files': None,
+ 'profile': None,
+ 'pydebugger': None,
+ 'repeat': 0,
+ 'server_root': None,
+ 'shuffle': False,
+ 'shuffle_seed': 2276870381009474531,
+ 'socket_timeout': 360.0,
+ 'sources': None,
+ 'startup_timeout': 60,
+ 'symbols_path': None,
+ 'test_tags': None,
+ 'tests': [u'/path/to/unit-tests.ini'],
+ 'testvars': None,
+ 'this_chunk': None,
+ 'timeout': None,
+ 'total_chunks': None,
+ 'verbose': None,
+ 'workspace': None,
+ 'logger': logger,
}
@pytest.fixture()
def runner(mach_parsed_kwargs):
"""
MarionetteTestRunner instance initialized with default options.
"""