[go: nahoru, domu]

blob: 8273bf7371e98f4280243b622eaf22ec293a0648 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './context_menu.js';
import './create_new_folder.js';
import './crostini.js';
import './directory_tree_context_menu.js';
import './dlp.js';
import './dlp_enterprise_connectors.js';
import './drive_specific.js';
import './file_dialog.js';
import './file_transfer_connector.js';
import './folder_shortcuts.js';
import './gear_menu.js';
import './holding_space.js';
import './install_linux_package_dialog.js';
import './recents.js';
import './search.js';
import './tab_index.js';
import './transfer.js';
import {FilesAppState} from '../prod/file_manager/shared_types.js';
import {RemoteCallFilesApp} from '../remote_call.js';
import {addEntries, getCaller, GetRootPathsResult, pending, repeatUntil, RootPath, sendBrowserTestCommand, sendTestMessage, TestEntryInfo} from '../test_util.js';
import {testcase} from '../testcase.js';
import {CHOOSE_ENTRY_PROPERTY} from './choose_entry_const.js';
import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
import {BASIC_CROSTINI_ENTRY_SET, BASIC_DRIVE_ENTRY_SET, BASIC_LOCAL_ENTRY_SET, FILE_MANAGER_EXTENSIONS_ID} from './test_data.js';
/** Application ID (URL) for File Manager System Web App (SWA). */
export const FILE_MANAGER_SWA_ID = 'chrome://file-manager';
export {FILE_MANAGER_EXTENSIONS_ID};
export let remoteCall: RemoteCallFilesApp;
/**
* Opens a Files app's main window.
* @param initialRoot Root path to be used as a default current directory during
* initialization. Can be null, for no default path.
* @param appState App state to be passed with on opening the Files app.
* @return Promise to be fulfilled after window creating.
*/
export async function openNewWindow(
initialRoot: null|string, appState?: null|FilesAppState): Promise<string> {
appState = appState ?? {};
if (initialRoot) {
const tail = `external${initialRoot}`;
appState.currentDirectoryURL = `filesystem:${FILE_MANAGER_SWA_ID}/${tail}`;
}
const launchDir = appState ? appState.currentDirectoryURL : undefined;
const type = appState ? appState.type : undefined;
const volumeFilter = appState ? appState.volumeFilter : undefined;
const searchQuery = appState ? appState.searchQuery : undefined;
const appId = await sendTestMessage({
name: 'launchFileManager',
launchDir,
type,
volumeFilter,
searchQuery,
});
return appId;
}
/**
* Opens a foreground window that makes a call to chrome.fileSystem.chooseEntry.
* This is due to the fact that this API shouldn't be called in the background
* page (see crbug.com/736930).
*
* @return Promise fulfilled when a foreground window opens.
*/
export async function openEntryChoosingWindow(
params: chrome.fileSystem.ChooseEntryOptions):
Promise<chrome.windows.Window> {
const json = JSON.stringify(params);
const url = 'file_manager/choose_entry.html?' +
new URLSearchParams({value: json}).toString();
return new Promise((resolve, reject) => {
chrome.windows.create({url, height: 600, width: 400}, (win) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(win);
}
});
});
}
/**
* Companion function to openEntryChoosingWindow function. This function waits
* until entry selected in a dialog shown by chooseEntry() is set.
* @return the entry set by the dialog shown via chooseEntry().
*/
export async function pollForChosenEntry(caller: string):
Promise<null|(Entry | Entry[])> {
await repeatUntil(() => {
if (window[CHOOSE_ENTRY_PROPERTY] !== undefined) {
return;
}
return pending(caller, 'Waiting for chooseEntry() result');
});
return window[CHOOSE_ENTRY_PROPERTY]!;
}
/**
* Opens a file dialog and waits for closing it.
* @param dialogParams Dialog parameters to be passed to
* openEntryChoosingWindow() function.
* @param volumeType Volume icon type passed to the directory page object's
* selectItemByType function.
* @param expectedSet Expected set of the entries.
* @param closeDialog Function to close the dialog.
* @param useBrowserOpen Whether to launch the select file dialog via a browser
* OpenFile() call.
* @param debug Whether to debug the waitForWindow().
* @return Promise to be fulfilled with the result entry of the dialog.
*/
export async function openAndWaitForClosingDialog(
dialogParams: chrome.fileSystem.ChooseEntryOptions, volumeType: string,
expectedSet: TestEntryInfo[], closeDialog: (a: string) => Promise<void>,
useBrowserOpen: boolean = false, debug: boolean = false): Promise<unknown> {
const caller = getCaller();
let resultPromise;
if (useBrowserOpen) {
await sendTestMessage({name: 'runSelectFileDialog'});
resultPromise = async () => {
return await sendTestMessage({name: 'waitForSelectFileDialogNavigation'});
};
} else {
await openEntryChoosingWindow(dialogParams);
resultPromise = () => {
return pollForChosenEntry(caller);
};
}
const appId = await remoteCall.waitForWindow(debug);
await remoteCall.waitForElement(appId, '#file-list');
await remoteCall.waitFor('isFileManagerLoaded', appId, true);
const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
await directoryTree.selectItemByType(volumeType);
await remoteCall.waitForFiles(
appId, TestEntryInfo.getExpectedRows(expectedSet));
await closeDialog(appId);
await repeatUntil(async () => {
const windows = await remoteCall.getWindows();
if (windows[appId] !== appId) {
return;
}
return pending(caller, 'Waiting for Window %s to hide.', appId);
});
return await resultPromise();
}
/**
* Opens a Files app's main window and waits until it is initialized. Fills
* the window with initial files. Should be called for the first window only.
* @param initialRoot Root path to be used as a default current directory during
* initialization. Can be null, for no default path.
* @param initialLocalEntries List of initial entries to load in Downloads
* (defaults to a basic entry set).
* @param initialDriveEntries List of initial entries to load in Google Drive
* (defaults to a basic entry set).
* @param appState App state to be passed with on opening the Files app.
* @return Promise to be fulfilled with the window ID.
*/
export async function setupAndWaitUntilReady(
initialRoot: null|string,
initialLocalEntries: TestEntryInfo[] = BASIC_LOCAL_ENTRY_SET,
initialDriveEntries: TestEntryInfo[] = BASIC_DRIVE_ENTRY_SET,
appState?: FilesAppState): Promise<string> {
const localEntriesPromise = addEntries(['local'], initialLocalEntries);
const driveEntriesPromise = addEntries(['drive'], initialDriveEntries);
const appId = await openNewWindow(initialRoot, appState);
await remoteCall.waitForElement(appId, '#detail-table');
// Wait until the elements are loaded in the table.
await Promise.all([
remoteCall.waitForFileListChange(appId, 0),
localEntriesPromise,
driveEntriesPromise,
]);
await remoteCall.waitFor('isFileManagerLoaded', appId, true);
return appId;
}
/**
* Returns the name of the given file list entry.
* @param fileListEntry An entry in a file list.
* @return Name of the file.
*/
export function getFileName(fileListEntry: string[]): string {
return fileListEntry[0] ?? '';
}
/**
* Returns the size of the given file list entry.
* @param fileListEntry An entry in a file list.
* @return Size of the file.
*/
export function getFileSize(fileListEntry: string[]): string {
return fileListEntry[1] ?? '';
}
/**
* Returns the type of the given file list entry.
* @param fileListEntry An entry in a file list.
* @return Type of the file.
*/
export function getFileType(fileListEntry: string[]): string {
return fileListEntry[2] ?? '';
}
/**
* For async function tests, wait for the test to complete, check for app errors
* unless skipped, and report the results.
* @param resultPromise A promise that resolves with the test result.
*/
async function awaitAsyncTestResult(resultPromise: Promise<void|any>) {
chrome.test.assertTrue(
resultPromise instanceof Promise, 'test did not return a Promise');
try {
await resultPromise;
} catch (error: any) {
// If the test has failed, ignore the exception and return.
if (error === 'chrome.test.failure') {
return;
}
// Otherwise, report the exception as a test failure. chrome.test.fail()
// emits an exception; catch it to avoid spurious logging about an uncaught
// exception.
try {
chrome.test.fail(error.stack || error);
} catch (_) {
return;
}
}
chrome.test.succeed();
}
/**
* When the FileManagerBrowserTest harness loads this test extension, request
* configuration and other details from that harness, including the test case
* name to run. Use the configuration/details to setup the test environment,
* then run the test case using chrome.test.RunTests.
*/
window.addEventListener('load', async () => {
// Request the guest mode state.
remoteCall = new RemoteCallFilesApp(FILE_MANAGER_SWA_ID);
const mode = await sendBrowserTestCommand({name: 'isInGuestMode'});
// Request the root entry paths.
if (JSON.parse(mode) !== chrome.extension.inIncognitoContext) {
return;
}
const paths = await sendBrowserTestCommand({name: 'getRootPaths'});
// Request the test case name.
const roots: GetRootPathsResult = JSON.parse(paths);
RootPath.DOWNLOADS = roots.downloads;
RootPath.MY_FILES = roots.my_files;
RootPath.DRIVE = roots.drive;
RootPath.ANDROID_FILES = roots.android_files;
const testCaseName = await sendBrowserTestCommand({name: 'getTestName'});
// Get the test function from testcase namespace testCaseName.
const test = testcase[testCaseName];
// Verify test is a Function without args.
if (!(test instanceof Function && test.length === 0)) {
chrome.test.fail('[' + testCaseName + '] not found.');
}
// Define the test case and its name for chrome.test logging.
const testCase = {
[testCaseName]: () => {
return awaitAsyncTestResult(test());
},
};
// Run the test.
chrome.test.runTests([testCase[testCaseName]!]);
});
/**
* Creates a folder shortcut to |directoryName| using the context menu. Note
* the current directory must be a parent of the given |directoryName|.
*
* @param appId Files app windowId.
* @param directoryName Directory of shortcut to be created.
* @return Promise fulfilled on success.
*/
export async function createShortcut(
appId: string, directoryName: string): Promise<void> {
await remoteCall.waitUntilSelected(appId, directoryName);
await remoteCall.waitForElement(appId, ['.table-row[selected]']);
chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
'fakeMouseRightClick', appId, ['.table-row[selected]']));
await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');
await remoteCall.waitForElement(
appId, '[command="#pin-folder"]:not([hidden]):not([disabled])');
chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
'fakeMouseClick', appId,
['[command="#pin-folder"]:not([hidden]):not([disabled])']));
const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
await directoryTree.waitForShortcutItemByLabel(directoryName);
}
/**
* Mounts crostini volume by clicking on the fake crostini root.
* @param appId Files app windowId.
* @param initialEntries List of initial entries to load in Crostini (defaults
* to a basic entry set).
*/
export async function mountCrostini(
appId: string, initialEntries: TestEntryInfo[] = BASIC_CROSTINI_ENTRY_SET) {
const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
// Add entries to crostini volume, but do not mount.
await addEntries(['crostini'], initialEntries);
// Linux files fake root is shown.
await directoryTree.waitForPlaceholderItemByType('crostini');
// Mount crostini, and ensure real root and files are shown.
await directoryTree.selectPlaceholderItemByType('crostini');
await directoryTree.waitForItemByType('crostini');
const files = TestEntryInfo.getExpectedRows(initialEntries);
await remoteCall.waitForFiles(appId, files);
}
/**
* Registers a GuestOS, mounts the volume, and populates it with tbe specified
* entries.
* @param appId Files app windowId.
* @param initialEntries List of initial entries to load in the volume.
*/
export async function mountGuestOs(
appId: string, initialEntries: TestEntryInfo[]) {
await sendTestMessage({
name: 'registerMountableGuest',
displayName: 'Bluejohn',
canMount: true,
vmType: 'bruschetta',
});
const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
// Wait for the GuestOS fake root then click it.
await directoryTree.selectPlaceholderItemByType('bruschetta');
// Wait for the volume to get mounted.
await directoryTree.waitForItemByType('bruschetta');
// Add entries to GuestOS volume
await addEntries(['guest_os_0'], initialEntries);
// Ensure real root and files are shown.
const files = TestEntryInfo.getExpectedRows(initialEntries);
await remoteCall.waitForFiles(appId, files);
}
/**
* Returns true if the SinglePartitionFormat flag is on.
* @param appId Files app windowId.
*/
export async function isSinglePartitionFormat(appId: string) {
const dialog = await remoteCall.waitForElement(
appId, ['files-format-dialog', 'cr-dialog']);
const flag = dialog.attributes['single-partition-format'] || '';
return !!flag;
}
/** Waits until the MediaApp/Backlight shows up. */
export async function waitForMediaApp() {
// The MediaApp window should open for the file.
const caller = getCaller();
const mediaAppAppId = 'jhdjimmaggjajfjphpljagpgkidjilnj';
await repeatUntil(async () => {
const result = await sendTestMessage({
name: 'hasSwaStarted',
swaAppId: mediaAppAppId,
});
if (result === 'true') {
return;
}
return pending(caller, 'Waiting for MediaApp to open');
});
}