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. 4 5/** 6 * @fileoverview 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. 11 * 12 * Provides basic functionality for JavaScript based browser test. 13 * 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 17 * method. 18 * For example: 19 * 20 * browserTest.My_Test = function() {}; 21 * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... }; 22 * 23 * The browser test is async in nature. It will keep running until 24 * browserTest.fail("My error message.") or browserTest.pass() is called. 25 * 26 * For example: 27 * 28 * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { 29 * window.setTimeout(function() { 30 * if (doSomething(myObjectLiteral)) { 31 * browserTest.pass(); 32 * } else { 33 * browserTest.fail('My error message.'); 34 * } 35 * }, 1000); 36 * }; 37 * 38 * You will then invoke the test in C++ by calling: 39 * 40 * RunJavaScriptTest(web_content, "My_Test", "{" 41 * "pin: '123123'" 42 * "}"); 43 */ 44 45'use strict'; 46 47var browserTest = {}; 48 49browserTest.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.'); 59 } else { 60 console.error('Test Failed.\n' + 61 result.error_message + '\n' + result.stack_trace); 62 } 63 } 64 }; 65}; 66 67browserTest.expect = function(expr, message) { 68 if (!expr) { 69 message = (message) ? '<' + message + '>' : ''; 70 browserTest.fail('Expectation failed.' + message); 71 } 72}; 73 74browserTest.fail = function(error) { 75 var error_message = error; 76 var stack_trace = base.debug.callstack(); 77 78 if (error instanceof Error) { 79 error_message = error.toString(); 80 stack_trace = error.stack; 81 } 82 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 90 // failure. 91 debugger; 92 93 browserTest.automationController_.send(JSON.stringify({ 94 succeeded: false, 95 error_message: error_message, 96 stack_trace: stack_trace 97 })); 98}; 99 100browserTest.pass = function() { 101 browserTest.automationController_.send(JSON.stringify({ 102 succeeded: true, 103 error_message: '', 104 stack_trace: '' 105 })); 106}; 107 108browserTest.clickOnControl = function(id) { 109 var element = document.getElementById(id); 110 browserTest.expect(element); 111 element.click(); 112}; 113 114/** @enum {number} */ 115browserTest.Timeout = { 116 NONE: -1, 117 DEFAULT: 5000 118}; 119 120browserTest.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); 128 } 129 130 return new Promise (function(fulfill, reject) { 131 var uiModeChanged = remoting.testEvents.Names.uiModeChanged; 132 var timerId = null; 133 134 if (opt_timeout === undefined) { 135 opt_timeout = browserTest.Timeout.DEFAULT; 136 } 137 138 function onTimeout() { 139 remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged); 140 reject('Timeout waiting for ' + expectedMode); 141 } 142 143 function onUIModeChanged(mode) { 144 if (mode == expectedMode) { 145 remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged); 146 window.clearTimeout(timerId); 147 timerId = null; 148 fulfill(); 149 } 150 } 151 152 if (opt_timeout != browserTest.Timeout.NONE) { 153 timerId = window.setTimeout(onTimeout, opt_timeout); 154 } 155 remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged); 156 }); 157}; 158 159browserTest.expectMe2MeError = function(errorTag) { 160 var AppMode = remoting.AppMode; 161 var Timeout = browserTest.Timeout; 162 163 var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.None); 164 var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME); 165 166 onConnected = onConnected.then(function() { 167 return Promise.reject( 168 'Expected the Me2Me connection to fail.'); 169 }); 170 171 onFailure = onFailure.then(function() { 172 var errorDiv = document.getElementById('connect-error-message'); 173 var actual = errorDiv.innerText; 174 var expected = l10n.getTranslationOrError(errorTag); 175 176 browserTest.clickOnControl('client-finished-me2me-button'); 177 178 if (actual != expected) { 179 return Promise.reject('Unexpected failure. actual:' + actual + 180 ' expected:' + expected); 181 } 182 }); 183 184 return Promise.race([onConnected, onFailure]); 185}; 186 187browserTest.expectMe2MeConnected = function() { 188 var AppMode = remoting.AppMode; 189 // Timeout if the session is not connected within 30 seconds. 190 var SESSION_CONNECTION_TIMEOUT = 30000; 191 var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, 192 SESSION_CONNECTION_TIMEOUT); 193 var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME, 194 browserTest.Timeout.NONE); 195 onFailure = onFailure.then(function() { 196 var errorDiv = document.getElementById('connect-error-message'); 197 var errorMsg = errorDiv.innerText; 198 return Promise.reject('Unexpected error - ' + errorMsg); 199 }); 200 return Promise.race([onConnected, onFailure]); 201}; 202 203browserTest.runTest = function(testClass, data) { 204 try { 205 var test = new testClass(); 206 browserTest.expect(typeof test.run == 'function'); 207 test.run(data); 208 } catch (e) { 209 browserTest.fail(e); 210 } 211}; 212 213browserTest.init();