/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; var gTestTab; var gContentAPI; var gContentWindow; function getHeartbeatNotification(aId, aChromeWindow = window) { let notificationBox = aChromeWindow.document.getElementById("high-priority-global-notificationbox"); // UITour.jsm prefixes the notification box ID with "heartbeat-" to prevent collisions. return notificationBox.getNotificationWithValue("heartbeat-" + aId); } /** * Simulate a click on a rating element in the Heartbeat notification. * * @param aId * The id of the notification box. * @param aScore * The score related to the rating element we want to click on. */ function simulateVote(aId, aScore) { let notification = getHeartbeatNotification(aId); let ratingContainer = notification.childNodes[0]; ok(ratingContainer, "The notification has a valid rating container."); let ratingElement = ratingContainer.getElementsByAttribute("data-score", aScore); ok(ratingElement[0], "The rating container contains the requested rating element."); ratingElement[0].click(); } /** * Simulate a click on the learn-more link. * * @param aId * The id of the notification box. */ function clickLearnMore(aId) { let notification = getHeartbeatNotification(aId); let learnMoreLabel = notification.childNodes[2]; ok(learnMoreLabel, "The notification has a valid learn more label."); learnMoreLabel.click(); } /** * Remove the notification box. * * @param aId * The id of the notification box to remove. * @param [aChromeWindow=window] * The chrome window the notification box is in. */ function cleanUpNotification(aId, aChromeWindow = window) { let notification = getHeartbeatNotification(aId, aChromeWindow); notification.close(); } /** * Check telemetry payload for proper format and expected content. * * @param aPayload * The Telemetry payload to verify * @param aFlowId * Expected value of the flowId field. * @param aExpectedFields * Array of expected fields. No other fields are allowed. */ function checkTelemetry(aPayload, aFlowId, aExpectedFields) { // Basic payload format is(aPayload.version, 1, "Telemetry ping must have heartbeat version=1"); is(aPayload.flowId, aFlowId, "Flow ID in the Telemetry ping must match"); // Check for superfluous fields let extraKeys = new Set(Object.keys(aPayload)); extraKeys.delete("version"); extraKeys.delete("flowId"); // Check for expected fields for (let field of aExpectedFields) { ok(field in aPayload, "The payload should have the field '" + field + "'"); if (field.endsWith("TS")) { let ts = aPayload[field]; ok(Number.isInteger(ts) && ts > 0, "Timestamp '" + field + "' must be a natural number"); } extraKeys.delete(field); } is(extraKeys.size, 0, "No unexpected fields in the Telemetry payload"); } /** * Waits for an UITour notification dispatched through |UITour.notify|. This should be * done with |gContentAPI.observe|. Unfortunately, in e10s, |gContentAPI.observe| doesn't * allow for multiple calls to the same callback, allowing to catch just the first * notification. * * @param aEventName * The notification name to wait for. * @return {Promise} Resolved with the data that comes with the event. */ function promiseWaitHeartbeatNotification(aEventName) { return ContentTask.spawn(gTestTab.linkedBrowser, aEventName, (aContentEventName) => { return new Promise(resolve => { addEventListener("mozUITourNotification", function listener(event) { if (event.detail.event !== aContentEventName) { return; } removeEventListener("mozUITourNotification", listener, false); resolve(event.detail.params); }, false); }); }); } /** * Waits for UITour notifications dispatched through |UITour.notify|. This works like * |promiseWaitHeartbeatNotification|, but waits for all the passed notifications to * be received before resolving. If it receives an unaccounted notification, it rejects. * * @param events * An array of expected notification names to wait for. * @return {Promise} Resolved with the data that comes with the event. Rejects with the * name of an undesired notification if received. */ function promiseWaitExpectedNotifications(events) { return ContentTask.spawn(gTestTab.linkedBrowser, events, contentEvents => { let stillToReceive = contentEvents; return new Promise((res, rej) => { addEventListener("mozUITourNotification", function listener(event) { if (stillToReceive.includes(event.detail.event)) { // Filter out the received event. stillToReceive = stillToReceive.filter(x => x !== event.detail.event); } else { removeEventListener("mozUITourNotification", listener, false); rej(event.detail.event); } // We still need to catch some notifications. Don't do anything. if (stillToReceive.length > 0) { return; } // We don't need to listen for other notifications. Resolve the promise. removeEventListener("mozUITourNotification", listener, false); res(); }, false); }); }); } function validateTimestamp(eventName, timestamp) { info("'" + eventName + "' notification received (timestamp " + timestamp.toString() + ")."); ok(Number.isFinite(timestamp), "Timestamp must be a number."); } add_task(function* test_setup() { yield setup_UITourTest(); requestLongerTimeout(2); registerCleanupFunction(() => { Services.prefs.clearUserPref("browser.uitour.surveyDuration"); }); }); /** * Check that the "stars" heartbeat UI correctly shows and closes. */ add_UITour_task(function* test_heartbeat_stars_show() { let flowId = "ui-ratefirefox-" + Math.random(); let engagementURL = "http://example.com"; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications( ["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL); // Validate the returned timestamp. let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Close the heartbeat notification. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); cleanUpNotification(flowId); data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received"); checkTelemetry(data, flowId, ["offeredTS", "closedTS"]); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Check that the heartbeat UI correctly takes optional icon URL. */ add_UITour_task(function* test_heartbeat_take_optional_icon_URL() { let flowId = "ui-ratefirefox-" + Math.random(); let engagementURL = "http://example.com"; let iconURL = "chrome://branding/content/icon48.png"; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications( ["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL, null, null, { iconURL }); // Validate the returned timestamp. let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Check the icon URL let notification = getHeartbeatNotification(flowId); is(notification.image, iconURL, "The optional icon URL is not taken correctly"); // Close the heartbeat notification. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); cleanUpNotification(flowId); data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received"); checkTelemetry(data, flowId, ["offeredTS", "closedTS"]); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Test that the heartbeat UI correctly works with null engagement URL. */ add_UITour_task(function* test_heartbeat_null_engagementURL() { let flowId = "ui-ratefirefox-" + Math.random(); let originalTabCount = gBrowser.tabs.length; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null); // Validate the returned timestamp. let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so // wait for them here. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); // The UI was just shown. We can simulate a click on a rating element (i.e., "star"). simulateVote(flowId, 2); data = yield votedPromise; validateTimestamp("Heartbeat:Voted", data.timestamp); // Validate the closing timestamp. data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened."); // Validate the data we send out. data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]); is(data.score, 2, "Checking Telemetry payload.score"); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Test that the heartbeat UI correctly works with an invalid, but non null, engagement URL. */ add_UITour_task(function* test_heartbeat_invalid_engagement_URL() { let flowId = "ui-ratefirefox-" + Math.random(); let originalTabCount = gBrowser.tabs.length; let invalidEngagementURL = "invalidEngagement"; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, invalidEngagementURL); // Validate the returned timestamp. let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so // wait for them here. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); // The UI was just shown. We can simulate a click on a rating element (i.e., "star"). simulateVote(flowId, 2); data = yield votedPromise; validateTimestamp("Heartbeat:Voted", data.timestamp); // Validate the closing timestamp. data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened."); // Validate the data we send out. data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]); is(data.score, 2, "Checking Telemetry payload.score"); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Test that the score is correctly reported. */ add_UITour_task(function* test_heartbeat_stars_vote() { const expectedScore = 4; let originalTabCount = gBrowser.tabs.length; let flowId = "ui-ratefirefox-" + Math.random(); // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null); // Validate the returned timestamp. let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so // wait for them here. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); // The UI was just shown. We can simulate a click on a rating element (i.e., "star"). simulateVote(flowId, expectedScore); data = yield votedPromise; validateTimestamp("Heartbeat:Voted", data.timestamp); is(data.score, expectedScore, "Should report a score of " + expectedScore); // Validate the closing timestamp and vote. data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened."); // Validate the data we send out. data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]); is(data.score, expectedScore, "Checking Telemetry payload.score"); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Test that the engagement page is correctly opened when voting. */ add_UITour_task(function* test_heartbeat_engagement_tab() { let engagementURL = "http://example.com"; let flowId = "ui-ratefirefox-" + Math.random(); let originalTabCount = gBrowser.tabs.length; const expectedTabCount = originalTabCount + 1; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL); // Validate the returned timestamp. let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so // wait for them here. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); // The UI was just shown. We can simulate a click on a rating element (i.e., "star"). simulateVote(flowId, 1); data = yield votedPromise; validateTimestamp("Heartbeat:Voted", data.timestamp); // Validate the closing timestamp, vote and make sure the engagement page was opened. data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); is(gBrowser.tabs.length, expectedTabCount, "Engagement URL should open in a new tab."); gBrowser.removeCurrentTab(); // Validate the data we send out. data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]); is(data.score, 1, "Checking Telemetry payload.score"); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Test that the engagement button opens the engagement URL. */ add_UITour_task(function* test_heartbeat_engagement_button() { let engagementURL = "http://example.com"; let flowId = "ui-engagewithfirefox-" + Math.random(); let originalTabCount = gBrowser.tabs.length; const expectedTabCount = originalTabCount + 1; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:Engaged", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL, null, null, { engagementButtonLabel: "Engage Me", }); let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Wait an the Engaged, Closed and Telemetry Sent events. They are fired together, so // wait for them here. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let engagedPromise = promiseWaitHeartbeatNotification("Heartbeat:Engaged"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); // Simulate user engagement. let notification = getHeartbeatNotification(flowId); is(notification.querySelectorAll(".star-x").length, 0, "No stars should be present"); // The UI was just shown. We can simulate a click on the engagement button. let engagementButton = notification.querySelector(".notification-button"); is(engagementButton.label, "Engage Me", "Check engagement button text"); engagementButton.doCommand(); data = yield engagedPromise; validateTimestamp("Heartbeat:Engaged", data.timestamp); // Validate the closing timestamp, vote and make sure the engagement page was opened. data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); is(gBrowser.tabs.length, expectedTabCount, "Engagement URL should open in a new tab."); gBrowser.removeCurrentTab(); // Validate the data we send out. data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "engagedTS", "closedTS"]); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Test that the learn more link is displayed and that the page is correctly opened when * clicking on it. */ add_UITour_task(function* test_heartbeat_learnmore() { let dummyURL = "http://example.com"; let flowId = "ui-ratefirefox-" + Math.random(); let originalTabCount = gBrowser.tabs.length; const expectedTabCount = originalTabCount + 1; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:LearnMore", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, dummyURL, "What is this?", dummyURL); let data = yield shownPromise; validateTimestamp("Heartbeat:Offered", data.timestamp); // Wait an the LearnMore, Closed and Telemetry Sent events. They are fired together, so // wait for them here. let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let learnMorePromise = promiseWaitHeartbeatNotification("Heartbeat:LearnMore"); let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); // The UI was just shown. Simulate a click on the learn more link. clickLearnMore(flowId); data = yield learnMorePromise; validateTimestamp("Heartbeat:LearnMore", data.timestamp); cleanUpNotification(flowId); // The notification was closed. data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); is(gBrowser.tabs.length, expectedTabCount, "Learn more URL should open in a new tab."); gBrowser.removeCurrentTab(); // Validate the data we send out. data = yield pingSentPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "learnMoreTS", "closedTS"]); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) add_UITour_task(function* test_invalidEngagementButtonLabel() { let engagementURL = "http://example.com"; let flowId = "invalidEngagementButtonLabel-" + Math.random(); let eventPromise = promisePageEvent(); gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL, null, null, { engagementButtonLabel: 42, }); yield eventPromise; ok(!isTourBrowser(gBrowser.selectedBrowser), "Invalid engagementButtonLabel should prevent init"); }) add_UITour_task(function* test_privateWindowsOnly_noneOpen() { let engagementURL = "http://example.com"; let flowId = "privateWindowsOnly_noneOpen-" + Math.random(); let eventPromise = promisePageEvent(); gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL, null, null, { engagementButtonLabel: "Yes!", privateWindowsOnly: true, }); yield eventPromise; ok(!isTourBrowser(gBrowser.selectedBrowser), "If there are no private windows opened, tour init should be prevented"); }) add_UITour_task(function* test_privateWindowsOnly_notMostRecent() { let engagementURL = "http://example.com"; let flowId = "notMostRecent-" + Math.random(); let privateWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true }); let mostRecentWin = yield BrowserTestUtils.openNewBrowserWindow(); let eventPromise = promisePageEvent(); gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL, null, null, { engagementButtonLabel: "Yes!", privateWindowsOnly: true, }); yield eventPromise; is(getHeartbeatNotification(flowId, window), null, "Heartbeat shouldn't appear in the default window"); is(!!getHeartbeatNotification(flowId, privateWin), true, "Heartbeat should appear in the most recent private window"); is(getHeartbeatNotification(flowId, mostRecentWin), null, "Heartbeat shouldn't appear in the most recent non-private window"); yield BrowserTestUtils.closeWindow(mostRecentWin); yield BrowserTestUtils.closeWindow(privateWin); }) add_UITour_task(function* test_privateWindowsOnly() { let engagementURL = "http://example.com"; let learnMoreURL = "http://example.org/learnmore/"; let flowId = "ui-privateWindowsOnly-" + Math.random(); let privateWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true }); yield new Promise((resolve) => { gContentAPI.observe(function(aEventName, aData) { info(aEventName + " notification received: " + JSON.stringify(aData, null, 2)); ok(false, "No heartbeat notifications should arrive for privateWindowsOnly"); }, resolve); }); gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL, "Learn More", learnMoreURL, { engagementButtonLabel: "Yes!", privateWindowsOnly: true, }); yield promisePageEvent(); ok(isTourBrowser(gBrowser.selectedBrowser), "UITour should have been init for the browser"); let notification = getHeartbeatNotification(flowId, privateWin); is(notification.querySelectorAll(".star-x").length, 0, "No stars should be present"); info("Test the learn more link."); let learnMoreLink = notification.querySelector(".text-link"); is(learnMoreLink.value, "Learn More", "Check learn more label"); let learnMoreTabPromise = BrowserTestUtils.waitForNewTab(privateWin.gBrowser, null); learnMoreLink.click(); let learnMoreTab = yield learnMoreTabPromise; is(learnMoreTab.linkedBrowser.currentURI.host, "example.org", "Check learn more site opened"); ok(PrivateBrowsingUtils.isBrowserPrivate(learnMoreTab.linkedBrowser), "Ensure the learn more tab is private"); yield BrowserTestUtils.removeTab(learnMoreTab); info("Test the engagement button's new tab."); let engagementButton = notification.querySelector(".notification-button"); is(engagementButton.label, "Yes!", "Check engagement button text"); let engagementTabPromise = BrowserTestUtils.waitForNewTab(privateWin.gBrowser, null); engagementButton.doCommand(); let engagementTab = yield engagementTabPromise; is(engagementTab.linkedBrowser.currentURI.host, "example.com", "Check enagement site opened"); ok(PrivateBrowsingUtils.isBrowserPrivate(engagementTab.linkedBrowser), "Ensure the engagement tab is private"); yield BrowserTestUtils.removeTab(engagementTab); yield BrowserTestUtils.closeWindow(privateWin); }) /** * Test that the survey closes itself after a while and submits Telemetry */ add_UITour_task(function* test_telemetry_surveyExpired() { let flowId = "survey-expired-" + Math.random(); let engagementURL = "http://example.com"; let surveyDuration = 1; // 1 second (pref is in seconds) Services.prefs.setIntPref("browser.uitour.surveyDuration", surveyDuration); // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:SurveyExpired", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL); let expiredPromise = promiseWaitHeartbeatNotification("Heartbeat:SurveyExpired"); let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let pingPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); yield Promise.all([shownPromise, expiredPromise, closedPromise]); // Validate the ping data. let data = yield pingPromise; checkTelemetry(data, flowId, ["offeredTS", "expiredTS", "closedTS"]); Services.prefs.clearUserPref("browser.uitour.surveyDuration"); // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; }) /** * Check that certain whitelisted experiment parameters get reflected in the * Telemetry ping */ add_UITour_task(function* test_telemetry_params() { let flowId = "telemetry-params-" + Math.random(); let engagementURL = "http://example.com"; let extraParams = { "surveyId": "foo", "surveyVersion": 1.5, "testing": true, "notWhitelisted": 123, }; let expectedFields = ["surveyId", "surveyVersion", "testing"]; // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener| // in UITour-lib.js, otherwise no message will get propagated. gContentAPI.observe(() => {}); let receivedExpectedPromise = promiseWaitExpectedNotifications( ["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:TelemetrySent"]); // Show the Heartbeat notification and wait for it to be displayed. let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered"); gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL, null, null, extraParams); yield shownPromise; let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed"); let pingPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent"); cleanUpNotification(flowId); // The notification was closed. let data = yield closedPromise; validateTimestamp("Heartbeat:NotificationClosed", data.timestamp); // Validate the data we send out. data = yield pingPromise; info("'Heartbeat:TelemetrySent' notification received."); checkTelemetry(data, flowId, ["offeredTS", "closedTS"].concat(expectedFields)); for (let param of expectedFields) { is(data[param], extraParams[param], "Whitelisted experiment configs should be copied into Telemetry pings"); } // This rejects whenever an unexpected notification is received. yield receivedExpectedPromise; })