diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/telemetry/tests/marionette/harness | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/telemetry/tests/marionette/harness')
15 files changed, 729 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/marionette/harness/MANIFEST.in b/toolkit/components/telemetry/tests/marionette/harness/MANIFEST.in new file mode 100644 index 0000000000..e24a6b1ba6 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/MANIFEST.in @@ -0,0 +1,3 @@ +exclude MANIFEST.in +include requirements.txt +recursive-include telemetry_harness/resources *
\ No newline at end of file diff --git a/toolkit/components/telemetry/tests/marionette/harness/requirements.txt b/toolkit/components/telemetry/tests/marionette/harness/requirements.txt new file mode 100644 index 0000000000..c15861abd7 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/requirements.txt @@ -0,0 +1,2 @@ +marionette-harness >= 4.0.0 +requests>=2.11.1
\ No newline at end of file diff --git a/toolkit/components/telemetry/tests/marionette/harness/setup.py b/toolkit/components/telemetry/tests/marionette/harness/setup.py new file mode 100644 index 0000000000..38d4d35662 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/setup.py @@ -0,0 +1,48 @@ +# 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 os + +from setuptools import find_packages, setup + +PACKAGE_VERSION = "0.1" + +THIS_DIR = os.path.dirname(os.path.realpath(__name__)) + + +def read(*parts): + with open(os.path.join(THIS_DIR, *parts)) as f: + return f.read() + + +setup( + name="telemetry-harness", + version=PACKAGE_VERSION, + description=( + "Custom Marionette runner classes and entry scripts for " + "Telemetry specific Marionette tests." + ), + classifiers=[ + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords="mozilla", + author="Firefox Test Engineering Team", + author_email="firefox-test-engineering@mozilla.org", + url="https://developer.mozilla.org/en-US/docs/Mozilla/QA/telemetry_harness", + license="MPL 2.0", + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=read("requirements.txt").splitlines(), + entry_points=""" + [console_scripts] + telemetry-harness = telemetry_harness.runtests:cli + """, +) diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/__init__.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/__init__.py new file mode 100644 index 0000000000..6fbe8159b2 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/__init__.py @@ -0,0 +1,3 @@ +# 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/. diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_filters.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_filters.py new file mode 100644 index 0000000000..d0d006fc0a --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_filters.py @@ -0,0 +1,31 @@ +# 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/. + + +class FOGPingFilter(object): + """Ping filter that accepts any FOG pings.""" + + def __call__(self, ping): + return True + + +class FOGDocTypePingFilter(FOGPingFilter): + """Ping filter that accepts FOG pings that match the doc-type.""" + + def __init__(self, doc_type): + super(FOGDocTypePingFilter, self).__init__() + self.doc_type = doc_type + + def __call__(self, ping): + if not super(FOGDocTypePingFilter, self).__call__(ping): + return False + + # Verify that the given ping was submitted to the URL for the doc_type + return ping["request_url"]["doc_type"] == self.doc_type + + +FOG_BACKGROUND_UPDATE_PING = FOGDocTypePingFilter("background-update") +FOG_BASELINE_PING = FOGDocTypePingFilter("baseline") +FOG_DELETION_REQUEST_PING = FOGDocTypePingFilter("deletion-request") +FOG_ONE_PING_ONLY_PING = FOGDocTypePingFilter("one-ping-only") diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_server.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_server.py new file mode 100644 index 0000000000..9ad2f60a59 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_server.py @@ -0,0 +1,86 @@ +# 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 json +import zlib + +import wptserve.logger +from marionette_harness.runner import httpd +from mozlog import get_default_logger +from six.moves.urllib import parse as urlparse + + +class FOGPingServer(object): + """HTTP server for receiving Firefox on Glean pings.""" + + def __init__(self, server_root, url): + self._logger = get_default_logger(component="fog_ping_server") + + # Ensure we see logs from wptserve + try: + wptserve.logger.set_logger(self._logger) + except Exception: + # Raises if already been set + pass + + self.pings = [] + + @httpd.handlers.handler + def pings_handler(request, response): + """Handler for HTTP requests to the ping server.""" + request_data = request.body + + if request.headers.get("Content-Encoding") == b"gzip": + request_data = zlib.decompress(request_data, zlib.MAX_WBITS | 16) + + request_url = request.route_match.copy() + + self.pings.append( + { + "request_url": request_url, + "payload": json.loads(request_data), + "debug_tag": request.headers.get("X-Debug-ID"), + } + ) + + self._logger.info( + "pings_handler received '{}' ping".format(request_url["doc_type"]) + ) + + status_code = 200 + content = "OK" + headers = [ + ("Content-Type", "text/plain"), + ("Content-Length", len(content)), + ] + + return (status_code, headers, content) + + self._httpd = httpd.FixtureServer(server_root, url=url) + + # See https://mozilla.github.io/glean/book/user/pings/index.html#ping-submission + self._httpd.router.register( + "POST", + "/submit/{application_id}/{doc_type}/{glean_schema_version}/{document_id}", + pings_handler, + ) + + @property + def url(self): + """Return the URL for the running HTTP FixtureServer.""" + return self._httpd.get_url("/") + + @property + def port(self): + """Return the port for the running HTTP FixtureServer.""" + parse_result = urlparse.urlparse(self.url) + return parse_result.port + + def start(self): + """Start the HTTP FixtureServer.""" + return self._httpd.start() + + def stop(self): + """Stop the HTTP FixtureServer.""" + return self._httpd.stop() diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_testcase.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_testcase.py new file mode 100644 index 0000000000..c5bc54e9d2 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_testcase.py @@ -0,0 +1,63 @@ +# 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 mozlog + +from telemetry_harness.fog_ping_server import FOGPingServer +from telemetry_harness.testcase import TelemetryTestCase + + +class FOGTestCase(TelemetryTestCase): + """Base testcase class for project FOG.""" + + def __init__(self, *args, **kwargs): + """Initialize the test case and create a ping server.""" + super(FOGTestCase, self).__init__(*args, **kwargs) + self._logger = mozlog.get_default_logger(component="FOGTestCase") + + def setUp(self, *args, **kwargs): + """Set up the test case and create a FOG ping server. + + This test is skipped if the build doesn't support FOG. + """ + super(FOGTestCase, self).setUp(*args, **kwargs) + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + fog_android = self.marionette.execute_script( + "return AppConstants.MOZ_GLEAN_ANDROID;" + ) + + if fog_android: + # Before we skip this test, we need to quit marionette and the ping + # server created in TelemetryTestCase by running tearDown + super(FOGTestCase, self).tearDown(*args, **kwargs) + self.skipTest("FOG is only initialized when not in an Android build.") + + self.fog_ping_server = FOGPingServer( + self.testvars["server_root"], "http://localhost:0" + ) + self.fog_ping_server.start() + + self._logger.info( + "Submitting to FOG ping server at {}".format(self.fog_ping_server.url) + ) + + self.marionette.enforce_gecko_prefs( + { + "telemetry.fog.test.localhost_port": self.fog_ping_server.port, + # Enable FOG logging. 5 means "Verbose". See + # https://firefox-source-docs.mozilla.org/xpcom/logging.html + # for details. + "logging.config.clear_on_startup": False, + "logging.config.sync": True, + "logging.fog::*": 5, + "logging.fog_control::*": 5, + "logging.glean::*": 5, + "logging.glean_core::*": 5, + } + ) + + def tearDown(self, *args, **kwargs): + super(FOGTestCase, self).tearDown(*args, **kwargs) + self.fog_ping_server.stop() diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_filters.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_filters.py new file mode 100644 index 0000000000..6e003b25d5 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_filters.py @@ -0,0 +1,75 @@ +# 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/. + + +class PingFilter(object): + """Ping filter that accepts any pings.""" + + def __call__(self, ping): + return True + + +class DeletionRequestPingFilter(PingFilter): + """Ping filter that accepts deletion-request pings.""" + + def __call__(self, ping): + if not super(DeletionRequestPingFilter, self).__call__(ping): + return False + + return ping["type"] == "deletion-request" + + +class EventPingFilter(PingFilter): + """Ping filter that accepts event pings.""" + + def __call__(self, ping): + if not super(EventPingFilter, self).__call__(ping): + return False + + return ping["type"] == "event" + + +class FirstShutdownPingFilter(PingFilter): + """Ping filter that accepts first-shutdown pings.""" + + def __call__(self, ping): + if not super(FirstShutdownPingFilter, self).__call__(ping): + return False + + return ping["type"] == "first-shutdown" + + +class MainPingFilter(PingFilter): + """Ping filter that accepts main pings.""" + + def __call__(self, ping): + if not super(MainPingFilter, self).__call__(ping): + return False + + return ping["type"] == "main" + + +class MainPingReasonFilter(MainPingFilter): + """Ping filter that accepts main pings that match the + specified reason. + """ + + def __init__(self, reason): + super(MainPingReasonFilter, self).__init__() + self.reason = reason + + def __call__(self, ping): + if not super(MainPingReasonFilter, self).__call__(ping): + return False + + return ping["payload"]["info"]["reason"] == self.reason + + +ANY_PING = PingFilter() +DELETION_REQUEST_PING = DeletionRequestPingFilter() +EVENT_PING = EventPingFilter() +FIRST_SHUTDOWN_PING = FirstShutdownPingFilter() +MAIN_PING = MainPingFilter() +MAIN_SHUTDOWN_PING = MainPingReasonFilter("shutdown") +MAIN_ENVIRONMENT_CHANGE_PING = MainPingReasonFilter("environment-change") diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_server.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_server.py new file mode 100644 index 0000000000..86487672c7 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_server.py @@ -0,0 +1,77 @@ +# 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 json +import zlib + +import mozlog +import wptserve.logger +from marionette_harness.runner import httpd + + +class PingServer(object): + """HTTP server for receiving Firefox Client Telemetry pings.""" + + def __init__(self, server_root, url): + self._logger = mozlog.get_default_logger(component="pingserver") + + # Ensure we see logs from wptserve + try: + wptserve.logger.set_logger(self._logger) + except Exception: + # Raises if already been set + pass + self.pings = [] + + @httpd.handlers.handler + def pings_handler(request, response): + """Handler for HTTP requests to the ping server.""" + request_data = request.body + + if request.headers.get("Content-Encoding") == b"gzip": + request_data = zlib.decompress(request_data, zlib.MAX_WBITS | 16) + + ping_data = json.loads(request_data) + + # We don't have another channel to hand, so stuff this in the ping payload. + ping_data["X-PingSender-Version"] = request.headers.get( + "X-PingSender-Version", b"" + ) + + # Store JSON data to self.pings to be used by wait_for_pings() + self.pings.append(ping_data) + + ping_type = ping_data["type"] + + log_message = "pings_handler received '{}' ping".format(ping_type) + + if ping_type == "main": + ping_reason = ping_data["payload"]["info"]["reason"] + log_message = "{} with reason '{}'".format(log_message, ping_reason) + + self._logger.info(log_message) + + status_code = 200 + content = "OK" + headers = [ + ("Content-Type", "text/plain"), + ("Content-Length", len(content)), + ] + + return (status_code, headers, content) + + self._httpd = httpd.FixtureServer(server_root, url=url) + self._httpd.router.register("POST", "/pings*", pings_handler) + + def get_url(self, *args, **kwargs): + """Return a URL from the HTTP server.""" + return self._httpd.get_url(*args, **kwargs) + + def start(self): + """Start the HTTP server.""" + return self._httpd.start() + + def stop(self): + """Stop the HTTP server.""" + return self._httpd.stop() diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/dynamic_addon/dynamic-probe-telemetry-extension-signed.xpi b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/dynamic_addon/dynamic-probe-telemetry-extension-signed.xpi Binary files differnew file mode 100644 index 0000000000..f399815c10 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/dynamic_addon/dynamic-probe-telemetry-extension-signed.xpi diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/helloworld.html b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/helloworld.html new file mode 100644 index 0000000000..146ad025d9 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/helloworld.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <style> + body { + background-color: lightgrey; + } + p { + font-size: 25px; + padding: 25px 50px; + } + </style> + </head> + <body> + <p>Hello World!</p> + </body> +</html> diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/manifest.json b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/manifest.json new file mode 100644 index 0000000000..0e35d8a2e3 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/manifest.json @@ -0,0 +1,12 @@ +{ + "description": "Extension to be installed in telemetry-tests-client tests.", + "manifest_version": 2, + "name": "helloworld", + "version": "1.0", + "homepage_url": "https://hg.mozilla.org/mozilla-central/", + "browser_action": { + "browser_style": true, + "default_title": "Hello World", + "default_popup": "helloworld.html" + } +} diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py new file mode 100644 index 0000000000..37a91023ce --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py @@ -0,0 +1,63 @@ +# 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 marionette_harness import BaseMarionetteTestRunner + +from telemetry_harness.testcase import TelemetryTestCase + +SERVER_URL = "http://localhost:8000" + + +class TelemetryTestRunner(BaseMarionetteTestRunner): + """TestRunner for the telemetry-tests-client suite.""" + + def __init__(self, **kwargs): + """Set test variables and preferences specific to Firefox client + telemetry. + """ + + # Select the appropriate GeckoInstance + kwargs["app"] = "fxdesktop" + + prefs = kwargs.pop("prefs", {}) + + prefs["fission.autostart"] = True + if kwargs["disable_fission"]: + prefs["fission.autostart"] = False + + # Set Firefox Client Telemetry specific preferences + prefs.update( + { + # Clear the region detection url to + # * avoid net access in tests + # * stabilize browser.search.region to avoid extra subsessions (bug 1579840#c40) + "browser.region.network.url": "", + # Disable smart sizing because it changes prefs at startup. (bug 1547750) + "browser.cache.disk.smart_size.enabled": False, + "toolkit.telemetry.server": "{}/pings".format(SERVER_URL), + "telemetry.fog.test.localhost_port": -1, + "toolkit.telemetry.initDelay": 1, + "toolkit.telemetry.minSubsessionLength": 0, + "datareporting.healthreport.uploadEnabled": True, + "datareporting.policy.dataSubmissionEnabled": True, + "datareporting.policy.dataSubmissionPolicyBypassNotification": True, + "toolkit.telemetry.log.level": "Trace", + "toolkit.telemetry.log.dump": True, + "toolkit.telemetry.send.overrideOfficialCheck": True, + "toolkit.telemetry.testing.disableFuzzingDelay": True, + # Disable Normandy to avoid extra subsessions due to Experiment + # activation in tests (bug 1641571) + "app.normandy.enabled": False, + # Disable Normandy a little harder (bug 1608807). + # This should also disable Nimbus. + "app.shield.optoutstudies.enabled": False, + } + ) + + super(TelemetryTestRunner, self).__init__(prefs=prefs, **kwargs) + + self.testvars["server_root"] = kwargs["server_root"] + self.testvars["server_url"] = SERVER_URL + + self.test_handlers = [TelemetryTestCase] diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runtests.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runtests.py new file mode 100644 index 0000000000..4ecee669f1 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runtests.py @@ -0,0 +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 marionette_harness.runtests import cli as mn_cli + +from telemetry_harness.runner import TelemetryTestRunner + + +def cli(args=None): + mn_cli(runner_class=TelemetryTestRunner, args=args) + + +if __name__ == "__main__": + cli() diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py new file mode 100644 index 0000000000..d30fd67986 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py @@ -0,0 +1,233 @@ +# 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 contextlib +import os +import re +import textwrap + +from marionette_driver.addons import Addons +from marionette_driver.errors import MarionetteException +from marionette_driver.wait import Wait +from marionette_harness import MarionetteTestCase +from marionette_harness.runner.mixins.window_manager import WindowManagerMixin + +from telemetry_harness.ping_server import PingServer + +CANARY_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0" +UUID_PATTERN = re.compile( + r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" +) + + +class TelemetryTestCase(WindowManagerMixin, MarionetteTestCase): + def __init__(self, *args, **kwargs): + """Initialize the test case and create a ping server.""" + super(TelemetryTestCase, self).__init__(*args, **kwargs) + + def setUp(self, *args, **kwargs): + """Set up the test case and start the ping server.""" + + self.ping_server = PingServer( + self.testvars["server_root"], self.testvars["server_url"] + ) + self.ping_server.start() + + super(TelemetryTestCase, self).setUp(*args, **kwargs) + + # Store IDs of addons installed via self.install_addon() + self.addon_ids = [] + + with self.marionette.using_context(self.marionette.CONTEXT_CONTENT): + self.marionette.navigate("about:about") + + def disable_telemetry(self): + """Disable the Firefox Data Collection and Use in the current browser.""" + self.marionette.instance.profile.set_persistent_preferences( + {"datareporting.healthreport.uploadEnabled": False} + ) + self.marionette.set_pref("datareporting.healthreport.uploadEnabled", False) + + def enable_telemetry(self): + """Enable the Firefox Data Collection and Use in the current browser.""" + self.marionette.instance.profile.set_persistent_preferences( + {"datareporting.healthreport.uploadEnabled": True} + ) + self.marionette.set_pref("datareporting.healthreport.uploadEnabled", True) + + @contextlib.contextmanager + def new_tab(self): + """Perform operations in a new tab and then close the new tab.""" + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + start_tab = self.marionette.current_window_handle + new_tab = self.open_tab(focus=True) + self.marionette.switch_to_window(new_tab) + + yield + + self.marionette.close() + self.marionette.switch_to_window(start_tab) + + def navigate_in_new_tab(self, url): + """Open a new tab and navigate to the provided URL.""" + + with self.new_tab(): + with self.marionette.using_context(self.marionette.CONTEXT_CONTENT): + self.marionette.navigate(url) + + def assertIsValidUUID(self, value): + """Check if the given UUID is valid.""" + + self.assertIsNotNone(value) + self.assertNotEqual(value, "") + + # Check for client ID that is used when Telemetry upload is disabled + self.assertNotEqual(value, CANARY_CLIENT_ID, msg="UUID is CANARY CLIENT ID") + + self.assertIsNotNone( + re.match(UUID_PATTERN, value), + msg="UUID does not match regular expression", + ) + + def wait_for_pings(self, action_func, ping_filter, count, ping_server=None): + """Call the given action and wait for pings to come in and return + the `count` number of pings, that match the given filter. + """ + + if ping_server is None: + ping_server = self.ping_server + + # Keep track of the current number of pings + current_num_pings = len(ping_server.pings) + + # New list to store new pings that satisfy the filter + filtered_pings = [] + + def wait_func(*args, **kwargs): + # Ignore existing pings in ping_server.pings + new_pings = ping_server.pings[current_num_pings:] + + # Filter pings to make sure we wait for the correct ping type + filtered_pings[:] = [p for p in new_pings if ping_filter(p)] + + return len(filtered_pings) >= count + + self.logger.info( + "wait_for_pings running action '{action}'.".format( + action=action_func.__name__ + ) + ) + + # Call given action and wait for a ping + action_func() + + try: + Wait(self.marionette, 60).until(wait_func) + except Exception as e: + self.fail("Error waiting for ping: {}".format(e)) + + return filtered_pings[:count] + + def wait_for_ping(self, action_func, ping_filter, ping_server=None): + """Call wait_for_pings() with the given action_func and ping_filter and + return the first result. + """ + [ping] = self.wait_for_pings( + action_func, ping_filter, 1, ping_server=ping_server + ) + return ping + + def restart_browser(self): + """Restarts browser while maintaining the same profile.""" + return self.marionette.restart(clean=False, in_app=True) + + def start_browser(self): + """Start the browser.""" + return self.marionette.start_session() + + def quit_browser(self): + """Quit the browser.""" + return self.marionette.quit() + + def install_addon(self): + """Install a minimal addon.""" + addon_name = "helloworld" + self._install_addon(addon_name) + + def install_dynamic_addon(self): + """Install a dynamic probe addon. + + Source Code: + https://github.com/mozilla-extensions/dynamic-probe-telemetry-extension + """ + addon_name = "dynamic_addon/dynamic-probe-telemetry-extension-signed.xpi" + self._install_addon(addon_name, temp=False) + + def _install_addon(self, addon_name, temp=True): + """Logic to install addon and add its ID to self.addons.ids""" + resources_dir = os.path.join(os.path.dirname(__file__), "resources") + addon_path = os.path.abspath(os.path.join(resources_dir, addon_name)) + + try: + # Ensure the Environment has init'd so the installed addon + # triggers an "environment-change" ping. + script = """\ + let [resolve] = arguments; + const { TelemetryEnvironment } = ChromeUtils.import( + "resource://gre/modules/TelemetryEnvironment.jsm" + ); + TelemetryEnvironment.onInitialized().then(resolve); + """ + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + self.marionette.execute_async_script(textwrap.dedent(script)) + + addons = Addons(self.marionette) + addon_id = addons.install(addon_path, temp=temp) + except MarionetteException as e: + self.fail("{} - Error installing addon: {} - ".format(e.cause, e)) + else: + self.addon_ids.append(addon_id) + + def set_persistent_profile_preferences(self, preferences): + """Wrapper for setting persistent preferences on a user profile""" + return self.marionette.instance.profile.set_persistent_preferences(preferences) + + def set_preferences(self, preferences): + """Wrapper for setting persistent preferences on a user profile""" + return self.marionette.set_prefs(preferences) + + @property + def client_id(self): + """Return the ID of the current client.""" + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + return self.marionette.execute_script( + """\ + const { ClientID } = ChromeUtils.import( + "resource://gre/modules/ClientID.jsm" + ); + return ClientID.getCachedClientID(); + """ + ) + + @property + def subsession_id(self): + """Return the ID of the current subsession.""" + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + ping_data = self.marionette.execute_script( + """\ + const { TelemetryController } = ChromeUtils.import( + "resource://gre/modules/TelemetryController.jsm" + ); + return TelemetryController.getCurrentPingData(true); + """ + ) + return ping_data["payload"]["info"]["subsessionId"] + + def tearDown(self, *args, **kwargs): + """Stop the ping server and tear down the testcase.""" + super(TelemetryTestCase, self).tearDown() + self.ping_server.stop() + self.marionette.quit(in_app=False, clean=True) |