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.
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
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.
24 * browserTest.My_Test.prototype.run(myObjectLiteral) = function() {
25 * window.setTimeout(function() {
26 * if (doSomething(myObjectLiteral)) {
29 * browserTest.fail('My error message.');
34 * You will then invoke the test in C++ by calling:
36 * RunJavaScriptTest(web_content, "My_Test", "{"
43 /** @suppress {duplicate} */
44 var browserTest
= browserTest
|| {};
46 /** @type {window.DomAutomationController} */
47 browserTest
.automationController_
= null;
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
;
62 browserTest
.automationController_
= {
63 send: function(json
) {
64 var result
= JSON
.parse(json
);
65 if (result
.succeeded
) {
66 console
.log('Test Passed.');
68 console
.error('Test Failed.\n' +
69 result
.error_message
+ '\n' + result
.stack_trace
);
77 * Fails the C++ calling browser test with |message| if |expr| is false.
79 * @param {string} message
82 browserTest
.expect = function(expr
, message
) {
84 message
= (message
) ? '<' + message
+ '>' : '';
85 browserTest
.fail('Expectation failed.' + message
);
90 * @param {string|Error} error
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
114 browserTest
.automationController_
.send(JSON
.stringify({
116 error_message
: error_message
,
117 stack_trace
: stack_trace
124 browserTest
.pass = function() {
125 browserTest
.automationController_
.send(JSON
.stringify({
136 browserTest
.clickOnControl = function(id
) {
137 var element
= document
.getElementById(id
);
138 browserTest
.expect(element
, 'No such element: ' + id
);
143 * @param {remoting.AppMode} expectedMode
144 * @param {number=} opt_timeout
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
;
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
);
180 if (opt_timeout
!= browserTest
.Timeout
.NONE
) {
181 timerId
= window
.setTimeout(onTimeout
,
182 /** @type {number} */ (opt_timeout
));
184 remoting
.testEvents
.addEventListener(uiModeChanged
, onUIModeChanged
);
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');
198 return browserTest
.onUIMode(AppMode
.CLIENT_HOST_NEEDS_UPGRADE
);
201 browserTest
.clickOnControl('host-needs-update-connect-button');
204 return Promise
.resolve();
206 return browserTest
.onUIMode(AppMode
.CLIENT_PIN_PROMPT
, 10000);
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
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');
246 if (opt_expectError
) {
247 return browserTest
.expectConnectionError(
248 remoting
.DesktopConnectedView
.Mode
.ME2ME
,
249 remoting
.Error
.INVALID_ACCESS_CODE
);
251 return browserTest
.expectConnected();
257 * @param {remoting.DesktopConnectedView.Mode} connectionMode
258 * @param {string} errorTag
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
]);
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
323 browserTest
.expectEvent = function(eventSource
, event
, timeoutMs
,
325 return new Promise(function(fullfil
, reject
) {
326 /** @param {string=} actualData */
327 var verifyEventParameters = function(actualData
) {
328 if (opt_expectedData
=== undefined || opt_expectedData
=== actualData
) {
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 ' +
344 * @param {Function} testClass
347 * @suppress {checkTypes|checkVars|reportUnknownTypes}
349 browserTest
.runTest = function(testClass
, data
) {
351 var test
= new testClass();
352 browserTest
.expect(typeof test
.run
== 'function');
354 } catch (/** @type {Error} */ e
) {
360 * @param {string} newPin
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
]);
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
404 browserTest
.ensureHostStartedWithPIN = function(pin
) {
405 // Return if host is already
406 return browserTest
.isLocalHostStarted().then(
407 /** @param {boolean} started */
410 console
.log('browserTest: Enabling remote connection.');
411 browserTest
.clickOnControl('start-daemon');
413 console
.log('browserTest: Changing the PIN of the host to: ' +
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() {
429 }, function(reason
) {
430 browserTest
.fail(reason
);