summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js')
-rw-r--r--browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js668
1 files changed, 668 insertions, 0 deletions
diff --git a/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js b/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
new file mode 100644
index 0000000000..5376c8bf60
--- /dev/null
+++ b/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
@@ -0,0 +1,668 @@
+"use strict";
+
+const { ExperimentFakes } = ChromeUtils.importESModule(
+ "resource://testing-common/NimbusTestUtils.sys.mjs"
+);
+
+const { AboutWelcomeTelemetry } = ChromeUtils.import(
+ "resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm"
+);
+
+const BASE_SCREEN_CONTENT = {
+ title: "Step 1",
+ primary_button: {
+ label: "Next",
+ action: {
+ navigate: true,
+ },
+ },
+ secondary_button: {
+ label: "link",
+ },
+};
+
+const makeTestContent = (id, contentAdditions) => {
+ return {
+ id,
+ content: Object.assign({}, BASE_SCREEN_CONTENT, contentAdditions),
+ };
+};
+
+async function openAboutWelcome(json) {
+ if (json) {
+ await setAboutWelcomeMultiStage(json);
+ }
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:welcome",
+ true
+ );
+ registerCleanupFunction(() => {
+ BrowserTestUtils.removeTab(tab);
+ });
+ return tab.linkedBrowser;
+}
+
+async function testAboutWelcomeLogoFor(logo = {}) {
+ info(`Testing logo: ${JSON.stringify(logo)}`);
+
+ let screens = [makeTestContent("TEST_LOGO_SELECTION_STEP", { logo })];
+
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: { enabled: true, screens },
+ });
+
+ let browser = await openAboutWelcome(JSON.stringify(screens));
+
+ let expected = [
+ `.brand-logo[src="${
+ logo.imageURL ?? "chrome://branding/content/about-logo.svg"
+ }"][alt="${logo.alt ?? ""}"]${logo.height ? `[style*="height"]` : ""}${
+ logo.alt ? "" : `[role="presentation"]`
+ }`,
+ ];
+ let unexpected = [];
+ if (!logo.height) {
+ unexpected.push(`.brand-logo[style*="height"]`);
+ }
+ if (logo.alt) {
+ unexpected.push(`.brand-logo[role="presentation"]`);
+ }
+ (logo.darkModeImageURL ? expected : unexpected).push(
+ `.logo-container source[media="(prefers-color-scheme: dark)"]${
+ logo.darkModeImageURL ? `[srcset="${logo.darkModeImageURL}"]` : ""
+ }`
+ );
+ (logo.reducedMotionImageURL ? expected : unexpected).push(
+ `.logo-container source[media="(prefers-reduced-motion: reduce)"]${
+ logo.reducedMotionImageURL
+ ? `[srcset="${logo.reducedMotionImageURL}"]`
+ : ""
+ }`
+ );
+ (logo.darkModeReducedMotionImageURL ? expected : unexpected).push(
+ `.logo-container source[media="(prefers-color-scheme: dark) and (prefers-reduced-motion: reduce)"]${
+ logo.darkModeReducedMotionImageURL
+ ? `[srcset="${logo.darkModeReducedMotionImageURL}"]`
+ : ""
+ }`
+ );
+ await test_screen_content(
+ browser,
+ "renders screen with passed logo",
+ expected,
+ unexpected
+ );
+
+ await doExperimentCleanup();
+ browser.closeBrowser();
+}
+
+/**
+ * Test rendering a screen in about welcome with decorative noodles
+ */
+add_task(async function test_aboutwelcome_with_noodles() {
+ const TEST_NOODLE_CONTENT = makeTestContent("TEST_NOODLE_STEP", {
+ has_noodles: true,
+ });
+ const TEST_NOODLE_JSON = JSON.stringify([TEST_NOODLE_CONTENT]);
+ let browser = await openAboutWelcome(TEST_NOODLE_JSON);
+
+ await test_screen_content(
+ browser,
+ "renders screen with noodles",
+ // Expected selectors:
+ [
+ "main.TEST_NOODLE_STEP[pos='center']",
+ "div.noodle.purple-C",
+ "div.noodle.orange-L",
+ "div.noodle.outline-L",
+ "div.noodle.yellow-circle",
+ ]
+ );
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a customized logo
+ */
+add_task(async function test_aboutwelcome_with_customized_logo() {
+ const TEST_LOGO_URL = "chrome://branding/content/icon64.png";
+ const TEST_LOGO_HEIGHT = "50px";
+ const TEST_LOGO_CONTENT = makeTestContent("TEST_LOGO_STEP", {
+ logo: {
+ height: TEST_LOGO_HEIGHT,
+ imageURL: TEST_LOGO_URL,
+ },
+ });
+ const TEST_LOGO_JSON = JSON.stringify([TEST_LOGO_CONTENT]);
+ let browser = await openAboutWelcome(TEST_LOGO_JSON);
+
+ await test_screen_content(
+ browser,
+ "renders screen with customized logo",
+ // Expected selectors:
+ ["main.TEST_LOGO_STEP[pos='center']", `.brand-logo[src="${TEST_LOGO_URL}"]`]
+ );
+
+ // Ensure logo has custom height
+ await test_element_styles(
+ browser,
+ ".brand-logo",
+ // Expected styles:
+ {
+ // Override default text-link styles
+ height: TEST_LOGO_HEIGHT,
+ }
+ );
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with empty logo used for padding
+ */
+add_task(async function test_aboutwelcome_with_empty_logo_spacing() {
+ const TEST_LOGO_HEIGHT = "50px";
+ const TEST_LOGO_CONTENT = makeTestContent("TEST_LOGO_STEP", {
+ logo: {
+ height: TEST_LOGO_HEIGHT,
+ imageURL: "none",
+ },
+ });
+ const TEST_LOGO_JSON = JSON.stringify([TEST_LOGO_CONTENT]);
+ let browser = await openAboutWelcome(TEST_LOGO_JSON);
+
+ await test_screen_content(
+ browser,
+ "renders screen with empty logo element",
+ // Expected selectors:
+ ["main.TEST_LOGO_STEP[pos='center']", ".brand-logo[src='none']"]
+ );
+
+ // Ensure logo has custom height
+ await test_element_styles(
+ browser,
+ ".brand-logo",
+ // Expected styles:
+ {
+ // Override default text-link styles
+ height: TEST_LOGO_HEIGHT,
+ }
+ );
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a title with custom styles.
+ */
+add_task(async function test_aboutwelcome_with_title_styles() {
+ const TEST_TITLE_STYLE_CONTENT = makeTestContent("TEST_TITLE_STYLE_STEP", {
+ title: {
+ fontSize: "36px",
+ fontWeight: 276,
+ letterSpacing: 0,
+ raw: "test",
+ },
+ title_style: "fancy shine",
+ });
+
+ const TEST_TITLE_STYLE_JSON = JSON.stringify([TEST_TITLE_STYLE_CONTENT]);
+ let browser = await openAboutWelcome(TEST_TITLE_STYLE_JSON);
+
+ await test_screen_content(
+ browser,
+ "renders screen with customized title style",
+ // Expected selectors:
+ [`div.welcome-text.fancy.shine`]
+ );
+
+ await test_element_styles(
+ browser,
+ "#mainContentHeader",
+ // Expected styles:
+ {
+ "font-weight": "276",
+ "font-size": "36px",
+ animation: "50s linear 0s infinite normal none running shine",
+ "letter-spacing": "normal",
+ }
+ );
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with an image for the dialog window's background
+ */
+add_task(async function test_aboutwelcome_with_background() {
+ const BACKGROUND_URL =
+ "chrome://activity-stream/content/data/content/assets/confetti.svg";
+ const TEST_BACKGROUND_CONTENT = makeTestContent("TEST_BACKGROUND_STEP", {
+ background: `url(${BACKGROUND_URL}) no-repeat center/cover`,
+ });
+
+ const TEST_BACKGROUND_JSON = JSON.stringify([TEST_BACKGROUND_CONTENT]);
+ let browser = await openAboutWelcome(TEST_BACKGROUND_JSON);
+
+ await test_screen_content(
+ browser,
+ "renders screen with dialog background image",
+ // Expected selectors:
+ [`div.main-content[style*='${BACKGROUND_URL}'`]
+ );
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a dismiss button
+ */
+add_task(async function test_aboutwelcome_dismiss_button() {
+ let browser = await openAboutWelcome(
+ JSON.stringify(
+ // Use 2 screens to test that the message is dismissed, not navigated
+ [1, 2].map(i =>
+ makeTestContent(`TEST_DISMISS_STEP_${i}`, {
+ dismiss_button: { action: { dismiss: true } },
+ })
+ )
+ )
+ );
+
+ // Click dismiss button
+ await onButtonClick(browser, "button.dismiss-button");
+
+ // Wait for about:home to load
+ await BrowserTestUtils.browserLoaded(browser, false, "about:home");
+ is(browser.currentURI.spec, "about:home", "about:home loaded");
+
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with the "split" position
+ */
+add_task(async function test_aboutwelcome_split_position() {
+ const TEST_SPLIT_STEP = makeTestContent("TEST_SPLIT_STEP", {
+ position: "split",
+ hero_text: "hero test",
+ });
+
+ const TEST_SPLIT_JSON = JSON.stringify([TEST_SPLIT_STEP]);
+ let browser = await openAboutWelcome(TEST_SPLIT_JSON);
+
+ await test_screen_content(
+ browser,
+ "renders screen secondary section containing hero text",
+ // Expected selectors:
+ [`main.screen[pos="split"]`, `.section-secondary`, `.message-text h1`]
+ );
+
+ // Ensure secondary section has split template styling
+ await test_element_styles(
+ browser,
+ "main.screen .section-secondary",
+ // Expected styles:
+ {
+ display: "flex",
+ margin: "auto 0px auto auto",
+ }
+ );
+
+ // Ensure secondary action has button styling
+ await test_element_styles(
+ browser,
+ ".action-buttons .secondary-cta .secondary",
+ // Expected styles:
+ {
+ // Override default text-link styles
+ "background-color": "rgba(21, 20, 26, 0.07)",
+ color: "rgb(21, 20, 26)",
+ }
+ );
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a URL value and default color for backdrop
+ */
+add_task(async function test_aboutwelcome_with_url_backdrop() {
+ const TEST_BACKDROP_URL = `url("chrome://activity-stream/content/data/content/assets/confetti.svg")`;
+ const TEST_BACKDROP_VALUE = `#212121 ${TEST_BACKDROP_URL} center/cover no-repeat fixed`;
+ const TEST_URL_BACKDROP_CONTENT = makeTestContent("TEST_URL_BACKDROP_STEP");
+
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: {
+ enabled: true,
+ backdrop: TEST_BACKDROP_VALUE,
+ screens: [TEST_URL_BACKDROP_CONTENT],
+ },
+ });
+ let browser = await openAboutWelcome();
+
+ await test_screen_content(
+ browser,
+ "renders screen with background image",
+ // Expected selectors:
+ [`div.outer-wrapper.onboardingContainer[style*='${TEST_BACKDROP_URL}']`]
+ );
+ await doExperimentCleanup();
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a color name for backdrop
+ */
+add_task(async function test_aboutwelcome_with_color_backdrop() {
+ const TEST_BACKDROP_COLOR = "transparent";
+ const TEST_BACKDROP_COLOR_CONTENT = makeTestContent(
+ "TEST_COLOR_NAME_BACKDROP_STEP"
+ );
+
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: {
+ enabled: true,
+ backdrop: TEST_BACKDROP_COLOR,
+ screens: [TEST_BACKDROP_COLOR_CONTENT],
+ },
+ });
+ let browser = await openAboutWelcome();
+
+ await test_screen_content(
+ browser,
+ "renders screen with background color",
+ // Expected selectors:
+ [`div.outer-wrapper.onboardingContainer[style*='${TEST_BACKDROP_COLOR}']`]
+ );
+ await doExperimentCleanup();
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a text color override
+ */
+add_task(async function test_aboutwelcome_with_text_color_override() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Override the system color scheme to dark
+ ["ui.systemUsesDarkTheme", 1],
+ ],
+ });
+
+ let screens = [];
+ // we need at least two screens to test the step indicator
+ for (let i = 0; i < 2; i++) {
+ screens.push(
+ makeTestContent("TEST_TEXT_COLOR_OVERRIDE_STEP", {
+ text_color: "dark",
+ background: "white",
+ })
+ );
+ }
+
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: {
+ enabled: true,
+ screens,
+ },
+ });
+ let browser = await openAboutWelcome(JSON.stringify(screens));
+
+ await test_screen_content(
+ browser,
+ "renders screen with dark text",
+ // Expected selectors:
+ [`main.screen.dark-text`, `.indicator.current`, `.indicator:not(.current)`],
+ // Unexpected selectors:
+ [`main.screen.light-text`]
+ );
+
+ // Ensure title inherits light text color
+ await test_element_styles(
+ browser,
+ "#mainContentHeader",
+ // Expected styles:
+ {
+ color: "rgb(21, 20, 26)",
+ }
+ );
+
+ // Ensure next step indicator inherits light color
+ await test_element_styles(
+ browser,
+ ".indicator:not(.current)",
+ // Expected styles:
+ {
+ color: "rgb(251, 251, 254)",
+ }
+ );
+
+ await doExperimentCleanup();
+ await SpecialPowers.popPrefEnv();
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with a "progress bar" style step indicator
+ */
+add_task(async function test_aboutwelcome_with_progress_bar() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["ui.systemUsesDarkTheme", 0],
+ ["ui.prefersReducedMotion", 0],
+ ],
+ });
+ let screens = [];
+ // we need at least three screens to test the progress bar styling
+ for (let i = 0; i < 3; i++) {
+ screens.push(
+ makeTestContent(`TEST_MR_PROGRESS_BAR_${i + 1}`, {
+ position: "split",
+ progress_bar: true,
+ primary_button: {
+ label: "next",
+ action: {
+ navigate: true,
+ },
+ },
+ })
+ );
+ }
+
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: {
+ enabled: true,
+ screens,
+ },
+ });
+ let browser = await openAboutWelcome(JSON.stringify(screens));
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ const progressBar = await ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(".progress-bar")
+ );
+ const indicator = await ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(".indicator")
+ );
+ // Progress bar should have a gray background.
+ is(
+ content.window.getComputedStyle(progressBar)["background-color"],
+ "rgba(21, 20, 26, 0.25)",
+ "Correct progress bar background"
+ );
+
+ const indicatorStyles = content.window.getComputedStyle(indicator);
+ for (let [key, val] of Object.entries({
+ // The filled "completed" element should have
+ // `background-color: var(--checkbox-checked-bgcolor);`
+ "background-color": "rgb(0, 97, 224)",
+ // Base progress bar step styles.
+ height: "6px",
+ "margin-inline": "-1px",
+ "padding-block": "0px",
+ })) {
+ is(indicatorStyles[key], val, `Correct indicator ${key} style`);
+ }
+ const indicatorX = indicator.getBoundingClientRect().x;
+ content.document.querySelector("button.primary").click();
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ content.document.querySelector(".indicator")?.getBoundingClientRect()
+ .x > indicatorX,
+ "Indicator should have grown"
+ );
+ });
+
+ await doExperimentCleanup();
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a message with session history updates disabled
+ */
+add_task(async function test_aboutwelcome_history_updates_disabled() {
+ let screens = [];
+ // we need at least two screens to test the history state
+ for (let i = 1; i < 3; i++) {
+ screens.push(makeTestContent(`TEST_PUSH_STATE_STEP_${i}`));
+ }
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: {
+ enabled: true,
+ disableHistoryUpdates: true,
+ screens,
+ },
+ });
+ let browser = await openAboutWelcome(JSON.stringify(screens));
+
+ let startHistoryLength = await SpecialPowers.spawn(browser, [], () => {
+ return content.window.history.length;
+ });
+ // Advance to second screen
+ await onButtonClick(browser, "button.primary");
+ let endHistoryLength = await SpecialPowers.spawn(browser, [], async () => {
+ // Ensure next screen has rendered
+ await ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(".TEST_PUSH_STATE_STEP_2")
+ );
+ return content.window.history.length;
+ });
+
+ ok(
+ startHistoryLength === endHistoryLength,
+ "No entries added to the session's history stack with history updates disabled"
+ );
+
+ await doExperimentCleanup();
+ browser.closeBrowser();
+});
+
+/**
+ * Test rendering a screen with different logos depending on reduced motion and
+ * color scheme preferences
+ */
+add_task(async function test_aboutwelcome_logo_selection() {
+ // Test a screen config that includes every logo parameter
+ await testAboutWelcomeLogoFor({
+ imageURL: "chrome://branding/content/icon16.png",
+ darkModeImageURL: "chrome://branding/content/icon32.png",
+ reducedMotionImageURL: "chrome://branding/content/icon64.png",
+ darkModeReducedMotionImageURL: "chrome://branding/content/icon128.png",
+ alt: "TEST_LOGO_SELECTION_ALT",
+ height: "16px",
+ });
+ // Test a screen config with no animated/static logos
+ await testAboutWelcomeLogoFor({
+ imageURL: "chrome://branding/content/icon16.png",
+ darkModeImageURL: "chrome://branding/content/icon32.png",
+ });
+ // Test a screen config with no dark mode logos
+ await testAboutWelcomeLogoFor({
+ imageURL: "chrome://branding/content/icon16.png",
+ reducedMotionImageURL: "chrome://branding/content/icon64.png",
+ });
+ // Test a screen config that includes only the default logo
+ await testAboutWelcomeLogoFor({
+ imageURL: "chrome://branding/content/icon16.png",
+ });
+ // Test a screen config with no logos
+ await testAboutWelcomeLogoFor();
+});
+
+/**
+ * Test rendering a message that starts on a specific screen
+ */
+add_task(async function test_aboutwelcome_start_screen_configured() {
+ let startScreen = 1;
+ let screens = [];
+ // we need at least two screens to test
+ for (let i = 1; i < 3; i++) {
+ screens.push(makeTestContent(`TEST_START_STEP_${i}`));
+ }
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "aboutwelcome",
+ value: {
+ enabled: true,
+ startScreen,
+ screens,
+ },
+ });
+
+ let sandbox = sinon.createSandbox();
+ let spy = sandbox.spy(AboutWelcomeTelemetry.prototype, "sendTelemetry");
+
+ let browser = await openAboutWelcome(JSON.stringify(screens));
+
+ let secondScreenShown = await SpecialPowers.spawn(browser, [], async () => {
+ // Ensure screen has rendered
+ await ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(".TEST_START_STEP_2")
+ );
+ return true;
+ });
+
+ ok(
+ secondScreenShown,
+ `Starts on second screen when configured with startScreen index equal to ${startScreen}`
+ );
+ // Wait for screen elements to render before checking impression pings
+ await test_screen_content(
+ browser,
+ "renders second screen elements",
+ // Expected selectors:
+ [`main.screen`, "div.secondary-cta"]
+ );
+
+ let expectedTelemetry = sinon.match({
+ event: "IMPRESSION",
+ message_id: `MR_WELCOME_DEFAULT_${startScreen}_TEST_START_STEP_${
+ startScreen + 1
+ }_${screens.map(({ id }) => id?.split("_")[1]?.[0]).join("")}`,
+ });
+ if (spy.calledWith(expectedTelemetry)) {
+ ok(
+ true,
+ "Impression events have the correct message id with start screen configured"
+ );
+ } else if (spy.called) {
+ ok(
+ false,
+ `Wrong telemetry sent: ${JSON.stringify(
+ spy.getCalls().map(c => c.args[0]),
+ null,
+ 2
+ )}`
+ );
+ } else {
+ ok(false, "No telemetry sent");
+ }
+
+ await doExperimentCleanup();
+ browser.closeBrowser();
+ sandbox.restore();
+});