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);