Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / browser_test / browser_test.js
bloba35e57d24ce014b7c4f5a648407ddccebf3f8abb
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  * @suppress {checkTypes}  By default, JSCompile is not run on test files.
8  *    However, you can modify |remoting_webapp_files.gypi| locally to include
9  *    the test in the package to expedite local development.  This suppress
10  *    is here so that JSCompile won't complain.
11  *
12  * Provides basic functionality for JavaScript based browser test.
13  *
14  * To define a browser test, create a class under the browserTest namespace.
15  * You can pass arbitrary object literals to the browser test from the C++ test
16  * harness as the test data.  Each browser test class should implement the run
17  * method.
18  * For example:
19  *
20  * browserTest.My_Test = function() {};
21  * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... };
22  *
23  * The browser test is async in nature.  It will keep running until
24  * browserTest.fail("My error message.") or browserTest.pass() is called.
25  *
26  * For example:
27  *
28  * browserTest.My_Test.prototype.run(myObjectLiteral) = function() {
29  *   window.setTimeout(function() {
30  *     if (doSomething(myObjectLiteral)) {
31  *       browserTest.pass();
32  *     } else {
33  *       browserTest.fail('My error message.');
34  *     }
35  *   }, 1000);
36  * };
37  *
38  * You will then invoke the test in C++ by calling:
39  *
40  *   RunJavaScriptTest(web_content, "My_Test", "{"
41  *    "pin: '123123'"
42  *  "}");
43  */
45 'use strict';
47 var browserTest = {};
49 browserTest.init = function() {
50   // The domAutomationController is used to communicate progress back to the
51   // C++ calling code.  It will only exist if chrome is run with the flag
52   // --dom-automation.  It is stubbed out here so that browser test can be run
53   // under the regular app.
54   browserTest.automationController_ = window.domAutomationController || {
55     send: function(json) {
56       var result = JSON.parse(json);
57       if (result.succeeded) {
58         console.log('Test Passed.');
59       } else {
60         console.error('Test Failed.\n' +
61             result.error_message + '\n' + result.stack_trace);
62       }
63     }
64   };
67 browserTest.expect = function(expr, message) {
68   if (!expr) {
69     message = (message) ? '<' + message + '>' : '';
70     browserTest.fail('Expectation failed.' + message);
71   }
74 browserTest.fail = function(error) {
75   var error_message = error;
76   var stack_trace = base.debug.callstack();
78   if (error instanceof Error) {
79     error_message = error.toString();
80     stack_trace = error.stack;
81   }
83   // To run browserTest locally:
84   // 1. Go to |remoting_webapp_files| and look for
85   //    |remoting_webapp_js_browser_test_files| and uncomment it
86   // 2. gclient runhooks
87   // 3. rebuild the webapp
88   // 4. Run it in the console browserTest.runTest(browserTest.MyTest, {});
89   // 5. The line below will trap the test in the debugger in case of
90   //    failure.
91   debugger;
93   browserTest.automationController_.send(JSON.stringify({
94     succeeded: false,
95     error_message: error_message,
96     stack_trace: stack_trace
97   }));
100 browserTest.pass = function() {
101   browserTest.automationController_.send(JSON.stringify({
102     succeeded: true,
103     error_message: '',
104     stack_trace: ''
105   }));
108 browserTest.clickOnControl = function(id) {
109   var element = document.getElementById(id);
110   browserTest.expect(element);
111   element.click();
114 /** @enum {number} */
115 browserTest.Timeout = {
116   NONE: -1,
117   DEFAULT: 5000
120 browserTest.onUIMode = function(expectedMode, opt_timeout) {
121   if (expectedMode == remoting.currentMode) {
122     // If the current mode is the same as the expected mode, return a fulfilled
123     // promise.  For some reason, if we fulfill the promise in the same
124     // callstack, V8 will assert at V8RecursionScope.h(66) with
125     // ASSERT(!ScriptForbiddenScope::isScriptForbidden()).
126     // To avoid the assert, execute the callback in a different callstack.
127     return base.Promise.sleep(0);
128   }
130   return new Promise (function(fulfill, reject) {
131     var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
132     var timerId = null;
134     if (opt_timeout === undefined) {
135       opt_timeout = browserTest.Timeout.DEFAULT;
136     }
138     function onTimeout() {
139       remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
140       reject('Timeout waiting for ' + expectedMode);
141     }
143     function onUIModeChanged(mode) {
144       if (mode == expectedMode) {
145         remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
146         window.clearTimeout(timerId);
147         timerId = null;
148         fulfill();
149       }
150     }
152     if (opt_timeout != browserTest.Timeout.NONE) {
153       timerId = window.setTimeout(onTimeout, opt_timeout);
154     }
155     remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
156   });
159 browserTest.connectMe2Me = function() {
160   var AppMode = remoting.AppMode;
161   browserTest.clickOnControl('this-host-connect');
162   return browserTest.onUIMode(AppMode.CLIENT_HOST_NEEDS_UPGRADE).then(
163     function() {
164       // On fulfilled.
165       browserTest.clickOnControl('host-needs-update-connect-button');
166     }, function() {
167       // On time out.
168       return Promise.resolve();
169     }).then(function() {
170       return browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT, 10000);
171     });
174 browserTest.disconnect = function() {
175   var AppMode = remoting.AppMode;
176   remoting.disconnect();
177   return browserTest.onUIMode(AppMode.CLIENT_SESSION_FINISHED_ME2ME).then(
178     function() {
179       browserTest.clickOnControl('client-finished-me2me-button');
180       return browserTest.onUIMode(AppMode.HOME);
181     });
184 browserTest.enterPIN = function(pin, opt_expectError) {
185   // Wait for 500ms before hitting the PIN button. From experiment, sometimes
186   // the PIN prompt does not dismiss without the timeout.
187   var CONNECT_PIN_WAIT = 500;
189   document.getElementById('pin-entry').value = pin;
191   return base.Promise.sleep(CONNECT_PIN_WAIT).then(function() {
192     browserTest.clickOnControl('pin-connect-button');
193   }).then(function() {
194     if (opt_expectError) {
195       return browserTest.expectMe2MeError(remoting.Error.INVALID_ACCESS_CODE);
196     } else {
197       return browserTest.expectMe2MeConnected();
198     }
199   });
202 browserTest.expectMe2MeError = function(errorTag) {
203   var AppMode = remoting.AppMode;
204   var Timeout = browserTest.Timeout;
206   var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.None);
207   var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME);
209   onConnected = onConnected.then(function() {
210     return Promise.reject(
211         'Expected the Me2Me connection to fail.');
212   });
214   onFailure = onFailure.then(function() {
215     var errorDiv = document.getElementById('connect-error-message');
216     var actual = errorDiv.innerText;
217     var expected = l10n.getTranslationOrError(errorTag);
219     browserTest.clickOnControl('client-finished-me2me-button');
221     if (actual != expected) {
222       return Promise.reject('Unexpected failure. actual:' + actual +
223                      ' expected:' + expected);
224     }
225   });
227   return Promise.race([onConnected, onFailure]);
230 browserTest.expectMe2MeConnected = function() {
231   var AppMode = remoting.AppMode;
232   // Timeout if the session is not connected within 30 seconds.
233   var SESSION_CONNECTION_TIMEOUT = 30000;
234   var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
235                                          SESSION_CONNECTION_TIMEOUT);
236   var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
237                                        browserTest.Timeout.NONE);
238   onFailure = onFailure.then(function() {
239     var errorDiv = document.getElementById('connect-error-message');
240     var errorMsg = errorDiv.innerText;
241     return Promise.reject('Unexpected error - ' + errorMsg);
242   });
243   return Promise.race([onConnected, onFailure]);
246 browserTest.expectEvent = function(eventSource, event, timeoutMs,
247                                    opt_expectedData) {
248   return new Promise(function(fullfil, reject) {
249     var verifyEventParameters = function(actualData) {
250       if (opt_expectedData === undefined || opt_expectedData === actualData) {
251         fullfil();
252       } else {
253         reject('Bad event data; expected ' + opt_expectedData +
254                '; got ' + actualData);
255       }
256     };
257     eventSource.addEventListener(event, verifyEventParameters);
258     base.Promise.sleep(timeoutMs).then(function() {
259       reject(Error('Event ' + event + ' not received after ' +
260                    timeoutMs + 'ms.'));
261     });
262   });
265 browserTest.runTest = function(testClass, data) {
266   try {
267     var test = new testClass();
268     browserTest.expect(typeof test.run == 'function');
269     test.run(data);
270   } catch (e) {
271     browserTest.fail(e);
272   }
275 browserTest.setupPIN = function(newPin) {
276   var AppMode = remoting.AppMode;
277   var HOST_SETUP_WAIT = 10000;
278   var Timeout = browserTest.Timeout;
280   return browserTest.onUIMode(AppMode.HOST_SETUP_ASK_PIN).then(function() {
281     document.getElementById('daemon-pin-entry').value = newPin;
282     document.getElementById('daemon-pin-confirm').value = newPin;
283     browserTest.clickOnControl('daemon-pin-ok');
285     var success = browserTest.onUIMode(AppMode.HOST_SETUP_DONE, Timeout.NONE);
286     var failure = browserTest.onUIMode(AppMode.HOST_SETUP_ERROR, Timeout.NONE);
287     failure = failure.then(function(){
288       return Promise.reject('Unexpected host setup failure');
289     });
290     return Promise.race([success, failure]);
291   }).then(function() {
292     console.log('browserTest: PIN Setup is done.');
293     browserTest.clickOnControl('host-config-done-dismiss');
295     // On Linux, we restart the host after changing the PIN, need to sleep
296     // for ten seconds before the host is ready for connection.
297     return base.Promise.sleep(HOST_SETUP_WAIT);
298   });
301 browserTest.isLocalHostStarted = function() {
302   return new Promise(function(resolve) {
303     remoting.hostController.getLocalHostState(function(state) {
304       resolve(remoting.HostController.State.STARTED == state);
305     });
306   });
309 browserTest.ensureHostStartedWithPIN = function(pin) {
310   // Return if host is already
311   return browserTest.isLocalHostStarted().then(function(started){
312     if (!started) {
313       console.log('browserTest: Enabling remote connection.');
314       browserTest.clickOnControl('start-daemon');
315     } else {
316       console.log('browserTest: Changing the PIN of the host to: ' + pin + '.');
317       browserTest.clickOnControl('change-daemon-pin');
318     }
319     return browserTest.setupPIN(pin);
320   });
323 // Called by Browser Test in C++
324 browserTest.ensureRemoteConnectionEnabled = function(pin) {
325   browserTest.ensureHostStartedWithPIN(pin).then(function(){
326     browserTest.automationController_.send(true);
327   }, function(errorMessage){
328     console.error(errorMessage);
329     browserTest.automationController_.send(false);
330   });
333 browserTest.init();