blob: 1fc3e3ea0bcc68b288f3b8a93c336c491afb1321 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Extension ID of the Files app.
* @type {string}
* @const
*/
const FILE_MANAGER_EXTENSIONS_ID = 'hhaomjibdihmijegdhdafkllkbggdgoj';
const remoteCall = new RemoteCallFilesApp(FILE_MANAGER_EXTENSIONS_ID);
/**
* Extension ID of Gallery.
* @type {string}
* @const
*/
const GALLERY_APP_ID = 'nlkncpkkdoccmpiclbokaimcnedabhhm';
const galleryApp = new RemoteCallGallery(GALLERY_APP_ID);
/**
* Extension ID of Audio Player.
* @type {string}
* @const
*/
const AUDIO_PLAYER_APP_ID = 'cjbfomnbifhcdnihkgipgfcihmgjfhbf';
const audioPlayerApp = new RemoteCall(AUDIO_PLAYER_APP_ID);
/**
* App ID of Video Player.
* @type {string}
* @const
*/
const VIDEO_PLAYER_APP_ID = 'jcgeabjmjgoblfofpppfkcoakmfobdko';
const videoPlayerApp = new RemoteCall(VIDEO_PLAYER_APP_ID);
/**
* Basic entry set for the local volume.
*
* @type {Array<TestEntryInfo>}
* @const
*/
const BASIC_LOCAL_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.world,
ENTRIES.desktop,
ENTRIES.beautiful,
ENTRIES.photos
];
/**
* Basic entry set for the drive volume that only includes read-write entries
* (no read-only or similar entries).
*
* TODO(hirono): Add a case for an entry cached by FileCache. For testing
* Drive, create more entries with Drive specific attributes.
* TODO(sashab): Merge items from COMPLEX_DRIVE_ENTRY_SET into here (so all
* tests run with read-only files) once crbug.com/850834 is fixed.
*
* @type {Array<TestEntryInfo>}
* @const
*/
const BASIC_DRIVE_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.world,
ENTRIES.desktop,
ENTRIES.beautiful,
ENTRIES.photos,
ENTRIES.unsupported,
ENTRIES.testDocument,
ENTRIES.testSharedDocument
];
/**
* Basic entry set for the local crostini volume.
* @type {!Array<!TestEntryInfo>}
* @const
*/
const BASIC_CROSTINI_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.world,
ENTRIES.desktop,
];
/**
* More complex entry set for Drive that includes entries with varying
* permissions (such as read-only entries).
*
* @type {Array<TestEntryInfo>}
* @const
*/
const COMPLEX_DRIVE_ENTRY_SET = [
ENTRIES.hello, ENTRIES.photos, ENTRIES.readOnlyFolder,
ENTRIES.readOnlyDocument, ENTRIES.readOnlyStrictDocument, ENTRIES.readOnlyFile
];
/**
* Nested entry set (directories inside each other).
*
* @type {Array<TestEntryInfo>}
* @const
*/
const NESTED_ENTRY_SET = [
ENTRIES.directoryA,
ENTRIES.directoryB,
ENTRIES.directoryC
];
/**
* Expected list of preset entries in fake test volumes. This should be in sync
* with FakeTestVolume::PrepareTestEntries in the test harness.
*
* @type {Array<TestEntryInfo>}
* @const
*/
const BASIC_FAKE_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.directoryA
];
/**
* Expected files shown in "Recent". Directories (e.g. 'photos') are not in this
* list as they are not expected in "Recent".
*
* @type {Array<TestEntryInfo>}
* @const
*/
const RECENT_ENTRY_SET = [
ENTRIES.desktop,
ENTRIES.beautiful,
];
/**
* Expected files shown in "Offline", which should have the files
* "available offline". Google Documents, Google Spreadsheets, and the files
* cached locally are "available offline".
*
* @type {Array<TestEntryInfo>}
* @const
*/
const OFFLINE_ENTRY_SET = [
ENTRIES.testDocument,
ENTRIES.testSharedDocument
];
/**
* Expected files shown in "Shared with me", which should be the entries labeled
* with "shared-with-me".
*
* @type {Array<TestEntryInfo>}
* @const
*/
const SHARED_WITH_ME_ENTRY_SET = [
ENTRIES.testSharedDocument
];
/**
* Entry set for Drive that includes team drives of various permissions and
* nested files with various permissions.
*
* TODO(sashab): Add support for capabilities of Team Drive roots.
*
* @type {Array<TestEntryInfo>}
* @const
*/
const TEAM_DRIVE_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.teamDriveA,
ENTRIES.teamDriveAFile,
ENTRIES.teamDriveADirectory,
ENTRIES.teamDriveAHostedFile,
ENTRIES.teamDriveB,
ENTRIES.teamDriveBFile,
];
/**
* Entry set for Drive that includes Computers, including nested computers with
* files and nested "USB and External Devices" with nested devices.
*/
let COMPUTERS_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.computerA,
ENTRIES.computerAFile,
ENTRIES.computerAdirectoryA,
];
/**
* Opens a Files app's main window.
*
* TODO(mtomasz): Pass a volumeId or an enum value instead of full paths.
*
* @param {?string} initialRoot Root path to be used as a default current
* directory during initialization. Can be null, for no default path.
* @param {Object} appState App state to be passed with on opening the Files
* app.
* @return {Promise} Promise to be fulfilled after window creating.
*/
function openNewWindow(initialRoot, appState = {}) {
// TODO(mtomasz): Migrate from full paths to a pair of a volumeId and a
// relative path. To compose the URL communicate via messages with
// file_manager_browser_test.cc.
if (initialRoot) {
appState.currentDirectoryURL = 'filesystem:chrome-extension://' +
FILE_MANAGER_EXTENSIONS_ID + '/external' + initialRoot;
}
return remoteCall.callRemoteTestUtil('openMainWindow', null, [appState]);
}
/**
* Opens a file dialog and waits for closing it.
*
* @param {Object} dialogParams Dialog parameters to be passed to chrome.
* fileSystem.chooseEntry() API.
* @param {string} volumeName Volume name passed to the selectVolume remote
* funciton.
* @param {Array<TestEntryInfo>} expectedSet Expected set of the entries.
* @param {function(appId:string):Promise} closeDialog Function to close the
* dialog.
* @param {boolean} useBrowserOpen Whether to launch the select file dialog via
* a browser OpenFile() call.
* @return {Promise} Promise to be fulfilled with the result entry of the
* dialog.
*/
async function openAndWaitForClosingDialog(
dialogParams, volumeName, expectedSet, closeDialog,
useBrowserOpen = false) {
const caller = getCaller();
let resultPromise;
if (useBrowserOpen) {
resultPromise = sendTestMessage({name: 'runSelectFileDialog'});
} else {
resultPromise = new Promise(fulfill => {
chrome.fileSystem.chooseEntry(dialogParams, entry => {
fulfill(entry);
});
chrome.test.assertTrue(!chrome.runtime.lastError, 'chooseEntry failed.');
});
}
const appId = await remoteCall.waitForWindow('dialog#');
await remoteCall.waitForElement(appId, '#file-list');
await remoteCall.waitFor('isFileManagerLoaded', appId, true);
chrome.test.assertTrue(
await remoteCall.callRemoteTestUtil('selectVolume', appId, [volumeName]),
'selectVolume failed');
await remoteCall.waitForFiles(
appId, TestEntryInfo.getExpectedRows(expectedSet));
await closeDialog(appId);
await repeatUntil(async () => {
const windows = await remoteCall.callRemoteTestUtil('getWindows', null, []);
if (windows[appId]) {
return pending(caller, 'Waiting for Window %s to hide.', appId);
}
});
return 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.
*
* TODO(mtomasz): Pass a volumeId or an enum value instead of full paths.
*
* @param {?string} initialRoot Root path to be used as a default current
* directory during initialization. Can be null, for no default path.
* @param {!Array<TestEntryInfo>>} initialLocalEntries List of initial
* entries to load in Google Drive (defaults to a basic entry set).
* @param {!Array<TestEntryInfo>>} initialDriveEntries List of initial
* entries to load in Google Drive (defaults to a basic entry set).
* @param {Object} appState App state to be passed with on opening the Files
* app.
* @return {Promise} Promise to be fulfilled with the window ID.
*/
async function setupAndWaitUntilReady(
initialRoot, initialLocalEntries = BASIC_LOCAL_ENTRY_SET,
initialDriveEntries = BASIC_DRIVE_ENTRY_SET, appState = {}) {
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 {Array<string>} file An entry in a file list.
* @return {string} Name of the file.
*/
function getFileName(fileListEntry) {
return fileListEntry[0];
}
/**
* Returns the size of the given file list entry.
* @param {Array<string>} An entry in a file list.
* @return {string} Size of the file.
*/
function getFileSize(fileListEntry) {
return fileListEntry[1];
}
/**
* Returns the type of the given file list entry.
* @param {Array<string>} An entry in a file list.
* @return {string} Type of the file.
*/
function getFileType(fileListEntry) {
return fileListEntry[2];
}
/**
* A value that when returned by an async test indicates that app errors should
* not be checked following completion of the test.
*/
const IGNORE_APP_ERRORS = Symbol('IGNORE_APP_ERRORS');
/**
* For async function tests, wait for the test to complete, check for app errors
* unless skipped, and report the results.
* @param {Promise} resultPromise A promise that resolves with the test result.
* @private
*/
async function awaitAsyncTestResult(resultPromise) {
chrome.test.assertTrue(
resultPromise instanceof Promise, 'test did not return a Promise');
// Hold a pending callback to ensure the test doesn't complete early.
const passCallback = chrome.test.callbackPass();
try {
const result = await resultPromise;
if (result !== IGNORE_APP_ERRORS) {
await checkIfNoErrorsOccuredOnApp(remoteCall);
}
} catch (error) {
// 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;
}
}
passCallback();
}
/**
* Namespace for test cases.
*/
const testcase = {};
/**
* 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 ennvironment,
* then run the test case using chrome.test.RunTests.
*/
window.addEventListener('load', () => {
const steps = [
// Request the guest mode state.
() => {
sendBrowserTestCommand({name: 'isInGuestMode'}, steps.shift());
},
// Request the root entry paths.
mode => {
if (JSON.parse(mode) != chrome.extension.inIncognitoContext) {
return;
}
sendBrowserTestCommand({name: 'getRootPaths'}, steps.shift());
},
// Request the test case name.
paths => {
const roots = JSON.parse(paths);
RootPath.DOWNLOADS = roots.downloads;
RootPath.DOWNLOADS_PATH = roots.downloads_path;
RootPath.DRIVE = roots.drive;
RootPath.ANDROID_FILES = roots.android_files;
sendBrowserTestCommand({name: 'getTestName'}, steps.shift());
},
// Run the test case.
testCaseName => {
// Get the test function from testcase namespace testCaseName.
const test = testcase[testCaseName];
// Verify test is an unnamed (aka 'anonymous') Function.
if (!(test instanceof Function) || test.name) {
chrome.test.fail('[' + testCaseName + '] not found.');
return;
}
// Define the test case and its name for chrome.test logging.
test.generatedName = testCaseName;
const testCaseSymbol = Symbol(testCaseName);
const testCase = {
[testCaseSymbol]: () => {
return awaitAsyncTestResult(test());
},
};
// Run the test.
chrome.test.runTests([testCase[testCaseSymbol]]);
}
];
steps.shift()();
});
/**
* Creates a folder shortcut to |directoryName| using the context menu. Note the
* current directory must be a parent of the given |directoryName|.
*
* @param {string} appId Files app windowId.
* @param {string} directoryName Directory of shortcut to be created.
* @return {Promise} Promise fulfilled on success.
*/
async function createShortcut(appId, directoryName) {
chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
'selectFile', 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="#create-folder-shortcut"]:not([hidden]):not([disabled])');
chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
'fakeMouseClick', appId,
['[command="#create-folder-shortcut"]:not([hidden]):not([disabled])']));
await remoteCall.waitForElement(
appId, `.tree-item[label="${directoryName}"]`);
}
/**
* Expands a tree item by clicking on its expand icon.
*
* @param {string} appId Files app windowId.
* @param {string} treeItem Query to the tree item that should be expanded.
* @return {Promise} Promise fulfilled on success.
*/
async function expandTreeItem(appId, treeItem) {
const expandIcon = treeItem + '> .tree-row[has-children=true] > .expand-icon';
await remoteCall.waitForElement(appId, expandIcon);
chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
'fakeMouseClick', appId, [expandIcon]));
const expandedSubtree = treeItem + '> .tree-children[expanded]';
await remoteCall.waitForElement(appId, expandedSubtree);
}