1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/
5 # ALL CHANGES TO THIS FILE MUST HAVE REVIEW FROM A MARIONETTE PEER!
7 # The Marionette Python client is used out-of-tree with various builds of
8 # Firefox. Removing a preference from this file will cause regressions,
9 # so please be careful and get review from a Testing :: Marionette peer
10 # before you make any changes to this file.
12 from __future__
import absolute_import
20 from copy
import deepcopy
24 from mozprofile
import Profile
25 from mozrunner
import Runner
, FennecEmulatorRunner
26 from six
import reraise
31 class GeckoInstance(object):
33 # Make sure Shield doesn't hit the network.
34 "app.normandy.api_url": "",
36 # Increase the APZ content response timeout in tests to 1 minute.
37 # This is to accommodate the fact that test environments tends to be slower
38 # than production environments (with the b2g emulator being the slowest of them
39 # all), resulting in the production timeout value sometimes being exceeded
40 # and causing false-positive test failures. See bug 1176798, bug 1177018,
42 "apz.content_response_timeout": 60000,
44 # Do not send Firefox health reports to the production server
45 # removed in Firefox 59
46 "datareporting.healthreport.about.reportUrl": "http://%(server)s/dummy/abouthealthreport/",
47 "datareporting.healthreport.documentServerURI": "http://%(server)s/dummy/healthreport/",
49 # Do not show datareporting policy notifications which can interfer with tests
50 "datareporting.policy.dataSubmissionPolicyBypassNotification": True,
52 # Automatically unload beforeunload alerts
53 "dom.disable_beforeunload": True,
55 # Disable the ProcessHangMonitor
56 "dom.ipc.reportProcessHangs": False,
58 # No slow script dialogs
59 "dom.max_chrome_script_run_time": 0,
60 "dom.max_script_run_time": 0,
63 "dom.push.connection.enabled": False,
65 # Only load extensions from the application and user profile
66 # AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
67 "extensions.autoDisableScopes": 0,
68 "extensions.enabledScopes": 5,
69 # Disable metadata caching for installed add-ons by default
70 "extensions.getAddons.cache.enabled": False,
71 # Disable intalling any distribution add-ons
72 "extensions.installDistroAddons": False,
73 # Make sure Shield doesn't hit the network.
74 # Removed in Firefox 60.
75 "extensions.shield-recipe-client.api_url": "",
76 # Disable extensions compatibility dialogue.
77 # Removed in Firefox 61.
78 "extensions.showMismatchUI": False,
79 # Turn off extension updates so they don't bother tests
80 "extensions.update.enabled": False,
81 "extensions.update.notifyUser": False,
82 # Make sure opening about:addons won"t hit the network
83 "extensions.webservice.discoverURL": "http://%(server)s/dummy/discoveryURL",
85 # Allow the application to have focus even it runs in the background
86 "focusmanager.testmode": True,
88 # Disable useragent updates
89 "general.useragent.updates.enabled": False,
91 # Always use network provider for geolocation tests
92 # so we bypass the OSX dialog raised by the corelocation provider
93 "geo.provider.testing": True,
95 "geo.wifi.scan": False,
97 "javascript.options.showInConsole": True,
99 # Enable Marionette component
100 "marionette.enabled": True,
101 # (deprecated and can be removed when Firefox 60 ships)
102 "marionette.defaultPrefs.enabled": True,
104 # Disable recommended automation prefs in CI
105 "marionette.prefs.recommended": False,
107 # Disable download and usage of OpenH264, and Widevine plugins
108 "media.gmp-manager.updateEnabled": False,
110 "media.volume_scale": "0.01",
112 # Do not prompt for temporary redirects
113 "network.http.prompt-temp-redirect": False,
114 # Disable speculative connections so they aren"t reported as leaking when they"re
116 "network.http.speculative-parallel-limit": 0,
117 # Do not automatically switch between offline and online
118 "network.manage-offline-status": False,
119 # Make sure SNTP requests don't hit the network
120 "network.sntp.pools": "%(server)s",
122 # Privacy and Tracking Protection
123 "privacy.trackingprotection.enabled": False,
125 # Don't do network connections for mitm priming
126 "security.certerrors.mitm.priming.enabled": False,
128 # Tests don't wait for the notification button security delay
129 "security.notification_enable_delay": 0,
131 # Ensure blocklist updates don't hit the network
132 "services.settings.server": "http://%(server)s/dummy/blocklist/",
134 # Disable password capture, so that tests that include forms aren"t
135 # influenced by the presence of the persistent doorhanger notification
136 "signon.rememberSignons": False,
138 # Prevent starting into safe mode after application crashes
139 "toolkit.startup.max_resumed_crashes": -1,
141 # We want to collect telemetry, but we don't want to send in the results
142 "toolkit.telemetry.server": "https://%(server)s/dummy/telemetry/",
144 # Enabling the support for File object creation in the content process.
145 "dom.file.createInChild": True,
148 def __init__(self
, host
=None, port
=None, bin
=None, profile
=None, addons
=None,
149 app_args
=None, symbols_path
=None, gecko_log
=None, prefs
=None,
150 workspace
=None, verbose
=0, headless
=False):
151 self
.runner_class
= Runner
152 self
.app_args
= app_args
or []
154 self
.symbols_path
= symbols_path
157 self
.marionette_host
= host
158 self
.marionette_port
= port
161 self
.required_prefs
= deepcopy(self
.required_prefs
)
163 self
.required_prefs
.update(prefs
)
165 self
._gecko
_log
_option
= gecko_log
166 self
._gecko
_log
= None
167 self
.verbose
= verbose
168 self
.headless
= headless
170 # keep track of errors to decide whether instance is unresponsive
171 self
.unresponsive_count
= 0
173 # Alternative to default temporary directory
174 self
.workspace
= workspace
176 # Don't use the 'profile' property here, because sub-classes could add
177 # further preferences and data, which would not be included in the new
179 self
._profile
= profile
184 return self
._gecko
_log
186 path
= self
._gecko
_log
_option
190 elif os
.path
.isdir(path
):
191 fname
= "gecko-{}.log".format(time
.time())
192 path
= os
.path
.join(path
, fname
)
194 path
= os
.path
.realpath(path
)
195 if os
.access(path
, os
.F_OK
):
198 self
._gecko
_log
= path
199 return self
._gecko
_log
206 def profile(self
, value
):
207 self
._update
_profile
(value
)
209 def _update_profile(self
, profile
=None, profile_name
=None):
210 """Check if the profile has to be created, or replaced
212 :param profile: A Profile instance to be used.
213 :param name: Profile name to be used in the path.
215 if self
.runner
and self
.runner
.is_running():
216 raise errors
.MarionetteException("The current profile can only be updated "
217 "when the instance is not running")
219 if isinstance(profile
, Profile
):
220 # Only replace the profile if it is not the current one
221 if hasattr(self
, "_profile") and profile
is self
._profile
:
225 profile_args
= self
.profile_args
226 profile_path
= profile
228 # If a path to a profile is given then clone it
229 if isinstance(profile_path
, basestring
):
230 profile_args
["path_from"] = profile_path
231 profile_args
["path_to"] = tempfile
.mkdtemp(
232 suffix
=u
".{}".format(profile_name
or os
.path
.basename(profile_path
)),
234 # The target must not exist yet
235 os
.rmdir(profile_args
["path_to"])
237 profile
= Profile
.clone(**profile_args
)
239 # Otherwise create a new profile
241 profile_args
["profile"] = tempfile
.mkdtemp(
242 suffix
=u
".{}".format(profile_name
or "mozrunner"),
244 profile
= Profile(**profile_args
)
245 profile
.create_new
= True
247 if isinstance(self
.profile
, Profile
):
248 self
.profile
.cleanup()
250 self
._profile
= profile
252 def switch_profile(self
, profile_name
=None, clone_from
=None):
253 """Switch the profile by using the given name, and optionally clone it.
255 Compared to :attr:`profile` this method allows to switch the profile
256 by giving control over the profile name as used for the new profile. It
257 also always creates a new blank profile, or as clone of an existent one.
259 :param profile_name: Optional, name of the profile, which will be used
260 as part of the profile path (folder name containing the profile).
261 :clone_from: Optional, if specified the new profile will be cloned
262 based on the given profile. This argument can be an instance of
263 ``mozprofile.Profile``, or the path of the profile.
265 if isinstance(clone_from
, Profile
):
266 clone_from
= clone_from
.profile
268 self
._update
_profile
(clone_from
, profile_name
=profile_name
)
271 def profile_args(self
):
272 args
= {"preferences": deepcopy(self
.required_prefs
)}
273 args
["preferences"]["marionette.port"] = self
.marionette_port
274 args
["preferences"]["marionette.defaultPrefs.port"] = self
.marionette_port
277 args
["preferences"].update(self
.prefs
)
280 level
= "Trace" if self
.verbose
>= 2 else "Debug"
281 args
["preferences"]["marionette.log.level"] = level
282 args
["preferences"]["marionette.logging"] = level
284 if "-jsdebugger" in self
.app_args
:
285 args
["preferences"].update({
286 "devtools.browsertoolbox.panel": "jsdebugger",
287 "devtools.debugger.remote-enabled": True,
288 "devtools.chrome.enabled": True,
289 "devtools.debugger.prompt-connection": False,
290 "marionette.debugging.clicktostart": True,
294 args
["addons"] = self
.addons
299 def create(cls
, app
=None, *args
, **kwargs
):
301 if not app
and kwargs
["bin"] is not None:
302 app_id
= mozversion
.get_version(binary
=kwargs
["bin"])["application_id"]
303 app
= app_ids
[app_id
]
305 instance_class
= apps
[app
]
306 except (IOError, KeyError):
307 exc
, val
, tb
= sys
.exc_info()
308 msg
= 'Application "{0}" unknown (should be one of {1})'
309 reraise(NotImplementedError, msg
.format(app
, apps
.keys()), tb
)
311 return instance_class(*args
, **kwargs
)
314 self
._update
_profile
(self
.profile
)
315 self
.runner
= self
.runner_class(**self
._get
_runner
_args
())
318 def _get_runner_args(self
):
320 "processOutputLine": [NullOutput()],
323 if self
.gecko_log
== "-":
324 process_args
["stream"] = sys
.stdout
326 process_args
["logfile"] = self
.gecko_log
328 env
= os
.environ
.copy()
331 env
["MOZ_HEADLESS"] = "1"
332 env
["DISPLAY"] = "77" # Set a fake display.
334 # environment variables needed for crashreporting
335 # https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
336 env
.update({"MOZ_CRASHREPORTER": "1",
337 "MOZ_CRASHREPORTER_NO_REPORT": "1",
338 "MOZ_CRASHREPORTER_SHUTDOWN": "1",
342 "binary": self
.binary
,
343 "profile": self
.profile
,
344 "cmdargs": ["-no-remote", "-marionette"] + self
.app_args
,
346 "symbols_path": self
.symbols_path
,
347 "process_args": process_args
350 def close(self
, clean
=False):
352 Close the managed Gecko process.
354 Depending on self.runner_class, setting `clean` to True may also kill
355 the emulator process in which this instance is running.
357 :param clean: If True, also perform runner cleanup.
362 self
.runner
.cleanup()
365 if isinstance(self
.profile
, Profile
):
366 self
.profile
.cleanup()
369 def restart(self
, prefs
=None, clean
=True):
371 Close then start the managed Gecko process.
373 :param prefs: Dictionary of preference names and values.
374 :param clean: If True, reset the profile before starting.
381 self
.close(clean
=clean
)
385 class FennecInstance(GeckoInstance
):
387 # Enable output for dump() and chrome console API
388 "browser.dom.window.dump.enabled": True,
389 "devtools.console.stdout.chrome": True,
391 # Disable safebrowsing components
392 "browser.safebrowsing.blockedURIs.enabled": False,
393 "browser.safebrowsing.downloads.enabled": False,
394 "browser.safebrowsing.passwords.enabled": False,
395 "browser.safebrowsing.malware.enabled": False,
396 "browser.safebrowsing.phishing.enabled": False,
398 # Do not restore the last open set of tabs if the browser has crashed
399 "browser.sessionstore.resume_from_crash": False,
401 # Disable e10s by default
402 "browser.tabs.remote.autostart": False,
404 # Do not allow background tabs to be zombified, otherwise for tests that
405 # open additional tabs, the test harness tab itself might get unloaded
406 "browser.tabs.disableBackgroundZombification": True,
409 def __init__(self
, emulator_binary
=None, avd_home
=None, avd
=None,
410 adb_path
=None, serial
=None, connect_to_running_emulator
=False,
411 package_name
=None, env
=None, *args
, **kwargs
):
412 required_prefs
= deepcopy(FennecInstance
.fennec_prefs
)
413 required_prefs
.update(kwargs
.get("prefs", {}))
415 super(FennecInstance
, self
).__init
__(*args
, **kwargs
)
416 self
.required_prefs
.update(required_prefs
)
418 self
.runner_class
= FennecEmulatorRunner
420 self
._package
_name
= package_name
421 self
.emulator_binary
= emulator_binary
422 self
.avd_home
= avd_home
423 self
.adb_path
= adb_path
427 self
.connect_to_running_emulator
= connect_to_running_emulator
430 def package_name(self
):
432 Name of app to run on emulator.
434 Note that FennecInstance does not use self.binary
436 if self
._package
_name
is None:
437 self
._package
_name
= "org.mozilla.fennec"
438 user
= os
.getenv("USER")
440 self
._package
_name
+= "_" + user
441 return self
._package
_name
444 self
._update
_profile
(self
.profile
)
445 self
.runner
= self
.runner_class(**self
._get
_runner
_args
())
447 if self
.connect_to_running_emulator
:
448 self
.runner
.device
.connect()
450 except Exception as e
:
451 exc
, val
, tb
= sys
.exc_info()
452 message
= "Error possibly due to runner or device args: {}"
453 reraise(exc
, message
.format(e
.message
), tb
)
455 # forward marionette port
456 self
.runner
.device
.device
.forward(
457 local
="tcp:{}".format(self
.marionette_port
),
458 remote
="tcp:{}".format(self
.marionette_port
))
460 def _get_runner_args(self
):
462 "processOutputLine": [NullOutput()],
466 "app": self
.package_name
,
467 "avd_home": self
.avd_home
,
468 "adb_path": self
.adb_path
,
469 "binary": self
.emulator_binary
,
471 "profile": self
.profile
,
472 "cmdargs": ["-marionette"] + self
.app_args
,
473 "symbols_path": self
.symbols_path
,
474 "process_args": process_args
,
475 "logdir": self
.workspace
or os
.getcwd(),
476 "serial": self
.serial
,
479 runner_args
["avd"] = self
.avd
483 def close(self
, clean
=False):
485 Close the managed Gecko process.
487 If `clean` is True and the Fennec instance is running in an
488 emulator managed by mozrunner, this will stop the emulator.
490 :param clean: If True, also perform runner cleanup.
492 super(FennecInstance
, self
).close(clean
)
493 if clean
and self
.runner
and self
.runner
.device
.connected
:
495 self
.runner
.device
.device
.remove_forwards(
496 "tcp:{}".format(self
.marionette_port
))
497 self
.unresponsive_count
= 0
499 self
.unresponsive_count
+= 1
500 traceback
.print_exception(*sys
.exc_info())
503 class DesktopInstance(GeckoInstance
):
505 # Disable Firefox old build background check
506 "app.update.checkInstallTime": False,
508 # Disable automatically upgrading Firefox
510 # Note: Possible update tests could reset or flip the value to allow
511 # updates to be downloaded and applied.
512 "app.update.disabledForTesting": True,
513 # !!! For backward compatibility up to Firefox 64. Only remove
514 # when this Firefox version is no longer supported by the client !!!
515 "app.update.auto": False,
517 # Don't show the content blocking introduction panel
518 # We use a larger number than the default 22 to have some buffer
519 "browser.contentblocking.introCount": 99,
521 # Enable output for dump() and chrome console API
522 "browser.dom.window.dump.enabled": True,
523 "devtools.console.stdout.chrome": True,
525 # Indicate that the download panel has been shown once so that whichever
526 # download test runs first doesn"t show the popup inconsistently
527 "browser.download.panel.shown": True,
529 # Do not show the EULA notification which can interfer with tests
530 "browser.EULA.override": True,
532 # Always display a blank page
533 "browser.newtabpage.enabled": False,
535 # Background thumbnails in particular cause grief, and disabling thumbnails
536 # in general can"t hurt - we re-enable them when tests need them
537 "browser.pagethumbnails.capturing_disabled": True,
539 # Disable safebrowsing components
540 "browser.safebrowsing.blockedURIs.enabled": False,
541 "browser.safebrowsing.downloads.enabled": False,
542 "browser.safebrowsing.passwords.enabled": False,
543 "browser.safebrowsing.malware.enabled": False,
544 "browser.safebrowsing.phishing.enabled": False,
546 # Disable updates to search engines
547 "browser.search.update": False,
549 # Do not restore the last open set of tabs if the browser has crashed
550 "browser.sessionstore.resume_from_crash": False,
552 # Don't check for the default web browser during startup
553 "browser.shell.checkDefaultBrowser": False,
555 # Disable e10s by default
556 "browser.tabs.remote.autostart": False,
558 # Needed for branded builds to prevent opening a second tab on startup
559 "browser.startup.homepage_override.mstone": "ignore",
560 # Start with a blank page by default
561 "browser.startup.page": 0,
563 # Disable browser animations
564 "toolkit.cosmeticAnimations.enabled": False,
566 # Do not warn when closing all open tabs
567 "browser.tabs.warnOnClose": False,
568 # Do not warn when closing all other open tabs
569 "browser.tabs.warnOnCloseOtherTabs": False,
570 # Do not warn when multiple tabs will be opened
571 "browser.tabs.warnOnOpen": False,
573 # Disable the UI tour
574 "browser.uitour.enabled": False,
576 # Turn off search suggestions in the location bar so as not to trigger network
578 "browser.urlbar.suggest.searches": False,
580 # Turn off the location bar search suggestions opt-in. It interferes with
581 # tests that don't expect it to be there.
582 "browser.urlbar.userMadeSearchSuggestionsChoice": True,
584 # Don't warn when exiting the browser
585 "browser.warnOnQuit": False,
587 # Disable first-run welcome page
588 "startup.homepage_welcome_url": "about:blank",
589 "startup.homepage_welcome_url.additional": "",
592 def __init__(self
, *args
, **kwargs
):
593 required_prefs
= deepcopy(DesktopInstance
.desktop_prefs
)
594 required_prefs
.update(kwargs
.get("prefs", {}))
596 super(DesktopInstance
, self
).__init
__(*args
, **kwargs
)
597 self
.required_prefs
.update(required_prefs
)
600 class ThunderbirdInstance(GeckoInstance
):
601 def __init__(self
, *args
, **kwargs
):
602 super(ThunderbirdInstance
, self
).__init
__(*args
, **kwargs
)
604 # Copied alongside in the test archive
605 from .thunderbirdinstance
import thunderbird_prefs
608 # Coming from source tree through virtualenv
609 from thunderbirdinstance
import thunderbird_prefs
611 thunderbird_prefs
= {}
612 self
.required_prefs
.update(thunderbird_prefs
)
615 class NullOutput(object):
616 def __call__(self
, line
):
621 'fennec': FennecInstance
,
622 'fxdesktop': DesktopInstance
,
623 'thunderbird': ThunderbirdInstance
,
627 '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'fennec',
628 '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'fxdesktop',
629 '{3550f703-e582-4d05-9a08-453d09bdfdc6}': 'thunderbird',