Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / browser_test / browser_test.js
blobc247a15e28bd86c244e53749e3525d162eff3598
1 // Copyright 2014 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.
5 /**
6  * @fileoverview
7  *
8  * Provides basic functionality for JavaScript based browser test.
9  *
10  * To define a browser test, create a class under the browserTest namespace.
11  * You can pass arbitrary object literals to the browser test from the C++ test
12  * harness as the test data.  Each browser test class should implement the run
13  * method.
14  * For example:
15  *
16  * browserTest.My_Test = function() {};
17  * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... };
18  *
19  * The browser test is async in nature.  It will keep running until
20  * browserTest.fail("My error message.") or browserTest.pass() is called.
21  *
22  * For example:
23  *
24  * browserTest.My_Test.prototype.run(myObjectLiteral) = function() {
25  *   window.setTimeout(function() {
26  *     if (doSomething(myObjectLiteral)) {
27  *       browserTest.pass();
28  *     } else {
29  *       browserTest.fail('My error message.');
30  *     }
31  *   }, 1000);
32  * };
33  *
34  * You will then invoke the test in C++ by calling:
35  *
36  *   RunJavaScriptTest(web_content, "My_Test", "{"
37  *    "pin: '123123'"
38  *  "}");
39  */
41 'use strict';
43 /** @suppress {duplicate} */
44 var browserTest = browserTest || {};
46 /** @type {window.DomAutomationController} */
47 browserTest.automationController_ = null;
49 /**
50  * @return {void}
51  * @suppress {checkTypes|reportUnknownTypes}
52  */
53 browserTest.init = function() {
54   // The domAutomationController is used to communicate progress back to the
55   // C++ calling code.  It will only exist if chrome is run with the flag
56   // --dom-automation.  It is stubbed out here so that browser test can be run
57   // under the regular app.
58   if (window.domAutomationController) {
59     /** @type {window.DomAutomationController} */
60     browserTest.automationController_ = window.domAutomationController;
61   } else {
62     browserTest.automationController_ = {
63       send: function(json) {
64         var result = JSON.parse(json);
65         if (result.succeeded) {
66           console.log('Test Passed.');
67         } else {
68           console.error('Test Failed.\n' +
69               result.error_message + '\n' + result.stack_trace);
70         }
71       }
72     };
73   };
76 /**
77  * Fails the C++ calling browser test with |message| if |expr| is false.
78  * @param {*} expr
79  * @param {string=} opt_message
80  * @return {void}
81  */
82 browserTest.expect = function(expr, opt_message) {
83   if (!expr) {
84     var message = (opt_message) ? '<' + opt_message + '>' : '';
85     browserTest.fail('Expectation failed.' + opt_message);
86   }
89 /**
90  * @param {string|Error} error
91  * @return {void}
92  */
93 browserTest.fail = function(error) {
94   var error_message = error;
95   var stack_trace = new base.Callstack().toString();
97   if (error instanceof Error) {
98     error_message = error.toString();
99     stack_trace = error.stack;
100   }
102   console.error(error_message);
104   // To run browserTest locally:
105   // 1. Go to |remoting_webapp_files| and look for
106   //    |remoting_webapp_js_browser_test_files| and uncomment it
107   // 2. gclient runhooks
108   // 3. rebuild the webapp
109   // 4. Run it in the console browserTest.runTest(browserTest.MyTest, {});
110   // 5. The line below will trap the test in the debugger in case of
111   //    failure.
112   debugger;
114   browserTest.automationController_.send(JSON.stringify({
115     succeeded: false,
116     error_message: error_message,
117     stack_trace: stack_trace
118   }));
122  * @return {void}
123  */
124 browserTest.pass = function() {
125   browserTest.automationController_.send(JSON.stringify({
126     succeeded: true,
127     error_message: '',
128     stack_trace: ''
129   }));
133  * @param {string} id The id or the selector of the element.
134  * @return {void}
135  */
136 browserTest.clickOnControl = function(id) {
137   var element = document.getElementById(id);
138   if (!element) {
139     element = document.querySelector(id);
140   }
141   browserTest.expect(element, 'No such element: ' + id);
142   element.click();
146  * @param {remoting.AppMode} expectedMode
147  * @param {number=} opt_timeout
148  * @return {Promise}
149  */
150 browserTest.onUIMode = function(expectedMode, opt_timeout) {
151   if (expectedMode == remoting.currentMode) {
152     // If the current mode is the same as the expected mode, return a fulfilled
153     // promise.  For some reason, if we fulfill the promise in the same
154     // callstack, V8 will assert at V8RecursionScope.h(66) with
155     // ASSERT(!ScriptForbiddenScope::isScriptForbidden()).
156     // To avoid the assert, execute the callback in a different callstack.
157     return base.Promise.sleep(0);
158   }
160   return new Promise (function(fulfill, reject) {
161     var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
162     var timerId = null;
164     if (opt_timeout === undefined) {
165       opt_timeout = browserTest.Timeout.DEFAULT;
166     }
168     function onTimeout() {
169       remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
170       reject('Timeout waiting for ' + expectedMode);
171     }
173     /** @param {remoting.AppMode} mode */
174     function onUIModeChanged(mode) {
175       if (mode == expectedMode) {
176         remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
177         window.clearTimeout(timerId);
178         timerId = null;
179         fulfill(true);
180       }
181     }
183     if (opt_timeout != browserTest.Timeout.NONE) {
184       timerId = window.setTimeout(onTimeout,
185                                   /** @type {number} */ (opt_timeout));
186     }
187     remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
188   });
192  * @return {Promise}
193  */
194 browserTest.connectMe2Me = function() {
195   var AppMode = remoting.AppMode;
196   // The one second timeout is necessary because the click handler of
197   // 'this-host-connect' is registered asynchronously.
198   return base.Promise.sleep(1000).then(function() {
199       browserTest.clickOnControl('local-host-connect-button');
200     }).then(function(){
201       return browserTest.onUIMode(AppMode.CLIENT_HOST_NEEDS_UPGRADE);
202     }).then(function() {
203       // On fulfilled.
204       browserTest.clickOnControl('#host-needs-update-dialog .connect-button');
205     }, function() {
206       // On time out.
207       return Promise.resolve();
208     }).then(function() {
209       return browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT, 10000);
210     });
214  * @return {Promise}
215  */
216 browserTest.disconnect = function() {
217   console.assert(remoting.app instanceof remoting.DesktopRemoting,
218                 '|remoting.app| is not an instance of DesktopRemoting.');
219   var drApp = /** @type {remoting.DesktopRemoting} */ (remoting.app);
220   var mode = drApp.getConnectionMode();
222   var AppMode = remoting.AppMode;
223   var finishedMode = AppMode.CLIENT_SESSION_FINISHED_ME2ME;
224   var finishedButton = 'client-finished-me2me-button';
225   if (mode === remoting.DesktopRemoting.Mode.IT2ME) {
226     finishedMode = AppMode.CLIENT_SESSION_FINISHED_IT2ME;
227     finishedButton = 'client-finished-it2me-button';
228   }
230   var activity = remoting.app.getActivity();
231   if (!activity) {
232     return Promise.resolve();
233   }
235   activity.stop();
237   return browserTest.onUIMode(finishedMode).then(function() {
238     browserTest.clickOnControl(finishedButton);
239     return browserTest.onUIMode(AppMode.HOME);
240   });
244  * @param {string} pin
245  * @param {boolean=} opt_expectError
246  * @return {Promise}
247  */
248 browserTest.enterPIN = function(pin, opt_expectError) {
249   // Wait for 500ms before hitting the PIN button. From experiment, sometimes
250   // the PIN prompt does not dismiss without the timeout.
251   var CONNECT_PIN_WAIT = 500;
253   document.getElementById('pin-entry').value = pin;
255   return base.Promise.sleep(CONNECT_PIN_WAIT).then(function() {
256     browserTest.clickOnControl('pin-connect-button');
257   }).then(function() {
258     if (opt_expectError) {
259       return browserTest.expectConnectionError(
260           remoting.DesktopRemoting.Mode.ME2ME,
261           [remoting.Error.Tag.INVALID_ACCESS_CODE]);
262     } else {
263       return browserTest.expectConnected();
264     }
265   });
269  * @param {remoting.DesktopRemoting.Mode} connectionMode
270  * @param {Array<remoting.Error.Tag>} errorTags
271  * @return {Promise}
272  */
273 browserTest.expectConnectionError = function(connectionMode, errorTags) {
274   var AppMode = remoting.AppMode;
275   var Timeout = browserTest.Timeout;
277   // Timeout if the session is not failed within 30 seconds.
278   var SESSION_CONNECTION_TIMEOUT = 30000;
280   var finishButton = 'client-finished-me2me-button';
281   var failureMode = AppMode.CLIENT_CONNECT_FAILED_ME2ME;
283   if (connectionMode == remoting.DesktopRemoting.Mode.IT2ME) {
284     finishButton = 'client-finished-it2me-button';
285     failureMode = AppMode.CLIENT_CONNECT_FAILED_IT2ME;
286   }
288   var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.NONE);
289   var onFailure = browserTest.onUIMode(failureMode, SESSION_CONNECTION_TIMEOUT);
291   onConnected = onConnected.then(function() {
292     return Promise.reject(
293         'Expected the connection to fail.');
294   });
296   onFailure = onFailure.then(function() {
297     /** @type {Element} */
298     var errorDiv = document.getElementById('connect-error-message');
299     var actual = errorDiv.innerText;
300     var expected = errorTags.map(function(/** string */errorTag) {
301       return l10n.getTranslationOrError(errorTag);
302     });
303     browserTest.clickOnControl(finishButton);
305     if (expected.indexOf(actual) === -1) {
306       return Promise.reject('Unexpected failure. actual: ' + actual +
307                             ' expected: ' + expected.join(','));
308     }
309   });
311   return Promise.race([onConnected, onFailure]);
315  * @return {Promise}
316  */
317 browserTest.expectConnected = function() {
318   var AppMode = remoting.AppMode;
319   // Timeout if the session is not connected within 30 seconds.
320   var SESSION_CONNECTION_TIMEOUT = 30000;
321   var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
322                                          SESSION_CONNECTION_TIMEOUT);
323   var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
324                                        browserTest.Timeout.NONE);
325   onFailure = onFailure.then(function() {
326     var errorDiv = document.getElementById('connect-error-message');
327     var errorMsg = errorDiv.innerText;
328     return Promise.reject('Unexpected error - ' + errorMsg);
329   });
330   return Promise.race([onConnected, onFailure]);
334  * @param {base.EventSource} eventSource
335  * @param {string} event
336  * @param {number} timeoutMs
337  * @param {*=} opt_expectedData
338  * @return {Promise}
339  */
340 browserTest.expectEvent = function(eventSource, event, timeoutMs,
341                                    opt_expectedData) {
342   return new Promise(function(fullfil, reject) {
343     /** @param {string=} actualData */
344     var verifyEventParameters = function(actualData) {
345       if (opt_expectedData === undefined || opt_expectedData === actualData) {
346         fullfil(true);
347       } else {
348         reject('Bad event data; expected ' + opt_expectedData +
349                '; got ' + actualData);
350       }
351     };
352     eventSource.addEventListener(event, verifyEventParameters);
353     base.Promise.sleep(timeoutMs).then(function() {
354       reject(Error('Event ' + event + ' not received after ' +
355                    timeoutMs + 'ms.'));
356     });
357   });
361  * @param {Function} testClass
362  * @param {*} data
363  * @return {void}
364  * @suppress {checkTypes|checkVars|reportUnknownTypes}
365  */
366 browserTest.runTest = function(testClass, data) {
367   try {
368     var test = new testClass();
369     browserTest.expect(typeof test.run == 'function');
370     test.run(data);
371   } catch (/** @type {Error} */ e) {
372     browserTest.fail(e);
373   }
377  * @param {string} newPin
378  * @return {Promise}
379  */
380 browserTest.setupPIN = function(newPin) {
381   var AppMode = remoting.AppMode;
382   var HOST_SETUP_WAIT = 10000;
383   var Timeout = browserTest.Timeout;
385   return browserTest.onUIMode(AppMode.HOST_SETUP_ASK_PIN).then(function() {
386     document.getElementById('daemon-pin-entry').value = newPin;
387     document.getElementById('daemon-pin-confirm').value = newPin;
388     browserTest.clickOnControl('daemon-pin-ok');
390     var success = browserTest.onUIMode(AppMode.HOST_SETUP_DONE, Timeout.NONE);
391     var failure = browserTest.onUIMode(AppMode.HOST_SETUP_ERROR, Timeout.NONE);
392     failure = failure.then(function(){
393       return Promise.reject('Unexpected host setup failure');
394     });
395     return Promise.race([success, failure]);
396   }).then(function() {
397     console.log('browserTest: PIN Setup is done.');
398     browserTest.clickOnControl('host-config-done-dismiss');
400     // On Linux, we restart the host after changing the PIN, need to sleep
401     // for ten seconds before the host is ready for connection.
402     return base.Promise.sleep(HOST_SETUP_WAIT);
403   });
407  * @return {Promise<boolean>}
408  */
409 browserTest.isLocalHostStarted = function() {
410   return new Promise(function(resolve) {
411     remoting.hostController.getLocalHostState(function(state) {
412       resolve(remoting.HostController.State.STARTED == state);
413     });
414   });
418  * @param {string} pin
419  * @return {Promise}
420  */
421 browserTest.ensureHostStartedWithPIN = function(pin) {
422   // Return if host is already
423   return browserTest.isLocalHostStarted().then(
424       /** @param {boolean} started */
425       function(started){
426         if (!started) {
427           console.log('browserTest: Enabling remote connection.');
428           browserTest.clickOnControl('.start-daemon');
429         } else {
430           console.log('browserTest: Changing the PIN of the host to: ' +
431               pin + '.');
432           browserTest.clickOnControl('.change-daemon-pin');
433         }
434         return browserTest.setupPIN(pin);
435       });
439  * Called by Browser Test in C++
440  * @param {string} pin
441  * @suppress {checkTypes}
442  */
443 browserTest.ensureRemoteConnectionEnabled = function(pin) {
444   browserTest.ensureHostStartedWithPIN(pin).then(function() {
445     browserTest.pass();
446   }, function(reason) {
447     browserTest.fail(reason);
448   });
451 browserTest.init();