| OLD | NEW |
| (Empty) |
| 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 * Interval of checking a condition in milliseconds. | |
| 41 * @type {number} | |
| 42 * @const | |
| 43 * @private | |
| 44 */ | |
| 45 test.util.WAITTING_INTERVAL_ = 50; | |
| 46 | |
| 47 /** | |
| 48 * Repeats the function until it returns true. | |
| 49 * @param {function()} closure Function expected to return true. | |
| 50 * @private | |
| 51 */ | |
| 52 test.util.repeatUntilTrue_ = function(closure) { | |
| 53 var step = function() { | |
| 54 if (closure()) | |
| 55 return; | |
| 56 setTimeout(step, test.util.WAITTING_INTERVAL_); | |
| 57 }; | |
| 58 step(); | |
| 59 }; | |
| 60 | |
| 61 /** | |
| 62 * Opens the main Files.app's window and waits until it is ready. | |
| 63 * | |
| 64 * @param {Object} appState App state. | |
| 65 * @param {function(string)} callback Completion callback with the new window's | |
| 66 * App ID. | |
| 67 */ | |
| 68 test.util.async.openMainWindow = function(appState, callback) { | |
| 69 var steps = [ | |
| 70 function() { | |
| 71 launchFileManager(appState, | |
| 72 undefined, // opt_type | |
| 73 undefined, // opt_id | |
| 74 steps.shift()); | |
| 75 }, | |
| 76 function(appId) { | |
| 77 test.util.repeatUntilTrue_(function() { | |
| 78 if (!appWindows[appId]) | |
| 79 return false; | |
| 80 var contentWindow = appWindows[appId].contentWindow; | |
| 81 var table = contentWindow.document.querySelector('#detail-table'); | |
| 82 if (!table) | |
| 83 return false; | |
| 84 callback(appId); | |
| 85 return true; | |
| 86 }); | |
| 87 } | |
| 88 ]; | |
| 89 steps.shift()(); | |
| 90 }; | |
| 91 | |
| 92 /** | |
| 93 * Waits for a window with the specified App ID prefix. Eg. `files` will match | |
| 94 * windows such as files#0, files#1, etc. | |
| 95 * | |
| 96 * @param {string} appIdPrefix ID prefix of the requested window. | |
| 97 * @param {function(string)} callback Completion callback with the new window's | |
| 98 * App ID. | |
| 99 */ | |
| 100 test.util.async.waitForWindow = function(appIdPrefix, callback) { | |
| 101 test.util.repeatUntilTrue_(function() { | |
| 102 for (var appId in appWindows) { | |
| 103 if (appId.indexOf(appIdPrefix) == 0 && | |
| 104 appWindows[appId].contentWindow) { | |
| 105 callback(appId); | |
| 106 return true; | |
| 107 } | |
| 108 } | |
| 109 return false; | |
| 110 }); | |
| 111 }; | |
| 112 | |
| 113 /** | |
| 114 * Gets a document in the Files.app's window, including iframes. | |
| 115 * | |
| 116 * @param {Window} contentWindow Window to be used. | |
| 117 * @param {string=} opt_iframeQuery Query for the iframe. | |
| 118 * @return {Document=} Returns the found document or undefined if not found. | |
| 119 * @private | |
| 120 */ | |
| 121 test.util.sync.getDocument_ = function(contentWindow, opt_iframeQuery) { | |
| 122 if (opt_iframeQuery) { | |
| 123 var iframe = contentWindow.document.querySelector(opt_iframeQuery); | |
| 124 return iframe && iframe.contentWindow && iframe.contentWindow.document; | |
| 125 } | |
| 126 | |
| 127 return contentWindow.document; | |
| 128 }; | |
| 129 | |
| 130 /** | |
| 131 * Gets total Javascript error count from each app window. | |
| 132 * @return {number} Error count. | |
| 133 */ | |
| 134 test.util.sync.getErrorCount = function() { | |
| 135 var totalCount = JSErrorCount; | |
| 136 for (var appId in appWindows) { | |
| 137 var contentWindow = appWindows[appId].contentWindow; | |
| 138 if (contentWindow.JSErrorCount) | |
| 139 totalCount += contentWindow.JSErrorCount; | |
| 140 } | |
| 141 return totalCount; | |
| 142 }; | |
| 143 | |
| 144 /** | |
| 145 * Resizes the window to the specified dimensions. | |
| 146 * | |
| 147 * @param {Window} contentWindow Window to be tested. | |
| 148 * @param {number} width Window width. | |
| 149 * @param {number} height Window height. | |
| 150 * @return {boolean} True for success. | |
| 151 */ | |
| 152 test.util.sync.resizeWindow = function(contentWindow, width, height) { | |
| 153 appWindows[contentWindow.appID].resizeTo(width, height); | |
| 154 return true; | |
| 155 }; | |
| 156 | |
| 157 /** | |
| 158 * Returns an array with the files currently selected in the file manager. | |
| 159 * | |
| 160 * @param {Window} contentWindow Window to be tested. | |
| 161 * @return {Array.<string>} Array of selected files. | |
| 162 */ | |
| 163 test.util.sync.getSelectedFiles = function(contentWindow) { | |
| 164 var table = contentWindow.document.querySelector('#detail-table'); | |
| 165 var rows = table.querySelectorAll('li'); | |
| 166 var selected = []; | |
| 167 for (var i = 0; i < rows.length; ++i) { | |
| 168 if (rows[i].hasAttribute('selected')) { | |
| 169 selected.push( | |
| 170 rows[i].querySelector('.filename-label').textContent); | |
| 171 } | |
| 172 } | |
| 173 return selected; | |
| 174 }; | |
| 175 | |
| 176 /** | |
| 177 * Returns an array with the files on the file manager's file list. | |
| 178 * | |
| 179 * @param {Window} contentWindow Window to be tested. | |
| 180 * @return {Array.<Array.<string>>} Array of rows. | |
| 181 */ | |
| 182 test.util.sync.getFileList = function(contentWindow) { | |
| 183 var table = contentWindow.document.querySelector('#detail-table'); | |
| 184 var rows = table.querySelectorAll('li'); | |
| 185 var fileList = []; | |
| 186 for (var j = 0; j < rows.length; ++j) { | |
| 187 var row = rows[j]; | |
| 188 fileList.push([ | |
| 189 row.querySelector('.filename-label').textContent, | |
| 190 row.querySelector('.size').textContent, | |
| 191 row.querySelector('.type').textContent, | |
| 192 row.querySelector('.date').textContent | |
| 193 ]); | |
| 194 } | |
| 195 return fileList; | |
| 196 }; | |
| 197 | |
| 198 /** | |
| 199 * Checkes if the given label and path of the volume are selected. | |
| 200 * @param {Window} contentWindow Window to be tested. | |
| 201 * @param {string} label Correct label the selected volume should have. | |
| 202 * @param {string} path Correct path the selected volume should have. | |
| 203 * @return {boolean} True for success. | |
| 204 */ | |
| 205 test.util.sync.checkSelectedVolume = function(contentWindow, label, path) { | |
| 206 var list = contentWindow.document.querySelector('#navigation-list'); | |
| 207 var rows = list.querySelectorAll('li'); | |
| 208 var selected = []; | |
| 209 for (var i = 0; i < rows.length; ++i) { | |
| 210 if (rows[i].hasAttribute('selected')) | |
| 211 selected.push(rows[i]); | |
| 212 } | |
| 213 // Selected item must be one. | |
| 214 if (selected.length !== 1) | |
| 215 return false; | |
| 216 | |
| 217 if (selected[0].modelItem.path !== path || | |
| 218 selected[0].querySelector('.root-label').textContent !== label) { | |
| 219 return false; | |
| 220 } | |
| 221 | |
| 222 return true; | |
| 223 }; | |
| 224 | |
| 225 /** | |
| 226 * Waits until the window is set to the specified dimensions. | |
| 227 * | |
| 228 * @param {Window} contentWindow Window to be tested. | |
| 229 * @param {number} width Requested width. | |
| 230 * @param {number} height Requested height. | |
| 231 * @param {function(Object)} callback Success callback with the dimensions. | |
| 232 */ | |
| 233 test.util.async.waitForWindowGeometry = function( | |
| 234 contentWindow, width, height, callback) { | |
| 235 test.util.repeatUntilTrue_(function() { | |
| 236 if (contentWindow.innerWidth == width && | |
| 237 contentWindow.innerHeight == height) { | |
| 238 callback({width: width, height: height}); | |
| 239 return true; | |
| 240 } | |
| 241 return false; | |
| 242 }); | |
| 243 }; | |
| 244 | |
| 245 /** | |
| 246 * Waits for an element and returns it as an array of it's attributes. | |
| 247 * | |
| 248 * @param {Window} contentWindow Window to be tested. | |
| 249 * @param {string} targetQuery Query to specify the element. | |
| 250 * @param {?string} iframeQuery Iframe selector or null if no iframe. | |
| 251 * @param {boolean=} opt_inverse True if the function should return if the | |
| 252 * element disappears, instead of appearing. | |
| 253 * @param {function(Object)} callback Callback with a hash array of attributes | |
| 254 * and contents as text. | |
| 255 */ | |
| 256 test.util.async.waitForElement = function( | |
| 257 contentWindow, targetQuery, iframeQuery, opt_inverse, callback) { | |
| 258 test.util.repeatUntilTrue_(function() { | |
| 259 var doc = test.util.sync.getDocument_(contentWindow, iframeQuery); | |
| 260 if (!doc) | |
| 261 return false; | |
| 262 var element = doc.querySelector(targetQuery); | |
| 263 if (!element) | |
| 264 return !!opt_inverse; | |
| 265 var attributes = {}; | |
| 266 for (var i = 0; i < element.attributes.length; i++) { | |
| 267 attributes[element.attributes[i].nodeName] = | |
| 268 element.attributes[i].nodeValue; | |
| 269 } | |
| 270 var text = element.textContent; | |
| 271 callback({attributes: attributes, text: text}); | |
| 272 return !opt_inverse; | |
| 273 }); | |
| 274 }; | |
| 275 | |
| 276 /** | |
| 277 * Calls getFileList until the number of displayed files is different from | |
| 278 * lengthBefore. | |
| 279 * | |
| 280 * @param {Window} contentWindow Window to be tested. | |
| 281 * @param {number} lengthBefore Number of items visible before. | |
| 282 * @param {function(Array.<Array.<string>>)} callback Change callback. | |
| 283 */ | |
| 284 test.util.async.waitForFileListChange = function( | |
| 285 contentWindow, lengthBefore, callback) { | |
| 286 test.util.repeatUntilTrue_(function() { | |
| 287 var files = test.util.sync.getFileList(contentWindow); | |
| 288 files.sort(); | |
| 289 var notReadyRows = files.filter(function(row) { | |
| 290 return row.filter(function(cell) { return cell == '...'; }).length; | |
| 291 }); | |
| 292 if (notReadyRows.length === 0 && | |
| 293 files.length !== lengthBefore && | |
| 294 files.length !== 0) { | |
| 295 callback(files); | |
| 296 return true; | |
| 297 } else { | |
| 298 return false; | |
| 299 } | |
| 300 }); | |
| 301 }; | |
| 302 | |
| 303 /** | |
| 304 * Returns an array of items on the file manager's autocomplete list. | |
| 305 * | |
| 306 * @param {Window} contentWindow Window to be tested. | |
| 307 * @return {Array.<string>} Array of items. | |
| 308 */ | |
| 309 test.util.sync.getAutocompleteList = function(contentWindow) { | |
| 310 var list = contentWindow.document.querySelector('#autocomplete-list'); | |
| 311 var lines = list.querySelectorAll('li'); | |
| 312 var items = []; | |
| 313 for (var j = 0; j < lines.length; ++j) { | |
| 314 var line = lines[j]; | |
| 315 items.push(line.innerText); | |
| 316 } | |
| 317 return items; | |
| 318 }; | |
| 319 | |
| 320 /** | |
| 321 * Performs autocomplete with the given query and waits until at least | |
| 322 * |numExpectedItems| items are shown, including the first item which | |
| 323 * always looks like "'<query>' - search Drive". | |
| 324 * | |
| 325 * @param {Window} contentWindow Window to be tested. | |
| 326 * @param {string} query Query used for autocomplete. | |
| 327 * @param {number} numExpectedItems number of items to be shown. | |
| 328 * @param {function(Array.<string>)} callback Change callback. | |
| 329 */ | |
| 330 test.util.async.performAutocompleteAndWait = function( | |
| 331 contentWindow, query, numExpectedItems, callback) { | |
| 332 // Dispatch a 'focus' event to the search box so that the autocomplete list | |
| 333 // is attached to the search box. Note that calling searchBox.focus() won't | |
| 334 // dispatch a 'focus' event. | |
| 335 var searchBox = contentWindow.document.querySelector('#search-box input'); | |
| 336 var focusEvent = contentWindow.document.createEvent('Event'); | |
| 337 focusEvent.initEvent('focus', true /* bubbles */, true /* cancelable */); | |
| 338 searchBox.dispatchEvent(focusEvent); | |
| 339 | |
| 340 // Change the value of the search box and dispatch an 'input' event so that | |
| 341 // the autocomplete query is processed. | |
| 342 searchBox.value = query; | |
| 343 var inputEvent = contentWindow.document.createEvent('Event'); | |
| 344 inputEvent.initEvent('input', true /* bubbles */, true /* cancelable */); | |
| 345 searchBox.dispatchEvent(inputEvent); | |
| 346 | |
| 347 test.util.repeatUntilTrue_(function() { | |
| 348 var items = test.util.sync.getAutocompleteList(contentWindow); | |
| 349 if (items.length >= numExpectedItems) { | |
| 350 callback(items); | |
| 351 return true; | |
| 352 } else { | |
| 353 return false; | |
| 354 } | |
| 355 }); | |
| 356 }; | |
| 357 | |
| 358 /** | |
| 359 * Waits until a dialog with an OK button is shown and accepts it. | |
| 360 * | |
| 361 * @param {Window} contentWindow Window to be tested. | |
| 362 * @param {function()} callback Success callback. | |
| 363 */ | |
| 364 test.util.async.waitAndAcceptDialog = function(contentWindow, callback) { | |
| 365 test.util.repeatUntilTrue_(function() { | |
| 366 var button = contentWindow.document.querySelector('.cr-dialog-ok'); | |
| 367 if (!button) | |
| 368 return false; | |
| 369 button.click(); | |
| 370 // Wait until the dialog is removed from the DOM. | |
| 371 test.util.repeatUntilTrue_(function() { | |
| 372 if (contentWindow.document.querySelector('.cr-dialog-container')) | |
| 373 return false; | |
| 374 callback(); | |
| 375 return true; | |
| 376 }); | |
| 377 return true; | |
| 378 }); | |
| 379 }; | |
| 380 | |
| 381 /** | |
| 382 * Fakes pressing the down arrow until the given |filename| is selected. | |
| 383 * | |
| 384 * @param {Window} contentWindow Window to be tested. | |
| 385 * @param {string} filename Name of the file to be selected. | |
| 386 * @return {boolean} True if file got selected, false otherwise. | |
| 387 */ | |
| 388 test.util.sync.selectFile = function(contentWindow, filename) { | |
| 389 var table = contentWindow.document.querySelector('#detail-table'); | |
| 390 var rows = table.querySelectorAll('li'); | |
| 391 for (var index = 0; index < rows.length; ++index) { | |
| 392 test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'Down', false); | |
| 393 var selection = test.util.sync.getSelectedFiles(contentWindow); | |
| 394 if (selection.length === 1 && selection[0] === filename) | |
| 395 return true; | |
| 396 } | |
| 397 console.error('Failed to select file "' + filename + '"'); | |
| 398 return false; | |
| 399 }; | |
| 400 | |
| 401 /** | |
| 402 * Open the file by selectFile and fakeMouseDoubleClick. | |
| 403 * | |
| 404 * @param {Window} contentWindow Window to be tested. | |
| 405 * @param {string} filename Name of the file to be opened. | |
| 406 * @return {boolean} True if file got selected and a double click message is | |
| 407 * sent, false otherwise. | |
| 408 */ | |
| 409 test.util.sync.openFile = function(contentWindow, filename) { | |
| 410 var query = '#file-list li.table-row[selected] .filename-label span'; | |
| 411 return test.util.sync.selectFile(contentWindow, filename) && | |
| 412 test.util.sync.fakeMouseDoubleClick(contentWindow, query); | |
| 413 }; | |
| 414 | |
| 415 /** | |
| 416 * Selects a volume specified by its icon name | |
| 417 * | |
| 418 * @param {Window} contentWindow Window to be tested. | |
| 419 * @param {string} iconName Name of the volume icon. | |
| 420 * @param {function(boolean)} callback Callback function to notify the caller | |
| 421 * whether the target is found and mousedown and click events are sent. | |
| 422 */ | |
| 423 test.util.async.selectVolume = function(contentWindow, iconName, callback) { | |
| 424 var query = '[volume-type-icon=' + iconName + ']'; | |
| 425 var driveQuery = '[volume-type-icon=drive]'; | |
| 426 var isDriveSubVolume = iconName == 'drive_recent' || | |
| 427 iconName == 'drive_shared_with_me' || | |
| 428 iconName == 'drive_offline'; | |
| 429 var preSelection = false; | |
| 430 var steps = { | |
| 431 checkQuery: function() { | |
| 432 if (contentWindow.document.querySelector(query)) { | |
| 433 steps.sendEvents(); | |
| 434 return; | |
| 435 } | |
| 436 // If the target volume is sub-volume of drive, we must click 'drive' | |
| 437 // before clicking the sub-item. | |
| 438 if (!preSelection) { | |
| 439 if (!isDriveSubVolume) { | |
| 440 callback(false); | |
| 441 return; | |
| 442 } | |
| 443 if (!(test.util.sync.fakeMouseDown(contentWindow, driveQuery) && | |
| 444 test.util.sync.fakeMouseClick(contentWindow, driveQuery))) { | |
| 445 callback(false); | |
| 446 return; | |
| 447 } | |
| 448 preSelection = true; | |
| 449 } | |
| 450 setTimeout(steps.checkQuery, 50); | |
| 451 }, | |
| 452 sendEvents: function() { | |
| 453 // To change the selected volume, we have to send both events 'mousedown' | |
| 454 // and 'click' to the navigation list. | |
| 455 callback(test.util.sync.fakeMouseDown(contentWindow, query) && | |
| 456 test.util.sync.fakeMouseClick(contentWindow, query)); | |
| 457 } | |
| 458 }; | |
| 459 steps.checkQuery(); | |
| 460 }; | |
| 461 | |
| 462 /** | |
| 463 * Waits the contents of file list becomes to equal to expected contents. | |
| 464 * | |
| 465 * @param {Window} contentWindow Window to be tested. | |
| 466 * @param {Array.<Array.<string>>} expected Expected contents of file list. | |
| 467 * @param {{orderCheck:boolean=, ignoreLastModifiedTime:boolean=}=} opt_options | |
| 468 * Options of the comparison. If orderCheck is true, it also compares the | |
| 469 * order of files. If ignoreLastModifiedTime is true, it compares the file | |
| 470 * without its last modified time. | |
| 471 * @param {function()} callback Callback function to notify the caller that | |
| 472 * expected files turned up. | |
| 473 */ | |
| 474 test.util.async.waitForFiles = function( | |
| 475 contentWindow, expected, opt_options, callback) { | |
| 476 var options = opt_options || {}; | |
| 477 test.util.repeatUntilTrue_(function() { | |
| 478 var files = test.util.sync.getFileList(contentWindow); | |
| 479 if (!options.orderCheck) { | |
| 480 files.sort(); | |
| 481 expected.sort(); | |
| 482 } | |
| 483 if (options.ignoreLastModifiedTime) { | |
| 484 for (var i = 0; i < Math.min(files.length, expected.length); i++) { | |
| 485 files[i][3] = ''; | |
| 486 expected[i][3] = ''; | |
| 487 } | |
| 488 } | |
| 489 if (chrome.test.checkDeepEq(expected, files)) { | |
| 490 callback(true); | |
| 491 return true; | |
| 492 } | |
| 493 return false; | |
| 494 }); | |
| 495 }; | |
| 496 | |
| 497 /** | |
| 498 * Executes Javascript code on a webview and returns the result. | |
| 499 * | |
| 500 * @param {Window} contentWindow Window to be tested. | |
| 501 * @param {string} webViewQuery Selector for the web view. | |
| 502 * @param {string} code Javascript code to be executed within the web view. | |
| 503 * @param {function(*)} callback Callback function with results returned by the | |
| 504 * script. | |
| 505 */ | |
| 506 test.util.async.executeScriptInWebView = function( | |
| 507 contentWindow, webViewQuery, code, callback) { | |
| 508 var webView = contentWindow.document.querySelector(webViewQuery); | |
| 509 webView.executeScript({code: code}, callback); | |
| 510 }; | |
| 511 | |
| 512 /** | |
| 513 * Sends an event to the element specified by |targetQuery|. | |
| 514 * | |
| 515 * @param {Window} contentWindow Window to be tested. | |
| 516 * @param {string} targetQuery Query to specify the element. | |
| 517 * @param {Event} event Event to be sent. | |
| 518 * @param {string=} opt_iframeQuery Optional iframe selector. | |
| 519 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 520 */ | |
| 521 test.util.sync.sendEvent = function( | |
| 522 contentWindow, targetQuery, event, opt_iframeQuery) { | |
| 523 var doc = test.util.sync.getDocument_(contentWindow, opt_iframeQuery); | |
| 524 if (doc) { | |
| 525 var target = doc.querySelector(targetQuery); | |
| 526 if (target) { | |
| 527 target.dispatchEvent(event); | |
| 528 return true; | |
| 529 } | |
| 530 } | |
| 531 console.error('Target element for ' + targetQuery + ' not found.'); | |
| 532 return false; | |
| 533 }; | |
| 534 | |
| 535 /** | |
| 536 * Sends an fake event having the specified type to the target query. | |
| 537 * | |
| 538 * @param {Window} contentWindow Window to be tested. | |
| 539 * @param {string} targetQuery Query to specify the element. | |
| 540 * @param {string} event Type of event. | |
| 541 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 542 */ | |
| 543 test.util.sync.fakeEvent = function(contentWindow, targetQuery, event) { | |
| 544 return test.util.sync.sendEvent( | |
| 545 contentWindow, targetQuery, new Event(event)); | |
| 546 }; | |
| 547 | |
| 548 /** | |
| 549 * Sends a fake key event to the element specified by |targetQuery| with the | |
| 550 * given |keyIdentifier| and optional |ctrl| modifier to the file manager. | |
| 551 * | |
| 552 * @param {Window} contentWindow Window to be tested. | |
| 553 * @param {string} targetQuery Query to specify the element. | |
| 554 * @param {string} keyIdentifier Identifier of the emulated key. | |
| 555 * @param {boolean} ctrl Whether CTRL should be pressed, or not. | |
| 556 * @param {string=} opt_iframeQuery Optional iframe selector. | |
| 557 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 558 */ | |
| 559 test.util.sync.fakeKeyDown = function( | |
| 560 contentWindow, targetQuery, keyIdentifier, ctrl, opt_iframeQuery) { | |
| 561 var event = new KeyboardEvent( | |
| 562 'keydown', | |
| 563 { bubbles: true, keyIdentifier: keyIdentifier, ctrlKey: ctrl }); | |
| 564 return test.util.sync.sendEvent( | |
| 565 contentWindow, targetQuery, event, opt_iframeQuery); | |
| 566 }; | |
| 567 | |
| 568 /** | |
| 569 * Sends a fake mouse click event (left button, single click) to the element | |
| 570 * specified by |targetQuery|. | |
| 571 * | |
| 572 * @param {Window} contentWindow Window to be tested. | |
| 573 * @param {string} targetQuery Query to specify the element. | |
| 574 * @param {string=} opt_iframeQuery Optional iframe selector. | |
| 575 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 576 */ | |
| 577 test.util.sync.fakeMouseClick = function( | |
| 578 contentWindow, targetQuery, opt_iframeQuery) { | |
| 579 var event = new MouseEvent('click', { bubbles: true, detail: 1 }); | |
| 580 return test.util.sync.sendEvent( | |
| 581 contentWindow, targetQuery, event, opt_iframeQuery); | |
| 582 }; | |
| 583 | |
| 584 /** | |
| 585 * Simulates a fake double click event (left button) to the element specified by | |
| 586 * |targetQuery|. | |
| 587 * | |
| 588 * @param {Window} contentWindow Window to be tested. | |
| 589 * @param {string} targetQuery Query to specify the element. | |
| 590 * @param {string=} opt_iframeQuery Optional iframe selector. | |
| 591 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 592 */ | |
| 593 test.util.sync.fakeMouseDoubleClick = function( | |
| 594 contentWindow, targetQuery, opt_iframeQuery) { | |
| 595 // Double click is always preceded with a single click. | |
| 596 if (!test.util.sync.fakeMouseClick( | |
| 597 contentWindow, targetQuery, opt_iframeQuery)) { | |
| 598 return false; | |
| 599 } | |
| 600 | |
| 601 // Send the second click event, but with detail equal to 2 (number of clicks) | |
| 602 // in a row. | |
| 603 var event = new MouseEvent('click', { bubbles: true, detail: 2 }); | |
| 604 if (!test.util.sync.sendEvent( | |
| 605 contentWindow, targetQuery, event, opt_iframeQuery)) { | |
| 606 return false; | |
| 607 } | |
| 608 | |
| 609 // Send the double click event. | |
| 610 var event = new MouseEvent('dblclick', { bubbles: true }); | |
| 611 if (!test.util.sync.sendEvent( | |
| 612 contentWindow, targetQuery, event, opt_iframeQuery)) { | |
| 613 return false; | |
| 614 } | |
| 615 | |
| 616 return true; | |
| 617 }; | |
| 618 | |
| 619 /** | |
| 620 * Sends a fake mouse down event to the element specified by |targetQuery|. | |
| 621 * | |
| 622 * @param {Window} contentWindow Window to be tested. | |
| 623 * @param {string} targetQuery Query to specify the element. | |
| 624 * @param {string=} opt_iframeQuery Optional iframe selector. | |
| 625 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 626 */ | |
| 627 test.util.sync.fakeMouseDown = function( | |
| 628 contentWindow, targetQuery, opt_iframeQuery) { | |
| 629 var event = new MouseEvent('mousedown', { bubbles: true }); | |
| 630 return test.util.sync.sendEvent( | |
| 631 contentWindow, targetQuery, event, opt_iframeQuery); | |
| 632 }; | |
| 633 | |
| 634 /** | |
| 635 * Sends a fake mouse up event to the element specified by |targetQuery|. | |
| 636 * | |
| 637 * @param {Window} contentWindow Window to be tested. | |
| 638 * @param {string} targetQuery Query to specify the element. | |
| 639 * @param {string=} opt_iframeQuery Optional iframe selector. | |
| 640 * @return {boolean} True if the event is sent to the target, false otherwise. | |
| 641 */ | |
| 642 test.util.sync.fakeMouseUp = function( | |
| 643 contentWindow, targetQuery, opt_iframeQuery) { | |
| 644 var event = new MouseEvent('mouseup', { bubbles: true }); | |
| 645 return test.util.sync.sendEvent( | |
| 646 contentWindow, targetQuery, event, opt_iframeQuery); | |
| 647 }; | |
| 648 | |
| 649 /** | |
| 650 * Selects |filename| and fakes pressing Ctrl+C, Ctrl+V (copy, paste). | |
| 651 * | |
| 652 * @param {Window} contentWindow Window to be tested. | |
| 653 * @param {string} filename Name of the file to be copied. | |
| 654 * @return {boolean} True if copying got simulated successfully. It does not | |
| 655 * say if the file got copied, or not. | |
| 656 */ | |
| 657 test.util.sync.copyFile = function(contentWindow, filename) { | |
| 658 if (!test.util.sync.selectFile(contentWindow, filename)) | |
| 659 return false; | |
| 660 // Ctrl+C and Ctrl+V | |
| 661 test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+0043', true); | |
| 662 test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+0056', true); | |
| 663 return true; | |
| 664 }; | |
| 665 | |
| 666 /** | |
| 667 * Selects |filename| and fakes pressing the Delete key. | |
| 668 * | |
| 669 * @param {Window} contentWindow Window to be tested. | |
| 670 * @param {string} filename Name of the file to be deleted. | |
| 671 * @return {boolean} True if deleting got simulated successfully. It does not | |
| 672 * say if the file got deleted, or not. | |
| 673 */ | |
| 674 test.util.sync.deleteFile = function(contentWindow, filename) { | |
| 675 if (!test.util.sync.selectFile(contentWindow, filename)) | |
| 676 return false; | |
| 677 // Delete | |
| 678 test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+007F', false); | |
| 679 return true; | |
| 680 }; | |
| 681 | |
| 682 /** | |
| 683 * Wait for the elements' style to be changed as the expected values. The | |
| 684 * queries argument is a list of object that have the query property and the | |
| 685 * styles property. The query property is a string query to specify the | |
| 686 * element. The styles property is a string map of the style name and its | |
| 687 * expected value. | |
| 688 * | |
| 689 * @param {Window} contentWindow Window to be tested. | |
| 690 * @param {Array.<object>} queries Queries that specifies the elements and | |
| 691 * expected styles. | |
| 692 * @param {function()} callback Callback function to be notified the change of | |
| 693 * the styles. | |
| 694 */ | |
| 695 test.util.async.waitForStyles = function(contentWindow, queries, callback) { | |
| 696 test.util.repeatUntilTrue_(function() { | |
| 697 for (var i = 0; i < queries.length; i++) { | |
| 698 var element = contentWindow.document.querySelector(queries[i].query); | |
| 699 var styles = queries[i].styles; | |
| 700 for (var name in styles) { | |
| 701 if (contentWindow.getComputedStyle(element)[name] != styles[name]) | |
| 702 return false; | |
| 703 } | |
| 704 } | |
| 705 callback(); | |
| 706 return true; | |
| 707 }); | |
| 708 }; | |
| 709 | |
| 710 /** | |
| 711 * Execute a command on the document in the specified window. | |
| 712 * | |
| 713 * @param {Window} contentWindow Window to be tested. | |
| 714 * @param {string} command Command name. | |
| 715 * @return {boolean} True if the command is executed successfully. | |
| 716 */ | |
| 717 test.util.sync.execCommand = function(contentWindow, command) { | |
| 718 return contentWindow.document.execCommand(command); | |
| 719 }; | |
| 720 | |
| 721 /** | |
| 722 * Registers message listener, which runs test utility functions. | |
| 723 */ | |
| 724 test.util.registerRemoteTestUtils = function() { | |
| 725 // Register the message listener. | |
| 726 var onMessage = chrome.runtime ? chrome.runtime.onMessageExternal : | |
| 727 chrome.extension.onMessageExternal; | |
| 728 // Return true for asynchronous functions and false for synchronous. | |
| 729 onMessage.addListener(function(request, sender, sendResponse) { | |
| 730 // Check the sender. | |
| 731 if (sender.id != test.util.TESTING_EXTENSION_ID) { | |
| 732 console.error('The testing extension must be white-listed.'); | |
| 733 return false; | |
| 734 } | |
| 735 // Set a global flag that we are in tests, so other components are aware | |
| 736 // of it. | |
| 737 window.IN_TEST = true; | |
| 738 // Check the function name. | |
| 739 if (!request.func || request.func[request.func.length - 1] == '_') { | |
| 740 request.func = ''; | |
| 741 } | |
| 742 // Prepare arguments. | |
| 743 var args = request.args.slice(); // shallow copy | |
| 744 if (request.appId) { | |
| 745 if (!appWindows[request.appId]) { | |
| 746 console.error('Specified window not found.'); | |
| 747 return false; | |
| 748 } | |
| 749 args.unshift(appWindows[request.appId].contentWindow); | |
| 750 } | |
| 751 // Call the test utility function and respond the result. | |
| 752 if (test.util.async[request.func]) { | |
| 753 args[test.util.async[request.func].length - 1] = function() { | |
| 754 console.debug('Received the result of ' + request.func); | |
| 755 sendResponse.apply(null, arguments); | |
| 756 }; | |
| 757 console.debug('Waiting for the result of ' + request.func); | |
| 758 test.util.async[request.func].apply(null, args); | |
| 759 return true; | |
| 760 } else if (test.util.sync[request.func]) { | |
| 761 sendResponse(test.util.sync[request.func].apply(null, args)); | |
| 762 return false; | |
| 763 } else { | |
| 764 console.error('Invalid function name.'); | |
| 765 return false; | |
| 766 } | |
| 767 }); | |
| 768 }; | |
| 769 | |
| 770 // Register the test utils. | |
| 771 test.util.registerRemoteTestUtils(); | |
| OLD | NEW |