diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/shopping/test/browser | |
parent | Initial commit. (diff) | |
download | firefox-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')
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.` + ); + } + ); +} |