diff options
Diffstat (limited to 'toolkit/components/telemetry/tests/integration')
9 files changed, 822 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/integration/tests/conftest.py b/toolkit/components/telemetry/tests/integration/tests/conftest.py new file mode 100644 index 0000000000..e9cbdeff08 --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/conftest.py @@ -0,0 +1,321 @@ +# 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 sys +import textwrap +import time + +import mozinstall +import pytest +from marionette_driver import By, keys +from marionette_driver.addons import Addons +from marionette_driver.errors import MarionetteException +from marionette_driver.marionette import Marionette +from marionette_driver.wait import Wait +from six import reraise +from telemetry_harness.ping_server import PingServer + +CANARY_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0" +SERVER_ROOT = "toolkit/components/telemetry/tests/marionette/harness/www" +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}$" +) + +here = os.path.abspath(os.path.dirname(__file__)) + +"""Get a build object we need to find a Firefox binary""" +try: + from mozbuild.base import MozbuildObject + + build = MozbuildObject.from_environment(cwd=here) +except ImportError: + build = None + + +@pytest.fixture(name="binary") +def fixture_binary(): + """Return a Firefox binary""" + try: + return build.get_binary_path() + except Exception: + print(str(Exception)) + + app = "firefox" + bindir = os.path.join(os.environ["PYTHON_TEST_TMP"], app) + if os.path.isdir(bindir): + try: + return mozinstall.get_binary(bindir, app_name=app) + except Exception: + print(str(Exception)) + + if "GECKO_BINARY_PATH" in os.environ: + return os.environ["GECKO_BINARY_PATH"] + + +@pytest.fixture(name="marionette") +def fixture_marionette(binary, ping_server): + """Start a marionette session with specific browser prefs""" + server_url = "{url}pings".format(url=ping_server.get_url("/")) + prefs = { + # Clear the region detection url to + # * avoid net access in tests + # * stabilize browser.search.region to avoid an extra subsession (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": 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, + } + yield Marionette(host="localhost", port=0, bin=binary, prefs=prefs) + + +@pytest.fixture(name="ping_server") +def fixture_ping_server(): + """Run a ping server on localhost on a free port assigned by the OS""" + server = PingServer(SERVER_ROOT, "http://localhost:0") + server.start() + yield server + server.stop() + + +class Browser(object): + def __init__(self, marionette, ping_server): + self.marionette = marionette + self.ping_server = ping_server + self.addon_ids = [] + + def disable_telemetry(self): + self.marionette.instance.profile.set_persistent_preferences( + {"datareporting.healthreport.uploadEnabled": False} + ) + self.marionette.set_pref("datareporting.healthreport.uploadEnabled", False) + + def enable_search_events(self): + """ + Event Telemetry categories are disabled by default. + Search events are in the "navigation" category and are not enabled by + default in builds of Firefox, so we enable them here. + """ + + script = """\ + Services.telemetry.setEventRecordingEnabled("navigation", true); + """ + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + self.marionette.execute_script(textwrap.dedent(script)) + + def enable_telemetry(self): + self.marionette.instance.profile.set_persistent_preferences( + {"datareporting.healthreport.uploadEnabled": True} + ) + self.marionette.set_pref("datareporting.healthreport.uploadEnabled", True) + + def get_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(); + """ + ) + + def get_default_search_engine(self): + """Retrieve the identifier of the default search engine. + + We found that it's required to initialize the search service before + attempting to retrieve the default search engine. Not calling init + would result in a JavaScript error (see bug 1543960 for more + information). + """ + + script = """\ + let [resolve] = arguments; + let searchService = Components.classes[ + "@mozilla.org/browser/search-service;1"] + .getService(Components.interfaces.nsISearchService); + return searchService.init().then(function () { + resolve(searchService.defaultEngine.identifier); + }); + """ + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + return self.marionette.execute_async_script(textwrap.dedent(script)) + + def install_addon(self): + 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; + 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=True) + except MarionetteException as e: + pytest.fail("{} - Error installing addon: {} - ".format(e.cause, e)) + else: + self.addon_ids.append(addon_id) + + @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 open_tab(self, focus=False): + current_tabs = self.marionette.window_handles + + try: + result = self.marionette.open(type="tab", focus=focus) + if result["type"] != "tab": + raise Exception( + "Newly opened browsing context is of type {} and not tab.".format( + result["type"] + ) + ) + except Exception: + exc_type, exc_value, exc_traceback = sys.exc_info() + reraise( + exc_type, + exc_type("Failed to trigger opening a new tab: {}".format(exc_value)), + exc_traceback, + ) + else: + Wait(self.marionette).until( + lambda mn: len(mn.window_handles) == len(current_tabs) + 1, + message="No new tab has been opened", + ) + + [new_tab] = list(set(self.marionette.window_handles) - set(current_tabs)) + + return new_tab + + def quit(self, in_app=True): + self.marionette.quit(in_app=in_app) + + def restart(self): + self.marionette.restart(clean=False, in_app=True) + + def search(self, text): + """Perform a search via the browser's URL bar.""" + + 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) + + # Wait for 0.1 seconds before proceeding to decrease the chance + # of Firefox being shut down before Telemetry is recorded + time.sleep(0.1) + + 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 start_session(self): + self.marionette.start_session() + + def wait_for_search_service_init(self): + script = """\ + let [resolve] = arguments; + let searchService = Components.classes["@mozilla.org/browser/search-service;1"] + .getService(Components.interfaces.nsISearchService); + searchService.init().then(resolve); + """ + + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + self.marionette.execute_async_script(textwrap.dedent(script)) + + +@pytest.fixture(name="browser") +def fixture_browser(marionette, ping_server): + """Return an instance of our Browser object""" + browser = Browser(marionette, ping_server) + browser.start_session() + yield browser + browser.quit() + + +class Helpers(object): + def __init__(self, ping_server, marionette): + self.ping_server = ping_server + self.marionette = marionette + + def assert_is_valid_uuid(self, value): + """Custom assertion for UUID's""" + assert value is not None + assert value != "" + assert value != CANARY_CLIENT_ID + assert re.match(UUID_PATTERN, value) is not None + + def wait_for_ping(self, action_func, ping_filter): + [ping] = self.wait_for_pings(action_func, ping_filter, 1) + return ping + + def wait_for_pings(self, action_func, ping_filter, count): + """Call the given action and wait for pings to come in and return + the `count` number of pings, that match the given filter.""" + # Keep track of the current number of pings + current_num_pings = len(self.ping_server.pings) + + # New list to store new pings that satisfy the filter + filtered_pings = [] + + def wait_func(*args, **kwargs): + # Ignore existing pings in self.ping_server.pings + new_pings = self.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 + + action_func() + + try: + Wait(self.marionette, 60).until(wait_func) + except Exception as e: + pytest.fail("Error waiting for ping: {}".format(e)) + + return filtered_pings[:count] + + +@pytest.fixture(name="helpers") +def fixture_helpers(ping_server, marionette): + """Return an instace of our helpers object""" + return Helpers(ping_server, marionette) diff --git a/toolkit/components/telemetry/tests/integration/tests/python.ini b/toolkit/components/telemetry/tests/integration/tests/python.ini new file mode 100644 index 0000000000..dca7fce97b --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/python.ini @@ -0,0 +1,8 @@ +[DEFAULT]
+subsuite = telemetry-integration-tests
+
+[test_deletion_request_ping.py]
+[test_event_ping.py]
+[test_main_tab_scalars.py]
+[test_search_counts_across_sessions.py]
+[test_subsession_management.py]
diff --git a/toolkit/components/telemetry/tests/integration/tests/resources/helloworld/helloworld.html b/toolkit/components/telemetry/tests/integration/tests/resources/helloworld/helloworld.html new file mode 100644 index 0000000000..146ad025d9 --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/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/integration/tests/resources/helloworld/manifest.json b/toolkit/components/telemetry/tests/integration/tests/resources/helloworld/manifest.json new file mode 100644 index 0000000000..14ab99fa1c --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/resources/helloworld/manifest.json @@ -0,0 +1,12 @@ +{ + "description": "Extension to be installed in Telemetry client integration 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/integration/tests/test_deletion_request_ping.py b/toolkit/components/telemetry/tests/integration/tests/test_deletion_request_ping.py new file mode 100644 index 0000000000..2eb74efe38 --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/test_deletion_request_ping.py @@ -0,0 +1,64 @@ +# 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 mozunit +from telemetry_harness.ping_filters import ( + ANY_PING, + DELETION_REQUEST_PING, + MAIN_SHUTDOWN_PING, +) + + +def test_deletion_request_ping(browser, helpers): + """Test the "deletion-request" ping behaviour across sessions""" + # Get the client_id after installing an addon + client_id = helpers.wait_for_ping(browser.install_addon, ANY_PING)["clientId"] + + # Make sure it's a valid UUID + helpers.assert_is_valid_uuid(client_id) + + # Trigger a "deletion-request" ping. + ping = helpers.wait_for_ping(browser.disable_telemetry, DELETION_REQUEST_PING) + + assert "clientId" in ping + assert "payload" in ping + assert "environment" not in ping["payload"] + + # Close Firefox cleanly. + browser.quit() + + # Start Firefox. + browser.start_session() + + # Trigger an environment change, which isn't allowed to send a ping. + browser.install_addon() + + # Ensure we've sent no pings since "optout". + assert browser.ping_server.pings[-1] == ping + + # Turn Telemetry back on. + browser.enable_telemetry() + + # Close Firefox cleanly, collecting its "main"/"shutdown" ping. + main_ping = helpers.wait_for_ping(browser.restart, MAIN_SHUTDOWN_PING) + + # Ensure the "main" ping has changed its client id. + assert "clientId" in main_ping + new_client_id = main_ping["clientId"] + helpers.assert_is_valid_uuid(new_client_id) + assert new_client_id != client_id + + # Ensure we note in the ping that the user opted in. + parent_scalars = main_ping["payload"]["processes"]["parent"]["scalars"] + + assert "telemetry.data_upload_optin" in parent_scalars + assert parent_scalars["telemetry.data_upload_optin"] is True + + # Ensure all pings sent during this test don't have the c0ffee client id. + for ping in browser.ping_server.pings: + if "clientId" in ping: + helpers.assert_is_valid_uuid(ping["clientId"]) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/integration/tests/test_event_ping.py b/toolkit/components/telemetry/tests/integration/tests/test_event_ping.py new file mode 100644 index 0000000000..9209c562eb --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/test_event_ping.py @@ -0,0 +1,50 @@ +# 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 mozunit +from telemetry_harness.ping_filters import EVENT_PING + + +def test_event_ping(browser, helpers): + """ + Barebones test for "event" ping: + Search, close Firefox, check "event" ping for search events. + """ + browser.enable_search_events() + browser.wait_for_search_service_init() + browser.search("mozilla firefox") + + payload = helpers.wait_for_ping(browser.restart, EVENT_PING)["payload"] + + assert "shutdown" == payload["reason"] + assert 0 == payload["lostEventsCount"] + assert "events" in payload + assert "parent" in payload["events"] + assert find_event(payload["events"]["parent"]) + + +def find_event(events): + """Return the first event that has the expected timestamp, category method and object""" + + for event in events: + # The event may optionally contain additonal fields + [timestamp, category, method, object_id] = event[:4] + + assert timestamp > 0 + + if category != "navigation": + continue + + if method != "search": + continue + + if object_id != "urlbar": + continue + + return True + + return False + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/integration/tests/test_main_tab_scalars.py b/toolkit/components/telemetry/tests/integration/tests/test_main_tab_scalars.py new file mode 100644 index 0000000000..8a7cdd77a4 --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/test_main_tab_scalars.py @@ -0,0 +1,33 @@ +# 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 mozunit +from telemetry_harness.ping_filters import MAIN_SHUTDOWN_PING + + +def test_main_tab_scalars(browser, helpers): + with browser.marionette.using_context(browser.marionette.CONTEXT_CHROME): + start_tab = browser.marionette.current_window_handle + tab2 = browser.open_tab(focus=True) + browser.marionette.switch_to_window(tab2) + tab3 = browser.open_tab(focus=True) + browser.marionette.switch_to_window(tab3) + browser.marionette.close() + browser.marionette.switch_to_window(tab2) + browser.marionette.close() + browser.marionette.switch_to_window(start_tab) + + ping = helpers.wait_for_ping(browser.restart, MAIN_SHUTDOWN_PING) + + assert "main" == ping["type"] + assert browser.get_client_id() == ping["clientId"] + + scalars = ping["payload"]["processes"]["parent"]["scalars"] + + assert 3 == scalars["browser.engagement.max_concurrent_tab_count"] + assert 2 == scalars["browser.engagement.tab_open_event_count"] + assert 1 == scalars["browser.engagement.max_concurrent_window_count"] + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/integration/tests/test_search_counts_across_sessions.py b/toolkit/components/telemetry/tests/integration/tests/test_search_counts_across_sessions.py new file mode 100644 index 0000000000..c2d9720b00 --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/test_search_counts_across_sessions.py @@ -0,0 +1,169 @@ +# 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 mozunit +from telemetry_harness.ping_filters import ( + MAIN_ENVIRONMENT_CHANGE_PING, + MAIN_SHUTDOWN_PING, +) + + +def test_search_counts(browser, helpers): + """Test for SEARCH_COUNTS across sessions.""" + + # Session S1, subsession 1: + # - Open browser + # - Open new tab + # - Perform search (awesome bar or search bar) + # - Restart browser in new session + search_engine = browser.get_default_search_engine() + browser.search_in_new_tab("mozilla firefox") + ping1 = helpers.wait_for_ping(browser.restart, MAIN_SHUTDOWN_PING) + + # Session S2, subsession 1: + # - Outcome 1 + # - Received a main ping P1 for previous session + # - Ping base contents: + # - clientId should be set + # - sessionId should be set + # - subsessionId should be set + # - previousSessionId should not be set + # - previousSubsessionId should not be set + # - subSessionCounter should be 1 + # - profileSubSessionCounter should be 1 + # - reason should be "shutdown" + # - Other ping contents: + # - SEARCH_COUNTS values should match performed search action + + client_id = ping1["clientId"] + helpers.assert_is_valid_uuid(client_id) + + ping1_info = ping1["payload"]["info"] + assert "shutdown" == ping1_info["reason"] + + s1_session_id = ping1_info["sessionId"] + assert s1_session_id != "" + + s1_s1_subsession_id = ping1_info["subsessionId"] + assert s1_s1_subsession_id != "" + + assert ping1_info["previousSessionId"] is None + assert ping1_info["previousSubsessionId"] is None + assert ping1_info["subsessionCounter"] == 1 + assert ping1_info["profileSubsessionCounter"] == 1 + + scalars1 = ping1["payload"]["processes"]["parent"]["scalars"] + assert "browser.engagement.window_open_event_count" not in scalars1 + assert scalars1["browser.engagement.tab_open_event_count"] == 1 + + keyed_histograms1 = ping1["payload"]["keyedHistograms"] + search_counts1 = keyed_histograms1["SEARCH_COUNTS"][ + "{}.urlbar".format(search_engine) + ] + + assert search_counts1 == { + u"range": [1, 2], + u"bucket_count": 3, + u"histogram_type": 4, + u"values": {u"1": 0, u"0": 1}, + u"sum": 1, + } + + # - Install addon + # Session S2, subsession 2: + # - Outcome 2 + # - Received a main ping P2 for previous subsession + # - Ping base contents: + # - clientId should be set to the same value + # - sessionId should be set to a new value + # - subsessionId should be set to a new value + # - previousSessionId should be set to P1s sessionId value + # - previousSubsessionId should be set to P1s subsessionId value + # - subSessionCounter should be 1 + # - profileSubSessionCounter should be 2 + # - reason should be "environment-change" + # - Other ping contents: + # - SEARCH_COUNTS values should not be in P2 + # - Verify that there should be no listing for tab scalar as we started a new + # session + + ping2 = helpers.wait_for_ping(browser.install_addon, MAIN_ENVIRONMENT_CHANGE_PING) + + assert client_id == ping2["clientId"] + + ping2_info = ping2["payload"]["info"] + assert ping2_info["reason"] == "environment-change" + + s2_session_id = ping2_info["sessionId"] + assert s2_session_id != s1_session_id + + s2_s1_subsession_id = ping2_info["subsessionId"] + assert s2_s1_subsession_id != s1_s1_subsession_id + + assert ping2_info["previousSessionId"] == s1_session_id + assert ping2_info["previousSubsessionId"] == s1_s1_subsession_id + assert ping2_info["subsessionCounter"] == 1 + assert ping2_info["profileSubsessionCounter"] == 2 + + scalars2 = ping2["payload"]["processes"]["parent"]["scalars"] + assert "browser.engagement.window_open_event_count" not in scalars2 + assert "browser.engagement.tab_open_event_count" not in scalars2 + + keyed_histograms2 = ping2["payload"]["keyedHistograms"] + assert "SEARCH_COUNTS" not in keyed_histograms2 + + # - Perform Search + # - Restart Browser + + browser.search("mozilla telemetry") + browser.search("python unittest") + browser.search("python pytest") + + ping3 = helpers.wait_for_ping(browser.restart, MAIN_SHUTDOWN_PING) + + # Session S3, subsession 1: + # - Outcome 3 + # - Received a main ping P3 for session 2, subsession 1 + # - Ping base contents: + # - clientId should be set to the same value + # - sessionId should be set to P2s sessionId value + # - subsessionId should be set to a new value + # - previousSessionId should be set to P1s sessionId value + # - previousSubsessionId should be set to P2s subsessionId value + # - subSessionCounter should be 2 + # - profileSubSessionCounter should be 3 + # - reason should be "shutdown" + # - Other ping contents: + # - SEARCH_COUNTS values should be set per above search + + assert client_id == ping3["clientId"] + + ping3_info = ping3["payload"]["info"] + assert ping3_info["reason"] == "shutdown" + assert ping3_info["sessionId"] == s2_session_id + + s2_s2_subsession_id = ping3_info["subsessionId"] + assert s2_s2_subsession_id != s1_s1_subsession_id + assert ping3_info["previousSessionId"] == s1_session_id + assert ping3_info["previousSubsessionId"] == s2_s1_subsession_id + assert ping3_info["subsessionCounter"] == 2 + assert ping3_info["profileSubsessionCounter"] == 3 + + scalars3 = ping3["payload"]["processes"]["parent"]["scalars"] + assert "browser.engagement.window_open_event_count" not in scalars3 + + keyed_histograms3 = ping3["payload"]["keyedHistograms"] + search_counts3 = keyed_histograms3["SEARCH_COUNTS"][ + "{}.urlbar".format(search_engine) + ] + assert search_counts3 == { + u"range": [1, 2], + u"bucket_count": 3, + u"histogram_type": 4, + u"values": {u"1": 0, u"0": 3}, + u"sum": 3, + } + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/telemetry/tests/integration/tests/test_subsession_management.py b/toolkit/components/telemetry/tests/integration/tests/test_subsession_management.py new file mode 100644 index 0000000000..b8697ed98b --- /dev/null +++ b/toolkit/components/telemetry/tests/integration/tests/test_subsession_management.py @@ -0,0 +1,147 @@ +# 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 mozunit +from telemetry_harness.ping_filters import ( + MAIN_ENVIRONMENT_CHANGE_PING, + MAIN_SHUTDOWN_PING, +) + + +def test_subsession_management(browser, helpers): + """Test for Firefox Telemetry subsession management.""" + + # Session S1, subsession 1 + # Actions: + # 1. Open browser + # 2. Open a new tab + # 3. Restart browser in new session + + with browser.new_tab(): + # If Firefox Telemetry is working correctly, this will + # be sufficient to record a tab open event. + pass + + ping1 = helpers.wait_for_ping(browser.restart, MAIN_SHUTDOWN_PING) + + # Session S2, subsession 1 + # Outcome 1: + # Received a main ping P1 for previous session + # - Ping base contents: + # - clientId should be a valid UUID + # - reason should be "shutdown" + # - sessionId should be set + # - subsessionId should be set + # - previousSessionId should not be set + # - previousSubsessionId should not be set + # - subSessionCounter should be 1 + # - profileSubSessionCounter should be 1 + # - Other ping contents: + # - tab_open_event_count in scalars + + client_id = ping1["clientId"] + helpers.assert_is_valid_uuid(client_id) + + ping1_info = ping1["payload"]["info"] + assert ping1_info["reason"] == "shutdown" + + s1_session_id = ping1_info["sessionId"] + assert s1_session_id != "" + + s1_s1_subsession_id = ping1_info["subsessionId"] + assert s1_s1_subsession_id != "" + assert ping1_info["previousSessionId"] is None + assert ping1_info["previousSubsessionId"] is None + assert ping1_info["subsessionCounter"] == 1 + assert ping1_info["profileSubsessionCounter"] == 1 + + scalars1 = ping1["payload"]["processes"]["parent"]["scalars"] + assert "browser.engagement.window_open_event_count" not in scalars1 + assert scalars1["browser.engagement.tab_open_event_count"] == 1 + + # Actions: + # 1. Install addon + + ping2 = helpers.wait_for_ping(browser.install_addon, MAIN_ENVIRONMENT_CHANGE_PING) + + [addon_id] = browser.addon_ids # Store the addon ID for verifying ping3 later + + # Session S2, subsession 2 + # Outcome 2: + # Received a main ping P2 for previous subsession + # - Ping base contents: + # - clientId should be set to the same value + # - sessionId should be set to a new value + # - subsessionId should be set to a new value + # - previousSessionId should be set to P1s sessionId value + # - previousSubsessionId should be set to P1s subsessionId value + # - subSessionCounter should be 1 + # - profileSubSessionCounter should be 2 + # - reason should be "environment-change" + # - Other ping contents: + # - tab_open_event_count not in scalars + + assert ping2["clientId"] == client_id + + ping2_info = ping2["payload"]["info"] + assert ping2_info["reason"] == "environment-change" + + s2_session_id = ping2_info["sessionId"] + assert s2_session_id != s1_session_id + + s2_s1_subsession_id = ping2_info["subsessionId"] + assert s2_s1_subsession_id != s1_s1_subsession_id + assert ping2_info["previousSessionId"] == s1_session_id + assert ping2_info["previousSubsessionId"] == s1_s1_subsession_id + assert ping2_info["subsessionCounter"] == 1 + assert ping2_info["profileSubsessionCounter"] == 2 + + scalars2 = ping2["payload"]["processes"]["parent"]["scalars"] + assert "browser.engagement.window_open_event_count" not in scalars2 + assert "browser.engagement.tab_open_event_count" not in scalars2 + + # Actions + # 1. Restart browser in new session + + ping3 = helpers.wait_for_ping(browser.restart, MAIN_SHUTDOWN_PING) + + # Session S3, subsession 1 + # Outcome 3: + # Received a main ping P3 for session 2, subsession 2 + # - Ping base contents: + # - clientId should be set to the same value + # - sessionId should be set to P2s sessionId value + # - subsessionId should be set to a new value + # - previousSessionId should be set to P1s sessionId value + # - previousSubsessionId should be set to P2s subsessionId value + # - subSessionCounter should be 2 + # - profileSubSessionCounter should be 3 + # - reason should be "shutdown" + # - Other ping contents: + # - addon ID in activeAddons in environment + + assert ping3["clientId"] == client_id + + ping3_info = ping3["payload"]["info"] + assert ping3_info["reason"] == "shutdown" + + assert ping3_info["sessionId"] == s2_session_id + + s2_s2_subsession_id = ping3_info["subsessionId"] + assert s2_s2_subsession_id != s1_s1_subsession_id + assert s2_s2_subsession_id != s2_s1_subsession_id + assert ping3_info["previousSessionId"] == s1_session_id + assert ping3_info["previousSubsessionId"] == s2_s1_subsession_id + assert ping3_info["subsessionCounter"] == 2 + assert ping3_info["profileSubsessionCounter"] == 3 + + scalars3 = ping3["payload"]["processes"]["parent"]["scalars"] + assert "browser.engagement.window_open_event_count" not in scalars3 + assert "browser.engagement.tab_open_event_count" not in scalars3 + + active_addons = ping3["environment"]["addons"]["activeAddons"] + assert addon_id in active_addons + + +if __name__ == "__main__": + mozunit.main() |