summaryrefslogtreecommitdiffstats
path: root/toolkit/components/shopping/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/shopping/test/browser
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/shopping/test/browser')
-rw-r--r--toolkit/components/shopping/test/browser/browser.toml31
-rw-r--r--toolkit/components/shopping/test/browser/browser_shopping_ad_not_available.js47
-rw-r--r--toolkit/components/shopping/test/browser/browser_shopping_ads_test.js236
-rw-r--r--toolkit/components/shopping/test/browser/browser_shopping_integration.js277
-rw-r--r--toolkit/components/shopping/test/browser/browser_shopping_request_telemetry.js149
-rw-r--r--toolkit/components/shopping/test/browser/browser_shopping_sidebar_messages.js195
-rw-r--r--toolkit/components/shopping/test/browser/head.js106
7 files changed, 1041 insertions, 0 deletions
diff --git a/toolkit/components/shopping/test/browser/browser.toml b/toolkit/components/shopping/test/browser/browser.toml
new file mode 100644
index 0000000000..878080690d
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/browser.toml
@@ -0,0 +1,31 @@
+[DEFAULT]
+prefs = [
+ "browser.shopping.experience2023.enabled=true",
+ "browser.shopping.experience2023.optedIn=1",
+ # Disable the fakespot feature callouts to avoid interference. Individual tests
+ # that need them can re-enable them as needed.
+ "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features=false",
+ "toolkit.shopping.environment=test",
+ "browser.shopping.experience2023.autoOpen.enabled=false",
+ "browser.shopping.experience2023.autoOpen.userEnabled=true",
+]
+support-files = [
+ "head.js",
+ "../mockapis/server_helper.js",
+ "../mockapis/analysis_status.sjs",
+ "../mockapis/analysis.sjs",
+ "../mockapis/analyze.sjs",
+ "../mockapis/attribution.sjs",
+ "../mockapis/recommendations.sjs",
+ "../mockapis/reporting.sjs",
+]
+
+["browser_shopping_ad_not_available.js"]
+
+["browser_shopping_ads_test.js"]
+
+["browser_shopping_integration.js"]
+
+["browser_shopping_request_telemetry.js"]
+
+["browser_shopping_sidebar_messages.js"]
diff --git a/toolkit/components/shopping/test/browser/browser_shopping_ad_not_available.js b/toolkit/components/shopping/test/browser/browser_shopping_ad_not_available.js
new file mode 100644
index 0000000000..c4b9c69277
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/browser_shopping_ad_not_available.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ["browser.shopping.experience2023.ads.enabled", true],
+ ["browser.shopping.experience2023.ads.userEnabled", true],
+ ],
+ });
+});
+
+/**
+ * Check that we send the right telemetry if no ad is available.
+ */
+add_task(async function test_no_ad_available_telemetry() {
+ await BrowserTestUtils.withNewTab(OTHER_PRODUCT_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+ Assert.ok(sidebar, "Sidebar should exist");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+
+ info("Waiting for sidebar to update.");
+ await promiseSidebarAdsUpdated(sidebar, OTHER_PRODUCT_TEST_URL);
+ // Test the lack of ad was recorded by telemetry
+ await Services.fog.testFlushAllChildren();
+ let noAdsAvailableEvents =
+ Glean.shopping.surfaceNoAdsAvailable.testGetValue();
+ Assert.equal(
+ noAdsAvailableEvents?.length,
+ 1,
+ "Should have recorded lack of ads."
+ );
+ let noAdsEvent = noAdsAvailableEvents?.[0];
+ Assert.equal(noAdsEvent?.category, "shopping");
+ Assert.equal(noAdsEvent?.name, "surface_no_ads_available");
+ });
+});
diff --git a/toolkit/components/shopping/test/browser/browser_shopping_ads_test.js b/toolkit/components/shopping/test/browser/browser_shopping_ads_test.js
new file mode 100644
index 0000000000..1061cf7fa6
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/browser_shopping_ads_test.js
@@ -0,0 +1,236 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+function recommendedAdsEventListener(eventName, sidebar) {
+ return SpecialPowers.spawn(
+ sidebar.querySelector("browser"),
+ [eventName],
+ name => {
+ let shoppingContainer =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ let adEl = shoppingContainer.recommendedAdEl;
+ return ContentTaskUtils.waitForEvent(adEl, name, false, null, true).then(
+ ev => null
+ );
+ }
+ );
+}
+
+function recommendedAdVisible(sidebar) {
+ return SpecialPowers.spawn(sidebar.querySelector("browser"), [], async () => {
+ await ContentTaskUtils.waitForCondition(() => {
+ let shoppingContainer =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ return (
+ shoppingContainer?.recommendedAdEl &&
+ ContentTaskUtils.isVisible(shoppingContainer?.recommendedAdEl)
+ );
+ });
+ });
+}
+
+add_setup(async function () {
+ await Services.fog.testFlushAllChildren();
+ Services.fog.testResetFOG();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ["browser.shopping.experience2023.ads.enabled", true],
+ ["browser.shopping.experience2023.ads.userEnabled", true],
+ ],
+ });
+});
+
+add_task(async function test_ad_attribution() {
+ await BrowserTestUtils.withNewTab(PRODUCT_TEST_URL, async browser => {
+ // Test that impression event is fired when opening sidebar
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+ Assert.ok(sidebar, "Sidebar should exist");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ let shoppingButton = document.getElementById("shopping-sidebar-button");
+ ok(
+ BrowserTestUtils.isVisible(shoppingButton),
+ "Shopping Button should be visible on a product page"
+ );
+
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL);
+ await recommendedAdVisible(sidebar);
+
+ info("Verifying product info for initial product.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+
+ // Test placement was recorded by telemetry
+ info("Verifying ad placement event.");
+ await Services.fog.testFlushAllChildren();
+ var adsPlacementEvents = Glean.shopping.surfaceAdsPlacement.testGetValue();
+ Assert.equal(adsPlacementEvents.length, 1, "should have recorded an event");
+ Assert.equal(adsPlacementEvents[0].category, "shopping");
+ Assert.equal(adsPlacementEvents[0].name, "surface_ads_placement");
+
+ let impressionEvent = recommendedAdsEventListener("AdImpression", sidebar);
+
+ info("Waiting for ad impression event.");
+ await impressionEvent;
+ Assert.ok(true, "Got ad impression event");
+
+ // Test the impression was recorded by telemetry
+ await Services.fog.testFlushAllChildren();
+ var adsImpressionEvents =
+ Glean.shopping.surfaceAdsImpression.testGetValue();
+ Assert.equal(
+ adsImpressionEvents.length,
+ 1,
+ "should have recorded an event"
+ );
+ Assert.equal(adsImpressionEvents[0].category, "shopping");
+ Assert.equal(adsImpressionEvents[0].name, "surface_ads_impression");
+
+ //
+ // Test that impression event is fired after switching to a tab that was
+ // opened in the background
+
+ let tab = BrowserTestUtils.addTab(gBrowser, PRODUCT_TEST_URL);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let tabSidebar = gBrowser
+ .getPanel(tab.linkedBrowser)
+ .querySelector("shopping-sidebar");
+ Assert.ok(tabSidebar, "Sidebar should exist");
+
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(tabSidebar, PRODUCT_TEST_URL);
+ await recommendedAdVisible(tabSidebar);
+
+ // Need to wait the impression timeout to confirm that no impression event
+ // has been dispatched
+ // Bug 1859029 should update this to use sinon fake timers instead of using
+ // setTimeout
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 2000));
+
+ let hasImpressed = await SpecialPowers.spawn(
+ tabSidebar.querySelector("browser"),
+ [],
+ () => {
+ let shoppingContainer =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ let adEl = shoppingContainer.recommendedAdEl;
+ return adEl.hasImpressed;
+ }
+ );
+ Assert.ok(!hasImpressed, "We haven't seend the ad yet");
+
+ impressionEvent = recommendedAdsEventListener("AdImpression", tabSidebar);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ await recommendedAdVisible(tabSidebar);
+
+ info("Waiting for ad impression event.");
+ await impressionEvent;
+ Assert.ok(true, "Got ad impression event");
+
+ //
+ // Test that the impression event is fired after opening foreground tab,
+ // switching away and the event is not fired, then switching back and the
+ // event does fire
+
+ gBrowser.removeTab(tab);
+
+ tab = BrowserTestUtils.addTab(gBrowser, PRODUCT_TEST_URL);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ tabSidebar = gBrowser
+ .getPanel(tab.linkedBrowser)
+ .querySelector("shopping-sidebar");
+ Assert.ok(tabSidebar, "Sidebar should exist");
+
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(tabSidebar, PRODUCT_TEST_URL);
+ await recommendedAdVisible(tabSidebar);
+
+ // Switch to new sidebar tab
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ // switch back to original tab
+ await BrowserTestUtils.switchTab(
+ gBrowser,
+ gBrowser.getTabForBrowser(browser)
+ );
+
+ // Need to wait the impression timeout to confirm that no impression event
+ // has been dispatched
+ // Bug 1859029 should update this to use sinon fake timers instead of using
+ // setTimeout
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 2000));
+
+ hasImpressed = await SpecialPowers.spawn(
+ tabSidebar.querySelector("browser"),
+ [],
+ () => {
+ let shoppingContainer =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ let adEl = shoppingContainer.recommendedAdEl;
+ return adEl.hasImpressed;
+ }
+ );
+ Assert.ok(!hasImpressed, "We haven't seend the ad yet");
+
+ impressionEvent = recommendedAdsEventListener("AdImpression", tabSidebar);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ await recommendedAdVisible(tabSidebar);
+
+ info("Waiting for ad impression event.");
+ await impressionEvent;
+ Assert.ok(true, "Got ad impression event");
+
+ gBrowser.removeTab(tab);
+
+ //
+ // Test ad clicked event
+
+ let adOpenedTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ PRODUCT_TEST_URL,
+ true
+ );
+
+ let clickedEvent = recommendedAdsEventListener("AdClicked", sidebar);
+ await SpecialPowers.spawn(sidebar.querySelector("browser"), [], () => {
+ let shoppingContainer =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ let adEl = shoppingContainer.recommendedAdEl;
+ adEl.linkEl.click();
+ });
+
+ let adTab = await adOpenedTabPromise;
+
+ info("Waiting for ad clicked event.");
+ await clickedEvent;
+ Assert.ok(true, "Got ad clicked event");
+
+ // Test the click was recorded by telemetry
+ await Services.fog.testFlushAllChildren();
+ var adsClickedEvents = Glean.shopping.surfaceAdsClicked.testGetValue();
+ Assert.equal(adsClickedEvents.length, 1, "should have recorded a click");
+ Assert.equal(adsClickedEvents[0].category, "shopping");
+ Assert.equal(adsClickedEvents[0].name, "surface_ads_clicked");
+
+ gBrowser.removeTab(adTab);
+ Services.fog.testResetFOG();
+ });
+});
diff --git a/toolkit/components/shopping/test/browser/browser_shopping_integration.js b/toolkit/components/shopping/test/browser/browser_shopping_integration.js
new file mode 100644
index 0000000000..d16b021eb3
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/browser_shopping_integration.js
@@ -0,0 +1,277 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_sidebar_navigation() {
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(PRODUCT_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+ Assert.ok(sidebar, "Sidebar should exist");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL);
+ info("Verifying product info for initial product.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+
+ // Navigate the browser from the parent:
+ let loadedPromise = Promise.all([
+ BrowserTestUtils.browserLoaded(browser, false, OTHER_PRODUCT_TEST_URL),
+ promiseSidebarUpdated(sidebar, OTHER_PRODUCT_TEST_URL),
+ ]);
+ BrowserTestUtils.startLoadingURIString(browser, OTHER_PRODUCT_TEST_URL);
+ info("Loading another product.");
+ await loadedPromise;
+ Assert.ok(sidebar, "Sidebar should exist.");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Verifying another product.");
+ await verifyProductInfo(sidebar, {
+ productURL: OTHER_PRODUCT_TEST_URL,
+ adjustedRating: "1",
+ letterGrade: "F",
+ });
+
+ // Navigate to a non-product URL:
+ loadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "https://example.com/1"
+ );
+ BrowserTestUtils.startLoadingURIString(browser, "https://example.com/1");
+ info("Go to a non-product.");
+ await loadedPromise;
+ Assert.ok(BrowserTestUtils.isHidden(sidebar));
+
+ // Navigate using pushState:
+ loadedPromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ PRODUCT_TEST_URL
+ );
+ info("Navigate to the first product using pushState.");
+ await SpecialPowers.spawn(browser, [PRODUCT_TEST_URL], urlToUse => {
+ content.history.pushState({}, null, urlToUse);
+ });
+ info("Waiting to load first product again.");
+ await loadedPromise;
+ info("Waiting for the sidebar to have updated.");
+ await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL);
+ Assert.ok(sidebar, "Sidebar should exist");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+
+ info("Waiting to verify the first product a second time.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+
+ // Navigate to a product URL with query params:
+ loadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ PRODUCT_TEST_URL + "?th=1"
+ );
+ // Navigate to the same product, but with a th=1 added.
+ BrowserTestUtils.startLoadingURIString(browser, PRODUCT_TEST_URL + "?th=1");
+ // When just comparing URLs product info would be cleared out,
+ // but when comparing the parsed product ids, we do nothing as the product
+ // has not changed.
+ info("Verifying product has not changed before load.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+ // Wait for the page to load, but don't wait for the sidebar to update so
+ // we can be sure we still have the previous product info.
+ await loadedPromise;
+ info("Verifying product has not changed after load.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+ });
+});
+
+add_task(async function test_button_visible_when_opted_out() {
+ await BrowserTestUtils.withNewTab(
+ {
+ url: PRODUCT_TEST_URL,
+ gBrowser,
+ },
+ async browser => {
+ let shoppingBrowser = gBrowser.ownerDocument.querySelector(
+ "browser.shopping-sidebar"
+ );
+
+ let shoppingButton = document.getElementById("shopping-sidebar-button");
+
+ ok(
+ BrowserTestUtils.isVisible(shoppingButton),
+ "Shopping Button should be visible on a product page"
+ );
+
+ let sidebar = gBrowser
+ .getPanel(browser)
+ .querySelector("shopping-sidebar");
+ Assert.ok(sidebar, "Sidebar should exist");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL);
+
+ await SpecialPowers.spawn(shoppingBrowser, [], async () => {
+ let shoppingContainer =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ await shoppingContainer.updateComplete;
+ let shoppingSettings = shoppingContainer.settingsEl;
+ await shoppingSettings.updateComplete;
+
+ shoppingSettings.shoppingCardEl.detailsEl.open = true;
+ let optOutButton = shoppingSettings.optOutButtonEl;
+ optOutButton.click();
+ });
+
+ await BrowserTestUtils.waitForMutationCondition(
+ shoppingButton,
+ { attributes: false, attributeFilter: ["shoppingsidebaropen"] },
+ () => shoppingButton.getAttribute("shoppingsidebaropen")
+ );
+
+ ok(
+ !Services.prefs.getBoolPref("browser.shopping.experience2023.active"),
+ "Shopping sidebar is no longer active"
+ );
+ is(
+ Services.prefs.getIntPref("browser.shopping.experience2023.optedIn"),
+ 2,
+ "Opted out of shopping experience"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(shoppingButton),
+ "Shopping Button should be visible after opting out"
+ );
+
+ Services.prefs.setBoolPref(
+ "browser.shopping.experience2023.active",
+ true
+ );
+ Services.prefs.setIntPref("browser.shopping.experience2023.optedIn", 1);
+ }
+ );
+});
+
+add_task(async function test_sidebar_button_open_close() {
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(PRODUCT_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+ Assert.ok(sidebar, "Sidebar should exist");
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ let shoppingButton = document.getElementById("shopping-sidebar-button");
+ ok(
+ BrowserTestUtils.isVisible(shoppingButton),
+ "Shopping Button should be visible on a product page"
+ );
+
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL);
+
+ info("Verifying product info for initial product.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+
+ // close the sidebar
+ shoppingButton.click();
+ ok(BrowserTestUtils.isHidden(sidebar), "Sidebar should be hidden");
+
+ // reopen the sidebar
+ shoppingButton.click();
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL);
+
+ info("Verifying product info for has not changed.");
+ await verifyProductInfo(sidebar, {
+ productURL: PRODUCT_TEST_URL,
+ adjustedRating: "4.1",
+ letterGrade: "B",
+ });
+ });
+});
+
+add_task(async function test_no_reliability_available() {
+ Services.fog.testResetFOG();
+ await Services.fog.testFlushAllChildren();
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(NEEDS_ANALYSIS_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+
+ Assert.ok(sidebar, "Sidebar should exist");
+
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, NEEDS_ANALYSIS_TEST_URL);
+ });
+
+ await Services.fog.testFlushAllChildren();
+ var sawPageEvents =
+ Glean.shopping.surfaceNoReviewReliabilityAvailable.testGetValue();
+
+ Assert.equal(sawPageEvents.length, 1);
+ Assert.equal(sawPageEvents[0].category, "shopping");
+ Assert.equal(
+ sawPageEvents[0].name,
+ "surface_no_review_reliability_available"
+ );
+});
diff --git a/toolkit/components/shopping/test/browser/browser_shopping_request_telemetry.js b/toolkit/components/shopping/test/browser/browser_shopping_request_telemetry.js
new file mode 100644
index 0000000000..d4b5147e48
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/browser_shopping_request_telemetry.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const INVALID_RESPONSE = "https://example.com/Some-Product/dp/INVALID123";
+const SERVICE_UNAVAILABLE = "https://example.com/Some-Product/dp/HTTPERR503";
+const TOO_MANY_REQUESTS = "https://example.com/Some-Product/dp/HTTPERR429";
+
+function assertEventMatches(gleanEvents, requiredValues) {
+ if (!gleanEvents?.length) {
+ return Assert.ok(
+ !!gleanEvents?.length,
+ `${requiredValues?.name} event recorded`
+ );
+ }
+ let limitedEvent = Object.assign({}, gleanEvents[0]);
+ for (let k of Object.keys(limitedEvent)) {
+ if (!requiredValues.hasOwnProperty(k)) {
+ delete limitedEvent[k];
+ }
+ }
+ return Assert.deepEqual(limitedEvent, requiredValues);
+}
+
+async function testProductURL(url) {
+ await BrowserTestUtils.withNewTab(
+ {
+ url,
+ gBrowser,
+ },
+ async browser => {
+ let sidebar = gBrowser
+ .getPanel(browser)
+ .querySelector("shopping-sidebar");
+
+ await promiseSidebarUpdated(sidebar, url);
+ }
+ );
+}
+
+add_task(async function test_shopping_server_failure_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ Services.fog.testResetFOG();
+
+ await testProductURL(SERVICE_UNAVAILABLE);
+
+ await Services.fog.testFlushAllChildren();
+
+ const events = Glean.shoppingProduct.serverFailure.testGetValue();
+ assertEventMatches(events, {
+ category: "shopping_product",
+ name: "server_failure",
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_shopping_request_failure_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ Services.fog.testResetFOG();
+
+ await testProductURL(TOO_MANY_REQUESTS);
+
+ await Services.fog.testFlushAllChildren();
+
+ const events = Glean.shoppingProduct.requestFailure.testGetValue();
+ assertEventMatches(events, {
+ category: "shopping_product",
+ name: "request_failure",
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_shopping_request_retried_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ Services.fog.testResetFOG();
+
+ await testProductURL(SERVICE_UNAVAILABLE);
+
+ await Services.fog.testFlushAllChildren();
+
+ const events = Glean.shoppingProduct.requestRetried.testGetValue();
+ assertEventMatches(events, {
+ category: "shopping_product",
+ name: "request_retried",
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_shopping_response_invalid_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ Services.fog.testResetFOG();
+
+ await testProductURL(INVALID_RESPONSE);
+
+ await Services.fog.testFlushAllChildren();
+ const events = Glean.shoppingProduct.invalidResponse.testGetValue();
+ assertEventMatches(events, {
+ category: "shopping_product",
+ name: "invalid_response",
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_shopping_ohttp_invalid_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", "https://example.com/api"],
+ ["toolkit.shopping.ohttpConfigURL", "https://example.com/config"],
+ ],
+ });
+ Services.fog.testResetFOG();
+
+ await testProductURL(PRODUCT_TEST_URL);
+
+ await Services.fog.testFlushAllChildren();
+
+ const events = Glean.shoppingProduct.invalidOhttpConfig.testGetValue();
+ assertEventMatches(events, {
+ category: "shopping_product",
+ name: "invalid_ohttp_config",
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/shopping/test/browser/browser_shopping_sidebar_messages.js b/toolkit/components/shopping/test/browser/browser_shopping_sidebar_messages.js
new file mode 100644
index 0000000000..969b49481d
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/browser_shopping_sidebar_messages.js
@@ -0,0 +1,195 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const NOT_ENOUGH_REVIEWS_TEST_URL =
+ "https://example.com/Bad-Product/dp/N0T3NOUGHR";
+const NOT_SUPPORTED_TEST_URL = "https://example.com/Bad-Product/dp/PAG3N0TSUP";
+const UNPROCESSABLE_TEST_URL = "https://example.com/Bad-Product/dp/UNPR0C3SSA";
+
+add_task(async function test_sidebar_error() {
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(BAD_PRODUCT_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+
+ Assert.ok(sidebar, "Sidebar should exist");
+
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, BAD_PRODUCT_TEST_URL);
+
+ info("Verifying a generic error is shown.");
+ await SpecialPowers.spawn(
+ sidebar.querySelector("browser"),
+ [],
+ async prodInfo => {
+ let doc = content.document;
+ let shoppingContainer =
+ doc.querySelector("shopping-container").wrappedJSObject;
+
+ ok(
+ shoppingContainer.shoppingMessageBarEl,
+ "Got shopping-message-bar element"
+ );
+ is(
+ shoppingContainer.shoppingMessageBarEl.getAttribute("type"),
+ "generic-error",
+ "generic-error type should be correct"
+ );
+ }
+ );
+ });
+});
+
+add_task(async function test_sidebar_analysis_status_page_not_supported() {
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+
+ // Product not supported status
+ await BrowserTestUtils.withNewTab(NOT_SUPPORTED_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+
+ Assert.ok(sidebar, "Sidebar should exist");
+
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, NOT_SUPPORTED_TEST_URL);
+
+ info("Verifying a generic error is shown.");
+ await SpecialPowers.spawn(
+ sidebar.querySelector("browser"),
+ [],
+ async prodInfo => {
+ let doc = content.document;
+ let shoppingContainer =
+ doc.querySelector("shopping-container").wrappedJSObject;
+
+ ok(
+ shoppingContainer.shoppingMessageBarEl,
+ "Got shopping-message-bar element"
+ );
+ is(
+ shoppingContainer.shoppingMessageBarEl.getAttribute("type"),
+ "page-not-supported",
+ "message type should be correct"
+ );
+ }
+ );
+ });
+});
+
+add_task(async function test_sidebar_analysis_status_unprocessable() {
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+
+ // Unprocessable status
+ await BrowserTestUtils.withNewTab(UNPROCESSABLE_TEST_URL, async browser => {
+ let sidebar = gBrowser.getPanel(browser).querySelector("shopping-sidebar");
+
+ Assert.ok(sidebar, "Sidebar should exist");
+
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, UNPROCESSABLE_TEST_URL);
+
+ info("Verifying a generic error is shown.");
+ await SpecialPowers.spawn(
+ sidebar.querySelector("browser"),
+ [],
+ async prodInfo => {
+ let doc = content.document;
+ let shoppingContainer =
+ doc.querySelector("shopping-container").wrappedJSObject;
+
+ ok(
+ shoppingContainer.shoppingMessageBarEl,
+ "Got shopping-message-bar element"
+ );
+ is(
+ shoppingContainer.shoppingMessageBarEl.getAttribute("type"),
+ "generic-error",
+ "message type should be correct"
+ );
+ }
+ );
+ });
+});
+
+add_task(async function test_sidebar_analysis_status_not_enough_reviews() {
+ // Disable OHTTP for now to get this landed; we'll re-enable with proper
+ // mocking in the near future.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["toolkit.shopping.ohttpRelayURL", ""],
+ ["toolkit.shopping.ohttpConfigURL", ""],
+ ],
+ });
+ // Not enough reviews status
+ await BrowserTestUtils.withNewTab(
+ NOT_ENOUGH_REVIEWS_TEST_URL,
+ async browser => {
+ let sidebar = gBrowser
+ .getPanel(browser)
+ .querySelector("shopping-sidebar");
+
+ Assert.ok(sidebar, "Sidebar should exist");
+
+ Assert.ok(
+ BrowserTestUtils.isVisible(sidebar),
+ "Sidebar should be visible."
+ );
+ info("Waiting for sidebar to update.");
+ await promiseSidebarUpdated(sidebar, NOT_ENOUGH_REVIEWS_TEST_URL);
+
+ info("Verifying a generic error is shown.");
+ await SpecialPowers.spawn(
+ sidebar.querySelector("browser"),
+ [],
+ async prodInfo => {
+ let doc = content.document;
+ let shoppingContainer =
+ doc.querySelector("shopping-container").wrappedJSObject;
+
+ ok(
+ shoppingContainer.shoppingMessageBarEl,
+ "Got shopping-message-bar element"
+ );
+ is(
+ shoppingContainer.shoppingMessageBarEl.getAttribute("type"),
+ "not-enough-reviews",
+ "message type should be correct"
+ );
+ }
+ );
+ }
+ );
+});
diff --git a/toolkit/components/shopping/test/browser/head.js b/toolkit/components/shopping/test/browser/head.js
new file mode 100644
index 0000000000..af676bbc33
--- /dev/null
+++ b/toolkit/components/shopping/test/browser/head.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PRODUCT_TEST_URL = "https://example.com/Some-Product/dp/ABCDEFG123";
+const OTHER_PRODUCT_TEST_URL =
+ "https://example.com/Another-Product/dp/HIJKLMN456";
+const BAD_PRODUCT_TEST_URL = "https://example.com/Bad-Product/dp/0000000000";
+const NEEDS_ANALYSIS_TEST_URL = "https://example.com/Bad-Product/dp/OPQRSTU789";
+
+async function promiseSidebarUpdated(sidebar, expectedProduct) {
+ let browser = sidebar.querySelector("browser");
+ if (
+ !browser.currentURI?.equals(Services.io.newURI("about:shoppingsidebar"))
+ ) {
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "about:shoppingsidebar"
+ );
+ }
+ return SpecialPowers.spawn(browser, [expectedProduct], prod => {
+ function isProductCurrent() {
+ let actor = content.windowGlobalChild.getExistingActor("ShoppingSidebar");
+ return actor?.getProductURI()?.spec == prod;
+ }
+ if (
+ isProductCurrent() &&
+ !!content.document.querySelector("shopping-container").wrappedJSObject
+ .data
+ ) {
+ info("Product already loaded.");
+ return true;
+ }
+ info(
+ "Waiting for product to be updated. Document: " +
+ content.document.location.href
+ );
+ return ContentTaskUtils.waitForEvent(
+ content.document,
+ "Update",
+ true,
+ e => {
+ info("Sidebar updated for product: " + JSON.stringify(e.detail));
+ return !!e.detail.data && isProductCurrent();
+ },
+ true
+ ).then(e => true);
+ });
+}
+
+async function promiseSidebarAdsUpdated(sidebar, expectedProduct) {
+ await promiseSidebarUpdated(sidebar, expectedProduct);
+ let browser = sidebar.querySelector("browser");
+ return SpecialPowers.spawn(browser, [], () => {
+ let container =
+ content.document.querySelector("shopping-container").wrappedJSObject;
+ if (container.recommendationData) {
+ return true;
+ }
+ return ContentTaskUtils.waitForEvent(
+ content.document,
+ "UpdateRecommendations",
+ true,
+ null,
+ true
+ ).then(e => true);
+ });
+}
+
+async function verifyProductInfo(sidebar, expectedProductInfo) {
+ await SpecialPowers.spawn(
+ sidebar.querySelector("browser"),
+ [expectedProductInfo],
+ async prodInfo => {
+ let doc = content.document;
+ let container = doc.querySelector("shopping-container");
+ let root = container.shadowRoot;
+ let reviewReliability = root.querySelector("review-reliability");
+ // The async fetch could take some time.
+ while (!reviewReliability) {
+ info("Waiting for update.");
+ await container.updateComplete;
+ }
+ let adjustedRating = root.querySelector("adjusted-rating");
+ Assert.equal(
+ reviewReliability.getAttribute("letter"),
+ prodInfo.letterGrade,
+ `Should have correct letter grade for product ${prodInfo.id}.`
+ );
+ Assert.equal(
+ adjustedRating.getAttribute("rating"),
+ prodInfo.adjustedRating,
+ `Should have correct adjusted rating for product ${prodInfo.id}.`
+ );
+ Assert.equal(
+ content.windowGlobalChild
+ .getExistingActor("ShoppingSidebar")
+ ?.getProductURI()?.spec,
+ prodInfo.productURL,
+ `Should have correct url in the child.`
+ );
+ }
+ );
+}