chromium/chrome/test/data/webui/chromeos/personalization_app/personalization_app_test.ts

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {DynamicColorElement, getThemeProvider, GooglePhotosAlbumsElement, GooglePhotosCollectionElement, GooglePhotosSharedAlbumDialogElement, PersonalizationRouterElement, PersonalizationThemeElement, SeaPenFeedbackElement, SeaPenFreeformElement, SeaPenImagesElement, SeaPenInputQueryElement, SeaPenPaths, SeaPenRecentWallpapersElement, SeaPenRouterElement, SeaPenSamplesElement, SeaPenTemplateQueryElement, setTransitionsEnabled, WallpaperCollectionsElement, WallpaperGridItemElement, WallpaperImagesElement} from 'chrome://personalization/js/personalization_app.js';
import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {CrIconButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import {CrInputElement} from 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import {SeaPenTemplateId} from 'chrome://resources/ash/common/sea_pen/sea_pen_generated.mojom-webui.js';
import {assertInstanceof} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
import {assertDeepEquals, assertEquals, assertFalse, assertGT, assertLE, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';

/**
 * @fileoverview E2E test suite for chrome://personalization.
 */

const ROOT_PAGE = 'chrome://personalization/';
const DEFAULT_WALLPAPER_NAME = 'Default Wallpaper';

/**
 * Wait until `func` returns a truthy value.
 * If `timeoutMs` milliseconds elapse, will reject with `message`.
 * `message` may either be a string, or function. It will be called with the
 * final value returned by `func`. Polls every `intervalMs` milliseconds.
 * Resolves with the final value of `func`.
 */
async function waitUntil<T>(
    func: () => T, message: string|((value: T) => string),
    intervalMs: number = 50,
    timeoutMs: number = 1001): Promise<NonNullable<T>> {
  const messageType = typeof message;
  if (messageType !== 'string' && messageType !== 'function') {
    throw new Error(
        `message must be a string|function but received ${messageType}`);
  }
  let rejectTimer: number|null = null;
  let pollTimer: number|null = null;
  let value: T;

  function cleanup() {
    if (rejectTimer) {
      window.clearTimeout(rejectTimer);
    }
    if (pollTimer) {
      window.clearInterval(pollTimer);
    }
  }

  return new Promise((resolve, reject) => {
    rejectTimer = window.setTimeout(() => {
      cleanup();
      const errorMessage =
          typeof message === 'function' ? message(value) : message;
      reject(new Error(errorMessage));
    }, timeoutMs);

    pollTimer = window.setInterval(() => {
      value = func();
      if (value) {
        cleanup();
        resolve(value!);
      }
    }, intervalMs);
  });
}

function getRouter(): PersonalizationRouterElement {
  return PersonalizationRouterElement.instance();
}

/** Returns an array of three numbers, representing the RGB values. */
function getBodyColorChannels() {
  return getComputedStyle(document.body)
      .backgroundColor.match(/rgb\((\d+), (\d+), (\d+)\)/)!.slice(1, 4)
      .map(x => parseInt(x, 10));
}

suite('main page', () => {
  setup(() => {
    // Disables transition animation for tests.
    setTransitionsEnabled(false);
  });

  // Tests that chrome://personalization loads the page and various contents
  // without javascript errors or a 404 or crash. Displays user preview,
  // wallpaper preview, ambient preview, and dynamic color controls.

  test('has root page content', () => {
    assertEquals(document.location.href, ROOT_PAGE);
    const userPreview = getRouter()
                            .shadowRoot?.querySelector('personalization-main')
                            ?.shadowRoot?.querySelector('user-preview');
    const wallpaperPreview =
        getRouter()
            .shadowRoot?.querySelector('personalization-main')
            ?.shadowRoot?.querySelector('wallpaper-preview');
    assertTrue(!!userPreview);
    assertTrue(!!wallpaperPreview);
  });

  test('shows theme buttons', async () => {
    const theme = getRouter()
                      .shadowRoot?.querySelector('personalization-main')
                      ?.shadowRoot?.querySelector('personalization-theme');

    const lightButton = await waitUntil(
        () => theme?.shadowRoot?.getElementById('lightMode'),
        'failed to find light button');
    assertEquals('false', lightButton.getAttribute('aria-checked'));
    const darkButton = theme?.shadowRoot?.getElementById('darkMode');
    assertTrue(!!darkButton);
    assertEquals('false', darkButton.getAttribute('aria-checked'));
    const autoButton = theme?.shadowRoot?.getElementById('autoMode');
    assertTrue(!!autoButton);
    assertEquals('true', autoButton.getAttribute('aria-checked'));
  });

  test('selects dark mode', async () => {
    const theme = getRouter()
                      .shadowRoot?.querySelector('personalization-main')
                      ?.shadowRoot?.querySelector('personalization-theme');
    const darkButton = theme?.shadowRoot?.getElementById('darkMode');
    assertTrue(!!darkButton, '#darkMode exists');

    darkButton.click();

    assertEquals('true', darkButton.getAttribute('aria-checked'));
    await waitUntil(
        () => getBodyColorChannels().every(channel => channel < 50),
        'failed to switch to dark mode');
  });

  test('selects light mode', async () => {
    const theme = getRouter()
                      .shadowRoot?.querySelector('personalization-main')
                      ?.shadowRoot?.querySelector('personalization-theme');
    const lightButton = theme?.shadowRoot?.getElementById('lightMode');
    assertTrue(!!lightButton, '#lightMode exists');

    lightButton.click();

    assertEquals('true', lightButton.getAttribute('aria-checked'));
    await waitUntil(
        () => getBodyColorChannels().every(channel => channel > 200),
        'failed to switch to light mode');
  });

  test('shows user info', async () => {
    const preview = getRouter()
                        .shadowRoot?.querySelector('personalization-main')
                        ?.shadowRoot?.querySelector('user-preview');

    const email = await waitUntil(
        () => preview?.shadowRoot?.getElementById('email'),
        'failed to find user email');
    assertEquals('fake-email', email.innerText);
    assertEquals(
        'Fake Name', preview?.shadowRoot?.getElementById('name')?.innerText);
  });
});


suite('ambient mode allowed', () => {
  setup(() => {
    // Disables transition animation for tests.
    setTransitionsEnabled(false);
  });

  test('shows ambient preview', () => {
    const preview = getRouter()
                        .shadowRoot?.querySelector('personalization-main')
                        ?.shadowRoot?.querySelector('ambient-preview-large');
    assertTrue(!!preview);
  });

  test('shows ambient subpage link', () => {
    const ambientSubpageLink =
        getRouter()
            .shadowRoot?.querySelector('personalization-main')
            ?.shadowRoot?.querySelector('ambient-preview-large')
            ?.shadowRoot?.querySelector('cr-icon-button');
    assertTrue(!!ambientSubpageLink);
  });
});


suite('ambient mode disallowed', () => {
  setup(() => {
    // Disables transition animation for tests.
    setTransitionsEnabled(false);
  });

  test('shows ambient preview', () => {
    const preview = getRouter()
                        .shadowRoot?.querySelector('personalization-main')
                        ?.shadowRoot?.querySelector('ambient-preview-large');

    assertTrue(!!preview, 'ambient-preview-large exists');
  });

  test('shows disabled ambient subpage link', () => {
    const ambientSubpageLink =
        getRouter()
            .shadowRoot?.querySelector('personalization-main')
            ?.shadowRoot?.querySelector('ambient-preview-large')
            ?.shadowRoot?.querySelector('cr-icon-button');

    assertTrue(!!ambientSubpageLink, 'ambient subpage link exists');
    assertTrue(ambientSubpageLink.disabled, 'ambient subpage link is disabled');
  });

  test('shows help link', () => {
    const helpLink = getRouter()
                         .shadowRoot?.querySelector('personalization-main')
                         ?.shadowRoot?.querySelector('ambient-preview-large')
                         ?.shadowRoot?.querySelector('cr-button')
                         ?.firstElementChild?.getAttribute('href');
    assertTrue(!!helpLink, 'help link exists');
    assertTrue(
        helpLink.includes('support.google.com'), 'help link content matches');
  });
});

suite('wallpaper subpage', () => {
  function clickWallpaperPreviewLink() {
    assertEquals(
        ROOT_PAGE, window.location.href,
        'wallpaper preview link only present on root page');
    getRouter()
        .shadowRoot?.querySelector('personalization-main')
        ?.shadowRoot?.querySelector('wallpaper-preview')
        ?.shadowRoot?.querySelector('cr-icon-button')
        ?.click();
    assertEquals(
        ROOT_PAGE + 'wallpaper', window.location.href,
        'should have navigated to wallpaper');
  }

  function getWallpaperSubpage() {
    const router = getRouter();
    assertTrue(!!router, 'personalization-router should be top level element');

    const wallpaperSubpage =
        router.shadowRoot?.querySelector('wallpaper-subpage');
    assertTrue(
        !!wallpaperSubpage,
        'wallpaper-subpage should be found under personalization-router');

    return wallpaperSubpage;
  }

  function getWallpaperSelected() {
    const subpage = getWallpaperSubpage();
    const wallpaperSelected =
        subpage.shadowRoot?.querySelector('wallpaper-selected');
    assertTrue(!!wallpaperSelected, 'wallpaper-selected should exist');
    return wallpaperSelected;
  }

  setup(async () => {
    // Reset to default state before each test to reduce order dependencies.
    await window.personalizationTestApi.reset();

    // Disables transition animation for tests.
    setTransitionsEnabled(false);

    clickWallpaperPreviewLink();
  });

  // Tests that chrome://personalization/wallpaper runs js file and that it
  // goes somewhere instead of 404ing or crashing.
  test('has wallpaper subpage url', () => {
    const title = document.querySelector('head > title');
    assertEquals('Wallpaper', (title as HTMLElement).innerText);
  });

  test('loads collections grid', () => {
    const wallpaperSubpage = getWallpaperSubpage();

    const collections =
        wallpaperSubpage.shadowRoot?.querySelector<WallpaperCollectionsElement>(
            'wallpaper-collections');
    assertTrue(
        !!collections,
        'wallpaper-collections should be found under wallpaper-subpage');

    assertFalse(
        collections.parentElement!.hidden, 'parent element should be visible');
    assertGT(
        collections.offsetWidth, 0,
        'wallpaper-collections should have visible width');
    assertGT(
        collections.offsetHeight, 0,
        'wallpaper-collections grid should have visible height');
  });

  suite('backdrop', function() {
    setup(() => {
      // Disables transition animation for tests.
      setTransitionsEnabled(false);
    });

    test('selects wallpaper', async () => {
      const wallpaperSelected = getWallpaperSelected();
      const textContainer =
          wallpaperSelected.shadowRoot?.getElementById('textContainer');
      assertTrue(!!textContainer, 'wallpaper text container exists');
      assertEquals(
          DEFAULT_WALLPAPER_NAME,
          textContainer?.querySelector('#imageTitle')?.textContent?.trim(),
          'default wallpaper is shown at first');

      const subpage = getWallpaperSubpage();
      const collections =
          subpage.shadowRoot?.querySelector('wallpaper-collections');

      const onlineTileToClick = await waitUntil(
          () =>
              Array
                  .from(collections!.shadowRoot!.querySelectorAll<
                        WallpaperGridItemElement>(
                      `wallpaper-grid-item[aria-disabled='false'][data-online]`))
                  .find(tile => tile.primaryText === 'Test Collection 2'),
          'waiting for online tile with title Test Collection 2 to load');

      onlineTileToClick.click();

      const wallpaperImages = await waitUntil(
          () => subpage.shadowRoot?.querySelector<WallpaperImagesElement>(
              'wallpaper-images'),
          'failed selecting wallpaper-images');

      assertFalse(wallpaperImages.hidden, 'wallpaper images now visible');
      assertGT(
          wallpaperImages.offsetWidth, 0,
          'wallpaper-images should have visible width');
      assertGT(
          wallpaperImages.offsetHeight, 0,
          'wallpaper-images should have visible height');

      const gridItem = await waitUntil(
          () =>
              wallpaperImages.shadowRoot
                  ?.querySelector<WallpaperGridItemElement>(
                      'wallpaper-grid-item:not([placeholder]):nth-of-type(2)'),
          'failed waiting for grid items to load');

      assertFalse(gridItem.selected!, 'wallpaper tile does not start selected');
      gridItem.click();

      const expectedImageTitle =
          'fake_attribution_fake_collection_id_2_asset_id_41_line_0';

      await waitUntil(
          () => textContainer.querySelector('#imageTitle')
                    ?.textContent?.trim() === expectedImageTitle,
          () => `failed waiting for expected image title ` +
              `${expectedImageTitle} after selecting wallpaper. ` +
              `html:\n${textContainer.outerHTML}`,
          /*intervalMs=*/ 500,
          /*timeoutMs=*/ 3001);

      assertEquals(
          'fake_attribution_fake_collection_id_2_asset_id_41_line_1',
          textContainer.querySelector('span:last-of-type')
              ?.textContent?.trim());

      assertTrue(gridItem.selected!, 'wallpaper tile is selected after click');
    });
  });

  suite('google photos', () => {
    setup(() => {
      // Disables transition animation for tests.
      setTransitionsEnabled(false);
    });

    async function openGooglePhotos(): Promise<GooglePhotosCollectionElement> {
      const subpage = getWallpaperSubpage();

      const googlePhotosTile = await waitUntil(
          () => subpage.shadowRoot?.querySelector('wallpaper-collections')
                    ?.shadowRoot?.querySelector<WallpaperGridItemElement>(
                        `wallpaper-grid-item[aria-disabled='false']` +
                        `[data-google-photos]`),
          'failed waiting for google photos tile to load');
      googlePhotosTile.click();

      return await waitUntil(
          () => subpage.shadowRoot?.querySelector('google-photos-collection'),
          'failed to find google photos collection');
    }

    async function openGooglePhotosAlbums():
        Promise<GooglePhotosAlbumsElement> {
      const googlePhotosCollection = await openGooglePhotos();
      const albumsTabButton = await waitUntil(
          () => googlePhotosCollection.shadowRoot?.getElementById('albumsTab'),
          'failed to find google photos album tab');

      albumsTabButton.click();

      return await waitUntil(
          () => googlePhotosCollection.shadowRoot?.querySelector(
              'google-photos-albums'),
          'failed to find albums');
    }

    async function openGooglePhotosSharedAlbumById(
        albumId: string, googlePhotosAlbumsPromise = openGooglePhotosAlbums()) {
      const googlePhotosAlbums = await googlePhotosAlbumsPromise;

      const ariaLabel = `${albumId} Shared`;

      const sharedAlbumToClick = await waitUntil(
          () =>
              googlePhotosAlbums.shadowRoot
                  ?.querySelector<WallpaperGridItemElement>(
                      `wallpaper-grid-item[aria-disabled='false'][aria-label='${
                          ariaLabel}']`),
          'failed to get shared album');
      sharedAlbumToClick.click();
    }

    // Returns null when the dialog is not open.
    function getSharedAlbumDialog(): GooglePhotosSharedAlbumDialogElement|null {
      const wallpaperSelected = getWallpaperSelected();
      return wallpaperSelected.shadowRoot!.querySelector(
          'google-photos-shared-album-dialog');
    }

    test('shared album sets correct query parameters', async () => {
      const googlePhotosAlbums = await openGooglePhotosAlbums();

      assertEquals(
          '', location.search,
          'location.search should be empty before selecting shared album');

      const sharedAlbumId = 'fake_google_photos_shared_album_id_1';

      await openGooglePhotosSharedAlbumById(
          sharedAlbumId, Promise.resolve(googlePhotosAlbums));

      assertDeepEquals(
          new Map([
            ['googlePhotosAlbumId', sharedAlbumId],
            ['googlePhotosAlbumIsShared', 'true'],
          ]),
          new Map(new URLSearchParams(location.search).entries()),
          'album id and is shared param should appear in location.search');
    });

    test('select shared album as daily refresh', async () => {
      const sharedAlbumId = 'fake_google_photos_shared_album_id_2';
      await openGooglePhotosSharedAlbumById(sharedAlbumId);

      const wallpaperSelected = getWallpaperSelected();
      const imageTitle =
          wallpaperSelected.shadowRoot?.getElementById('imageTitle');
      assertTrue(!!imageTitle, 'image title exists');
      assertEquals(
          DEFAULT_WALLPAPER_NAME, imageTitle.textContent?.trim(),
          'default wallpaper is shown at first');

      const dailyRefreshButton = await waitUntil(
          () => wallpaperSelected.shadowRoot?.getElementById('dailyRefresh'),
          'failed to find daily refresh button');
      dailyRefreshButton.click();

      const sharedAlbumDialog = await waitUntil(
          () => getSharedAlbumDialog(),
          'failed to select google photos shared album dialog');

      const sharedAlbumDialogAcceptButton =
          sharedAlbumDialog.shadowRoot?.getElementById('accept');
      sharedAlbumDialogAcceptButton?.click();

      const dailyRefreshRegex =
          /^Daily Refresh\: fake_google_photos_photo_id_\d$/;
      await waitUntil(
          () => dailyRefreshRegex.test(imageTitle.textContent!.trim()),
          () => `Expected Daily refresh text to match regex ` +
              `${dailyRefreshRegex.source} but received:\n` +
              `${imageTitle.outerHTML}`,
          /*intervalMs=*/ 500,
          /*timeoutMs=*/ 3001);

      assertEquals(
          null, getSharedAlbumDialog(),
          'google photos shared album dialog is gone');
    });
  });
});


suite('sea pen', () => {
  function clickWallpaperPreviewLink() {
    assertEquals(
        ROOT_PAGE, window.location.href,
        'wallpaper preview link only present on root page');
    getRouter()
        .shadowRoot?.querySelector('personalization-main')
        ?.shadowRoot?.querySelector('wallpaper-preview')
        ?.shadowRoot?.querySelector('cr-icon-button')
        ?.click();
    assertEquals(
        ROOT_PAGE + 'wallpaper', window.location.href,
        'should have navigated to wallpaper');
  }

  function getWallpaperSubpage() {
    const router = getRouter();
    assertTrue(!!router, 'personalization-router should be top level element');

    const wallpaperSubpage =
        router.shadowRoot?.querySelector('wallpaper-subpage');
    assertTrue(
        !!wallpaperSubpage,
        'wallpaper-subpage should be found under personalization-router');

    return wallpaperSubpage;
  }

  async function getSeaPenRouter(): Promise<SeaPenRouterElement> {
    const subpage = getWallpaperSubpage();
    const seaPenTile = await waitUntil(
        () => subpage.shadowRoot?.querySelector('wallpaper-collections')
                  ?.shadowRoot?.querySelector<WallpaperGridItemElement>(
                      `wallpaper-grid-item[aria-disabled='false']` +
                      `[data-sea-pen]`),
        'waiting for sea-pen-tile');
    seaPenTile.click();
    const seaPenRouter = await waitUntil(
        () => getRouter().shadowRoot?.querySelector<SeaPenRouterElement>(
            'sea-pen-router')!,
        'waiting for sea-pen-router');
    return seaPenRouter;
  }

  async function getSeaPenTemplateQuery(templateIndex: number):
      Promise<SeaPenTemplateQueryElement> {
    const seaPenRouter = SeaPenRouterElement.instance();
    const templates = await waitUntil(
        () => seaPenRouter.shadowRoot?.querySelector('sea-pen-templates')
                  ?.shadowRoot?.querySelectorAll<WallpaperGridItemElement>(
                      `wallpaper-grid-item[data-sea-pen-image]`),
        'waiting for sea-pen-tile');
    templates[templateIndex]!.click();

    return await waitUntil(
        () =>
            seaPenRouter.shadowRoot?.querySelector<SeaPenTemplateQueryElement>(
                'sea-pen-template-query')!,
        'waiting for sea-pen-template-query');
  }

  function getSeaPenTemplatePrompt(
      seaPenTemplateQuery: SeaPenTemplateQueryElement): string {
    const templateTokens =
        seaPenTemplateQuery.shadowRoot!.querySelectorAll<HTMLElement>(
            '.chip-container, .template-text');
    assertTrue(
        templateTokens.length > 0, 'template tokens should be available');

    const templateText: string[] = [];
    for (const token of templateTokens) {
      if (token.style.display === 'none') {
        continue;
      }
      if (token.classList.contains('template-text')) {
        templateText.push(token.innerText);
        continue;
      }
      const chipTextElement =
          (token as HTMLElement)
              .querySelector<HTMLElement>('sea-pen-chip-text');
      assertTrue(!!chipTextElement, 'sea-pen-chip-text should be available');
      const chipText =
          chipTextElement.shadowRoot?.getElementById('chipText')?.innerText;
      if (chipText) {
        templateText.push(chipText);
      }
    }
    return templateText.join(' ');
  }

  setup(async () => {
    // Disables transition animation for tests.
    setTransitionsEnabled(false);

    // Reset to default state before each test to reduce order dependencies.
    await window.personalizationTestApi.reset();

    clickWallpaperPreviewLink();
  });

  teardown(() => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: false});
  });

  suite('feedback', async () => {
    // At the end of this test, a feedback dialog is expected to be opened in an
    // external window.
    test(`open feedback dialog`, async () => {
      const seaPenRouter = await getSeaPenRouter();
      const seaPenTemplateQuery = await getSeaPenTemplateQuery(6);
      // Creates images.
      seaPenTemplateQuery.shadowRoot?.getElementById('searchButton')!.click();

      // Presses thumbs up feedback button.
      const seaPenImages = await waitUntil(
          () => seaPenRouter.shadowRoot?.querySelector<SeaPenImagesElement>(
              'sea-pen-images'),
          'waiting for sea-pen-images');

      const feedbacks = await waitUntil(
          () => Array.from(
              seaPenImages!.shadowRoot!.querySelectorAll<SeaPenFeedbackElement>(
                  `sea-pen-feedback`)),
          'waiting for thumbnails load');
      assertTrue(!!feedbacks, 'feedbacks should exist');

      const thumbsUpButton =
          feedbacks[0]?.shadowRoot?.getElementById('thumbsUp');
      assertTrue(!!thumbsUpButton, 'thumbsUpButton should exist');
      assertEquals(
          thumbsUpButton?.getAttribute('iron-icon'), 'cr:thumbs-up',
          'thumbsUpButton should not be filled');
      thumbsUpButton!.click();
      assertEquals(
          thumbsUpButton?.getAttribute('iron-icon'), 'cr:thumbs-up-filled',
          'thumbsUpButton should be filled');
    });
  });

  test('has selected wallpaper on root page', async () => {
    await getSeaPenRouter();

    const wallpaperSelected = await waitUntil(
        () => getRouter().shadowRoot?.getElementById('wallpaperSelected')!,
        'waiting for sea-pen-router wallpaper-selected', /*intervalMs=*/ 500,
        /*timeoutMs=*/ 3001);
    assertTrue(!!wallpaperSelected, 'wallpaper-selected should exist');
  });

  test('hides selected wallpaper on non root page', async () => {
    await getSeaPenRouter();

    let wallpaperSelected =
        getRouter().shadowRoot?.getElementById('wallpaperSelected')!;
    assertTrue(!!wallpaperSelected, 'wallpaper-selected should exist');
    assertNotEquals(getComputedStyle(wallpaperSelected).display, 'none');

    const seaPenTemplateQuery = await getSeaPenTemplateQuery(0);
    assertTrue(!!seaPenTemplateQuery, 'sea-pen-template-query should exist');

    wallpaperSelected =
        getRouter().shadowRoot?.getElementById('wallpaperSelected')!;
    assertFalse(!!wallpaperSelected, 'wallpaper-selected should not exist');
  });

  test('show more option chips', async () => {
    const seaPenRouter = await getSeaPenRouter();

    const closeIntroductionButton = await waitUntil(
        () => seaPenRouter.shadowRoot
                  ?.querySelector('sea-pen-introduction-dialog')
                  ?.shadowRoot?.querySelector<HTMLElement>('#close'),
        'wait for close button to load');
    closeIntroductionButton.click();

    const seaPenTemplateQuery = await getSeaPenTemplateQuery(6);
    assertTrue(!!seaPenTemplateQuery, 'Characters template should show up');

    const seaPenChips = await waitUntil(
        () => seaPenTemplateQuery.shadowRoot?.querySelectorAll<HTMLDivElement>(
            '#template > .chip-container > .chip-text'),
        'waiting for chips');
    assertEquals(
        3, seaPenChips.length,
        'there should be 3 chips in the Characters template');
    seaPenChips[1]!.click();

    const seaPenOptions = await waitUntil(
        () => seaPenTemplateQuery.shadowRoot?.querySelector('sea-pen-options'),
        'waiting for sea-pen-options to load');

    let options = await waitUntil(
        () => seaPenOptions.shadowRoot?.querySelectorAll<CrButtonElement>(
            '.option'),
        'waiting for options to load');
    const numOptionsInitiallyShown = options.length;
    assertTrue(numOptionsInitiallyShown > 0, 'some options are shown');

    const expandButton = await waitUntil(
        () => seaPenOptions.shadowRoot?.querySelector<CrButtonElement>(
            '#expandButton'),
        'wait for expand button');
    assertTrue(!!expandButton, 'expand button should show up');

    expandButton.click();
    options = await waitUntil(
        () => seaPenOptions.shadowRoot?.querySelectorAll<CrButtonElement>(
            '.option'),
        'waiting for options to load');
    assertLE(
        numOptionsInitiallyShown, options.length,
        'more options should show up after clicking expand button');
  });

  [false, true].forEach((useInspire, i) => {
    test(`creates images with inspire ${useInspire}`, async () => {
      const seaPenRouter = await getSeaPenRouter();
      const seaPenTemplateQuery = await getSeaPenTemplateQuery(6);

      {
        // Creates images.
        assertTrue(!!seaPenTemplateQuery, 'Characters template should show up');
        if (useInspire) {
          seaPenTemplateQuery.shadowRoot?.getElementById('inspire')!.click();
        } else {
          seaPenTemplateQuery.shadowRoot?.getElementById(
                                            'searchButton')!.click();
        }
      }

      {
        // Selects an image.
        const seaPenImages = await waitUntil(
            () => seaPenRouter.shadowRoot?.querySelector<SeaPenImagesElement>(
                'sea-pen-images'),
            'waiting for sea-pen-images');

        const thumbnailsToClick = await waitUntil(
            () => Array.from(seaPenImages!.shadowRoot!.querySelectorAll<
                             WallpaperGridItemElement>(
                `wallpaper-grid-item[aria-disabled='false'][data-sea-pen-image]`)),
            'waiting for thumbnails load');
        assertTrue(!!thumbnailsToClick, 'thumbnails should show up');

        thumbnailsToClick[i]!.click();
        assertTrue(
            thumbnailsToClick[i]?.getAttribute('aria-selected') === 'true',
            'thumbnail should be selected');
      }

      const expectedWallpaperTitle =
          seaPenTemplateQuery.shadowRoot?.getElementById('template')
              ?.textContent?.replace(/\s+/gmi, '')
              .trim();

      // Goes back to sea pen root page.
      seaPenRouter.goToRoute(SeaPenPaths.TEMPLATES);

      {
        // Verifies the image is set properly.
        const wallpaperSelected = await waitUntil(
            () => getRouter().shadowRoot?.getElementById('wallpaperSelected')!,
            'waiting for sea-pen-router wallpaper-selected');
        assertTrue(!!wallpaperSelected, 'wallpaper-selected should exist');
        const textContainer = await waitUntil(
            () => wallpaperSelected.shadowRoot?.getElementById('textContainer'),
            'waiting for wallpaper text container', /*intervalMs=*/ 500,
            /*timeoutMs=*/ 3001);
        assertTrue(!!textContainer, 'wallpaper text container exists');
        assertEquals(
            expectedWallpaperTitle,
            textContainer?.querySelector('#imageTitle')
                ?.textContent?.replace(/\s+/gmi, '')
                .trim(),
            'image title is correct');
      }

      {
        // Verifies the image is visible in recent images.
        const recentImages = await waitUntil(
            () => seaPenRouter.shadowRoot
                      ?.querySelector<SeaPenRecentWallpapersElement>(
                          'sea-pen-recent-wallpapers'),
            'waiting for sea-pen-recent-wallpapers');
        assertTrue(!!recentImages, 'recent images should exist');

        const selectedRecentImage =
            recentImages.shadowRoot?.querySelector<WallpaperGridItemElement>(
                'wallpaper-grid-item[aria-selected=true]');
        assertTrue(
            !!selectedRecentImage, 'the new recent image should be selected');

        const menuButton = recentImages.shadowRoot?.querySelector<
            CrIconButtonElement>(
            `wallpaper-grid-item[aria-selected=true] + .menu-icon-container cr-icon-button`);
        assertTrue(!!menuButton, 'menu button exists');
        menuButton!.click();

        const aboutButton = await waitUntil(
            () => recentImages.shadowRoot?.querySelector<HTMLButtonElement>(
                `wallpaper-grid-item[aria-selected=true] ~ cr-action-menu .wallpaper-info-option`),
            'waiting for about wallpaper button');
        assertTrue(!!aboutButton, 'about wallpaper button exists');
        aboutButton!.click();

        const dialog = await waitUntil(
            () => recentImages.shadowRoot?.querySelector<CrDialogElement>(
                'cr-dialog'),
            'waiting for about wallpaper dialog');
        assertTrue(!!dialog, 'about wallpaper dialog exists');

        const promptInfo =
            recentImages.shadowRoot?.querySelector<HTMLParagraphElement>(
                'p.about-prompt-info');
        assertTrue(
            !!promptInfo?.textContent?.replace(/\s+/gmi, '')
                  .trim()
                  .includes(expectedWallpaperTitle!),
            `prompt info should include ${expectedWallpaperTitle}`);
      }
    });
  });

  test('selects recent image', async () => {
    const seaPenRouter = await getSeaPenRouter();
    const recentImages = await waitUntil(
        () => seaPenRouter.shadowRoot
                  ?.querySelector<SeaPenRecentWallpapersElement>(
                      'sea-pen-recent-wallpapers'),
        'waiting for sea-pen-recent-wallpapers');
    assertTrue(!!recentImages, 'recent images should exist');

    {
      // Selects first non-selected image.
      const image =
          recentImages.shadowRoot?.querySelector<WallpaperGridItemElement>(
              `wallpaper-grid-item[aria-selected=false]`);
      assertTrue(!!image, 'image exists');
      image!.click();
      assertTrue(
          image?.getAttribute('aria-selected') === 'true',
          'image should be selected');
    }

    {
      // Verifies the image is set properly.
      const menuButton = recentImages.shadowRoot?.querySelector<
          CrIconButtonElement>(
          `wallpaper-grid-item[aria-selected=true] + .menu-icon-container cr-icon-button`);
      menuButton!.click();

      const aboutButton = await waitUntil(
          () => recentImages.shadowRoot?.querySelector<HTMLButtonElement>(
              `wallpaper-grid-item[aria-selected=true] ~ cr-action-menu .wallpaper-info-option`),
          'waiting for about wallpaper button');
      assertTrue(!!aboutButton, 'about wallpaper button exists');
      aboutButton!.click();

      const dialog = await waitUntil(
          () => recentImages.shadowRoot?.querySelector<CrDialogElement>(
              'cr-dialog'),
          'waiting for about wallpaper dialog');
      assertTrue(!!dialog, 'about wallpaper dialog exists');

      const promptInfo =
          recentImages.shadowRoot?.querySelector<HTMLParagraphElement>(
              'p.about-prompt-info');
      const promptText = promptInfo?.textContent?.trim();

      // Verifies the image is set properly.
      const wallpaperSelected = await waitUntil(
          () => getRouter().shadowRoot?.getElementById('wallpaperSelected')!,
          'waiting for sea-pen-router wallpaper-selected');
      assertTrue(!!wallpaperSelected, 'wallpaper-selected should exist');
      const textContainer = await waitUntil(
          () => wallpaperSelected.shadowRoot?.getElementById('textContainer'),
          'waiting for wallpaper text container', /*intervalMs=*/ 500,
          /*timeoutMs=*/ 3001);
      assertTrue(!!textContainer, 'wallpaper text container exists');
      await waitUntil(
          () => promptText?.includes(
              textContainer.querySelector('#imageTitle')?.textContent?.trim()!),
          () => `failed waiting for expected image title ` +
              `after selecting wallpaper. ` +
              `html:\n${textContainer.outerHTML}`,
          /*intervalMs=*/ 500,
          /*timeoutMs=*/ 3001);
    }
  });

  test('observer sets new image id', async () => {
    const seaPenRouter = await getSeaPenRouter();
    const seaPenTemplateQuery = await getSeaPenTemplateQuery(6);
    {
      // Creates images.
      assertTrue(!!seaPenTemplateQuery, 'Characters template should show up');
      seaPenTemplateQuery.shadowRoot?.getElementById('inspire')!.click();
    }

    const seaPenImages = await waitUntil(
        () => seaPenRouter.shadowRoot?.querySelector<SeaPenImagesElement>(
            'sea-pen-images'),
        'waiting for sea-pen-images');

    {
      // Selects an image.
      const thumbnailsToClick = await waitUntil(
          () => Array.from(seaPenImages!.shadowRoot!.querySelectorAll<
                           WallpaperGridItemElement>(
              `wallpaper-grid-item[aria-disabled='false'][data-sea-pen-image]`)),
          'waiting for thumbnails load');
      assertTrue(!!thumbnailsToClick, 'thumbnails should show up');

      thumbnailsToClick[0]!.click();
      assertTrue(
          thumbnailsToClick[0]?.getAttribute('aria-selected') === 'true',
          'thumbnail should be selected');
    }

    {
      // Wait for the observer to update the app.
      const store = seaPenImages.getStore();
      assertTrue(store.data.loading.currentSelected);
      assertEquals(null, store.data.currentSelected);

      const newCurrentSelected = await waitUntil(
          () => store.data.currentSelected,
          'failed waiting for SeaPen currentSelected');
      assertEquals(
          1, newCurrentSelected,
          'current selected set to clicked thumbnail id');
    }
  });

  test('create more template generated recent image', async () => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: true});
    const seaPenRouter = await getSeaPenRouter();
    const recentImages = await waitUntil(
        () => seaPenRouter.shadowRoot
                  ?.querySelector<SeaPenRecentWallpapersElement>(
                      'sea-pen-recent-wallpapers'),
        'waiting for sea-pen-recent-wallpapers');
    assertTrue(!!recentImages, 'recent images should exist');

    const images =
        Array.from(recentImages.shadowRoot!.querySelectorAll<HTMLElement>(
            `.recent-image-container:not([hidden])`));
    assertTrue(images.length > 0, 'there should be at least 1 recent image');

    const targetRecentImage = images.find(
        (image) => (image as HTMLElement)
                       .querySelector('.menu-icon-button')
                       ?.ariaDescription === 'test template query');
    assertTrue(!!targetRecentImage, 'target recent image should be available');

    const menuButton = await waitUntil(
        () => (targetRecentImage as HTMLElement)
                  .querySelector<CrIconButtonElement>('.menu-icon-button'),
        'wait for menu button');
    assertTrue(!!menuButton, 'menu button should be available');
    menuButton!.click();

    const createMoreButton = await waitUntil(
        () => (targetRecentImage as HTMLElement)
                  .querySelector<HTMLButtonElement>('.create-more-option'),
        'wait for create more button');
    assertTrue(!!createMoreButton, 'create more button exists');
    createMoreButton!.click();

    const seaPenTemplateQuery = await waitUntil(
        () =>
            seaPenRouter.shadowRoot?.querySelector<SeaPenTemplateQueryElement>(
                'sea-pen-template-query'),
        'waiting for sea-pen-template-query');
    assertTrue(!!seaPenTemplateQuery, 'template query element exists');

    const templatePrompt =
        seaPenTemplateQuery.shadowRoot?.getElementById('template')
            ?.textContent?.replace(/\s+/gmi, ' ')
            .trim();
    assertEquals('A radiant light bluegarden rose', templatePrompt);

    const queryParams = new URLSearchParams(window.location.search);
    assertEquals(
        SeaPenTemplateId.kFlower.toString(),
        queryParams.get('seaPenTemplateId'),
        'routed to Airbrushed template results page');

    const seaPenImages = await waitUntil(
        () => seaPenRouter.shadowRoot?.querySelector<SeaPenImagesElement>(
            'sea-pen-images'),
        'waiting for sea-pen-images');
    assertTrue(!!seaPenImages, 'Sea Pen images element exists');
  });

  test('create more free text generated recent image', async () => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: true});
    const seaPenRouter = await getSeaPenRouter();
    const recentImages = await waitUntil(
        () => seaPenRouter.shadowRoot
                  ?.querySelector<SeaPenRecentWallpapersElement>(
                      'sea-pen-recent-wallpapers'),
        'waiting for sea-pen-recent-wallpapers');
    assertTrue(!!recentImages, 'recent images should exist');

    const images =
        Array.from(recentImages.shadowRoot!.querySelectorAll<HTMLElement>(
            `.recent-image-container:not([hidden])`));
    assertTrue(images.length > 0, 'there should be at least 1 recent image');

    const targetRecentImage = images.find(
        (image) => (image as HTMLElement)
                       .querySelector('.menu-icon-button')
                       ?.ariaDescription === 'test free text query');
    assertTrue(!!targetRecentImage, 'target recent image should be available');

    const menuButton = await waitUntil(
        () => (targetRecentImage as HTMLElement)
                  .querySelector<CrIconButtonElement>('.menu-icon-button'),
        'wait for menu button');
    assertTrue(!!menuButton, 'menu button should be available');
    menuButton!.click();

    const createMoreButton = await waitUntil(
        () => (targetRecentImage as HTMLElement)
                  .querySelector<HTMLButtonElement>('.create-more-option'),
        'wait for create more button');
    assertTrue(!!createMoreButton, 'create more button exists');
    createMoreButton!.click();

    const seaPenInputQuery = await waitUntil(
        () => seaPenRouter.shadowRoot?.querySelector<SeaPenInputQueryElement>(
            'sea-pen-input-query'),
        'waiting for sea-pen-input-query');
    assertTrue(!!seaPenInputQuery, 'input query element exists');

    const inputPrompt = seaPenInputQuery.shadowRoot
                            ?.querySelector<CrInputElement>('#queryInput')
                            ?.value;
    assertEquals(
        'test free text query', inputPrompt,
        'the free text prompt should match');

    assertTrue(
        window.location.href.endsWith(SeaPenPaths.FREEFORM),
        'routed to Freeform page');

    const seaPenFreeform = await waitUntil(
        () => seaPenRouter.shadowRoot?.querySelector<SeaPenFreeformElement>(
            'sea-pen-freeform'),
        'waiting for sea-pen-freeform');

    const seaPenImages = await waitUntil(
        () => seaPenFreeform.shadowRoot?.querySelector<SeaPenImagesElement>(
            'sea-pen-images'),
        'waiting for sea-pen-images');
    assertTrue(!!seaPenImages, 'Sea Pen images element exists');
  });

  test('click sample prompt', async () => {
    loadTimeData.overrideValues({isSeaPenTextInputEnabled: true});
    const seaPenRouter = await getSeaPenRouter();
    const freeformElement =
        seaPenRouter!.shadowRoot!.querySelector(SeaPenFreeformElement.is);
    const samplesElement =
        freeformElement?.shadowRoot?.querySelector(SeaPenSamplesElement.is);
    const sampleList = samplesElement?.shadowRoot?.querySelectorAll(
        `${WallpaperGridItemElement.is}:not([hidden])`);
    const selectedSample = sampleList?.[0] as HTMLElement;
    const selectedText = selectedSample?.textContent;

    const inputQueryElement =
        seaPenRouter.shadowRoot!.querySelector(SeaPenInputQueryElement.is);
    const input = inputQueryElement?.shadowRoot?.querySelector<CrInputElement>(
        '#queryInput');

    selectedSample?.click();

    await waitUntil(
        () => input?.innerText === selectedText,
        'failed to insert sample prompt into text input');
  });

  test('delete recent image', async () => {
    const seaPenRouter = await getSeaPenRouter();
    let recentImages = await waitUntil(
        () => seaPenRouter.shadowRoot
                  ?.querySelector<SeaPenRecentWallpapersElement>(
                      'sea-pen-recent-wallpapers'),
        'waiting for sea-pen-recent-wallpapers');
    assertTrue(!!recentImages, 'recent images should exist');

    const images = recentImages.shadowRoot?.querySelectorAll<HTMLElement>(
        `.recent-image-container:not([hidden])`);
    assertTrue(!!images, 'images should exist');
    assertTrue(images.length > 0, 'there should be at least 1 recent image');
    const numImages = images.length;

    const menuButton =
        recentImages.shadowRoot?.querySelector<CrIconButtonElement>(
            `wallpaper-grid-item + .menu-icon-container
        cr-icon-button`);
    menuButton!.click();

    const deleteButton = await waitUntil(
        () => recentImages.shadowRoot?.querySelector<HTMLButtonElement>(
            `wallpaper-grid-item ~ cr-action-menu .delete-wallpaper-option`),
        'waiting for delete wallpaper button');
    assertTrue(!!deleteButton, 'delete wallpaper button exists');
    deleteButton!.click();

    recentImages = await waitUntil(
        () => seaPenRouter.shadowRoot
                  ?.querySelector<SeaPenRecentWallpapersElement>(
                      'sea-pen-recent-wallpapers'),
        'waiting for sea-pen-recent-wallpapers');
    await waitUntil(
        () => numImages - 1 ===
            recentImages.shadowRoot
                ?.querySelectorAll<HTMLElement>(
                    `.recent-image-container:not([hidden])`)
                ?.length,
        'a recent image has been deleted');
  });

  test('switch template update prompt', async () => {
    await getSeaPenRouter();

    const seaPenTemplateQuery = await getSeaPenTemplateQuery(6);
    assertTrue(!!seaPenTemplateQuery, 'Characters template should show up');

    assertEquals(
        getSeaPenTemplatePrompt(seaPenTemplateQuery),
        'pink lemons on a purple background',
        'default prompt of Characters template should display');

    setTransitionsEnabled(true);

    const seaPenChips = await waitUntil(
        () => seaPenTemplateQuery.shadowRoot?.querySelectorAll<HTMLDivElement>(
            '#template > .chip-container > .chip-text'),
        'waiting for chips');
    assertEquals(
        3, seaPenChips.length,
        'there should be 3 chips in the Characters template');
    assertEquals(
        seaPenChips[1]?.shadowRoot?.querySelector('#chipText')?.textContent,
        'lemons');
    seaPenChips[1]!.click();

    const seaPenOptions = await waitUntil(
        () => seaPenTemplateQuery.shadowRoot?.querySelector('sea-pen-options'),
        'waiting for sea-pen-options to load');

    const options = await waitUntil(
        () => seaPenOptions.shadowRoot?.querySelectorAll<CrButtonElement>(
            '.option'),
        'waiting for options to load');
    // Select "cherries" option.
    options[3]!.click();

    await waitUntil(
        () => seaPenChips[1]
                  ?.shadowRoot?.querySelector('#chipText')
                  ?.textContent === 'cherries',
        'chip text changed to the selected option');

    // Switch to Classic Art template using breadcrumb.
    const breadcrumb =
        getRouter().shadowRoot?.querySelector('personalization-breadcrumb');
    assertTrue(!!breadcrumb, 'personalization-breadcrumb should be found');

    const dropdownIcon = await waitUntil(
        () => breadcrumb.shadowRoot?.querySelector<HTMLElement>(
            '#seaPenDropdown'),
        'SeaPen breadcrumb drop down icon available');
    dropdownIcon!.click();

    let dropdownMenu = await waitUntil(
        () => breadcrumb.shadowRoot?.querySelector('cr-action-menu'),
        'wait for drop down menu open');
    const classicArtTemplate =
        dropdownMenu!.querySelectorAll<HTMLElement>('button')[4];
    assertTrue(
        !!classicArtTemplate, 'Classic Art option is avaiable to select');
    classicArtTemplate!.click();

    const classicArtDefaultPrompt =
        'A painting of a field of flowers in the avant-garde style';
    await waitUntil(
        () => getSeaPenTemplatePrompt(seaPenTemplateQuery) ===
            classicArtDefaultPrompt,
        'default prompt of Classic Art template should display');

    // Switch to Dreamscapes template using breadcrumb.
    dropdownIcon!.click();

    dropdownMenu = await waitUntil(
        () => breadcrumb.shadowRoot?.querySelector('cr-action-menu'),
        'wait for drop down menu open again');
    // Switch to Classic Art template.
    const dreamscapeTemplate =
        dropdownMenu!.querySelectorAll<HTMLElement>('button')[1];
    assertTrue(
        !!dreamscapeTemplate, 'Dreamscapes option is avaiable to select');
    dreamscapeTemplate!.click();

    const dreamscapeDefaultPrompt =
        'A surreal bicycle made of flowers in pink and purple';
    await waitUntil(
        () => getSeaPenTemplatePrompt(seaPenTemplateQuery) ===
            dreamscapeDefaultPrompt,
        'default prompt of Dreamscape template should display');
  });
});

suite('dynamic color', () => {
  const themeProvider = getThemeProvider();

  function getDynamicColorElement(): DynamicColorElement {
    const dynamicColor =
        getRouter()
            .shadowRoot?.querySelector('personalization-main')
            ?.shadowRoot?.querySelector('personalization-theme')
            ?.shadowRoot?.querySelector<DynamicColorElement>('dynamic-color');
    assertTrue(!!dynamicColor);
    return dynamicColor;
  }

  // cr-toggle is already defined in customElements registry so re-importing it
  // will cause issues if tests are run while optimize_webui=true.
  type CrToggleElement = HTMLElementTagNameMap['cr-toggle'];
  function getDynamicColorToggle(): CrToggleElement {
    const toggle = getDynamicColorElement().shadowRoot?.getElementById(
        'dynamicColorToggle');
    assertTrue(!!toggle, 'dynamic color toggle exists');
    assertInstanceof(
        toggle, customElements.get('cr-toggle')!, 'toggle is CrToggle');
    return toggle as CrToggleElement;
  }

  function getColorSchemeSelector(): IronSelectorElement {
    const colorScheme = getDynamicColorElement().shadowRoot?.getElementById(
        'colorSchemeSelector');
    assertTrue(!!colorScheme, 'color scheme selector exists');
    return colorScheme as IronSelectorElement;
  }

  function getStaticColorSelector(): IronSelectorElement {
    const staticColor = getDynamicColorElement().shadowRoot?.getElementById(
        'staticColorSelector');
    assertTrue(!!staticColor, 'static color selector exists');
    return staticColor as IronSelectorElement;
  }

  function setDynamicColorToggle(checkedState: boolean) {
    const toggle = getDynamicColorToggle();
    if (checkedState !== toggle.checked) {
      toggle.click();
    }
  }

  setup(() => {
    // Reset to default state before each test to reduce dependencies.
    window.personalizationTestApi.goToRootPath();

    // Disables transition animation for tests.
    setTransitionsEnabled(false);
  });

  test('shows dynamic color options', () => {
    assertTrue(!!getDynamicColorToggle());
    assertTrue(!!getColorSchemeSelector());
    assertTrue(!!getStaticColorSelector());
  });

  test('clicks toggle', async () => {
    const toggle = getDynamicColorToggle();
    assertTrue(!!toggle.checked, 'toggle starts checked');

    {
      const {staticColor} = await themeProvider.getStaticColor();
      assertEquals(
          null, staticColor, 'static color is null when dynamic color on');
    }

    setDynamicColorToggle(false);

    {
      const {staticColor: {value}} =
          await themeProvider.getStaticColor() as {staticColor: SkColor};
      assertGT(
          value, 0, 'static color is positive number when dynamic color off');
    }

    setDynamicColorToggle(true);

    {
      const {staticColor} = await themeProvider.getStaticColor();
      assertEquals(
          null, staticColor, 'static color null when dynamic color toggle on');
    }
  });

  test('shows color scheme options', async () => {
    setDynamicColorToggle(true);

    assertTrue(getDynamicColorToggle().checked);
    assertTrue(getStaticColorSelector().hidden);
    assertFalse(getColorSchemeSelector().hidden);
  });

  test('selects color scheme options', async () => {
    await window.personalizationTestApi.setDefaultColorScheme();
    const toggleDescription =
        getDynamicColorElement().shadowRoot?.getElementById(
            'dynamicColorToggleDescription');
    assertTrue(!!toggleDescription, 'toggle description exists');
    setDynamicColorToggle(true);
    const {staticColor} = await themeProvider.getStaticColor();
    assertEquals(
        null, staticColor, 'setting dynamic color on forces null staticColor');

    // Click all of the color scheme buttons and save the text color of
    // the toggle description to a set.
    const seenTextColors = new Set();

    const colorSchemeButtons =
        Array.from(getColorSchemeSelector().querySelectorAll('cr-button'));

    assertDeepEquals(
        ['true', 'false', 'false', 'false'],
        colorSchemeButtons.map(button => button.ariaChecked),
        '4 buttons and first is checked');

    // Iterate in reverse order so that we always click a non-selected
    // button.
    for (const button of colorSchemeButtons.toReversed()) {
      assertEquals('false', button.ariaChecked, 'button starts not checked');
      button.click();
      // Wait for the button click above to flush through mojom.
      const {colorScheme} = await themeProvider.getColorScheme();
      assertEquals(
          button.dataset['colorSchemeId'], `${colorScheme}`,
          'correct color scheme now selected');
      assertEquals('true', button.ariaChecked, 'button has aria checked true');

      await waitUntil(
          () => !seenTextColors.has(getComputedStyle(toggleDescription).color),
          'failed waiting for text colors to change');
      seenTextColors.add(getComputedStyle(toggleDescription).color);
    }

    assertEquals(4, seenTextColors.size, '4 unique colors seen');
  });

  test('shows static color options', async () => {
    const toggleButton = getDynamicColorToggle();

    setDynamicColorToggle(false);

    assertFalse(toggleButton.checked);
    assertFalse(getStaticColorSelector().hidden);
    assertTrue(getColorSchemeSelector().hidden);
  });

  test('selects static color options', async () => {
    await window.personalizationTestApi.setDefaultColorScheme();
    const theme = getRouter()
                      .shadowRoot?.querySelector('personalization-main')
                      ?.shadowRoot?.querySelector<PersonalizationThemeElement>(
                          'personalization-theme');
    const lightButton = theme?.shadowRoot?.getElementById('lightMode');
    assertTrue(!!lightButton, 'lightMode button exists');
    lightButton.click();
    const {darkModeEnabled} = await themeProvider.isDarkModeEnabled();
    assertFalse(
        darkModeEnabled,
        'darkModeEnabled must be false after clicking light button');
    assertEquals(
        'true', lightButton.getAttribute('aria-checked'),
        'lightMode button should be aria-checked');

    {
      const {staticColor} = await themeProvider.getStaticColor();
      assertEquals(null, staticColor, 'static color not set yet');
    }
    setDynamicColorToggle(false);
    {
      const {staticColor: {value}} =
          await themeProvider.getStaticColor() as {staticColor: SkColor};
      assertGT(
          value, 0,
          'static color set to positive number when dynamic color off');
    }

    // Click all of the static color buttons and save the observed color
    // values to a set.
    const seenButtonColors = new Set();
    const staticColorButtons =
        Array.from(getStaticColorSelector().querySelectorAll('cr-button'));
    assertDeepEquals(
        ['true', 'false', 'false', 'false'],
        staticColorButtons.map(button => button.ariaChecked),
        'should be 4 buttons and first is checked');
    // Iterate backwards to always click a button that isn't checked yet.
    for (const button of staticColorButtons.toReversed()) {
      assertEquals('false', button.ariaChecked, 'button starts not checked');
      button.click();
      assertEquals(
          'true', button.ariaChecked, 'button is set to checked when clicked');
      // Wait for mojom to finish processing by requesting current static
      // color.
      const {staticColor: {value}} =
          await themeProvider.getStaticColor() as {staticColor: SkColor};
      assertGT(value, 0, 'static color is positive numeric value');

      await waitUntil(
          () => !seenButtonColors.has(
              getComputedStyle(lightButton).backgroundColor),
          'failed waiting for button background color to change');
      seenButtonColors.add(getComputedStyle(lightButton).backgroundColor);
    }

    assertEquals(4, seenButtonColors.size, '4 unique static colors seen');
  });
});