Home | History | Annotate | Download | only in js
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * Namespace for test related things.
      7  */
      8 var test = test || {};
      9 
     10 /**
     11  * Namespace for test utility functions.
     12  *
     13  * Public functions in the test.util.sync and the test.util.async namespaces are
     14  * published to test cases and can be called by using callRemoteTestUtil. The
     15  * arguments are serialized as JSON internally. If application ID is passed to
     16  * callRemoteTestUtil, the content window of the application is added as the
     17  * first argument. The functions in the test.util.async namespace are passed the
     18  * callback function as the last argument.
     19  */
     20 test.util = {};
     21 
     22 /**
     23  * Namespace for synchronous utility functions.
     24  */
     25 test.util.sync = {};
     26 
     27 /**
     28  * Namespace for asynchronous utility functions.
     29  */
     30 test.util.async = {};
     31 
     32 /**
     33  * Extension ID of the testing extension.
     34  * @type {string}
     35  * @const
     36  */
     37 test.util.TESTING_EXTENSION_ID = 'oobinhbdbiehknkpbpejbbpdbkdjmoco';
     38 
     39 /**
     40  * Opens the main Files.app's window and waits until it is ready.
     41  *
     42  * @param {Object} appState App state.
     43  * @param {function(string)} callback Completion callback with the new window's
     44  *     App ID.
     45  */
     46 test.util.async.openMainWindow = function(appState, callback) {
     47   launchFileManager(appState,
     48                     undefined,  // opt_type
     49                     undefined,  // opt_id
     50                     callback);
     51 };
     52 
     53 /**
     54  * Obtains window information.
     55  *
     56  * @return {Object.<string, {innerWidth:number, innerHeight:number}>} Map window
     57  *     ID and window information.
     58  */
     59 test.util.sync.getWindows = function() {
     60   var windows = {};
     61   for (var id in background.appWindows) {
     62     var windowWrapper = background.appWindows[id];
     63     windows[id] = {
     64       innerWidth: windowWrapper.contentWindow.innerWidth,
     65       innerHeight: windowWrapper.contentWindow.innerHeight
     66     };
     67   }
     68   for (var id in background.dialogs) {
     69     windows[id] = {
     70       innerWidth: background.dialogs[id].innerWidth,
     71       innerHeight: background.dialogs[id].innerHeight
     72     };
     73   }
     74   return windows;
     75 };
     76 
     77 /**
     78  * Closes the specified window.
     79  *
     80  * @param {string} appId AppId of window to be closed.
     81  * @return {boolean} Result: True if success, false otherwise.
     82  */
     83 test.util.sync.closeWindow = function(appId) {
     84   if (appId in background.appWindows &&
     85       background.appWindows[appId].contentWindow) {
     86     background.appWindows[appId].close();
     87     return true;
     88   }
     89   return false;
     90 };
     91 
     92 /**
     93  * Gets a document in the Files.app's window, including iframes.
     94  *
     95  * @param {Window} contentWindow Window to be used.
     96  * @param {string=} opt_iframeQuery Query for the iframe.
     97  * @return {Document=} Returns the found document or undefined if not found.
     98  * @private
     99  */
    100 test.util.sync.getDocument_ = function(contentWindow, opt_iframeQuery) {
    101   if (opt_iframeQuery) {
    102     var iframe = contentWindow.document.querySelector(opt_iframeQuery);
    103     return iframe && iframe.contentWindow && iframe.contentWindow.document;
    104   }
    105 
    106   return contentWindow.document;
    107 };
    108 
    109 /**
    110  * Gets total Javascript error count from background page and each app window.
    111  * @return {number} Error count.
    112  */
    113 test.util.sync.getErrorCount = function() {
    114   var totalCount = JSErrorCount;
    115   for (var appId in background.appWindows) {
    116     var contentWindow = background.appWindows[appId].contentWindow;
    117     if (contentWindow.JSErrorCount)
    118       totalCount += contentWindow.JSErrorCount;
    119   }
    120   return totalCount;
    121 };
    122 
    123 /**
    124  * Resizes the window to the specified dimensions.
    125  *
    126  * @param {Window} contentWindow Window to be tested.
    127  * @param {number} width Window width.
    128  * @param {number} height Window height.
    129  * @return {boolean} True for success.
    130  */
    131 test.util.sync.resizeWindow = function(contentWindow, width, height) {
    132   background.appWindows[contentWindow.appID].resizeTo(width, height);
    133   return true;
    134 };
    135 
    136 /**
    137  * Returns an array with the files currently selected in the file manager.
    138  * TODO(hirono): Integrate the method into getFileList method.
    139  *
    140  * @param {Window} contentWindow Window to be tested.
    141  * @return {Array.<string>} Array of selected files.
    142  */
    143 test.util.sync.getSelectedFiles = function(contentWindow) {
    144   var table = contentWindow.document.querySelector('#detail-table');
    145   var rows = table.querySelectorAll('li');
    146   var selected = [];
    147   for (var i = 0; i < rows.length; ++i) {
    148     if (rows[i].hasAttribute('selected')) {
    149       selected.push(
    150           rows[i].querySelector('.filename-label').textContent);
    151     }
    152   }
    153   return selected;
    154 };
    155 
    156 /**
    157  * Returns an array with the files on the file manager's file list.
    158  *
    159  * @param {Window} contentWindow Window to be tested.
    160  * @return {Array.<Array.<string>>} Array of rows.
    161  */
    162 test.util.sync.getFileList = function(contentWindow) {
    163   var table = contentWindow.document.querySelector('#detail-table');
    164   var rows = table.querySelectorAll('li');
    165   var fileList = [];
    166   for (var j = 0; j < rows.length; ++j) {
    167     var row = rows[j];
    168     fileList.push([
    169       row.querySelector('.filename-label').textContent,
    170       row.querySelector('.size').textContent,
    171       row.querySelector('.type').textContent,
    172       row.querySelector('.date').textContent
    173     ]);
    174   }
    175   return fileList;
    176 };
    177 
    178 /**
    179  * Queries all elements.
    180  *
    181  * @param {Window} contentWindow Window to be tested.
    182  * @param {string} targetQuery Query to specify the element.
    183  * @param {?string} iframeQuery Iframe selector or null if no iframe.
    184  * @param {Array.<string>=} opt_styleNames List of CSS property name to be
    185  *     obtained.
    186  * @return {Array.<{attributes:Object.<string, string>, text:string,
    187  *                  styles:Object.<string, string>, hidden:boolean}>} Element
    188  *     information that contains contentText, attribute names and
    189  *     values, hidden attribute, and style names and values.
    190  */
    191 test.util.sync.queryAllElements = function(
    192     contentWindow, targetQuery, iframeQuery, opt_styleNames) {
    193   var doc = test.util.sync.getDocument_(contentWindow, iframeQuery);
    194   if (!doc)
    195     return [];
    196   // The return value of querySelectorAll is not an array.
    197   return Array.prototype.map.call(
    198       doc.querySelectorAll(targetQuery),
    199       function(element) {
    200         var attributes = {};
    201         for (var i = 0; i < element.attributes.length; i++) {
    202           attributes[element.attributes[i].nodeName] =
    203               element.attributes[i].nodeValue;
    204         }
    205         var styles = {};
    206         var styleNames = opt_styleNames || [];
    207         var computedStyles = contentWindow.getComputedStyle(element);
    208         for (var i = 0; i < styleNames.length; i++) {
    209           styles[styleNames[i]] = computedStyles[styleNames[i]];
    210         }
    211         var text = element.textContent;
    212         return {
    213           attributes: attributes,
    214           text: text,
    215           styles: styles,
    216           // The hidden attribute is not in the element.attributes even if
    217           // element.hasAttribute('hidden') is true.
    218           hidden: !!element.hidden
    219         };
    220       });
    221 };
    222 
    223 /**
    224  * Assigns the text to the input element.
    225  * @param {Window} contentWindow Window to be tested.
    226  * @param {string} query Query for the input element.
    227  * @param {string} text Text to be assigned.
    228  */
    229 test.util.sync.inputText = function(contentWindow, query, text) {
    230   var input = contentWindow.document.querySelector(query);
    231   input.value = text;
    232 };
    233 
    234 /**
    235  * Fakes pressing the down arrow until the given |filename| is selected.
    236  *
    237  * @param {Window} contentWindow Window to be tested.
    238  * @param {string} filename Name of the file to be selected.
    239  * @return {boolean} True if file got selected, false otherwise.
    240  */
    241 test.util.sync.selectFile = function(contentWindow, filename) {
    242   var rows = contentWindow.document.querySelectorAll('#detail-table li');
    243   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'Home', false);
    244   for (var index = 0; index < rows.length; ++index) {
    245     var selection = test.util.sync.getSelectedFiles(contentWindow);
    246     if (selection.length === 1 && selection[0] === filename)
    247       return true;
    248     test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'Down', false);
    249   }
    250   console.error('Failed to select file "' + filename + '"');
    251   return false;
    252 };
    253 
    254 /**
    255  * Open the file by selectFile and fakeMouseDoubleClick.
    256  *
    257  * @param {Window} contentWindow Window to be tested.
    258  * @param {string} filename Name of the file to be opened.
    259  * @return {boolean} True if file got selected and a double click message is
    260  *     sent, false otherwise.
    261  */
    262 test.util.sync.openFile = function(contentWindow, filename) {
    263   var query = '#file-list li.table-row[selected] .filename-label span';
    264   return test.util.sync.selectFile(contentWindow, filename) &&
    265          test.util.sync.fakeMouseDoubleClick(contentWindow, query);
    266 };
    267 
    268 /**
    269  * Selects a volume specified by its icon name
    270  *
    271  * @param {Window} contentWindow Window to be tested.
    272  * @param {string} iconName Name of the volume icon.
    273  * @param {function(boolean)} callback Callback function to notify the caller
    274  *     whether the target is found and mousedown and click events are sent.
    275  */
    276 test.util.async.selectVolume = function(contentWindow, iconName, callback) {
    277   var query = '[volume-type-icon=' + iconName + ']';
    278   var driveQuery = '[volume-type-icon=drive]';
    279   var isDriveSubVolume = iconName == 'drive_recent' ||
    280                          iconName == 'drive_shared_with_me' ||
    281                          iconName == 'drive_offline';
    282   var preSelection = false;
    283   var steps = {
    284     checkQuery: function() {
    285       if (contentWindow.document.querySelector(query)) {
    286         steps.sendEvents();
    287         return;
    288       }
    289       // If the target volume is sub-volume of drive, we must click 'drive'
    290       // before clicking the sub-item.
    291       if (!preSelection) {
    292         if (!isDriveSubVolume) {
    293           callback(false);
    294           return;
    295         }
    296         if (!(test.util.sync.fakeMouseDown(contentWindow, driveQuery) &&
    297               test.util.sync.fakeMouseClick(contentWindow, driveQuery))) {
    298           callback(false);
    299           return;
    300         }
    301         preSelection = true;
    302       }
    303       setTimeout(steps.checkQuery, 50);
    304     },
    305     sendEvents: function() {
    306       // To change the selected volume, we have to send both events 'mousedown'
    307       // and 'click' to the navigation list.
    308       callback(test.util.sync.fakeMouseDown(contentWindow, query) &&
    309                test.util.sync.fakeMouseClick(contentWindow, query));
    310     }
    311   };
    312   steps.checkQuery();
    313 };
    314 
    315 /**
    316  * Executes Javascript code on a webview and returns the result.
    317  *
    318  * @param {Window} contentWindow Window to be tested.
    319  * @param {string} webViewQuery Selector for the web view.
    320  * @param {string} code Javascript code to be executed within the web view.
    321  * @param {function(*)} callback Callback function with results returned by the
    322  *     script.
    323  */
    324 test.util.async.executeScriptInWebView = function(
    325     contentWindow, webViewQuery, code, callback) {
    326   var webView = contentWindow.document.querySelector(webViewQuery);
    327   webView.executeScript({code: code}, callback);
    328 };
    329 
    330 /**
    331  * Sends an event to the element specified by |targetQuery|.
    332  *
    333  * @param {Window} contentWindow Window to be tested.
    334  * @param {string} targetQuery Query to specify the element.
    335  * @param {Event} event Event to be sent.
    336  * @param {string=} opt_iframeQuery Optional iframe selector.
    337  * @return {boolean} True if the event is sent to the target, false otherwise.
    338  */
    339 test.util.sync.sendEvent = function(
    340     contentWindow, targetQuery, event, opt_iframeQuery) {
    341   var doc = test.util.sync.getDocument_(contentWindow, opt_iframeQuery);
    342   if (doc) {
    343     var target = doc.querySelector(targetQuery);
    344     if (target) {
    345       target.dispatchEvent(event);
    346       return true;
    347     }
    348   }
    349   console.error('Target element for ' + targetQuery + ' not found.');
    350   return false;
    351 };
    352 
    353 /**
    354  * Sends an fake event having the specified type to the target query.
    355  *
    356  * @param {Window} contentWindow Window to be tested.
    357  * @param {string} targetQuery Query to specify the element.
    358  * @param {string} eventType Type of event.
    359  * @param {Object=} opt_additionalProperties Object contaning additional
    360  *     properties.
    361  * @return {boolean} True if the event is sent to the target, false otherwise.
    362  */
    363 test.util.sync.fakeEvent = function(contentWindow,
    364                                     targetQuery,
    365                                     eventType,
    366                                     opt_additionalProperties) {
    367   var event = new Event(eventType, opt_additionalProperties || {});
    368   if (opt_additionalProperties) {
    369     for (var name in opt_additionalProperties) {
    370       event[name] = opt_additionalProperties[name];
    371     }
    372   }
    373   return test.util.sync.sendEvent(contentWindow, targetQuery, event);
    374 };
    375 
    376 /**
    377  * Sends a fake key event to the element specified by |targetQuery| with the
    378  * given |keyIdentifier| and optional |ctrl| modifier to the file manager.
    379  *
    380  * @param {Window} contentWindow Window to be tested.
    381  * @param {string} targetQuery Query to specify the element.
    382  * @param {string} keyIdentifier Identifier of the emulated key.
    383  * @param {boolean} ctrl Whether CTRL should be pressed, or not.
    384  * @param {string=} opt_iframeQuery Optional iframe selector.
    385  * @return {boolean} True if the event is sent to the target, false otherwise.
    386  */
    387 test.util.sync.fakeKeyDown = function(
    388     contentWindow, targetQuery, keyIdentifier, ctrl, opt_iframeQuery) {
    389   var event = new KeyboardEvent(
    390       'keydown',
    391       { bubbles: true, keyIdentifier: keyIdentifier, ctrlKey: ctrl });
    392   return test.util.sync.sendEvent(
    393       contentWindow, targetQuery, event, opt_iframeQuery);
    394 };
    395 
    396 /**
    397  * Simulates a fake mouse click (left button, single click) on the element
    398  * specified by |targetQuery|. If the element has the click method, just calls
    399  * it. Otherwise, this sends 'mouseover', 'mousedown', 'mouseup' and 'click'
    400  * events in turns.
    401  *
    402  * @param {Window} contentWindow Window to be tested.
    403  * @param {string} targetQuery Query to specify the element.
    404  * @param {string=} opt_iframeQuery Optional iframe selector.
    405  * @return {boolean} True if the all events are sent to the target, false
    406  *     otherwise.
    407  */
    408 test.util.sync.fakeMouseClick = function(
    409     contentWindow, targetQuery, opt_iframeQuery) {
    410   var mouseOverEvent = new MouseEvent('mouseover', {bubbles: true, detail: 1});
    411   var resultMouseOver = test.util.sync.sendEvent(
    412       contentWindow, targetQuery, mouseOverEvent, opt_iframeQuery);
    413   var mouseDownEvent = new MouseEvent('mousedown', {bubbles: true, detail: 1});
    414   var resultMouseDown = test.util.sync.sendEvent(
    415       contentWindow, targetQuery, mouseDownEvent, opt_iframeQuery);
    416   var mouseUpEvent = new MouseEvent('mouseup', {bubbles: true, detail: 1});
    417   var resultMouseUp = test.util.sync.sendEvent(
    418       contentWindow, targetQuery, mouseUpEvent, opt_iframeQuery);
    419   var clickEvent = new MouseEvent('click', {bubbles: true, detail: 1});
    420   var resultClick = test.util.sync.sendEvent(
    421       contentWindow, targetQuery, clickEvent, opt_iframeQuery);
    422   return resultMouseOver && resultMouseDown && resultMouseUp && resultClick;
    423 };
    424 
    425 /**
    426  * Simulates a fake mouse click (right button, single click) on the element
    427  * specified by |targetQuery|.
    428  *
    429  * @param {Window} contentWindow Window to be tested.
    430  * @param {string} targetQuery Query to specify the element.
    431  * @param {string=} opt_iframeQuery Optional iframe selector.
    432  * @return {boolean} True if the event is sent to the target, false
    433  *     otherwise.
    434  */
    435 test.util.sync.fakeMouseRightClick = function(
    436     contentWindow, targetQuery, opt_iframeQuery) {
    437   var contextMenuEvent = new MouseEvent('contextmenu', {bubbles: true});
    438   var result = test.util.sync.sendEvent(
    439       contentWindow, targetQuery, contextMenuEvent, opt_iframeQuery);
    440   return result;
    441 };
    442 
    443 /**
    444  * Simulates a fake double click event (left button) to the element specified by
    445  * |targetQuery|.
    446  *
    447  * @param {Window} contentWindow Window to be tested.
    448  * @param {string} targetQuery Query to specify the element.
    449  * @param {string=} opt_iframeQuery Optional iframe selector.
    450  * @return {boolean} True if the event is sent to the target, false otherwise.
    451  */
    452 test.util.sync.fakeMouseDoubleClick = function(
    453     contentWindow, targetQuery, opt_iframeQuery) {
    454   // Double click is always preceded with a single click.
    455   if (!test.util.sync.fakeMouseClick(
    456       contentWindow, targetQuery, opt_iframeQuery)) {
    457     return false;
    458   }
    459 
    460   // Send the second click event, but with detail equal to 2 (number of clicks)
    461   // in a row.
    462   var event = new MouseEvent('click', { bubbles: true, detail: 2 });
    463   if (!test.util.sync.sendEvent(
    464       contentWindow, targetQuery, event, opt_iframeQuery)) {
    465     return false;
    466   }
    467 
    468   // Send the double click event.
    469   var event = new MouseEvent('dblclick', { bubbles: true });
    470   if (!test.util.sync.sendEvent(
    471       contentWindow, targetQuery, event, opt_iframeQuery)) {
    472     return false;
    473   }
    474 
    475   return true;
    476 };
    477 
    478 /**
    479  * Sends a fake mouse down event to the element specified by |targetQuery|.
    480  *
    481  * @param {Window} contentWindow Window to be tested.
    482  * @param {string} targetQuery Query to specify the element.
    483  * @param {string=} opt_iframeQuery Optional iframe selector.
    484  * @return {boolean} True if the event is sent to the target, false otherwise.
    485  */
    486 test.util.sync.fakeMouseDown = function(
    487     contentWindow, targetQuery, opt_iframeQuery) {
    488   var event = new MouseEvent('mousedown', { bubbles: true });
    489   return test.util.sync.sendEvent(
    490       contentWindow, targetQuery, event, opt_iframeQuery);
    491 };
    492 
    493 /**
    494  * Sends a fake mouse up event to the element specified by |targetQuery|.
    495  *
    496  * @param {Window} contentWindow Window to be tested.
    497  * @param {string} targetQuery Query to specify the element.
    498  * @param {string=} opt_iframeQuery Optional iframe selector.
    499  * @return {boolean} True if the event is sent to the target, false otherwise.
    500  */
    501 test.util.sync.fakeMouseUp = function(
    502     contentWindow, targetQuery, opt_iframeQuery) {
    503   var event = new MouseEvent('mouseup', { bubbles: true });
    504   return test.util.sync.sendEvent(
    505       contentWindow, targetQuery, event, opt_iframeQuery);
    506 };
    507 
    508 /**
    509  * Selects |filename| and fakes pressing Ctrl+C, Ctrl+V (copy, paste).
    510  *
    511  * @param {Window} contentWindow Window to be tested.
    512  * @param {string} filename Name of the file to be copied.
    513  * @return {boolean} True if copying got simulated successfully. It does not
    514  *     say if the file got copied, or not.
    515  */
    516 test.util.sync.copyFile = function(contentWindow, filename) {
    517   if (!test.util.sync.selectFile(contentWindow, filename))
    518     return false;
    519   // Ctrl+C and Ctrl+V
    520   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+0043', true);
    521   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+0056', true);
    522   return true;
    523 };
    524 
    525 /**
    526  * Selects |filename| and fakes pressing the Delete key.
    527  *
    528  * @param {Window} contentWindow Window to be tested.
    529  * @param {string} filename Name of the file to be deleted.
    530  * @return {boolean} True if deleting got simulated successfully. It does not
    531  *     say if the file got deleted, or not.
    532  */
    533 test.util.sync.deleteFile = function(contentWindow, filename) {
    534   if (!test.util.sync.selectFile(contentWindow, filename))
    535     return false;
    536   // Delete
    537   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+007F', false);
    538   return true;
    539 };
    540 
    541 /**
    542  * Execute a command on the document in the specified window.
    543  *
    544  * @param {Window} contentWindow Window to be tested.
    545  * @param {string} command Command name.
    546  * @return {boolean} True if the command is executed successfully.
    547  */
    548 test.util.sync.execCommand = function(contentWindow, command) {
    549   return contentWindow.document.execCommand(command);
    550 };
    551 
    552 /**
    553  * Override the installWebstoreItem method in private api for test.
    554  *
    555  * @param {Window} contentWindow Window to be tested.
    556  * @param {string} expectedItemId Item ID to be called this method with.
    557  * @param {?string} intendedError Error message to be returned when the item id
    558  *     matches. 'null' represents no error.
    559  * @return {boolean} Always return true.
    560  */
    561 test.util.sync.overrideInstallWebstoreItemApi =
    562     function(contentWindow, expectedItemId, intendedError) {
    563   var setLastError = function(message) {
    564     contentWindow.chrome.runtime.lastError =
    565         message ? {message: message} : null;
    566   };
    567 
    568   var installWebstoreItem = function(itemId, callback) {
    569     setTimeout(function() {
    570       if (itemId !== expectedItemId) {
    571         setLastError('Invalid Chrome Web Store item ID');
    572         callback();
    573         return;
    574       }
    575 
    576       setLastError(intendedError);
    577       callback();
    578     });
    579   };
    580 
    581   test.util.executedTasks_ = [];
    582   contentWindow.chrome.fileBrowserPrivate.installWebstoreItem =
    583       installWebstoreItem;
    584   return true;
    585 };
    586 
    587 /**
    588  * Override the task-related methods in private api for test.
    589  *
    590  * @param {Window} contentWindow Window to be tested.
    591  * @param {Array.<Object>} taskList List of tasks to be returned in
    592  *     fileBrowserPrivate.getFileTasks().
    593  * @return {boolean} Always return true.
    594  */
    595 test.util.sync.overrideTasks = function(contentWindow, taskList) {
    596   var getFileTasks = function(urls, mime, onTasks) {
    597     // Call onTask asynchronously (same with original getFileTasks).
    598     setTimeout(function() {
    599       onTasks(taskList);
    600     });
    601   };
    602 
    603   var executeTask = function(taskId, url) {
    604     test.util.executedTasks_.push(taskId);
    605   };
    606 
    607   var setDefaultTask = function(taskId) {
    608     for (var i = 0; i < taskList.length; i++) {
    609       taskList[i].isDefault = taskList[i].taskId === taskId;
    610     }
    611   };
    612 
    613   test.util.executedTasks_ = [];
    614   contentWindow.chrome.fileBrowserPrivate.getFileTasks = getFileTasks;
    615   contentWindow.chrome.fileBrowserPrivate.executeTask = executeTask;
    616   contentWindow.chrome.fileBrowserPrivate.setDefaultTask = setDefaultTask;
    617   return true;
    618 };
    619 
    620 /**
    621  * Obtains the list of executed tasks.
    622  * @param {Window} contentWindow Window to be tested.
    623  * @return {Array.<string>} List of executed task ID.
    624  */
    625 test.util.sync.getExecutedTasks = function(contentWindow) {
    626   if (!test.util.executedTasks_) {
    627     console.error('Please call overrideTasks() first.');
    628     return null;
    629   }
    630   return test.util.executedTasks_;
    631 };
    632 
    633 /**
    634  * Invoke chrome.fileBrowserPrivate.visitDesktop(profileId) to cause window
    635  * teleportation.
    636  *
    637  * @param {Window} contentWindow Window to be tested.
    638  * @param {string} profileId Destination profile's ID.
    639  * @return {boolean} Always return true.
    640  */
    641 test.util.sync.visitDesktop = function(contentWindow, profileId) {
    642   contentWindow.chrome.fileBrowserPrivate.visitDesktop(profileId);
    643   return true;
    644 };
    645 
    646 /**
    647  * Runs the 'Move to profileId' menu.
    648  *
    649  * @param {Window} contentWindow Window to be tested.
    650  * @param {string} profileId Destination profile's ID.
    651  * @return {boolean} True if the menu is found and run.
    652  */
    653 test.util.sync.runVisitDesktopMenu = function(contentWindow, profileId) {
    654   var list = contentWindow.document.querySelectorAll('.visit-desktop');
    655   for (var i = 0; i < list.length; ++i) {
    656     if (list[i].label.indexOf(profileId) != -1) {
    657       var activateEvent = contentWindow.document.createEvent('Event');
    658       activateEvent.initEvent('activate');
    659       list[i].dispatchEvent(activateEvent);
    660       return true;
    661     }
    662   }
    663   return false;
    664 };
    665 
    666 /**
    667  * Registers message listener, which runs test utility functions.
    668  */
    669 test.util.registerRemoteTestUtils = function() {
    670   // Register the message listener.
    671   var onMessage = chrome.runtime ? chrome.runtime.onMessageExternal :
    672       chrome.extension.onMessageExternal;
    673   // Return true for asynchronous functions and false for synchronous.
    674   onMessage.addListener(function(request, sender, sendResponse) {
    675     // Check the sender.
    676     if (sender.id != test.util.TESTING_EXTENSION_ID) {
    677       console.error('The testing extension must be white-listed.');
    678       return false;
    679     }
    680     // Set a global flag that we are in tests, so other components are aware
    681     // of it.
    682     window.IN_TEST = true;
    683     // Check the function name.
    684     if (!request.func || request.func[request.func.length - 1] == '_') {
    685       request.func = '';
    686     }
    687     // Prepare arguments.
    688     var args = request.args.slice();  // shallow copy
    689     if (request.appId) {
    690       if (background.appWindows[request.appId]) {
    691         args.unshift(background.appWindows[request.appId].contentWindow);
    692       } else if (background.dialogs[request.appId]) {
    693         args.unshift(background.dialogs[request.appId]);
    694       } else {
    695         console.error('Specified window not found: ' + request.appId);
    696         return false;
    697       }
    698     }
    699     // Call the test utility function and respond the result.
    700     if (test.util.async[request.func]) {
    701       args[test.util.async[request.func].length - 1] = function() {
    702         console.debug('Received the result of ' + request.func);
    703         sendResponse.apply(null, arguments);
    704       };
    705       console.debug('Waiting for the result of ' + request.func);
    706       test.util.async[request.func].apply(null, args);
    707       return true;
    708     } else if (test.util.sync[request.func]) {
    709       sendResponse(test.util.sync[request.func].apply(null, args));
    710       return false;
    711     } else {
    712       console.error('Invalid function name.');
    713       return false;
    714     }
    715   });
    716 };
    717 
    718 // Register the test utils.
    719 test.util.registerRemoteTestUtils();
    720