chromium/ui/file_manager/integration_tests/file_manager/quick_view.ts

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

import {DialogType, type ElementObject} from '../prod/file_manager/shared_types.js';
import {ExecuteScriptError} from '../remote_call.js';
import {addEntries, ENTRIES, EntryType, getCaller, getHistogramCount, pending, repeatUntil, RootPath, sanitizeDate, sendTestMessage, TestEntryInfo, wait} from '../test_util.js';

import {remoteCall} from './background.js';
import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
import {BASIC_ANDROID_ENTRY_SET, BASIC_FAKE_ENTRY_SET, BASIC_LOCAL_ENTRY_SET, MODIFIED_ENTRY_SET} from './test_data.js';

/** The tag used to create a safe environment to display the preview. */
const previewTag = 'iframe';

/** The JS code used to query the content window for preview. */
const contentWindowQuery = 'document.querySelector("iframe").contentWindow';

/** The name of the UMA emitted to track how Quick View is opened. */
const QuickViewUmaWayToOpenHistogramName = 'FileBrowser.QuickView.WayToOpen';

/**
 * The UMA's enumeration values (must be consistent with enums.xml, previously
 * histograms.xml).
 */
const QuickViewUmaWayToOpenHistogramValues = {
  CONTEXT_MENU: 0,
  SPACE_KEY: 1,
  SELECTION_MENU: 2,
};

/**
 * Check the background color for the content inside the quick view is one of
 * the 2 allowed colors
 */
async function checkBackgroundColor(backgroundColor: string): Promise<void> {
  const validBackground = [
    // Dark mode:
    'rgb(0, 0, 0)',
    // Light mode: the preview body backgroundColor should be transparent black.
    'rgba(0, 0, 0, 0)',
  ];
  // b/361293031: Accept either value, it was flaking between the values due to
  // issues outside the Files app.
  chrome.test.assertTrue(
      validBackground.includes(backgroundColor),
      `Unexepcted background color: ${backgroundColor}`);
}

/**
 * Waits for Quick View dialog to be open.
 *
 * @param appId Files app windowId.
 */
async function waitQuickViewOpen(appId: string) {
  const caller = getCaller();

  function checkQuickViewElementsDisplayBlock(elements: ElementObject[]) {
    const haveElements = Array.isArray(elements) && elements.length !== 0;
    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
      return pending(caller, 'Waiting for Quick View to open.');
    }
    return;
  }

  await repeatUntil(async () => {
    const elements = ['#quick-view', '#dialog[open]'];
    return checkQuickViewElementsDisplayBlock(
        await remoteCall.callRemoteTestUtil(
            'deepQueryAllElements', appId, [elements, ['display']]));
  });
}

/**
 * Waits for Quick View dialog to be closed.
 *
 * @param appId Files app windowId.
 */
async function waitQuickViewClose(appId: string) {
  const caller = getCaller();

  function checkQuickViewElementsDisplayNone(elements: ElementObject[]) {
    chrome.test.assertTrue(Array.isArray(elements));
    if (elements.length === 0 || elements[0]!.styles!['display'] !== 'none') {
      return pending(caller, 'Waiting for Quick View to close.');
    }

    return;
  }

  // Check: the Quick View dialog should not be shown.
  await repeatUntil(async () => {
    const elements = ['#quick-view', '#dialog:not([open])'];
    return checkQuickViewElementsDisplayNone(
        await remoteCall.callRemoteTestUtil(
            'deepQueryAllElements', appId, [elements, ['display']]));
  });
}

/**
 * Opens the Quick View dialog on a given file |name|. The file must be
 * present in the Files app file list.
 *
 * @param appId Files app windowId.
 * @param name File name.
 */
async function openQuickViewEx(appId: string, name: string) {
  // Select file |name| in the file list.
  await remoteCall.waitUntilSelected(appId, name);

  // Press the space key.
  const space = ['#file-list', ' ', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, space),
      'fakeKeyDown failed');

  // Check: the Quick View dialog should be shown.
  return waitQuickViewOpen(appId);
}

/**
 * Opens the Quick View dialog by right clicking on the file |name| and
 * using the "Get Info" command from the context menu.
 *
 * @param appId Files app windowId.
 * @param name File name.
 */
async function openQuickViewViaContextMenu(appId: string, name: string) {
  // Right-click the file in the file-list.
  const query = `#file-list [file-name="${name}"]`;
  await remoteCall.waitAndRightClick(appId, query);

  // Wait because WebUI Menu ignores the following click if it happens in
  // <200ms from the previous click.
  await wait(300);

  // Click the file-list context menu "Get info" command.
  const getInfoMenuItem = '#file-context-menu:not([hidden]) ' +
      ' [command="#get-info"]:not([hidden])';
  await remoteCall.waitAndClickElement(appId, getInfoMenuItem);

  // Check: the Quick View dialog should be shown.
  await waitQuickViewOpen(appId);
}

/**
 * Opens the Quick View dialog with given file |names|. The files must be
 * present and check-selected in the Files app file list.
 *
 * @param appId Files app windowId.
 * @param names File names.
 */
async function openQuickViewMultipleSelection(appId: string, names: string[]) {
  // Get the file-list rows that are check-selected (multi-selected).
  const selectedRows = await remoteCall.callRemoteTestUtil<ElementObject[]>(
      'deepQueryAllElements', appId, ['#file-list li[selected]']);

  // Check: the selection should contain the given file names.
  chrome.test.assertEq(names.length, selectedRows.length);
  for (let i = 0; i < names.length; i++) {
    chrome.test.assertTrue(
        selectedRows[i]?.attributes['file-name']?.includes(names[i]!)!);
  }

  // Open Quick View via its keyboard shortcut.
  const space = ['#file-list', ' ', false, false, false];
  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, space);

  // Check: the Quick View dialog should be shown.
  await waitQuickViewOpen(appId);
}

/**
 * Mount and select USB.
 *
 * @param appId Files app windowId.
 */
async function mountAndSelectUsb(appId: string) {
  // Mount a USB volume.
  await sendTestMessage({name: 'mountFakeUsb'});

  // Wait for the USB volume to mount and click to open the USB volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('removable');

  // Check: the USB files should appear in the file list.
  const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
}

/**
 * Assuming that Quick View is currently open per openQuickView above, closes
 * the Quick View dialog.
 *
 * @param appId Files app windowId.
 */
async function closeQuickViewEx(appId: string) {
  // Click on Quick View to close it.
  const panelElements = ['#quick-view', '#contentPanel'];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, [panelElements]),
      'fakeMouseClick failed');

  // Check: the Quick View dialog should not be shown.
  await waitQuickViewClose(appId);
}

/**
 * Assuming that Quick View is currently open per openQuickView above, return
 * the text shown in the QuickView Metadata Box field |name|. If the optional
 * |hidden| is 'hidden', the field |name| should not be visible.
 *
 * @param appId Files app windowId.
 * @param name QuickView Metadata Box field name.
 * @param hidden Whether the field name should be visible.
 *
 * @return text Text value in the field name.
 */
async function getQuickViewMetadataBoxField(
    appId: string, name: string, hidden: string = ''): Promise<string> {
  let filesMetadataBox = 'files-metadata-box';

  /**
   * <files-metadata-box> field rendering is async. The field name has been
   * rendered when its 'metadata' attribute indicates that.
   */
  switch (name) {
    case 'Size':
      filesMetadataBox += '[metadata~="size"]';
      break;
    case 'Date modified':
    case 'Type':
      filesMetadataBox += '[metadata~="mime"]';
      break;
    case 'File location':
      filesMetadataBox += '[metadata~="location"]';
      break;
    case 'Original location':
      filesMetadataBox += '[metadata~="originalLocation"]';
      break;
    default:
      filesMetadataBox += '[metadata~="meta"]';
      break;
  }

  /**
   * The <files-metadata-box> element resides in the #quick-view shadow DOM
   * as a child of the #dialog element.
   */
  const quickViewQuery = ['#quick-view', `#dialog[open] ${filesMetadataBox}`];

  /**
   * The <files-metadata-entry key="name"> element resides in the shadow DOM
   * of the <files-metadata-box>.
   */
  quickViewQuery.push(`files-metadata-entry[key="${name}"]`);

  /**
   * It has a #value div child in its shadow DOM containing the field value,
   * but if |hidden| was given, the field should not be visible.
   */
  if (hidden !== 'hidden') {
    quickViewQuery.push('#value > div:not([hidden])');
  } else {
    quickViewQuery.push('#box[hidden]');
  }

  const element = await remoteCall.waitForElement(appId, quickViewQuery);
  if (name === 'Date modified') {
    return sanitizeDate(element.text || '');
  } else {
    return element.text ?? '';
  }
}

/**
 * Executes a script in the context of a <preview-tag> element and returns its
 * output. Returns undefined when ExecuteScriptError is caught.
 *
 * @param appId App window Id.
 * @param query Query to the <preview-tag> element (this is
 *     ignored for SWA).
 * @param statement Javascript statement to be executed within the
 *     <preview-tag>.
 */
async function executeJsInPreviewTagAndCatchErrors<T>(
    appId: string, query: string[], statement: string): Promise<T|undefined> {
  try {
    return await remoteCall.executeJsInPreviewTag<T>(appId, query, statement);
  } catch (e) {
    if (e instanceof ExecuteScriptError) {
      return undefined;
    } else {
      throw (e);
    }
  }
}

/**
 * Tests opening Quick View on a local downloads file.
 */
export async function openQuickView() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Check the open button is shown.
  await remoteCall.waitForElement(
      appId, ['#quick-view', '#open-button:not([hidden])']);
}

/**
 * Tests opening Quick View on a local downloads file in an open file dialog.
 */
export async function openQuickViewDialog() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], [],
      {type: DialogType.SELECT_OPEN_FILE});

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Check the open button is not shown as we're in an open file dialog.
  await remoteCall.waitForElement(
      appId, ['#quick-view', '#open-button[hidden]']);
}

/**
 * Tests that Quick View opens via the context menu with a single selection.
 */
export async function openQuickViewViaContextMenuSingleSelection() {
  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Select the file in the file list.
  await remoteCall.waitUntilSelected(appId, ENTRIES.hello.nameText);

  // Check: clicking the context menu "Get Info" should open Quick View.
  await openQuickViewViaContextMenu(appId, ENTRIES.hello.nameText);
}

/**
 * Tests that Quick View opens via the context menu when multiple files
 * are selected (file-list check-select mode).
 */
export async function openQuickViewViaContextMenuCheckSelections() {
  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Ctrl+A to select all files in the file-list.
  await remoteCall.fakeKeyDown(appId, '#file-list', 'a', true, false, false);

  // Check: clicking the context menu "Get Info" should open Quick View.
  await openQuickViewViaContextMenu(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening then closing Quick View on a local downloads file.
 */
export async function closeQuickView() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Close Quick View.
  await closeQuickViewEx(appId);
}

/**
 * Tests opening Quick View on a Drive file.
 */
export async function openQuickViewDrive() {
  // Open Files app on Drive containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Check: the correct mimeType should be displayed (see crbug.com/1067499
  // for details on mimeType differences between Drive and local filesystem).
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('text/plain', mimeType);

  // Check: the correct file location should be displayed in Drive.
  const location = await getQuickViewMetadataBoxField(appId, 'File location');
  chrome.test.assertEq('My Drive/hello.txt', location);
}

/**
 * Tests opening Quick View on a Smbfs file.
 */
export async function openQuickViewSmbfs() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Populate Smbfs with some files.
  await addEntries(['smbfs'], BASIC_LOCAL_ENTRY_SET);

  // Mount Smbfs volume.
  await sendTestMessage({name: 'mountSmbfs'});

  // Wait for the Smbfs volume to mount and click to open the Smbfs volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('smb');

  const files = TestEntryInfo.getExpectedRows(BASIC_LOCAL_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening Quick View on a USB file.
 */
export async function openQuickViewUsb() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Open a USB file in Quick View.
  await mountAndSelectUsb(appId);
  await openQuickViewEx(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening Quick View on a removable partition.
 */
export async function openQuickViewRemovablePartitions() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Mount USB device containing partitions.
  await sendTestMessage({name: 'mountUsbWithPartitions'});

  // Wait for the USB root to be available.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemByLabel('Drive Label');
  await directoryTree.navigateToPath('/Drive Label');

  // Wait for 2 removable partitions to appear in the directory tree.
  await directoryTree.expandTreeItemByLabel('Drive Label');
  await directoryTree.waitForChildItemsCountByLabel('Drive Label', 2);

  // Click to open the first partition.
  await directoryTree.selectItemByType('removable');

  // Check: the USB files should appear in the file list.
  const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening Quick View on an item that was Trashed shows original location
 * instead of the current file location.
 */
export async function openQuickViewTrash() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Select hello.txt.
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="hello.txt"]');

  // Delete item and wait for it to be removed (no dialog).
  await remoteCall.waitAndClickElement(appId, '#move-to-trash-button');
  await remoteCall.waitForElementLost(
      appId, '#file-list [file-name="hello.txt"]');

  // Navigate to /Trash and ensure the file is shown.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/Trash');
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="hello.txt"]');

  // Open the file in Quick View.
  await openQuickViewEx(appId, 'hello.txt');

  // Check: the original location should be shown instead of the actual file
  // location.
  const location =
      await getQuickViewMetadataBoxField(appId, 'Original location');
  chrome.test.assertEq('My files/Downloads/hello.txt', location);
}

/**
 * Tests seeing dashes for an empty last_modified for DocumentsProvider.
 */
export async function openQuickViewLastModifiedMetaData() {
  const documentsProviderVolumeType = 'documents_provider';

  // Add files to the DocumentsProvider volume.
  await addEntries(['documents_provider'], MODIFIED_ENTRY_SET);

  // Open Files app.
  const appId = await remoteCall.openNewWindow(RootPath.DOWNLOADS);

  // Wait for the DocumentsProvider volume to mount and then click to open
  // DocumentsProvider Volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemToHaveChildrenByType(
      documentsProviderVolumeType, /* hasChildren= */ true);
  await directoryTree.selectItemByType(documentsProviderVolumeType);

  // Check: the DocumentsProvider files should appear in the file list.
  const files = TestEntryInfo.getExpectedRows(MODIFIED_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open a DocumentsProvider file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  const lastValidModifiedText =
      await getQuickViewMetadataBoxField(appId, 'Date modified');
  chrome.test.assertEq(ENTRIES.hello.lastModifiedTime, lastValidModifiedText);

  await closeQuickViewEx(appId);

  // Open a DocumentsProvider file in Quick View.
  await openQuickViewEx(appId, ENTRIES.invalidLastModifiedDate.nameText);

  // Modified time should be displayed as "--" when it's absent.
  const lastInvalidModifiedText =
      await getQuickViewMetadataBoxField(appId, 'Date modified');
  chrome.test.assertEq('--', lastInvalidModifiedText);
}

/**
 * Tests opening Quick View on an MTP file.
 */
export async function openQuickViewMtp() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Mount a non-empty MTP volume.
  await sendTestMessage({name: 'mountFakeMtp'});

  // Wait for the MTP volume to mount and click to open the MTP volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('mtp');

  // Check: the MTP files should appear in the file list.
  const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open an MTP file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening Quick View on a Crostini file.
 */
export async function openQuickViewCrostini() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Open a Crostini file in Quick View.
  await remoteCall.mountCrostini(appId);
  await openQuickViewEx(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening Quick View on a GuestOS file.
 */
export async function openQuickViewGuestOs() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Open a GuestOS file in Quick View.
  await remoteCall.mountGuestOs(appId, BASIC_LOCAL_ENTRY_SET);
  await openQuickViewEx(appId, ENTRIES.hello.nameText);
}

/**
 * Tests opening Quick View on an Android file.
 */
export async function openQuickViewAndroid() {
  // Open Files app on Android files.
  const appId = await remoteCall.openNewWindow(RootPath.ANDROID_FILES);

  // Add files to the Android files volume.
  const entrySet = BASIC_ANDROID_ENTRY_SET.concat([ENTRIES.documentsText]);
  await addEntries(['android_files'], entrySet);

  // Wait for the file list to appear.
  await remoteCall.waitForElement(appId, '#file-list');

  // Check: the basic Android file set should appear in the file list.
  let files = TestEntryInfo.getExpectedRows(BASIC_ANDROID_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Navigate to the Android files '/Documents' directory.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files/Play files/Documents');

  // Check: the 'android.txt' file should appear in the file list.
  files = [ENTRIES.documentsText.getExpectedRow()];
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open the Android file in Quick View.
  const documentsFileName = ENTRIES.documentsText.nameText;
  await openQuickViewEx(appId, documentsFileName);
}

/**
 * Tests opening Quick View on an Android file on GuestOS.
 */
export async function openQuickViewAndroidGuestOs() {
  // Open Files app on Android files.
  const appId = await remoteCall.openNewWindow(RootPath.ANDROID_FILES);

  // Add files to the Android files volume.
  const entrySet = BASIC_ANDROID_ENTRY_SET.concat([ENTRIES.documentsText]);
  await addEntries(['android_files'], entrySet);

  // Wait for the file list to appear.
  await remoteCall.waitForElement(appId, '#file-list');

  // Check: the basic Android file set should appear in the file list.
  let files = TestEntryInfo.getExpectedRows(BASIC_ANDROID_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Navigate to the Android files '/Documents' directory.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files/Play files/Documents');

  // Check: the 'android.txt' file should appear in the file list.
  files = [ENTRIES.documentsText.getExpectedRow()];
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open the Android file in Quick View.
  const documentsFileName = ENTRIES.documentsText.nameText;
  await openQuickViewEx(appId, documentsFileName);
}

/**
 * Tests opening Quick View on a DocumentsProvider root.
 */
export async function openQuickViewDocumentsProvider() {
  const DOCUMENTS_PROVIDER_VOLUME_TYPE = 'documents_provider';

  // Add files to the DocumentsProvider volume.
  await addEntries(['documents_provider'], BASIC_LOCAL_ENTRY_SET);

  // Open Files app.
  const appId = await remoteCall.openNewWindow(RootPath.DOWNLOADS);

  // Wait for the DocumentsProvider volume to mount.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemToHaveChildrenByType(
      DOCUMENTS_PROVIDER_VOLUME_TYPE, /* hasChildren= */ true);

  // Click to open the DocumentsProvider volume.
  await directoryTree.selectItemByType(DOCUMENTS_PROVIDER_VOLUME_TYPE);

  // Check: the DocumentsProvider files should appear in the file list.
  const files = TestEntryInfo.getExpectedRows(BASIC_LOCAL_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Open a DocumentsProvider file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Wait for the Quick View preview to load and display its content.
  const caller = getCaller();
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Wait until the preview displays the file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    // Check: the content of text file should be shown.
    if (!text || !text[0] || !text[0].includes('chocolate and chips')) {
      return pending(caller, `Waiting for ${previewTag} content.`);
    }
    return;
  });

  // Check: the correct size and date modified values should be displayed.
  const sizeText = await getQuickViewMetadataBoxField(appId, 'Size');
  chrome.test.assertEq(ENTRIES.hello.sizeText, sizeText);
  const lastModifiedText =
      await getQuickViewMetadataBoxField(appId, 'Date modified');
  chrome.test.assertEq(ENTRIES.hello.lastModifiedTime, lastModifiedText);
}

/**
 * Tests opening Quick View with a local text document identified as text from
 * file sniffing (the first word of the file is "From ", note trailing space).
 */
export async function openQuickViewSniffedText() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing ENTRIES.plainText.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.plainText], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.plainText.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('text/plain', mimeType);
}

/**
 * Tests opening Quick View with a local text document whose MIME type cannot
 * be identified by MIME type sniffing.
 */
export async function openQuickViewTextFileWithUnknownMimeType() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the mimeType field should not be displayed.
  await getQuickViewMetadataBoxField(appId, 'Type', 'hidden');
}

/**
 * Tests opening Quick View with a text file containing some UTF-8 encoded
 * characters: crbug.com/1064855
 */
export async function openQuickViewUtf8Text() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing ENTRIES.utf8Text.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.utf8Text], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.utf8Text.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Wait until the preview displays the file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];

    // Check: the content of ENTRIES.utf8Text should be shown.
    if (!text || !text[0] || !text[0].includes('їсти मुझे |∊☀✌✂♁ 🙂\n')) {
      return pending(caller, 'Waiting for preview content.');
    }

    return;
  });

  // Check: the correct file size should be displayed.
  const size = await getQuickViewMetadataBoxField(appId, 'Size');
  chrome.test.assertEq('191 bytes', size);
}

/**
 * Tests opening Quick View and scrolling its preview contents which contains a
 * tall text document.
 */
export async function openQuickViewScrollText() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  function scrollQuickViewTextBy(y: number) {
    const doScrollBy = `${contentWindowQuery}.scrollBy(0,${y})`;
    return remoteCall.executeJsInPreviewTag(appId, preview, doScrollBy);
  }

  async function checkQuickViewTextScrollY(scrollY: {toString: () => string}) {
    if (!scrollY || Number(scrollY.toString()) <= 150) {
      await scrollQuickViewTextBy(100);
      return pending(caller, 'Waiting for Quick View to scroll.');
    }
    return;
  }

  // Open Files app on Downloads containing ENTRIES.tallText.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallText], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallText.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Statement to get the Quick View preview scrollY.
  const getScrollY = `${contentWindowQuery}.scrollY`;

  // The initial preview scrollY should be 0.
  await repeatUntil(async () => {
    const scrollY =
        await executeJsInPreviewTagAndCatchErrors(appId, preview, getScrollY);
    if (String(scrollY) !== '0') {
      return pending(caller, 'Waiting for preview text to load.');
    }
    return;
  });

  // Scroll the preview and verify that it scrolled.
  await repeatUntil(async () => {
    const scrollY = await remoteCall.executeJsInPreviewTag<string>(
        appId, preview, getScrollY);
    return checkQuickViewTextScrollY(scrollY!);
  });
}

/**
 * Tests opening Quick View containing a PDF document.
 */
export async function openQuickViewPdf() {
  const caller = getCaller();

  /**
   * The PDF preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.content`];

  // Open Files app on Downloads containing ENTRIES.tallPdf.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallPdf], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallPdf.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewPdfLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewPdfLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview embed type attribute.
  function checkPdfEmbedType(type: ElementObject[]) {
    const haveElements = Array.isArray(type) && type.length === 1;
    if (!haveElements || !type[0]!.toString().includes('pdf')) {
      return pending(caller, 'Waiting for plugin <embed> type.');
    }
    return type[0]!;
  }
  const type = await repeatUntil(async () => {
    const getType =
        contentWindowQuery + '.document.querySelector("embed").type';
    const type = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getType) as ElementObject[];
    return checkPdfEmbedType(type);
  });

  // Check: the preview embed type should be PDF mime type.
  chrome.test.assertEq('application/pdf', type);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('application/pdf', mimeType);
}

/**
 * Tests opening Quick View on a PDF document that opens a popup JS dialog.
 */
export async function openQuickViewPdfPopup() {
  const caller = getCaller();

  /**
   * The PDF preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.content`];

  // Open Files app on Downloads containing ENTRIES.popupPdf.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.popupPdf], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.popupPdf.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewPdfLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewPdfLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview embed type attribute.
  function checkPdfEmbedType(type: unknown) {
    const haveElements = Array.isArray(type) && type.length === 1;
    if (!haveElements || !type[0].toString().includes('pdf')) {
      return pending(caller, 'Waiting for plugin <embed> type.');
    }
    return type[0];
  }
  const type = await repeatUntil(async () => {
    const getType =
        contentWindowQuery + '.document.querySelector("embed").type';
    const type =
        await executeJsInPreviewTagAndCatchErrors(appId, preview, getType);
    return checkPdfEmbedType(type);
  });

  // Check: the preview embed type should be PDF mime type.
  chrome.test.assertEq('application/pdf', type);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('application/pdf', mimeType);
}

/**
 * Tests that Quick View does not display a PDF file preview when that is
 * disabled by system settings (preferences).
 */
export async function openQuickViewPdfPreviewsDisabled() {
  const caller = getCaller();

  /**
   * The #innerContentPanel resides in the #quick-view shadow DOM as a child
   * of the #dialog element, and contains the file preview result.
   */
  const contentPanel = ['#quick-view', '#dialog[open] #innerContentPanel'];

  // Disable PDF previews.
  await sendTestMessage({name: 'setPdfPreviewEnabled', enabled: false});

  // Open Files app on Downloads containing ENTRIES.tallPdf.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallPdf], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallPdf.nameText);

  // Wait for the innerContentPanel to load and display its content.
  function checkInnerContentPanel(elements: ElementObject[]) {
    const haveElements = Array.isArray(elements) && elements.length === 1;
    if (!haveElements || elements[0]!.styles!['display'] !== 'flex') {
      return pending(caller, 'Waiting for inner content panel to load.');
    }
    // Check: the PDF preview should not be shown.
    chrome.test.assertEq('No preview available', elements[0]!.text);
    return;
  }
  await repeatUntil(async () => {
    return checkInnerContentPanel(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [contentPanel, ['display']]));
  });

  // Check: the correct file mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('application/pdf', mimeType);
}

/**
 * Tests opening Quick View with a '.mhtml' filename extension.
 */
export async function openQuickViewMhtml() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', 'files-safe-media[type="html"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.plainText.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.mHtml], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.mHtml.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('text/plain', mimeType);

  // Check: the correct file location should be displayed.
  const location = await getQuickViewMetadataBoxField(appId, 'File location');
  chrome.test.assertEq('My files/Downloads/page.mhtml', location);
}

/**
 * Tests opening Quick View and scrolling its preview contents which contains a
 * tall html document.
 */
export async function openQuickViewScrollHtml() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="html"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="html"]', previewTag];

  function scrollQuickViewHtmlBy(y: number) {
    const doScrollBy = `window.scrollBy(0,${y})`;
    return remoteCall.executeJsInPreviewTag(appId, preview, doScrollBy);
  }

  async function checkQuickViewHtmlScrollY(scrollY: {toString: () => any}) {
    if (!scrollY || Number(scrollY.toString()) <= 200) {
      await scrollQuickViewHtmlBy(100);
      return pending(caller, 'Waiting for Quick View to scroll.');
    }
    return;
  }

  // Open Files app on Downloads containing ENTRIES.tallHtml.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallHtml], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallHtml.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewHtmlLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewHtmlLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the Quick View preview scrollY.
  const getScrollY = 'window.scrollY';

  // The initial preview scrollY should be 0.
  await repeatUntil(async () => {
    const scrollY = await executeJsInPreviewTagAndCatchErrors<string>(
        appId, preview, getScrollY);
    if (String(scrollY) !== '0') {
      return pending(caller, `Waiting for preview text to load.`);
    }
    return;
  });

  // Scroll the preview and verify that it scrolled.
  await repeatUntil(async () => {
    const scrollY = await remoteCall.executeJsInPreviewTag<string>(
        appId, preview, getScrollY);
    return checkQuickViewHtmlScrollY(scrollY!);
  });

  // Check: the mimeType field should not be displayed.
  await getQuickViewMetadataBoxField(appId, 'Type', 'hidden');
}

/**
 * Tests opening Quick View on an html document to verify that the background
 * color of the <files-safe-media type="html"> that contains the preview is
 * solid white.
 */
export async function openQuickViewBackgroundColorHtml() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="html"> shadow DOM,
   * which is a child of the #quick-view shadow DOM. This test only needs to
   * examine the <files-safe-media>'s iframe element.
   */
  const preview = ['#quick-view', 'files-safe-media[type="html"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.tallHtml.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallHtml], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallHtml.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewHtmlLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewHtmlLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);

  chrome.test.assertEq('rgba(0, 0, 0, 0)', backgroundColor[0]);
}

/**
 * Tests opening Quick View containing an audio file without album preview.
 */
export async function openQuickViewAudio() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="audio"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="audio"]', previewTag];

  /**
   * The album artwork preview resides in the <files-safe-media> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const albumArtworkPreview = ['#quick-view', '#audio-artwork'];

  // Open Files app on Downloads containing ENTRIES.beautiful song.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.beautiful.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewAudioLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewAudioLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the audio artwork is not shown on the preview page.
  const albumArtworkElements = await remoteCall.callRemoteTestUtil(
      'deepQueryAllElements', appId, [albumArtworkPreview, ['display']]);
  const hasArtworkElements =
      Array.isArray(albumArtworkElements) && albumArtworkElements.length > 0;
  chrome.test.assertFalse(hasArtworkElements);

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);
  await checkBackgroundColor(backgroundColor[0]!);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('audio/ogg', mimeType);
}

/**
 * Tests opening Quick View containing an audio file on Drive.
 */
export async function openQuickViewAudioOnDrive() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="audio"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="audio"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.beautiful song.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.beautiful]);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.beautiful.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewAudioLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewAudioLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);
  await checkBackgroundColor(backgroundColor[0]!);
}

/**
 * Tests opening Quick View containing an audio file that has an album art
 * image in its metadata.
 */
export async function openQuickViewAudioWithImageMetadata() {
  const caller = getCaller();

  // Define a test file containing audio file with metadata.
  const id3Audio = new TestEntryInfo({
    type: EntryType.FILE,
    sourceFileName: 'id3Audio.mp3',
    targetPath: 'id3Audio.mp3',
    mimeType: 'audio/mpeg',
    lastModifiedTime: 'December 25 2015, 11:16 PM',
    nameText: 'id3Audio.mp3',
    sizeText: '5KB',
    typeText: 'id3 encoded MP3 audio',
  });

  /**
   * The preview resides in the <files-safe-media> shadow DOM, which
   * is a child of the #quick-view shadow DOM.
   */
  const albumArtWebView = ['#quick-view', '#audio-artwork', previewTag];

  // Open Files app on Downloads containing the audio test file.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [id3Audio], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, id3Audio.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  // Wait until the preview has loaded the album image of the audio file.
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [albumArtWebView, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('audio/mpeg', mimeType);

  // Check: the audio album metadata should also be displayed.
  const album = await getQuickViewMetadataBoxField(appId, 'Album');
  chrome.test.assertEq(album, 'OK Computer');
}

/**
 * Tests opening Quick View containing an image with extension 'jpg'.
 */
export async function openQuickViewImageJpg() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.smallJpeg.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.smallJpeg], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.smallJpeg.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);
  await checkBackgroundColor(backgroundColor[0]!);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/jpeg', mimeType);
}

/**
 * Tests opening Quick View containing an image with extension 'jpeg'.
 */
export async function openQuickViewImageJpeg() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.sampleJpeg.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.sampleJpeg], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.sampleJpeg.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);
  await checkBackgroundColor(backgroundColor[0]!);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/jpeg', mimeType);
}

/**
 * Tests that opening Quick View on a JPEG image with EXIF displays the EXIF
 * information in the QuickView Metadata Box.
 */
export async function openQuickViewImageExif() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.exifImage.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.exifImage], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.exifImage.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/jpeg', mimeType);

  // Check: the correct file modified time should be displayed.
  const time = await getQuickViewMetadataBoxField(appId, 'Date modified');
  chrome.test.assertEq('Jan 18, 2038, 1:02 AM', time);

  // Check: the correct image EXIF metadata should be displayed.
  const size = await getQuickViewMetadataBoxField(appId, 'Dimensions');
  chrome.test.assertEq('378 x 272', size);
  const model = await getQuickViewMetadataBoxField(appId, 'Device model');
  chrome.test.assertEq('FinePix S5000', model);
  const film = await getQuickViewMetadataBoxField(appId, 'Device settings');
  chrome.test.assertEq('f/2.8 0.004 5.7mm ISO200', film);
}

/**
 * Tests opening Quick View on an RAW image. The RAW image has EXIF and that
 * information should be displayed in the QuickView metadata box.
 */
export async function openQuickViewImageRaw() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.rawImage.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.rawImage], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.rawImage.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/x-olympus-orf', mimeType);

  // Check: the RAW image EXIF metadata should be displayed.
  const size = await getQuickViewMetadataBoxField(appId, 'Dimensions');
  chrome.test.assertEq('4608 x 3456', size);
  const model = await getQuickViewMetadataBoxField(appId, 'Device model');
  chrome.test.assertEq('E-M1', model);
  const film = await getQuickViewMetadataBoxField(appId, 'Device settings');
  chrome.test.assertEq('f/8 0.002 12mm ISO200', film);
}

/**
 * Tests opening Quick View on an RAW .NEF image and that the dimensions
 * shown in the metadata box respect the image EXIF orientation.
 */
export async function openQuickViewImageRawWithOrientation() {
  const caller = getCaller();

  /**
   * The <files-safe-media type="image"> element is a shadow DOM child of
   * the #quick-view element, and has a shadow DOM child <webview> or <iframe>
   * for the preview.
   */
  const filesSafeMedia = ['#quick-view', 'files-safe-media[type="image"]'];

  // Open Files app on Downloads containing ENTRIES.rawNef.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.nefImage], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.nefImage.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    const preview = filesSafeMedia.concat([previewTag]);
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct image dimensions should be displayed.
  const size = await getQuickViewMetadataBoxField(appId, 'Dimensions');
  chrome.test.assertEq('1324 x 4028', size);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/tiff', mimeType);

  // Get the fileSafeMedia element preview thumbnail image size.
  const element = await remoteCall.waitForElement(appId, filesSafeMedia);
  const image = new Image();
  let imageSize = '';
  image.onload = () => {
    imageSize = `${image.naturalWidth} x ${image.naturalHeight}`;
  };

  const sourceContent = JSON.parse(element.attributes['src']!);

  chrome.test.assertTrue(!!sourceContent.data);
  image.src = sourceContent.data as string;

  // Check: the preview thumbnail should have an orientated size.
  await repeatUntil(async () => {
    if (!image.complete || imageSize !== '120 x 160') {
      return pending(caller, 'Waiting for preview thumbnail size.');
    }

    return;
  });
}

/**
 * Tests opening Quick View with a VP8X format WEBP image.
 */
export async function openQuickViewImageWebp() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.rawImage.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.webpImage], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.webpImage.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/webp', mimeType);

  // Check: the correct dimensions should be displayed.
  const size = await getQuickViewMetadataBoxField(appId, 'Dimensions');
  chrome.test.assertEq('400 x 175', size);
}

/**
 * Tests that opening Quick View on an image and clicking the image does not
 * focus the image. Instead, the user should still be able to cycle through
 * file list items in Quick View: crbug.com/1038835.
 */
export async function openQuickViewImageClick() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing two images.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.desktop, ENTRIES.image3], []);

  // Open the first image in Quick View.
  await openQuickViewEx(appId, ENTRIES.desktop.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  let mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/png', mimeType);

  // Click the image in Quick View to attempt to focus it.
  await remoteCall.waitAndClickElement(appId, preview);

  // Press the down arrow key to attempt to select the next file.
  const downArrow = ['#quick-view', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));

  // Wait for the Quick View preview to load and display its content.
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct mimeType should be displayed.
  mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/jpeg', mimeType);

  // Check: Quick View should be able to close.
  await closeQuickViewEx(appId);
}

/**
 * Tests that opening a broken image in Quick View displays the "no-preview
 * available" generic icon and has a [load-error] attribute.
 */
export async function openQuickViewBrokenImage() {
  const caller = getCaller();

  /**
   * The [generic-thumbnail] element resides in the #quick-view shadow DOM
   * as a child of .no-preview-container which is a sibling of the
   * files-safe-media[type="image"] element.
   */
  const genericThumbnail = [
    '#quick-view',
    'files-safe-media[type="image"][hidden] + .no-preview-container > [generic-thumbnail="image"]',
  ];

  // Open Files app on Downloads containing ENTRIES.brokenJpeg.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.brokenJpeg], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.brokenJpeg.nameText);

  // Check: the quick view element should have a 'load-error' attribute.
  await remoteCall.waitForElement(appId, '#quick-view[load-error]');

  // Wait for the generic thumbnail to load and display its content.
  function checkForGenericThumbnail(elements: ElementObject[]) {
    const haveElements = Array.isArray(elements) && elements.length === 1;
    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
      return pending(caller, 'Waiting for generic thumbnail to load.');
    }
    return;
  }

  // Check: the generic thumbnail icon should be displayed.
  await repeatUntil(async () => {
    return checkForGenericThumbnail(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [genericThumbnail, ['display']]));
  });
}

/**
 * Tests opening Quick View containing a video.
 */
export async function openQuickViewVideo() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="video"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="video"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.webm video.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.webm], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.webm.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewVideoLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewVideoLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);
  await checkBackgroundColor(backgroundColor[0]!);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('video/webm', mimeType);

  // Close Quick View.
  await closeQuickViewEx(appId);

  // Check: closing Quick View should remove the video <files-safe-media>
  // preview element, so it stops playing the video. crbug.com/970192
  await remoteCall.waitForElementLost(appId, preview);
}

/**
 * Tests opening Quick View containing a video on Drive.
 */
export async function openQuickViewVideoOnDrive() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="video"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="video"]', previewTag];

  // Open Files app on Downloads containing ENTRIES.webm video.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.webm]);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.webm.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewVideoLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewVideoLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Get the preview document.body backgroundColor style.
  const getBackgroundStyle =
      'window.getComputedStyle(document.body).backgroundColor';
  const backgroundColor = await remoteCall.executeJsInPreviewTag<string[]>(
      appId, preview, getBackgroundStyle);
  chrome.test.assertTrue(!!backgroundColor);
  await checkBackgroundColor(backgroundColor[0]!);

  // Check: the correct mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('video/webm', mimeType);

  // Close Quick View.
  await closeQuickViewEx(appId);

  // Check: closing Quick View should remove the video <files-safe-media>
  // preview element, so it stops playing the video. crbug.com/970192
  await remoteCall.waitForElementLost(appId, preview);
}

/**
 * Tests opening Quick View with multiple files and using the up/down arrow
 * keys to select and view their content.
 */
export async function openQuickViewKeyboardUpDownChangesView() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing two text files.
  const files = [ENTRIES.hello, ENTRIES.tallText];
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);

  // Open the last file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallText.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Press the down arrow key to select the next file.
  const downArrow = ['#quick-view', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    if (!text || !text[0] || !text[0].includes('This is a sample file')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });

  // Press the up arrow key to select the previous file.
  const upArrow = ['#quick-view', 'ArrowUp', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, upArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    if (!text || !text[0] || !text[0].includes('42 tall text')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });
}

/**
 * Tests opening Quick View with multiple files and using the left/right arrow
 * keys to select and view their content.
 */
export async function openQuickViewKeyboardLeftRightChangesView() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing two text files.
  const files = [ENTRIES.hello, ENTRIES.tallText];
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);

  // Open the last file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallText.nameText);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Press the right arrow key to select the next file item.
  const rightArrow = ['#quick-view', 'ArrowRight', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, rightArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    if (!text || !text[0] || !text[0].includes('This is a sample file')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });

  // Press the left arrow key to select the previous file item.
  const leftArrow = ['#quick-view', 'ArrowLeft', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, leftArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    if (!text || !text[0] || !text[0].includes('42 tall text')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });
}

/**
 * Tests that the metadatabox can be toggled opened/closed by pressing the
 * Enter key on the Quick View toolbar info button.
 */
export async function openQuickViewToggleInfoButtonKeyboard() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Check: the metadatabox should be open.
  const metaShown = ['#quick-view', '#contentPanel[metadata-box-active]'];
  await remoteCall.waitForElement(appId, metaShown);

  // The toolbar info button query differs in files-ng.
  const quickView = await remoteCall.waitForElement(appId, ['#quick-view']);
  let infoButton = ['#quick-view', '#metadata-button'];
  if (quickView.attributes['files-ng'] !== undefined) {
    infoButton = ['#quick-view', '#info-button'];
  }

  // Press Enter key on the info button.
  const key = [infoButton, 'Enter', false, false, false];
  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key);

  // Check: the metadatabox should close.
  await remoteCall.waitForElementLost(appId, metaShown);

  // Press Enter key on the info button.
  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key);

  // Check: the metadatabox should open.
  await remoteCall.waitForElement(appId, metaShown);
}

/**
 * Tests that the metadatabox can be toggled opened/closed by clicking the
 * the Quick View toolbar info button.
 */
export async function openQuickViewToggleInfoButtonClick() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Check: the metadatabox should be open.
  const metaShown = ['#quick-view', '#contentPanel[metadata-box-active]'];
  await remoteCall.waitForElement(appId, metaShown);

  // The toolbar info button query differs in files-ng.
  const quickView = await remoteCall.waitForElement(appId, ['#quick-view']);
  let infoButton = ['#quick-view', '#metadata-button'];
  if (quickView.attributes['files-ng'] !== undefined) {
    infoButton = ['#quick-view', '#info-button'];
  }

  // Click the info button.
  await remoteCall.waitAndClickElement(appId, infoButton);

  // Check: the metadatabox should close.
  await remoteCall.waitForElementLost(appId, metaShown);

  // Click the info button.
  await remoteCall.waitAndClickElement(appId, infoButton);

  // Check: the metadatabox should open.
  await remoteCall.waitForElement(appId, metaShown);
}

/**
 * Tests that Quick View opens with multiple files selected.
 */
export async function openQuickViewWithMultipleFiles() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Add item 3 to the check-selection, ENTRIES.desktop.
  const downKey = ['#file-list', 'ArrowDown', false, false, false];
  for (let i = 0; i < 3; i++) {
    chrome.test.assertTrue(
        !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downKey),
        'ArrowDown failed');
  }
  const ctrlSpace = ['#file-list', ' ', true, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Add item 5 to the check-selection, ENTRIES.hello.
  const ctrlDown = ['#file-list', 'ArrowDown', true, false, false];
  for (let i = 0; i < 2; i++) {
    chrome.test.assertTrue(
        !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
        'Ctrl+ArrowDown failed');
  }
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Open Quick View with the check-selected files.
  await openQuickViewMultipleSelection(appId, ['Desktop', 'hello']);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  // Check: ENTRIES.desktop should be displayed in the preview.
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Check: the correct file mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('image/png', mimeType);
}

/**
 * Tests that Quick View displays text files when multiple files are
 * selected.
 */
export async function openQuickViewWithMultipleFilesText() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  const files = [ENTRIES.tallText, ENTRIES.hello, ENTRIES.smallJpeg];
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);

  // Add item 1 to the check-selection, ENTRIES.smallJpeg.
  const downKey = ['#file-list', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downKey),
      'ArrowDown failed');
  const ctrlSpace = ['#file-list', ' ', true, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Add item 3 to the check-selection, ENTRIES.hello.
  const ctrlDown = ['#file-list', 'ArrowDown', true, false, false];
  for (let i = 0; i < 2; i++) {
    chrome.test.assertTrue(
        !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
        'Ctrl+ArrowDown failed');
  }
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Open Quick View with the check-selected files.
  await openQuickViewMultipleSelection(appId, ['small', 'hello']);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  // Check: the image file should be displayed in the content panel.
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const textView = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Press the down arrow key to select the next file.
  const downArrow = ['#quick-view', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  // Check: the text file should be displayed in the content panel.
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [textView, ['display']]));
  });

  // Check: the open button should be displayed.
  await remoteCall.waitForElement(
      appId, ['#quick-view', '#open-button:not([hidden])']);
}

/**
 * Tests that Quick View displays pdf files when multiple files are
 * selected.
 */
export async function openQuickViewWithMultipleFilesPdf() {
  const caller = getCaller();

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  const files = [ENTRIES.tallPdf, ENTRIES.desktop, ENTRIES.smallJpeg];
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);

  // Add item 1 to the check-selection, ENTRIES.smallJpeg.
  const downKey = ['#file-list', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downKey),
      'ArrowDown failed');
  const ctrlSpace = ['#file-list', ' ', true, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Add item 3 to the check-selection, ENTRIES.tallPdf.
  const ctrlDown = ['#file-list', 'ArrowDown', true, false, false];
  for (let i = 0; i < 2; i++) {
    chrome.test.assertTrue(
        !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
        'Ctrl+ArrowDown failed');
  }
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Open Quick View with the check-selected files.
  await openQuickViewMultipleSelection(appId, ['small', 'tall']);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  // Check: the image file should be displayed in the content panel.
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  /**
   * The PDF preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const pdfView = ['#quick-view', `#dialog[open] ${previewTag}.content`];

  // Press the down arrow key to select the next file.
  const downArrow = ['#quick-view', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewPdfLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  // Check: the pdf file should be displayed in the content panel.
  await repeatUntil(async () => {
    return checkPreviewPdfLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [pdfView, ['display']]));
  });

  // Check: the open button should be displayed.
  await remoteCall.waitForElement(
      appId, ['#quick-view', '#open-button:not([hidden])']);
}

/**
 * Tests that the content panel changes when using the up/down arrow keys
 * when multiple files are selected.
 */
export async function openQuickViewWithMultipleFilesKeyboardUpDown() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing three text files.
  const files = [ENTRIES.hello, ENTRIES.tallText, ENTRIES.plainText];
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);

  // Add item 1 to the check-selection, ENTRIES.tallText.
  const downKey = ['#file-list', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downKey),
      'ArrowDown failed');
  const ctrlSpace = ['#file-list', ' ', true, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Add item 3 to the check-selection, ENTRIES.hello.
  const ctrlDown = ['#file-list', 'ArrowDown', true, false, false];
  for (let i = 0; i < 2; i++) {
    chrome.test.assertTrue(
        !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
        'Ctrl+ArrowDown failed');
  }
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Open Quick View with the check-selected files.
  await openQuickViewMultipleSelection(appId, ['tall', 'hello']);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Press the down arrow key to select the next file.
  const downArrow = ['#quick-view', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    // Check: the content of ENTRIES.hello should be shown.
    if (!text || !text[0] || !text[0].includes('This is a sample file')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });

  // Press the up arrow key to select the previous file.
  const upArrow = ['#quick-view', 'ArrowUp', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, upArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    // Check: the content of ENTRIES.tallText should be shown.
    if (!text || !text[0] || !text[0].includes('42 tall text')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });
}

/**
 * Tests that the content panel changes when using the left/right arrow keys
 * when multiple files are selected.
 */
export async function openQuickViewWithMultipleFilesKeyboardLeftRight() {
  const caller = getCaller();

  /**
   * The text preview resides in the #quick-view shadow DOM, as a child of
   * the #dialog element.
   */
  const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];

  // Open Files app on Downloads containing three text files.
  const files = [ENTRIES.hello, ENTRIES.tallText, ENTRIES.plainText];
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);

  // Add item 1 to the check-selection, ENTRIES.tallText.
  const downKey = ['#file-list', 'ArrowDown', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downKey),
      'ArrowDown failed');
  const ctrlSpace = ['#file-list', ' ', true, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Add item 3 to the check-selection, ENTRIES.hello.
  const ctrlDown = ['#file-list', 'ArrowDown', true, false, false];
  for (let i = 0; i < 2; i++) {
    chrome.test.assertTrue(
        !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
        'Ctrl+ArrowDown failed');
  }
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Ctrl+Space failed');

  // Open Quick View with the check-selected files.
  await openQuickViewMultipleSelection(appId, ['tall', 'hello']);

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewTextLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || !elements[0]!.attributes['src']) {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewTextLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [preview, ['display']]));
  });

  // Press the right arrow key to select the next file item.
  const rightArrow = ['#quick-view', 'ArrowRight', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, rightArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors(
                     appId, preview, getTextContent) as string[];
    // Check: the content of ENTRIES.hello should be shown.
    if (!text || !text[0] || !text[0].includes('This is a sample file')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });

  // Press the left arrow key to select the previous file item.
  const leftArrow = ['#quick-view', 'ArrowLeft', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, leftArrow));

  // Wait until the preview displays that file's content.
  await repeatUntil(async () => {
    const getTextContent = contentWindowQuery + '.document.body.textContent';
    const text = await executeJsInPreviewTagAndCatchErrors<string>(
        appId, preview, getTextContent);
    // Check: the content of ENTRIES.tallText should be shown.
    if (!text || !text[0] || !text[0].includes('42 tall text')) {
      return pending(caller, 'Waiting for preview content.');
    }
    return;
  });
}

/**
 * Tests opening Quick View and closing with Escape key returns focus to file
 * list.
 */
export async function openQuickViewAndEscape() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Hit Escape key to close Quick View.
  const panelElements = ['#quick-view', '#contentPanel'];
  const key = [panelElements, 'Escape', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key),
      'fakeKeyDown Escape failed');

  // Check: the Quick View element should not be shown.
  await waitQuickViewClose(appId);

  // Check: the file list should gain the focus.
  const element = await remoteCall.waitForElement(appId, '#file-list:focus');
  chrome.test.assertEq(
      'file-list', element.attributes['id'], '#file-list should be focused');
}

/**
 * Test opening Quick View when Directory Tree is focused it should display if
 * there is only 1 file/folder selected in the file list.
 */
export async function openQuickViewFromDirectoryTree() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Focus Directory Tree.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.focusTree();

  // Ctrl+A to select the only file.
  const ctrlA = [directoryTree.rootSelector, 'a', true, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...ctrlA);

  // Use selection menu button to open Quick View.
  await remoteCall.simulateUiClick(
      appId, '#selection-menu-button:not([hidden])');

  // Wait because WebUI Menu ignores the following click if it happens in
  // <200ms from the previous click.
  await wait(300);

  // Click the Menu item to show the Quick View.
  const getInfoMenuItem = '#file-context-menu:not([hidden]) ' +
      ' [command="#get-info"]:not([hidden])';
  await remoteCall.simulateUiClick(appId, getInfoMenuItem);

  // Check: the Quick View dialog should be shown.
  const caller = getCaller();
  await repeatUntil(async () => {
    const query = ['#quick-view', '#dialog[open]'];
    const elements = await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [query, ['display']]);
    const haveElements = Array.isArray(elements) && elements.length !== 0;
    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
      return pending(caller, 'Waiting for Quick View to open.');
    }
    return true;
  });
}

/**
 * Tests the tab-index focus order when sending tab keys when an image file is
 * shown in Quick View.
 */
export async function openQuickViewTabIndexImage() {
  // Prepare a list of tab-index focus queries.
  const tabQueries = [
    {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Open"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Delete"]:focus`]},
    {'query': ['#quick-view', `[aria-label="File info"]:focus`]},
  ];

  // Open Files app on Downloads containing ENTRIES.smallJpeg.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.smallJpeg], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.smallJpeg.nameText);

  for (const query of tabQueries) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: the queried element should gain the focus.
    await remoteCall.waitForElement(appId, query.query);
  }
}

/**
 * Tests the tab-index focus order when sending tab keys when a text file is
 * shown in Quick View.
 */
export async function openQuickViewTabIndexText() {
  // Prepare a list of tab-index focus queries.
  const tabQueries = [
    {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Open"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Delete"]:focus`]},
    {'query': ['#quick-view', `[aria-label="File info"]:focus`]},
    {'query': ['#quick-view']},  // Tab past the content panel.
    {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
  ];

  // Open Files app on Downloads containing ENTRIES.tallText.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallText], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallText.nameText);

  for (const query of tabQueries) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: the queried element should gain the focus.
    await remoteCall.waitForElement(appId, query.query);
  }
}

/**
 * Tests the tab-index focus order when sending tab keys when an HTML file is
 * shown in Quick View.
 */
export async function openQuickViewTabIndexHtml() {
  // Prepare a list of tab-index focus queries.
  const tabQueries = [
    {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Open"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Delete"]:focus`]},
    {'query': ['#quick-view', `[aria-label="File info"]:focus`]},
  ];

  // Open Files app on Downloads containing ENTRIES.tallHtml.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.tallHtml], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.tallHtml.nameText);

  for (const query of tabQueries) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: the queried element should gain the focus.
    await remoteCall.waitForElement(appId, query.query);
  }
}

/**
 * Tests the tab-index focus order when sending tab keys when an audio file
 * is shown in Quick View.
 */
export async function openQuickViewTabIndexAudio() {
  // Open Files app on Downloads containing ENTRIES.beautiful song.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.beautiful.nameText);

  // Prepare a list of tab-index focus queries.
  const tabQueries = [
    {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Open"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Delete"]:focus`]},
    {'query': ['#quick-view', `[aria-label="File info"]:focus`]},
  ];

  for (const query of tabQueries) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: the queried element should gain the focus.
    await remoteCall.waitForElement(appId, query.query);
  }

  // Send tab keys until Back gains the focus again.
  while (true) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: back should eventually get the focus again.
    const activeElement =
        await remoteCall.callRemoteTestUtil<ElementObject|null>(
            'deepGetActiveElement', appId, []);
    if (activeElement && activeElement.attributes['aria-label'] === 'Back') {
      break;
    }
  }
}

/**
 * Tests the tab-index focus order when sending tab keys when a video file is
 * shown in Quick View.
 */
export async function openQuickViewTabIndexVideo() {
  // Open Files app on Downloads containing ENTRIES.webm video.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.webm], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.webm.nameText);

  // Prepare a list of tab-index focus queries.
  const tabQueries = [
    {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Open"]:focus`]},
    {'query': ['#quick-view', `[aria-label="Delete"]:focus`]},
    {'query': ['#quick-view', `[aria-label="File info"]:focus`]},
  ];

  for (const query of tabQueries) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: the queried element should gain the focus.
    await remoteCall.waitForElement(appId, query.query);
  }

  // Send tab keys until Back gains the focus again.
  while (true) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: back should eventually get the focus again.
    const activeElement =
        await remoteCall.callRemoteTestUtil<ElementObject|null>(
            'deepGetActiveElement', appId, []);
    if (activeElement && activeElement.attributes['aria-label'] === 'Back') {
      break;
    }
  }
}

/**
 * Tests that the tab-index focus stays within the delete confirm dialog.
 */
export async function openQuickViewTabIndexDeleteDialog() {
  // Open Files app.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Open a USB file in Quick View. USB delete never uses trash and always
  // shows the delete dialog.
  await mountAndSelectUsb(appId);
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Open the Quick View delete confirm dialog.
  const deleteKey = ['#quick-view', 'Delete', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, deleteKey),
      'Pressing Delete failed.');

  // Check: the Quick View delete confirm dialog should open.
  await remoteCall.waitForElement(
      appId,  // The <cr-dialog> is a child of the Quick View shadow DOM.
      ['#quick-view', '.cr-dialog-container.shown .cr-dialog-cancel:focus']);

  // Prepare a list of tab-index focus queries.
  const tabQueries = [
    {'query': ['#quick-view', '.cr-dialog-ok:not([hidden])']},
    {'query': ['#quick-view', '.cr-dialog-cancel:not([hidden])']},
  ];

  for (const query of tabQueries) {
    // Make the browser dispatch a tab key event to FilesApp.
    const result =
        await sendTestMessage({name: 'dispatchTabKey', shift: false});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    // Note: Allow 500ms between key events to filter out the focus
    // traversal problems noted in crbug.com/907380#c10.
    await wait(500);

    // Check: the queried element should gain the focus.
    await remoteCall.waitForElement(appId, query.query);
  }
}

/**
 * Tests deleting an item from Quick View when in single select mode, and
 * that Quick View closes when there are no more items to view.
 */
export async function openQuickViewAndDeleteSingleSelection() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Press delete key.
  const deleteKey = ['#quick-view', 'Delete', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, deleteKey),
      'Pressing Delete failed.');

  // Check: |hello.txt| should have been deleted.
  await remoteCall.waitForElementLost(
      appId, '#file-list [file-name="hello.txt"]');

  // Check: the Quick View dialog should close.
  await waitQuickViewClose(appId);
}

/**
 * Tests deleting an item from Quick View while in check-selection mode.
 * Deletes the item at the bottom of the file list, and checks that
 * the item below the item deleted is shown in Quick View after the item's
 * deletion.
 */
export async function openQuickViewAndDeleteCheckSelection() {
  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  const caller = getCaller();

  // Ctrl+A to select all files in the file-list.
  const ctrlA = ['#file-list', 'a', true, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlA),
      'Ctrl+A failed');

  // Open Quick View via its keyboard shortcut.
  const space = ['#file-list', ' ', false, false, false];
  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, space);

  // Check: the Quick View dialog should be shown.
  await waitQuickViewOpen(appId);

  // Press the up arrow to go to the last file in the selection.
  const quickViewArrowUp = ['#quick-view', 'ArrowUp', false, false, false];
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeKeyDown', appId, quickViewArrowUp));

  // Press delete key.
  const deleteKey = ['#quick-view', 'Delete', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, deleteKey),
      'Pressing Delete failed.');

  // Check: |hello.txt| should have been deleted.
  await remoteCall.waitForElementLost(
      appId, '#file-list [file-name="hello.txt"]');

  // Check: Quick View should display the entry below |hello.txt|,
  // which is |world.ogv|.
  function checkPreviewVideoLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }

  const videoWebView =
      ['#quick-view', 'files-safe-media[type="video"]', previewTag];
  await repeatUntil(async () => {
    return checkPreviewVideoLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [videoWebView, ['display']]));
  });

  // Check: the mimeType of |world.ogv| should be 'video/ogg'.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('video/ogg', mimeType);
}

/**
 * Tests that deleting all items in a check-selection closes the Quick View.
 */
export async function openQuickViewDeleteEntireCheckSelection() {
  const caller = getCaller();

  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Check-select Beautiful Song.ogg and My Desktop Background.png.
  const ctrlDown = ['#file-list', 'ArrowDown', true, false, false];
  const ctrlSpace = ['#file-list', ' ', true, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
      'Pressing Ctrl+Down failed.');

  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
      'Pressing Ctrl+Down failed.');

  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Pressing Ctrl+Space failed.');

  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown),
      'Pressing Ctrl+Down failed.');

  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace),
      'Pressing Ctrl+Space failed.');

  // Open Quick View on the check-selected files.
  await openQuickViewMultipleSelection(appId, ['Beautiful', 'Desktop']);

  /**
   * The preview resides in the <files-safe-media type="audio"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const audioWebView =
      ['#quick-view', 'files-safe-media[type="audio"]', previewTag];

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewAudioLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewAudioLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [audioWebView, ['display']]));
  });

  // Press delete.
  const deleteKey = ['#quick-view', 'Delete', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, deleteKey),
      'Pressing Delete failed.');

  // Check: |Beautiful Song.ogg| should have been deleted.
  await remoteCall.waitForElementLost(
      appId, '#file-list [file-name="Beautiful Song.ogg"]');

  /**
   * The preview resides in the <files-safe-media type="image"> shadow DOM,
   * which is a child of the #quick-view shadow DOM.
   */
  const imageWebView =
      ['#quick-view', 'files-safe-media[type="image"]', previewTag];

  // Wait for the Quick View preview to load and display its content.
  function checkPreviewImageLoaded(elements: ElementObject[]) {
    let haveElements = Array.isArray(elements) && elements.length === 1;
    if (haveElements) {
      haveElements = elements[0]!.styles!['display']!.includes('block');
    }
    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
      return pending(caller, `Waiting for ${previewTag} to load.`);
    }
    return;
  }
  await repeatUntil(async () => {
    return checkPreviewImageLoaded(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [imageWebView, ['display']]));
  });

  // Press delete.
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, deleteKey),
      'Pressing Delete failed.');

  // Check: |My Desktop Background.png| should have been deleted.
  await remoteCall.waitForElementLost(
      appId, '#file-list [file-name="My Desktop Background.png"]');

  // Check: the Quick View dialog should close.
  await waitQuickViewClose(appId);
}

/**
 * Tests that an item can be deleted using the Quick View delete button.
 */
export async function openQuickViewClickDeleteButton() {
  // Open Files app on Downloads containing ENTRIES.hello.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.hello.nameText);

  // Click the Quick View delete button.
  const quickViewDeleteButton = ['#quick-view', '#delete-button:not([hidden])'];
  await remoteCall.waitAndClickElement(appId, quickViewDeleteButton);

  // Check: |hello.txt| should have been deleted.
  await remoteCall.waitForElementLost(
      appId, '#file-list [file-name="hello.txt"]');

  // Check: the Quick View dialog should close.
  await waitQuickViewClose(appId);
}

/**
 * Tests that the delete button is not shown if the file displayed in Quick
 * View cannot be deleted.
 */
export async function openQuickViewDeleteButtonNotShown() {
  // Open Files app on My Files
  const appId = await remoteCall.openNewWindow('');

  // Wait for the file list to appear.
  await remoteCall.waitForElement(appId, '#file-list');

  // Check: My Files should contain the expected entries.
  const expectedRows = [
    ['Play files', '--', 'Folder'],
    ['Downloads', '--', 'Folder'],
    ['Linux files', '--', 'Folder'],
  ];
  await remoteCall.waitForFiles(
      appId, expectedRows, {ignoreLastModifiedTime: true});

  // Open Play files in Quick View, which cannot be deleted.
  await openQuickViewEx(appId, 'Play files');

  // Check: the delete button should not be shown.
  const quickViewDeleteButton = ['#quick-view', '#delete-button[hidden]'];
  await remoteCall.waitForElement(appId, quickViewDeleteButton);
}

/**
 * Tests that the correct WayToOpen UMA histogram is recorded when opening
 * a single file via Quick View using "Get Info" from the context menu.
 */
export async function openQuickViewUmaViaContextMenu() {
  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Record the UMA value's bucket count before we use the menu option.
  const contextMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  // Open Quick View via the entry context menu.
  await openQuickViewViaContextMenu(appId, ENTRIES.hello.nameText);

  // Check: the context menu histogram should increment by 1.
  const contextMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  chrome.test.assertEq(
      contextMenuUMAValueAfterOpening, contextMenuUMAValueBeforeOpening + 1);
  chrome.test.assertEq(
      selectionMenuUMAValueAfterOpening, selectionMenuUMAValueBeforeOpening);
}

/**
 * Tests that the correct WayToOpen UMA histogram is recorded when using
 * Quick View in check-select mode using "Get Info" from the context
 * menu.
 */
export async function openQuickViewUmaForCheckSelectViaContextMenu() {
  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Record the UMA value's bucket count before we use the menu option.
  const contextMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  // Ctrl+A to select all files in the file-list.
  const ctrlA = ['#file-list', 'a', true, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...ctrlA);

  // Open Quick View using the context menu.
  await openQuickViewViaContextMenu(appId, ENTRIES.hello.nameText);

  // Check: the context menu histogram should increment by 1.
  const contextMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  chrome.test.assertEq(
      contextMenuUMAValueAfterOpening, contextMenuUMAValueBeforeOpening + 1);
  chrome.test.assertEq(
      selectionMenuUMAValueAfterOpening, selectionMenuUMAValueBeforeOpening);
}

/**
 * Tests that the correct WayToOpen UMA histogram is recorded when using
 * Quick View in check-select mode using "Get Info" from the Selection
 * menu.
 */
export async function openQuickViewUmaViaSelectionMenu() {
  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Ctrl+A to select all files in the file-list.
  const ctrlA = ['#file-list', 'a', true, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...ctrlA);

  const caller = getCaller();

  // Wait until the selection menu is visible.
  function checkElementsDisplayVisible(elements: ElementObject[]) {
    chrome.test.assertTrue(Array.isArray(elements));
    if (elements.length === 0 || elements[0]!.styles!['display'] === 'none') {
      return pending(caller, 'Waiting for Selection Menu to be visible.');
    }
    return;
  }

  await repeatUntil(async () => {
    const elements = ['#selection-menu-button'];
    return checkElementsDisplayVisible(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [elements, ['display']]));
  });

  // Record the UMA value's bucket count before we use the menu option.
  const contextMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  // Click the Selection Menu button. Using fakeMouseClick causes
  // the focus to switch from file-list such that crbug.com/1046997
  // cannot be tested, use simulateUiClick() instead.
  await remoteCall.simulateUiClick(
      appId, '#selection-menu-button:not([hidden])');

  // Wait because WebUI Menu ignores the following click if it happens in
  // <200ms from the previous click.
  await wait(300);

  // Click the file-list context menu "Get info" command.
  await remoteCall.simulateUiClick(
      appId,
      '#file-context-menu:not([hidden]) [command="#get-info"]:not([hidden])');

  // Check: the Quick View dialog should be shown.
  await repeatUntil(async () => {
    const query = ['#quick-view', '#dialog[open]'];
    const elements = await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [query, ['display']]);
    const haveElements = Array.isArray(elements) && elements.length !== 0;
    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
      return pending(caller, 'Waiting for Quick View to open.');
    }
    return true;
  });

  // Check: the context menu histogram should increment by 1.
  const contextMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  chrome.test.assertEq(
      contextMenuUMAValueAfterOpening, contextMenuUMAValueBeforeOpening);
  chrome.test.assertEq(
      selectionMenuUMAValueAfterOpening,
      selectionMenuUMAValueBeforeOpening + 1);
}

/**
 * Tests that the correct WayToOpen UMA histogram is recorded when using
 * Quick View in check-select mode using "Get Info" from the context
 * menu opened via keyboard tabbing (not mouse).
 */
export async function openQuickViewUmaViaSelectionMenuKeyboard() {
  const caller = getCaller();

  // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

  // Ctrl+A to select all files in the file-list.
  const ctrlA = ['#file-list', 'a', true, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...ctrlA);

  // Wait until the selection menu is visible.
  function checkElementsDisplayVisible(elements: ElementObject[]) {
    chrome.test.assertTrue(Array.isArray(elements));
    if (elements.length === 0 || elements[0]!.styles!['display'] === 'none') {
      return pending(caller, 'Waiting for Selection Menu to be visible.');
    }
    return;
  }

  await repeatUntil(async () => {
    const elements = ['#selection-menu-button'];
    return checkElementsDisplayVisible(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [elements, ['display']]));
  });

  // Record the UMA value's bucket count before we use the menu option.
  const contextMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueBeforeOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  // Tab to the Selection Menu button.
  await repeatUntil(async () => {
    const result = await sendTestMessage({name: 'dispatchTabKey'});
    chrome.test.assertEq(
        'tabKeyDispatched', result, 'Tab key dispatch failure');

    const element = await remoteCall.callRemoteTestUtil<ElementObject|null>(
        'getActiveElement', appId, []);

    if (element && element.attributes['id'] === 'selection-menu-button') {
      return true;
    }
    return pending(
        caller, 'Waiting for selection-menu-button to become active');
  });

  // Key down to the "Get Info" command.
  await repeatUntil(async () => {
    const keyDown =
        ['#selection-menu-button', 'ArrowDown', false, false, false];
    chrome.test.assertTrue(
        await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, keyDown));

    const element = await remoteCall.callRemoteTestUtil<ElementObject|null>(
        'getActiveElement', appId, []);

    if (element && element.attributes['command'] === '#get-info') {
      return true;
    }
    return pending(caller, 'Waiting for get-info command to become active');
  });

  // Select the "Get Info" command using the Enter key.
  const keyEnter = ['#selection-menu-button', 'Enter', false, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, keyEnter));

  // Check: the Quick View dialog should be shown.
  await repeatUntil(async () => {
    const query = ['#quick-view', '#dialog[open]'];
    const elements = await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [query, ['display']]);
    const haveElements = Array.isArray(elements) && elements.length !== 0;
    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
      return pending(caller, 'Waiting for Quick View to open.');
    }
    return true;
  });

  // Check: the context menu histogram should increment by 1.
  const contextMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.CONTEXT_MENU);

  const selectionMenuUMAValueAfterOpening = await getHistogramCount(
      QuickViewUmaWayToOpenHistogramName,
      QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);

  chrome.test.assertEq(
      contextMenuUMAValueAfterOpening, contextMenuUMAValueBeforeOpening);
  chrome.test.assertEq(
      selectionMenuUMAValueAfterOpening,
      selectionMenuUMAValueBeforeOpening + 1);
}

/**
 * Tests that Quick View does not display a CSE file preview.
 */
export async function openQuickViewEncryptedFile() {
  const caller = getCaller();

  /**
   * The #innerContentPanel resides in the #quick-view shadow DOM as a child
   * of the #dialog element, and contains the file preview result.
   */
  const contentPanel = ['#quick-view', '#dialog[open] #innerContentPanel'];

  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.testCSEFile]);

  // Open the file in Quick View.
  await openQuickViewEx(appId, ENTRIES.testCSEFile.nameText);

  // Wait for the innerContentPanel to load and display its content.
  function checkInnerContentPanel(elements: ElementObject[]) {
    const haveElements = Array.isArray(elements) && elements.length === 1;
    if (!haveElements || elements[0]!.styles!['display'] !== 'flex') {
      return pending(caller, 'Waiting for inner content panel to load.');
    }
    // Check: the preview should not be shown.
    chrome.test.assertEq('No preview available', elements[0]!.innerText);
    return;
  }
  await repeatUntil(async () => {
    return checkInnerContentPanel(await remoteCall.callRemoteTestUtil(
        'deepQueryAllElements', appId, [contentPanel, ['display']]));
  });

  // Check: the correct file mimeType should be displayed.
  const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
  chrome.test.assertEq('Encrypted text/plain', mimeType);
}