From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- .../telemetry/tests/marionette/harness/MANIFEST.in | 3 + .../tests/marionette/harness/requirements.txt | 2 + .../telemetry/tests/marionette/harness/setup.py | 48 ++++ .../harness/telemetry_harness/__init__.py | 3 + .../harness/telemetry_harness/fog_ping_filters.py | 29 +++ .../harness/telemetry_harness/fog_ping_server.py | 77 +++++++ .../harness/telemetry_harness/fog_testcase.py | 53 +++++ .../harness/telemetry_harness/ping_filters.py | 75 +++++++ .../harness/telemetry_harness/ping_server.py | 65 ++++++ .../resources/helloworld/helloworld.html | 18 ++ .../resources/helloworld/manifest.json | 12 + .../marionette/harness/telemetry_harness/runner.py | 56 +++++ .../harness/telemetry_harness/runtests.py | 14 ++ .../harness/telemetry_harness/testcase.py | 242 +++++++++++++++++++++ 14 files changed, 697 insertions(+) create mode 100644 toolkit/components/telemetry/tests/marionette/harness/MANIFEST.in create mode 100644 toolkit/components/telemetry/tests/marionette/harness/requirements.txt create mode 100644 toolkit/components/telemetry/tests/marionette/harness/setup.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/__init__.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_filters.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_server.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_testcase.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_filters.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_server.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/helloworld.html create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/resources/helloworld/manifest.json create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runtests.py create mode 100644 toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py (limited to 'toolkit/components/telemetry/tests/marionette/harness') 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..a95e794fe0 --- /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..f650eb18ed --- /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 setup, find_packages + +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..9db0f5a72f --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_ping_filters.py @@ -0,0 +1,29 @@ +# 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_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..7d84b40d77 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_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 + +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") + 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") == "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..fad2781a80 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/fog_testcase.py @@ -0,0 +1,53 @@ +# 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_present = self.marionette.execute_script( + "return AppConstants.MOZ_GLEAN;" + ) + + if not fog_present: + # 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 present in AppConstants.MOZ_GLEAN builds.") + + 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, + } + ) + + 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..d05c265504 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/ping_server.py @@ -0,0 +1,65 @@ +# 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 + +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") + 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") == "gzip": + request_data = zlib.decompress(request_data, zlib.MAX_WBITS | 16) + + ping_data = json.loads(request_data) + + # 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/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 @@ + + + + + + + +

Hello World!

+ + 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..d95b17c03c --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py @@ -0,0 +1,56 @@ +# 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 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", {}) + + # 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, + } + ) + + 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..9b4b8872c9 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runtests.py @@ -0,0 +1,14 @@ +# 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 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..b4646e6565 --- /dev/null +++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py @@ -0,0 +1,242 @@ +# 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_driver import By, keys +from marionette_harness import MarionetteTestCase +from marionette_harness.runner.mixins.window_manager import WindowManagerMixin + +from 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) + + self.ping_server = PingServer( + self.testvars["server_root"], self.testvars["server_url"] + ) + + def setUp(self, *args, **kwargs): + """Set up the test case and start the ping server.""" + 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") + + self.ping_server.start() + + 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 search(self, text): + """Perform a search via the browser's URL bar.""" + + # Reload newtab to prevent urlbar from not accepting correct input + with self.marionette.using_context(self.marionette.CONTEXT_CONTENT): + self.marionette.navigate("about:newtab") + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + self.marionette.execute_script("gURLBar.select();") + urlbar = self.marionette.find_element(By.ID, "urlbar-input") + urlbar.send_keys(keys.Keys.DELETE) + urlbar.send_keys(text + keys.Keys.ENTER) + # This script checks that the search terms used for searching + # appear in the URL when the page loads. + script = """\ + let location = document.location.toString() + function validate(term){ + return location.includes(term) + } + return arguments[0].every(validate) + """ + # Wait for search page to load + with self.marionette.using_context(self.marionette.CONTEXT_CONTENT): + Wait(self.marionette, 30, 0.5).until( + lambda driver: driver.execute_script( + script, script_args=[text.split()] + ), + message="Search terms not found, maybe the page didn't load?", + ) + + def search_in_new_tab(self, text): + """Open a new tab and perform a search via the browser's URL bar, + then close the new tab.""" + + with self.new_tab(): + self.search(text) + + 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.message)) + + 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(in_app=True) + + def install_addon(self): + """Install a minimal addon and add its ID to self.addon_ids.""" + + resources_dir = os.path.join(os.path.dirname(__file__), "resources") + addon_path = os.path.abspath(os.path.join(resources_dir, "helloworld")) + + try: + # Ensure the Environment has init'd so the installed addon + # triggers an "environment-change" ping. + script = """\ + let [resolve] = arguments; + Cu.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=True) + except MarionetteException as e: + self.fail("{} - Error installing addon: {} - ".format(e.cause, e.message)) + 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( + 'Cu.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( + 'Cu.import("resource://gre/modules/TelemetryController.jsm");' + "return TelemetryController.getCurrentPingData(true);" + ) + return ping_data[u"payload"][u"info"][u"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(clean=True) -- cgit v1.2.3