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