Roll src/third_party/skia 4cd6713:0edd965
[chromium-blink-merge.git] / remoting / webapp / browser_test / browser_test.js
blob67169883e023f8ca6bd04c424c8c954c4a94fce8
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 = base.debug.callstack();
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   base.debug.assert(remoting.app instanceof remoting.DesktopRemoting);
218   var drApp = /** @type {remoting.DesktopRemoting} */ (remoting.app);
219   var mode = drApp.getConnectionMode();
221   var AppMode = remoting.AppMode;
222   var finishedMode = AppMode.CLIENT_SESSION_FINISHED_ME2ME;
223   var finishedButton = 'client-finished-me2me-button';
224   if (mode === remoting.DesktopRemoting.Mode.IT2ME) {
225     finishedMode = AppMode.CLIENT_SESSION_FINISHED_IT2ME;
226     finishedButton = 'client-finished-it2me-button';
227   }
229   var activity = remoting.app.getActivity();
230   if (!activity) {
231     return Promise.resolve();
232   }
234   activity.stop();
236   return browserTest.onUIMode(finishedMode).then(function() {
237     browserTest.clickOnControl(finishedButton);
238     return browserTest.onUIMode(AppMode.HOME);
239   });
243  * @param {string} pin
244  * @param {boolean=} opt_expectError
245  * @return {Promise}
246  */
247 browserTest.enterPIN = function(pin, opt_expectError) {
248   // Wait for 500ms before hitting the PIN button. From experiment, sometimes
249   // the PIN prompt does not dismiss without the timeout.
250   var CONNECT_PIN_WAIT = 500;
252   document.getElementById('pin-entry').value = pin;
254   return base.Promise.sleep(CONNECT_PIN_WAIT).then(function() {
255     browserTest.clickOnControl('pin-connect-button');
256   }).then(function() {
257     if (opt_expectError) {
258       return browserTest.expectConnectionError(
259           remoting.DesktopRemoting.Mode.ME2ME,
260           [remoting.Error.Tag.INVALID_ACCESS_CODE]);
261     } else {
262       return browserTest.expectConnected();
263     }
264   });
268  * @param {remoting.DesktopRemoting.Mode} connectionMode
269  * @param {Array<remoting.Error.Tag>} errorTags
270  * @return {Promise}
271  */
272 browserTest.expectConnectionError = function(connectionMode, errorTags) {
273   var AppMode = remoting.AppMode;
274   var Timeout = browserTest.Timeout;
276   var finishButton = 'client-finished-me2me-button';
278   if (connectionMode == remoting.DesktopRemoting.Mode.IT2ME) {
279     finishButton = 'client-finished-it2me-button';
280   }
282   var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.NONE);
283   var onFailure = Promise.race([
284       browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME),
285       browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_IT2ME)]);
287   onConnected = onConnected.then(function() {
288     return Promise.reject(
289         'Expected the connection to fail.');
290   });
292   onFailure = onFailure.then(function() {
293     /** @type {Element} */
294     var errorDiv = document.getElementById('connect-error-message');
295     var actual = errorDiv.innerText;
296     var expected = errorTags.map(function(/** string */errorTag) {
297       return l10n.getTranslationOrError(errorTag);
298     });
299     browserTest.clickOnControl(finishButton);
301     if (expected.indexOf(actual) === -1) {
302       return Promise.reject('Unexpected failure. actual: ' + actual +
303                             ' expected: ' + expected.join(','));
304     }
305   });
307   return Promise.race([onConnected, onFailure]);
311  * @return {Promise}
312  */
313 browserTest.expectConnected = function() {
314   var AppMode = remoting.AppMode;
315   // Timeout if the session is not connected within 30 seconds.
316   var SESSION_CONNECTION_TIMEOUT = 30000;
317   var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
318                                          SESSION_CONNECTION_TIMEOUT);
319   var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
320                                        browserTest.Timeout.NONE);
321   onFailure = onFailure.then(function() {
322     var errorDiv = document.getElementById('connect-error-message');
323     var errorMsg = errorDiv.innerText;
324     return Promise.reject('Unexpected error - ' + errorMsg);
325   });
326   return Promise.race([onConnected, onFailure]);
330  * @param {base.EventSource} eventSource
331  * @param {string} event
332  * @param {number} timeoutMs
333  * @param {*=} opt_expectedData
334  * @return {Promise}
335  */
336 browserTest.expectEvent = function(eventSource, event, timeoutMs,
337                                    opt_expectedData) {
338   return new Promise(function(fullfil, reject) {
339     /** @param {string=} actualData */
340     var verifyEventParameters = function(actualData) {
341       if (opt_expectedData === undefined || opt_expectedData === actualData) {
342         fullfil(true);
343       } else {
344         reject('Bad event data; expected ' + opt_expectedData +
345                '; got ' + actualData);
346       }
347     };
348     eventSource.addEventListener(event, verifyEventParameters);
349     base.Promise.sleep(timeoutMs).then(function() {
350       reject(Error('Event ' + event + ' not received after ' +
351                    timeoutMs + 'ms.'));
352     });
353   });
357  * @param {Function} testClass
358  * @param {*} data
359  * @return {void}
360  * @suppress {checkTypes|checkVars|reportUnknownTypes}
361  */
362 browserTest.runTest = function(testClass, data) {
363   try {
364     var test = new testClass();
365     browserTest.expect(typeof test.run == 'function');
366     test.run(data);
367   } catch (/** @type {Error} */ e) {
368     browserTest.fail(e);
369   }
373  * @param {string} newPin
374  * @return {Promise}
375  */
376 browserTest.setupPIN = function(newPin) {
377   var AppMode = remoting.AppMode;
378   var HOST_SETUP_WAIT = 10000;
379   var Timeout = browserTest.Timeout;
381   return browserTest.onUIMode(AppMode.HOST_SETUP_ASK_PIN).then(function() {
382     document.getElementById('daemon-pin-entry').value = newPin;
383     document.getElementById('daemon-pin-confirm').value = newPin;
384     browserTest.clickOnControl('daemon-pin-ok');
386     var success = browserTest.onUIMode(AppMode.HOST_SETUP_DONE, Timeout.NONE);
387     var failure = browserTest.onUIMode(AppMode.HOST_SETUP_ERROR, Timeout.NONE);
388     failure = failure.then(function(){
389       return Promise.reject('Unexpected host setup failure');
390     });
391     return Promise.race([success, failure]);
392   }).then(function() {
393     console.log('browserTest: PIN Setup is done.');
394     browserTest.clickOnControl('host-config-done-dismiss');
396     // On Linux, we restart the host after changing the PIN, need to sleep
397     // for ten seconds before the host is ready for connection.
398     return base.Promise.sleep(HOST_SETUP_WAIT);
399   });
403  * @return {Promise<boolean>}
404  */
405 browserTest.isLocalHostStarted = function() {
406   return new Promise(function(resolve) {
407     remoting.hostController.getLocalHostState(function(state) {
408       resolve(remoting.HostController.State.STARTED == state);
409     });
410   });
414  * @param {string} pin
415  * @return {Promise}
416  */
417 browserTest.ensureHostStartedWithPIN = function(pin) {
418   // Return if host is already
419   return browserTest.isLocalHostStarted().then(
420       /** @param {boolean} started */
421       function(started){
422         if (!started) {
423           console.log('browserTest: Enabling remote connection.');
424           browserTest.clickOnControl('.start-daemon');
425         } else {
426           console.log('browserTest: Changing the PIN of the host to: ' +
427               pin + '.');
428           browserTest.clickOnControl('.change-daemon-pin');
429         }
430         return browserTest.setupPIN(pin);
431       });
435  * Called by Browser Test in C++
436  * @param {string} pin
437  * @suppress {checkTypes}
438  */
439 browserTest.ensureRemoteConnectionEnabled = function(pin) {
440   browserTest.ensureHostStartedWithPIN(pin).then(function() {
441     browserTest.pass();
442   }, function(reason) {
443     browserTest.fail(reason);
444   });
447 browserTest.init();