browser_test.js revision 116680a4aac90f2aa7413d9095a592090648e557
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.
4
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 */
44
45'use strict';
46
47var browserTest = {};
48
49browserTest.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  };
65};
66
67browserTest.expect = function(expr, message) {
68  if (!expr) {
69    message = (message) ? '<' + message + '>' : '';
70    browserTest.fail('Expectation failed.' + message);
71  }
72};
73
74browserTest.fail = function(error) {
75  var error_message = error;
76  var stack_trace = base.debug.callstack();
77
78  if (error instanceof Error) {
79    error_message = error.toString();
80    stack_trace = error.stack;
81  }
82
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;
92
93  browserTest.automationController_.send(JSON.stringify({
94    succeeded: false,
95    error_message: error_message,
96    stack_trace: stack_trace
97  }));
98};
99
100browserTest.pass = function() {
101  browserTest.automationController_.send(JSON.stringify({
102    succeeded: true,
103    error_message: '',
104    stack_trace: ''
105  }));
106};
107
108browserTest.clickOnControl = function(id) {
109  var element = document.getElementById(id);
110  browserTest.expect(element);
111  element.click();
112};
113
114/** @enum {number} */
115browserTest.Timeout = {
116  NONE: -1,
117  DEFAULT: 5000
118};
119
120browserTest.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  }
129
130  return new Promise (function(fulfill, reject) {
131    var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
132    var timerId = null;
133
134    if (opt_timeout === undefined) {
135      opt_timeout = browserTest.Timeout.DEFAULT;
136    }
137
138    function onTimeout() {
139      remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
140      reject('Timeout waiting for ' + expectedMode);
141    }
142
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    }
151
152    if (opt_timeout != browserTest.Timeout.NONE) {
153      timerId = window.setTimeout(onTimeout, opt_timeout);
154    }
155    remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
156  });
157};
158
159browserTest.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);
171    });
172}
173
174browserTest.expectMe2MeError = function(errorTag) {
175  var AppMode = remoting.AppMode;
176  var Timeout = browserTest.Timeout;
177
178  var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.None);
179  var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME);
180
181  onConnected = onConnected.then(function() {
182    return Promise.reject(
183        'Expected the Me2Me connection to fail.');
184  });
185
186  onFailure = onFailure.then(function() {
187    var errorDiv = document.getElementById('connect-error-message');
188    var actual = errorDiv.innerText;
189    var expected = l10n.getTranslationOrError(errorTag);
190
191    browserTest.clickOnControl('client-finished-me2me-button');
192
193    if (actual != expected) {
194      return Promise.reject('Unexpected failure. actual:' + actual +
195                     ' expected:' + expected);
196    }
197  });
198
199  return Promise.race([onConnected, onFailure]);
200};
201
202browserTest.expectMe2MeConnected = function() {
203  var AppMode = remoting.AppMode;
204  // Timeout if the session is not connected within 30 seconds.
205  var SESSION_CONNECTION_TIMEOUT = 30000;
206  var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
207                                         SESSION_CONNECTION_TIMEOUT);
208  var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
209                                       browserTest.Timeout.NONE);
210  onFailure = onFailure.then(function() {
211    var errorDiv = document.getElementById('connect-error-message');
212    var errorMsg = errorDiv.innerText;
213    return Promise.reject('Unexpected error - ' + errorMsg);
214  });
215  return Promise.race([onConnected, onFailure]);
216};
217
218browserTest.runTest = function(testClass, data) {
219  try {
220    var test = new testClass();
221    browserTest.expect(typeof test.run == 'function');
222    test.run(data);
223  } catch (e) {
224    browserTest.fail(e);
225  }
226};
227
228browserTest.init();