diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /browser/components/uitour/test/browser_UITour_heartbeat.js | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'browser/components/uitour/test/browser_UITour_heartbeat.js')
-rw-r--r-- | browser/components/uitour/test/browser_UITour_heartbeat.js | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/browser/components/uitour/test/browser_UITour_heartbeat.js b/browser/components/uitour/test/browser_UITour_heartbeat.js new file mode 100644 index 000000000..61be1d44b --- /dev/null +++ b/browser/components/uitour/test/browser_UITour_heartbeat.js @@ -0,0 +1,755 @@ +/* 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 }, + function({ aEventName }) { + return new Promise(resolve => { + addEventListener("mozUITourNotification", function listener(event) { + if (event.detail.event !== aEventName) { + 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 }, + function({ events }) { + let stillToReceive = events; + 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: 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; +}) |