blob: a35e57d24ce014b7c4f5a648407ddccebf3f8abb [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* @suppress {checkTypes} By default, JSCompile is not run on test files.
* However, you can modify |remoting_webapp_files.gypi| locally to include
* the test in the package to expedite local development. This suppress
* is here so that JSCompile won't complain.
*
* Provides basic functionality for JavaScript based browser test.
*
* To define a browser test, create a class under the browserTest namespace.
* You can pass arbitrary object literals to the browser test from the C++ test
* harness as the test data. Each browser test class should implement the run
* method.
* For example:
*
* browserTest.My_Test = function() {};
* browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... };
*
* The browser test is async in nature. It will keep running until
* browserTest.fail("My error message.") or browserTest.pass() is called.
*
* For example:
*
* browserTest.My_Test.prototype.run(myObjectLiteral) = function() {
* window.setTimeout(function() {
* if (doSomething(myObjectLiteral)) {
* browserTest.pass();
* } else {
* browserTest.fail('My error message.');
* }
* }, 1000);
* };
*
* You will then invoke the test in C++ by calling:
*
* RunJavaScriptTest(web_content, "My_Test", "{"
* "pin: '123123'"
* "}");
*/
'use strict';
var browserTest = {};
browserTest.init = function() {
// The domAutomationController is used to communicate progress back to the
// C++ calling code. It will only exist if chrome is run with the flag
// --dom-automation. It is stubbed out here so that browser test can be run
// under the regular app.
browserTest.automationController_ = window.domAutomationController || {
send: function(json) {
var result = JSON.parse(json);
if (result.succeeded) {
console.log('Test Passed.');
} else {
console.error('Test Failed.\n' +
result.error_message + '\n' + result.stack_trace);
}
}
};
};
browserTest.expect = function(expr, message) {
if (!expr) {
message = (message) ? '<' + message + '>' : '';
browserTest.fail('Expectation failed.' + message);
}
};
browserTest.fail = function(error) {
var error_message = error;
var stack_trace = base.debug.callstack();
if (error instanceof Error) {
error_message = error.toString();
stack_trace = error.stack;
}
// To run browserTest locally:
// 1. Go to |remoting_webapp_files| and look for
// |remoting_webapp_js_browser_test_files| and uncomment it
// 2. gclient runhooks
// 3. rebuild the webapp
// 4. Run it in the console browserTest.runTest(browserTest.MyTest, {});
// 5. The line below will trap the test in the debugger in case of
// failure.
debugger;
browserTest.automationController_.send(JSON.stringify({
succeeded: false,
error_message: error_message,
stack_trace: stack_trace
}));
};
browserTest.pass = function() {
browserTest.automationController_.send(JSON.stringify({
succeeded: true,
error_message: '',
stack_trace: ''
}));
};
browserTest.clickOnControl = function(id) {
var element = document.getElementById(id);
browserTest.expect(element);
element.click();
};
/** @enum {number} */
browserTest.Timeout = {
NONE: -1,
DEFAULT: 5000
};
browserTest.onUIMode = function(expectedMode, opt_timeout) {
if (expectedMode == remoting.currentMode) {
// If the current mode is the same as the expected mode, return a fulfilled
// promise. For some reason, if we fulfill the promise in the same
// callstack, V8 will assert at V8RecursionScope.h(66) with
// ASSERT(!ScriptForbiddenScope::isScriptForbidden()).
// To avoid the assert, execute the callback in a different callstack.
return base.Promise.sleep(0);
}
return new Promise (function(fulfill, reject) {
var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
var timerId = null;
if (opt_timeout === undefined) {
opt_timeout = browserTest.Timeout.DEFAULT;
}
function onTimeout() {
remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
reject('Timeout waiting for ' + expectedMode);
}
function onUIModeChanged(mode) {
if (mode == expectedMode) {
remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
window.clearTimeout(timerId);
timerId = null;
fulfill();
}
}
if (opt_timeout != browserTest.Timeout.NONE) {
timerId = window.setTimeout(onTimeout, opt_timeout);
}
remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
});
};
browserTest.connectMe2Me = function() {
var AppMode = remoting.AppMode;
browserTest.clickOnControl('this-host-connect');
return browserTest.onUIMode(AppMode.CLIENT_HOST_NEEDS_UPGRADE).then(
function() {
// On fulfilled.
browserTest.clickOnControl('host-needs-update-connect-button');
}, function() {
// On time out.
return Promise.resolve();
}).then(function() {
return browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT, 10000);
});
};
browserTest.disconnect = function() {
var AppMode = remoting.AppMode;
remoting.disconnect();
return browserTest.onUIMode(AppMode.CLIENT_SESSION_FINISHED_ME2ME).then(
function() {
browserTest.clickOnControl('client-finished-me2me-button');
return browserTest.onUIMode(AppMode.HOME);
});
};
browserTest.enterPIN = function(pin, opt_expectError) {
// Wait for 500ms before hitting the PIN button. From experiment, sometimes
// the PIN prompt does not dismiss without the timeout.
var CONNECT_PIN_WAIT = 500;
document.getElementById('pin-entry').value = pin;
return base.Promise.sleep(CONNECT_PIN_WAIT).then(function() {
browserTest.clickOnControl('pin-connect-button');
}).then(function() {
if (opt_expectError) {
return browserTest.expectMe2MeError(remoting.Error.INVALID_ACCESS_CODE);
} else {
return browserTest.expectMe2MeConnected();
}
});
};
browserTest.expectMe2MeError = function(errorTag) {
var AppMode = remoting.AppMode;
var Timeout = browserTest.Timeout;
var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.None);
var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME);
onConnected = onConnected.then(function() {
return Promise.reject(
'Expected the Me2Me connection to fail.');
});
onFailure = onFailure.then(function() {
var errorDiv = document.getElementById('connect-error-message');
var actual = errorDiv.innerText;
var expected = l10n.getTranslationOrError(errorTag);
browserTest.clickOnControl('client-finished-me2me-button');
if (actual != expected) {
return Promise.reject('Unexpected failure. actual:' + actual +
' expected:' + expected);
}
});
return Promise.race([onConnected, onFailure]);
};
browserTest.expectMe2MeConnected = function() {
var AppMode = remoting.AppMode;
// Timeout if the session is not connected within 30 seconds.
var SESSION_CONNECTION_TIMEOUT = 30000;
var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
SESSION_CONNECTION_TIMEOUT);
var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
browserTest.Timeout.NONE);
onFailure = onFailure.then(function() {
var errorDiv = document.getElementById('connect-error-message');
var errorMsg = errorDiv.innerText;
return Promise.reject('Unexpected error - ' + errorMsg);
});
return Promise.race([onConnected, onFailure]);
};
browserTest.expectEvent = function(eventSource, event, timeoutMs,
opt_expectedData) {
return new Promise(function(fullfil, reject) {
var verifyEventParameters = function(actualData) {
if (opt_expectedData === undefined || opt_expectedData === actualData) {
fullfil();
} else {
reject('Bad event data; expected ' + opt_expectedData +
'; got ' + actualData);
}
};
eventSource.addEventListener(event, verifyEventParameters);
base.Promise.sleep(timeoutMs).then(function() {
reject(Error('Event ' + event + ' not received after ' +
timeoutMs + 'ms.'));
});
});
};
browserTest.runTest = function(testClass, data) {
try {
var test = new testClass();
browserTest.expect(typeof test.run == 'function');
test.run(data);
} catch (e) {
browserTest.fail(e);
}
};
browserTest.setupPIN = function(newPin) {
var AppMode = remoting.AppMode;
var HOST_SETUP_WAIT = 10000;
var Timeout = browserTest.Timeout;
return browserTest.onUIMode(AppMode.HOST_SETUP_ASK_PIN).then(function() {
document.getElementById('daemon-pin-entry').value = newPin;
document.getElementById('daemon-pin-confirm').value = newPin;
browserTest.clickOnControl('daemon-pin-ok');
var success = browserTest.onUIMode(AppMode.HOST_SETUP_DONE, Timeout.NONE);
var failure = browserTest.onUIMode(AppMode.HOST_SETUP_ERROR, Timeout.NONE);
failure = failure.then(function(){
return Promise.reject('Unexpected host setup failure');
});
return Promise.race([success, failure]);
}).then(function() {
console.log('browserTest: PIN Setup is done.');
browserTest.clickOnControl('host-config-done-dismiss');
// On Linux, we restart the host after changing the PIN, need to sleep
// for ten seconds before the host is ready for connection.
return base.Promise.sleep(HOST_SETUP_WAIT);
});
};
browserTest.isLocalHostStarted = function() {
return new Promise(function(resolve) {
remoting.hostController.getLocalHostState(function(state) {
resolve(remoting.HostController.State.STARTED == state);
});
});
};
browserTest.ensureHostStartedWithPIN = function(pin) {
// Return if host is already
return browserTest.isLocalHostStarted().then(function(started){
if (!started) {
console.log('browserTest: Enabling remote connection.');
browserTest.clickOnControl('start-daemon');
} else {
console.log('browserTest: Changing the PIN of the host to: ' + pin + '.');
browserTest.clickOnControl('change-daemon-pin');
}
return browserTest.setupPIN(pin);
});
};
// Called by Browser Test in C++
browserTest.ensureRemoteConnectionEnabled = function(pin) {
browserTest.ensureHostStartedWithPIN(pin).then(function(){
browserTest.automationController_.send(true);
}, function(errorMessage){
console.error(errorMessage);
browserTest.automationController_.send(false);
});
};
browserTest.init();