"use strict"; const { ExperimentAPI } = ChromeUtils.importESModule( "resource://nimbus/ExperimentAPI.sys.mjs" ); const { ExperimentFakes } = ChromeUtils.importESModule( "resource://testing-common/NimbusTestUtils.sys.mjs" ); const { TelemetryTestUtils } = ChromeUtils.importESModule( "resource://testing-common/TelemetryTestUtils.sys.mjs" ); const TEST_PROTON_CONTENT = [ { id: "AW_STEP1", content: { title: "Step 1", primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "link", }, secondary_button_top: { label: "link top", action: { type: "SHOW_FIREFOX_ACCOUNTS", data: { entrypoint: "test" }, }, }, help_text: { text: "Here's some sample help text", }, has_noodles: true, }, }, { id: "AW_STEP2", content: { title: "Step 2", primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "link", }, has_noodles: true, }, }, { id: "AW_STEP3", content: { title: "Step 3", tiles: { type: "theme", action: { theme: "", }, data: [ { theme: "automatic", label: "theme-1", tooltip: "test-tooltip", }, { theme: "dark", label: "theme-2", }, ], }, primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "Import", action: { type: "SHOW_MIGRATION_WIZARD", data: { source: "chrome" }, }, }, has_noodles: true, }, }, { id: "AW_STEP4", content: { title: "Step 4", primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "link", }, has_noodles: true, }, }, ]; /** * Test the zero onboarding using ExperimentAPI */ add_task(async function test_multistage_zeroOnboarding_experimentAPI() { await setAboutWelcomePref(true); await ExperimentAPI.ready(); let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ featureId: "aboutwelcome", value: { enabled: false }, }); let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:welcome", true ); registerCleanupFunction(() => { BrowserTestUtils.removeTab(tab); }); const browser = tab.linkedBrowser; await test_screen_content( browser, "Opens new tab", // Expected selectors: ["div.search-wrapper", "body.activity-stream"], // Unexpected selectors: ["div.onboardingContainer", "main.AW_STEP1"] ); await doExperimentCleanup(); }); /** * Test the multistage welcome UI with test content theme as first screen */ add_task(async function test_multistage_aboutwelcome_experimentAPI() { const TEST_CONTENT = [ { id: "AW_STEP1", content: { title: "Step 1", tiles: { type: "theme", action: { theme: "", }, data: [ { theme: "automatic", label: "theme-1", tooltip: "test-tooltip", }, { theme: "dark", label: "theme-2", }, ], }, primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "link", }, secondary_button_top: { label: "link top", action: { type: "SHOW_FIREFOX_ACCOUNTS", data: { entrypoint: "test" }, }, }, has_noodles: true, }, }, { id: "AW_STEP2", content: { zap: true, title: "Step 2 test", primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "link", }, has_noodles: true, }, }, { id: "AW_STEP3", content: { logo: {}, title: "Step 3", primary_button: { label: "Next", action: { navigate: true, }, }, secondary_button: { label: "Import", action: { type: "SHOW_MIGRATION_WIZARD", data: { source: "chrome" }, }, }, has_noodles: true, }, }, ]; const sandbox = sinon.createSandbox(); NimbusFeatures.aboutwelcome._didSendExposureEvent = false; await setAboutWelcomePref(true); await ExperimentAPI.ready(); let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ featureId: "aboutwelcome", enabled: true, value: { id: "my-mochitest-experiment", screens: TEST_CONTENT, }, }); sandbox.spy(ExperimentAPI, "recordExposureEvent"); Services.telemetry.clearScalars(); let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:welcome", true ); const browser = tab.linkedBrowser; let aboutWelcomeActor = await getAboutWelcomeParent(browser); // Stub AboutWelcomeParent Content Message Handler sandbox.spy(aboutWelcomeActor, "onContentMessage"); registerCleanupFunction(() => { BrowserTestUtils.removeTab(tab); sandbox.restore(); }); // Test first (theme) screen for non-win7. if (!win7Content) { await test_screen_content( browser, "multistage step 1", // Expected selectors: [ "div.onboardingContainer", "main.AW_STEP1", "div.secondary-cta", "div.secondary-cta.top", "button[value='secondary_button']", "button[value='secondary_button_top']", "label.theme", "input[type='radio']", ], // Unexpected selectors: ["main.AW_STEP2", "main.AW_STEP3", "div.tiles-container.info"] ); await onButtonClick(browser, "button.primary"); const { callCount } = aboutWelcomeActor.onContentMessage; ok(callCount >= 1, `${callCount} Stub was called`); let clickCall; for (let i = 0; i < callCount; i++) { const call = aboutWelcomeActor.onContentMessage.getCall(i); info(`Call #${i}: ${call.args[0]} ${JSON.stringify(call.args[1])}`); if (call.calledWithMatch("", { event: "CLICK_BUTTON" })) { clickCall = call; } } Assert.equal( clickCall.args[0], "AWPage:TELEMETRY_EVENT", "send telemetry event" ); Assert.equal( clickCall.args[1].message_id, "MY-MOCHITEST-EXPERIMENT_0_AW_STEP1", "Telemetry should join id defined in feature value with screen" ); } await test_screen_content( browser, "multistage step 2", // Expected selectors: [ "div.onboardingContainer", "main.AW_STEP2", "button[value='secondary_button']", ], // Unexpected selectors: ["main.AW_STEP1", "main.AW_STEP3", "div.secondary-cta.top"] ); await onButtonClick(browser, "button.primary"); await test_screen_content( browser, "multistage step 3", // Expected selectors: [ "div.onboardingContainer", "main.AW_STEP3", "img.brand-logo", "div.welcome-text", ], // Unexpected selectors: ["main.AW_STEP1", "main.AW_STEP2"] ); await onButtonClick(browser, "button.primary"); await test_screen_content( browser, "home", // Expected selectors: ["body.activity-stream"], // Unexpected selectors: ["div.onboardingContainer"] ); Assert.equal( ExperimentAPI.recordExposureEvent.callCount, 1, "Called only once for exposure event" ); const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); TelemetryTestUtils.assertKeyedScalar( scalars, "telemetry.event_counts", "normandy#expose#nimbus_experiment", 1 ); await doExperimentCleanup(); }); /** * Test the multistage proton welcome UI using ExperimentAPI with transitions */ add_task(async function test_multistage_aboutwelcome_transitions() { const sandbox = sinon.createSandbox(); await setAboutWelcomePref(true); await ExperimentAPI.ready(); let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ featureId: "aboutwelcome", value: { id: "my-mochitest-experiment", enabled: true, screens: TEST_PROTON_CONTENT, transitions: true, }, }); let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:welcome", true ); const browser = tab.linkedBrowser; let aboutWelcomeActor = await getAboutWelcomeParent(browser); // Stub AboutWelcomeParent Content Message Handler sandbox.spy(aboutWelcomeActor, "onContentMessage"); registerCleanupFunction(() => { BrowserTestUtils.removeTab(tab); sandbox.restore(); }); await test_screen_content( browser, "multistage proton step 1", // Expected selectors: ["div.proton.transition- .screen"], // Unexpected selectors: ["div.proton.transition-out"] ); // Double click should still only transition once. await onButtonClick(browser, "button.primary"); await onButtonClick(browser, "button.primary"); await test_screen_content( browser, "multistage proton step 1 transition to 2", // Expected selectors: ["div.proton.transition-out .screen", "div.proton.transition- .screen-1"] ); await doExperimentCleanup(); }); /** * Test the multistage proton welcome UI using ExperimentAPI without transitions */ add_task(async function test_multistage_aboutwelcome_transitions_off() { const sandbox = sinon.createSandbox(); await setAboutWelcomePref(true); await ExperimentAPI.ready(); let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ featureId: "aboutwelcome", value: { id: "my-mochitest-experiment", enabled: true, screens: TEST_PROTON_CONTENT, transitions: false, }, }); let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:welcome", true ); const browser = tab.linkedBrowser; let aboutWelcomeActor = await getAboutWelcomeParent(browser); // Stub AboutWelcomeParent Content Message Handler sandbox.spy(aboutWelcomeActor, "onContentMessage"); registerCleanupFunction(() => { BrowserTestUtils.removeTab(tab); sandbox.restore(); }); await test_screen_content( browser, "multistage proton step 1", // Expected selectors: ["div.proton.transition- .screen"], // Unexpected selectors: ["div.proton.transition-out"] ); await onButtonClick(browser, "button.primary"); await test_screen_content( browser, "multistage proton step 1 no transition to 2", // Expected selectors: [], // Unexpected selectors: ["div.proton.transition-out .screen-0"] ); await doExperimentCleanup(); }); /* Test multistage custom backdrop */ add_task(async function test_multistage_aboutwelcome_backdrop() { const sandbox = sinon.createSandbox(); const TEST_BACKDROP = "blue"; const TEST_CONTENT = [ { id: "TEST_SCREEN", content: { position: "split", logo: {}, title: "test", }, }, ]; await setAboutWelcomePref(true); await ExperimentAPI.ready(); await pushPrefs(["browser.aboutwelcome.backdrop", TEST_BACKDROP]); const doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ featureId: "aboutwelcome", value: { id: "my-mochitest-experiment", screens: TEST_CONTENT, }, }); const tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:welcome", true ); const browser = tab.linkedBrowser; registerCleanupFunction(() => { BrowserTestUtils.removeTab(tab); sandbox.restore(); }); await test_screen_content( browser, "multistage step 1", // Expected selectors: [`div.outer-wrapper.onboardingContainer[style*='${TEST_BACKDROP}']`] ); await doExperimentCleanup(); }); add_task(async function test_multistage_aboutwelcome_utm_term() { const sandbox = sinon.createSandbox(); const TEST_CONTENT = [ { id: "TEST_SCREEN", content: { position: "split", logo: {}, title: "test", secondary_button_top: { label: "test", style: "link", action: { type: "OPEN_URL", data: { args: "https://www.mozilla.org/", }, }, }, }, }, ]; await setAboutWelcomePref(true); await ExperimentAPI.ready(); const doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ featureId: "aboutwelcome", value: { id: "my-mochitest-experiment", screens: TEST_CONTENT, UTMTerm: "test", }, }); const tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:welcome", true ); const browser = tab.linkedBrowser; const aboutWelcomeActor = await getAboutWelcomeParent(browser); sandbox.stub(aboutWelcomeActor, "onContentMessage"); await onButtonClick(browser, "button[value='secondary_button_top']"); let actionCall; const { callCount } = aboutWelcomeActor.onContentMessage; for (let i = 0; i < callCount; i++) { const call = aboutWelcomeActor.onContentMessage.getCall(i); info(`Call #${i}: ${call.args[0]} ${JSON.stringify(call.args[1])}`); if (call.calledWithMatch("SPECIAL")) { actionCall = call; } } Assert.equal( actionCall.args[1].data.args, "https://www.mozilla.org/?utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=test-screen", "UTMTerm set in mobile" ); registerCleanupFunction(() => { sandbox.restore(); BrowserTestUtils.removeTab(tab); }); await doExperimentCleanup(); });