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 |