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.
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.
12 * Provides basic functionality for JavaScript based browser test.
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
20 * browserTest.My_Test = function() {};
21 * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... };
23 * The browser test is async in nature. It will keep running until
24 * browserTest.fail("My error message.") or browserTest.pass() is called.
28 * browserTest.My_Test.prototype.run(myObjectLiteral) = function() {
29 * window.setTimeout(function() {
30 * if (doSomething(myObjectLiteral)) {
33 * browserTest.fail('My error message.');
38 * You will then invoke the test in C++ by calling:
40 * RunJavaScriptTest(web_content, "My_Test", "{"
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.');
60 console.error('Test Failed.\n' +
61 result.error_message + '\n' + result.stack_trace);
67 browserTest.expect = function(expr, message) {
69 message = (message) ? '<' + message + '>' : '';
70 browserTest.fail('Expectation failed.' + message);
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;
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
93 browserTest.automationController_.send(JSON.stringify({
95 error_message: error_message,
96 stack_trace: stack_trace
100 browserTest.pass = function() {
101 browserTest.automationController_.send(JSON.stringify({
108 browserTest.clickOnControl = function(id) {
109 var element = document.getElementById(id);
110 browserTest.expect(element);
114 /** @enum {number} */
115 browserTest.Timeout = {
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);
130 return new Promise (function(fulfill, reject) {
131 var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
134 if (opt_timeout === undefined) {
135 opt_timeout = browserTest.Timeout.DEFAULT;
138 function onTimeout() {
139 remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
140 reject('Timeout waiting for ' + expectedMode);
143 function onUIModeChanged(mode) {
144 if (mode == expectedMode) {
145 remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
146 window.clearTimeout(timerId);
152 if (opt_timeout != browserTest.Timeout.NONE) {
153 timerId = window.setTimeout(onTimeout, opt_timeout);
155 remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
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(
165 browserTest.clickOnControl('host-needs-update-connect-button');
168 return Promise.resolve();
170 return browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT, 10000);
174 browserTest.disconnect = function() {
175 var AppMode = remoting.AppMode;
176 remoting.disconnect();
177 return browserTest.onUIMode(AppMode.CLIENT_SESSION_FINISHED_ME2ME).then(
179 browserTest.clickOnControl('client-finished-me2me-button');
180 return browserTest.onUIMode(AppMode.HOME);
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');
194 if (opt_expectError) {
195 return browserTest.expectMe2MeError(remoting.Error.INVALID_ACCESS_CODE);
197 return browserTest.expectMe2MeConnected();
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.');
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);
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);
243 return Promise.race([onConnected, onFailure]);
246 browserTest.expectEvent = function(eventSource, event, timeoutMs,
248 return new Promise(function(fullfil, reject) {
249 var verifyEventParameters = function(actualData) {
250 if (opt_expectedData === undefined || opt_expectedData === actualData) {
253 reject('Bad event data; expected ' + opt_expectedData +
254 '; got ' + actualData);
257 eventSource.addEventListener(event, verifyEventParameters);
258 base.Promise.sleep(timeoutMs).then(function() {
259 reject(Error('Event ' + event + ' not received after ' +
265 browserTest.runTest = function(testClass, data) {
267 var test = new testClass();
268 browserTest.expect(typeof test.run == 'function');
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');
290 return Promise.race([success, failure]);
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);
301 browserTest.isLocalHostStarted = function() {
302 return new Promise(function(resolve) {
303 remoting.hostController.getLocalHostState(function(state) {
304 resolve(remoting.HostController.State.STARTED == state);
309 browserTest.ensureHostStartedWithPIN = function(pin) {
310 // Return if host is already
311 return browserTest.isLocalHostStarted().then(function(started){
313 console.log('browserTest: Enabling remote connection.');
314 browserTest.clickOnControl('start-daemon');
316 console.log('browserTest: Changing the PIN of the host to: ' + pin + '.');
317 browserTest.clickOnControl('change-daemon-pin');
319 return browserTest.setupPIN(pin);
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);