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