Rewrite AndroidSyncSettings to be significantly simpler.
[chromium-blink-merge.git] / remoting / webapp / browser_test / browser_test.js
blob88f9ee445615def71a2129d6535b266869ab5d95
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
8 * Provides basic functionality for JavaScript based browser test.
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:
16 * browserTest.My_Test = function() {};
17 * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... };
19 * The browser test is async in nature. It will keep running until
20 * browserTest.fail("My error message.") or browserTest.pass() is called.
22 * For example:
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 * };
34 * You will then invoke the test in C++ by calling:
36 * RunJavaScriptTest(web_content, "My_Test", "{"
37 * "pin: '123123'"
38 * "}");
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}
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);
76 /**
77 * Fails the C++ calling browser test with |message| if |expr| is false.
78 * @param {*} expr
79 * @param {string} message
80 * @return {void}
82 browserTest.expect = function(expr, message) {
83 if (!expr) {
84 message = (message) ? '<' + message + '>' : '';
85 browserTest.fail('Expectation failed.' + message);
89 /**
90 * @param {string|Error} error
91 * @return {void}
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;
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}
124 browserTest.pass = function() {
125 browserTest.automationController_.send(JSON.stringify({
126 succeeded: true,
127 error_message: '',
128 stack_trace: ''
129 }));
133 * @param {string} id
134 * @return {void}
136 browserTest.clickOnControl = function(id) {
137 var element = document.getElementById(id);
138 browserTest.expect(element, 'No such element: ' + id);
139 element.click();
143 * @param {remoting.AppMode} expectedMode
144 * @param {number=} opt_timeout
145 * @return {Promise}
147 browserTest.onUIMode = function(expectedMode, opt_timeout) {
148 if (expectedMode == remoting.currentMode) {
149 // If the current mode is the same as the expected mode, return a fulfilled
150 // promise. For some reason, if we fulfill the promise in the same
151 // callstack, V8 will assert at V8RecursionScope.h(66) with
152 // ASSERT(!ScriptForbiddenScope::isScriptForbidden()).
153 // To avoid the assert, execute the callback in a different callstack.
154 return base.Promise.sleep(0);
157 return new Promise (function(fulfill, reject) {
158 var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
159 var timerId = null;
161 if (opt_timeout === undefined) {
162 opt_timeout = browserTest.Timeout.DEFAULT;
165 function onTimeout() {
166 remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
167 reject('Timeout waiting for ' + expectedMode);
170 /** @param {remoting.AppMode} mode */
171 function onUIModeChanged(mode) {
172 if (mode == expectedMode) {
173 remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
174 window.clearTimeout(timerId);
175 timerId = null;
176 fulfill(true);
180 if (opt_timeout != browserTest.Timeout.NONE) {
181 timerId = window.setTimeout(onTimeout,
182 /** @type {number} */ (opt_timeout));
184 remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
189 * @return {Promise}
191 browserTest.connectMe2Me = function() {
192 var AppMode = remoting.AppMode;
193 // The one second timeout is necessary because the click handler of
194 // 'this-host-connect' is registered asynchronously.
195 return base.Promise.sleep(1000).then(function() {
196 browserTest.clickOnControl('this-host-connect');
197 }).then(function(){
198 return browserTest.onUIMode(AppMode.CLIENT_HOST_NEEDS_UPGRADE);
199 }).then(function() {
200 // On fulfilled.
201 browserTest.clickOnControl('host-needs-update-connect-button');
202 }, function() {
203 // On time out.
204 return Promise.resolve();
205 }).then(function() {
206 return browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT, 10000);
211 * @return {Promise}
213 browserTest.disconnect = function() {
214 var AppMode = remoting.AppMode;
215 var finishedMode = AppMode.CLIENT_SESSION_FINISHED_ME2ME;
216 var finishedButton = 'client-finished-me2me-button';
217 if (remoting.desktopConnectedView.getMode() ==
218 remoting.DesktopConnectedView.Mode.IT2ME) {
219 finishedMode = AppMode.CLIENT_SESSION_FINISHED_IT2ME;
220 finishedButton = 'client-finished-it2me-button';
223 remoting.disconnect();
225 return browserTest.onUIMode(finishedMode).then(function() {
226 browserTest.clickOnControl(finishedButton);
227 return browserTest.onUIMode(AppMode.HOME);
232 * @param {string} pin
233 * @param {?boolean} opt_expectError
234 * @return {Promise}
236 browserTest.enterPIN = function(pin, opt_expectError) {
237 // Wait for 500ms before hitting the PIN button. From experiment, sometimes
238 // the PIN prompt does not dismiss without the timeout.
239 var CONNECT_PIN_WAIT = 500;
241 document.getElementById('pin-entry').value = pin;
243 return base.Promise.sleep(CONNECT_PIN_WAIT).then(function() {
244 browserTest.clickOnControl('pin-connect-button');
245 }).then(function() {
246 if (opt_expectError) {
247 return browserTest.expectConnectionError(
248 remoting.DesktopConnectedView.Mode.ME2ME,
249 remoting.Error.INVALID_ACCESS_CODE);
250 } else {
251 return browserTest.expectConnected();
257 * @param {remoting.DesktopConnectedView.Mode} connectionMode
258 * @param {string} errorTag
259 * @return {Promise}
261 browserTest.expectConnectionError = function(connectionMode, errorTag) {
262 var AppMode = remoting.AppMode;
263 var Timeout = browserTest.Timeout;
265 var finishButton = 'client-finished-me2me-button';
267 if (connectionMode == remoting.DesktopConnectedView.Mode.IT2ME) {
268 finishButton = 'client-finished-it2me-button';
271 var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.NONE);
272 var onFailure = Promise.race([
273 browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME),
274 browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_IT2ME)]);
276 onConnected = onConnected.then(function() {
277 return Promise.reject(
278 'Expected the connection to fail.');
281 onFailure = onFailure.then(function() {
282 /** @type {Element} */
283 var errorDiv = document.getElementById('connect-error-message');
284 var actual = errorDiv.innerText;
285 var expected = l10n.getTranslationOrError(errorTag);
286 browserTest.clickOnControl(finishButton);
288 if (actual != expected) {
289 return Promise.reject('Unexpected failure. actual:' + actual +
290 ' expected:' + expected);
294 return Promise.race([onConnected, onFailure]);
298 * @return {Promise}
300 browserTest.expectConnected = function() {
301 var AppMode = remoting.AppMode;
302 // Timeout if the session is not connected within 30 seconds.
303 var SESSION_CONNECTION_TIMEOUT = 30000;
304 var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
305 SESSION_CONNECTION_TIMEOUT);
306 var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
307 browserTest.Timeout.NONE);
308 onFailure = onFailure.then(function() {
309 var errorDiv = document.getElementById('connect-error-message');
310 var errorMsg = errorDiv.innerText;
311 return Promise.reject('Unexpected error - ' + errorMsg);
313 return Promise.race([onConnected, onFailure]);
317 * @param {base.EventSource} eventSource
318 * @param {string} event
319 * @param {number} timeoutMs
320 * @param {*=} opt_expectedData
321 * @return {Promise}
323 browserTest.expectEvent = function(eventSource, event, timeoutMs,
324 opt_expectedData) {
325 return new Promise(function(fullfil, reject) {
326 /** @param {string=} actualData */
327 var verifyEventParameters = function(actualData) {
328 if (opt_expectedData === undefined || opt_expectedData === actualData) {
329 fullfil(true);
330 } else {
331 reject('Bad event data; expected ' + opt_expectedData +
332 '; got ' + actualData);
335 eventSource.addEventListener(event, verifyEventParameters);
336 base.Promise.sleep(timeoutMs).then(function() {
337 reject(Error('Event ' + event + ' not received after ' +
338 timeoutMs + 'ms.'));
344 * @param {Function} testClass
345 * @param {*} data
346 * @return {void}
347 * @suppress {checkTypes|checkVars|reportUnknownTypes}
349 browserTest.runTest = function(testClass, data) {
350 try {
351 var test = new testClass();
352 browserTest.expect(typeof test.run == 'function');
353 test.run(data);
354 } catch (/** @type {Error} */ e) {
355 browserTest.fail(e);
360 * @param {string} newPin
361 * @return {Promise}
363 browserTest.setupPIN = function(newPin) {
364 var AppMode = remoting.AppMode;
365 var HOST_SETUP_WAIT = 10000;
366 var Timeout = browserTest.Timeout;
368 return browserTest.onUIMode(AppMode.HOST_SETUP_ASK_PIN).then(function() {
369 document.getElementById('daemon-pin-entry').value = newPin;
370 document.getElementById('daemon-pin-confirm').value = newPin;
371 browserTest.clickOnControl('daemon-pin-ok');
373 var success = browserTest.onUIMode(AppMode.HOST_SETUP_DONE, Timeout.NONE);
374 var failure = browserTest.onUIMode(AppMode.HOST_SETUP_ERROR, Timeout.NONE);
375 failure = failure.then(function(){
376 return Promise.reject('Unexpected host setup failure');
378 return Promise.race([success, failure]);
379 }).then(function() {
380 console.log('browserTest: PIN Setup is done.');
381 browserTest.clickOnControl('host-config-done-dismiss');
383 // On Linux, we restart the host after changing the PIN, need to sleep
384 // for ten seconds before the host is ready for connection.
385 return base.Promise.sleep(HOST_SETUP_WAIT);
390 * @return {Promise<boolean>}
392 browserTest.isLocalHostStarted = function() {
393 return new Promise(function(resolve) {
394 remoting.hostController.getLocalHostState(function(state) {
395 resolve(remoting.HostController.State.STARTED == state);
401 * @param {string} pin
402 * @return {Promise}
404 browserTest.ensureHostStartedWithPIN = function(pin) {
405 // Return if host is already
406 return browserTest.isLocalHostStarted().then(
407 /** @param {boolean} started */
408 function(started){
409 if (!started) {
410 console.log('browserTest: Enabling remote connection.');
411 browserTest.clickOnControl('start-daemon');
412 } else {
413 console.log('browserTest: Changing the PIN of the host to: ' +
414 pin + '.');
415 browserTest.clickOnControl('change-daemon-pin');
417 return browserTest.setupPIN(pin);
422 * Called by Browser Test in C++
423 * @param {string} pin
424 * @suppress {checkTypes}
426 browserTest.ensureRemoteConnectionEnabled = function(pin) {
427 browserTest.ensureHostStartedWithPIN(pin).then(function() {
428 browserTest.pass();
429 }, function(reason) {
430 browserTest.fail(reason);
434 browserTest.init();