| // Copyright 2016 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 using private properties isn't a Closure violation in tests. | |
| * @suppress {accessControls} | |
| */ | |
| /* eslint-disable no-console */ | |
| /** | |
| * @return {boolean} | |
| */ | |
| export function _isDebugTest() { | |
| return !self.testRunner || !!Root.Runtime.queryParam('debugFrontend'); | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| export function _isStartupTest() { | |
| return Root.Runtime.queryParam('test').includes('/startup/'); | |
| } | |
| /** | |
| * @param {string} messageType | |
| */ | |
| export function _consoleOutputHook(messageType) { | |
| addResult(messageType + ': ' + Array.prototype.slice.call(arguments, 1)); | |
| } | |
| /** | |
| * This monkey patches console functions in DevTools context so the console | |
| * messages are shown in the right places, instead of having all of the console | |
| * messages printed at the top of the test expectation file (default behavior). | |
| */ | |
| export function _printDevToolsConsole() { | |
| if (_isDebugTest()) { | |
| return; | |
| } | |
| console.log = _consoleOutputHook.bind(null, 'log'); | |
| console.error = _consoleOutputHook.bind(null, 'error'); | |
| console.info = _consoleOutputHook.bind(null, 'info'); | |
| } | |
| /** | |
| * @param {string|!Event} message | |
| * @param {string} source | |
| * @param {number} lineno | |
| * @param {number} colno | |
| * @param {!Error} error | |
| */ | |
| function completeTestOnError(message, source, lineno, colno, error) { | |
| addResult('TEST ENDED IN ERROR: ' + error.stack); | |
| completeTest(); | |
| } | |
| self['onerror'] = completeTestOnError; | |
| _printDevToolsConsole(); | |
| // TODO(crbug.com/1032477): Re-enable once test timeouts are handled in Chromium | |
| // setTimeout(() => { | |
| // addResult('TEST TIMED OUT!'); | |
| // completeTest(); | |
| // }, 6000); | |
| /** @type {!Array<string>} */ | |
| let _results = []; | |
| let _innerAddResult = text => { | |
| _results.push(String(text)); | |
| }; | |
| /** | |
| * @param {*} text | |
| */ | |
| export function addResult(text) { | |
| _innerAddResult(text); | |
| } | |
| let completed = false; | |
| let _innerCompleteTest = () => { | |
| if (completed) { | |
| return; | |
| } | |
| completed = true; | |
| flushResults(); | |
| self.testRunner.notifyDone(); | |
| }; | |
| export function completeTest() { | |
| _innerCompleteTest(); | |
| } | |
| self.TestRunner = { | |
| _startupTestSetupFinished: () => {} | |
| }; | |
| /** | |
| * Only tests in web_tests/http/tests/devtools/startup/ need to call | |
| * this method because these tests need certain activities to be exercised | |
| * in the inspected page prior to the DevTools session. | |
| * @param {string} path | |
| * @return {!Promise<undefined>} | |
| */ | |
| export function setupStartupTest(path) { | |
| const absoluteURL = url(path); | |
| const promise = new Promise(f => TestRunner._startupTestSetupFinished = () => { | |
| TestRunner._initializeTargetForStartupTest(); | |
| delete TestRunner._startupTestSetupFinished; | |
| f(); | |
| }); | |
| self.testRunner.navigateSecondaryWindow(absoluteURL); | |
| return promise; | |
| } | |
| /** | |
| * @suppressGlobalPropertiesCheck | |
| */ | |
| export function flushResults() { | |
| Array.prototype.forEach.call(document.documentElement.childNodes, x => x.remove()); | |
| const outputElement = document.createElement('div'); | |
| // Support for svg - add to document, not body, check for style. | |
| if (outputElement.style) { | |
| outputElement.style.whiteSpace = 'pre'; | |
| outputElement.style.height = '10px'; | |
| outputElement.style.overflow = 'hidden'; | |
| } | |
| document.documentElement.appendChild(outputElement); | |
| for (let i = 0; i < _results.length; i++) { | |
| outputElement.appendChild(document.createTextNode(_results[i])); | |
| outputElement.appendChild(document.createElement('br')); | |
| } | |
| _results = []; | |
| } | |
| export function _executeTestScript() { | |
| const testScriptURL = /** @type {string} */ (Root.Runtime.queryParam('test')); | |
| fetch(testScriptURL) | |
| .then(data => data.text()) | |
| .then(testScript => { | |
| if (_isDebugTest()) { | |
| _innerAddResult = console.log; | |
| _innerCompleteTest = () => console.log('Test completed'); | |
| // Auto-start unit tests | |
| if (!self.testRunner) { | |
| eval(`(function test(){${testScript}})()\n//# sourceURL=${testScriptURL}`); | |
| } else { | |
| self.eval(`function test(){${testScript}}\n//# sourceURL=${testScriptURL}`); | |
| } | |
| return; | |
| } | |
| // Convert the test script into an expression (if needed) | |
| testScript = testScript.trimRight(); | |
| if (testScript.endsWith(';')) { | |
| testScript = testScript.slice(0, testScript.length - 1); | |
| } | |
| (async function() { | |
| try { | |
| await eval(testScript + `\n//# sourceURL=${testScriptURL}`); | |
| } catch (err) { | |
| addResult('TEST ENDED EARLY DUE TO UNCAUGHT ERROR:'); | |
| addResult(err && err.stack || err); | |
| addResult('=== DO NOT COMMIT THIS INTO -expected.txt ==='); | |
| completeTest(); | |
| } | |
| })(); | |
| }) | |
| .catch(error => { | |
| addResult(`Unable to execute test script because of error: ${error}`); | |
| completeTest(); | |
| }); | |
| } | |
| /** | |
| * @param {!Array<string>} textArray | |
| */ | |
| export function addResults(textArray) { | |
| if (!textArray) { | |
| return; | |
| } | |
| for (let i = 0, size = textArray.length; i < size; ++i) { | |
| addResult(textArray[i]); | |
| } | |
| } | |
| /** | |
| * @param {!Array<function()>} tests | |
| */ | |
| export function runTests(tests) { | |
| nextTest(); | |
| function nextTest() { | |
| const test = tests.shift(); | |
| if (!test) { | |
| completeTest(); | |
| return; | |
| } | |
| addResult('\ntest: ' + test.name); | |
| let testPromise = test(); | |
| if (!(testPromise instanceof Promise)) { | |
| testPromise = Promise.resolve(); | |
| } | |
| testPromise.then(nextTest); | |
| } | |
| } | |
| /** | |
| * @param {!Object} receiver | |
| * @param {string} methodName | |
| * @param {!Function} override | |
| * @param {boolean=} opt_sticky | |
| */ | |
| export function addSniffer(receiver, methodName, override, opt_sticky) { | |
| override = safeWrap(override); | |
| const original = receiver[methodName]; | |
| if (typeof original !== 'function') { | |
| throw new Error('Cannot find method to override: ' + methodName); | |
| } | |
| receiver[methodName] = function(var_args) { | |
| let result; | |
| try { | |
| result = original.apply(this, arguments); | |
| } finally { | |
| if (!opt_sticky) { | |
| receiver[methodName] = original; | |
| } | |
| } | |
| // In case of exception the override won't be called. | |
| try { | |
| Array.prototype.push.call(arguments, result); | |
| override.apply(this, arguments); | |
| } catch (e) { | |
| throw new Error('Exception in overriden method \'' + methodName + '\': ' + e); | |
| } | |
| return result; | |
| }; | |
| } | |
| /** | |
| * @param {!Object} receiver | |
| * @param {string} methodName | |
| * @return {!Promise<*>} | |
| */ | |
| export function addSnifferPromise(receiver, methodName) { | |
| return new Promise(function(resolve, reject) { | |
| const original = receiver[methodName]; | |
| if (typeof original !== 'function') { | |
| reject('Cannot find method to override: ' + methodName); | |
| return; | |
| } | |
| receiver[methodName] = function(var_args) { | |
| let result; | |
| try { | |
| result = original.apply(this, arguments); | |
| } finally { | |
| receiver[methodName] = original; | |
| } | |
| // In case of exception the override won't be called. | |
| try { | |
| Array.prototype.push.call(arguments, result); | |
| resolve.apply(this, arguments); | |
| } catch (e) { | |
| reject('Exception in overridden method \'' + methodName + '\': ' + e); | |
| completeTest(); | |
| } | |
| return result; | |
| }; | |
| }); | |
| } | |
| /** @type {function():void} */ | |
| let _resolveOnFinishInits; | |
| /** | |
| * @param {string} module | |
| * @return {!Promise<undefined>} | |
| */ | |
| export async function loadModule(module) { | |
| const promise = new Promise(resolve => _resolveOnFinishInits = resolve); | |
| await self.runtime.loadModulePromise(module); | |
| if (!_pendingInits) { | |
| return; | |
| } | |
| return promise; | |
| } | |
| /** | |
| * @param {string} panel | |
| * @return {!Promise.<?UI.Panel>} | |
| */ | |
| export function showPanel(panel) { | |
| return UI.viewManager.showView(panel); | |
| } | |
| /** | |
| * @param {string} key | |
| * @param {boolean=} ctrlKey | |
| * @param {boolean=} altKey | |
| * @param {boolean=} shiftKey | |
| * @param {boolean=} metaKey | |
| * @return {!KeyboardEvent} | |
| */ | |
| export function createKeyEvent(key, ctrlKey, altKey, shiftKey, metaKey) { | |
| return new KeyboardEvent('keydown', { | |
| key: key, | |
| bubbles: true, | |
| cancelable: true, | |
| ctrlKey: !!ctrlKey, | |
| altKey: !!altKey, | |
| shiftKey: !!shiftKey, | |
| metaKey: !!metaKey | |
| }); | |
| } | |
| /** | |
| * Wraps a test function with an exception filter. Does not work | |
| * correctly for async functions; use safeAsyncWrap instead. | |
| * @param {!Function|undefined} func | |
| * @param {!Function=} onexception | |
| * @return {!Function} | |
| */ | |
| export function safeWrap(func, onexception) { | |
| /** | |
| * @this {*} | |
| */ | |
| function result() { | |
| if (!func) { | |
| return; | |
| } | |
| const wrapThis = this; | |
| try { | |
| return func.apply(wrapThis, arguments); | |
| } catch (e) { | |
| addResult('Exception while running: ' + func + '\n' + (e.stack || e)); | |
| if (onexception) { | |
| safeWrap(onexception)(); | |
| } else { | |
| completeTest(); | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Wraps a test function that returns a Promise with an exception | |
| * filter. Does not work correctly for functions which don't return | |
| * a Promise; use safeWrap instead. | |
| * @param {function(...):Promise<*>} func | |
| * @return {function(...):Promise<*>} | |
| */ | |
| export function safeAsyncWrap(func) { | |
| /** | |
| * @this {*} | |
| */ | |
| async function result() { | |
| if (!func) { | |
| return; | |
| } | |
| const wrapThis = this; | |
| try { | |
| return await func.apply(wrapThis, arguments); | |
| } catch (e) { | |
| addResult('Exception while running: ' + func + '\n' + (e.stack || e)); | |
| completeTest(); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * @param {!Node} node | |
| * @return {string} | |
| */ | |
| export function textContentWithLineBreaks(node) { | |
| function padding(currentNode) { | |
| let result = 0; | |
| while (currentNode && currentNode !== node) { | |
| if (currentNode.nodeName === 'OL' && | |
| !(currentNode.classList && currentNode.classList.contains('object-properties-section'))) { | |
| ++result; | |
| } | |
| currentNode = currentNode.parentNode; | |
| } | |
| return Array(result * 4 + 1).join(' '); | |
| } | |
| let buffer = ''; | |
| let currentNode = node; | |
| let ignoreFirst = false; | |
| while (currentNode.traverseNextNode(node)) { | |
| currentNode = currentNode.traverseNextNode(node); | |
| if (currentNode.nodeType === Node.TEXT_NODE) { | |
| buffer += currentNode.nodeValue; | |
| } else if (currentNode.nodeName === 'LI' || currentNode.nodeName === 'TR') { | |
| if (!ignoreFirst) { | |
| buffer += '\n' + padding(currentNode); | |
| } else { | |
| ignoreFirst = false; | |
| } | |
| } else if (currentNode.nodeName === 'STYLE') { | |
| currentNode = currentNode.traverseNextNode(node); | |
| continue; | |
| } else if (currentNode.classList && currentNode.classList.contains('object-properties-section')) { | |
| ignoreFirst = true; | |
| } | |
| } | |
| return buffer; | |
| } | |
| /** | |
| * @param {!Node} node | |
| * @return {string} | |
| */ | |
| export function textContentWithoutStyles(node) { | |
| let buffer = ''; | |
| let currentNode = node; | |
| while (currentNode.traverseNextNode(node)) { | |
| currentNode = currentNode.traverseNextNode(node); | |
| if (currentNode.nodeType === Node.TEXT_NODE) { | |
| buffer += currentNode.nodeValue; | |
| } else if (currentNode.nodeName === 'STYLE') { | |
| currentNode = currentNode.traverseNextNode(node); | |
| } | |
| } | |
| return buffer; | |
| } | |
| /** | |
| * @param {!SDK.Target} target | |
| */ | |
| export function _setupTestHelpers(target) { | |
| TestRunner.BrowserAgent = target.browserAgent(); | |
| TestRunner.CSSAgent = target.cssAgent(); | |
| TestRunner.DeviceOrientationAgent = target.deviceOrientationAgent(); | |
| TestRunner.DOMAgent = target.domAgent(); | |
| TestRunner.DOMDebuggerAgent = target.domdebuggerAgent(); | |
| TestRunner.DebuggerAgent = target.debuggerAgent(); | |
| TestRunner.EmulationAgent = target.emulationAgent(); | |
| TestRunner.HeapProfilerAgent = target.heapProfilerAgent(); | |
| TestRunner.InputAgent = target.inputAgent(); | |
| TestRunner.InspectorAgent = target.inspectorAgent(); | |
| TestRunner.NetworkAgent = target.networkAgent(); | |
| TestRunner.OverlayAgent = target.overlayAgent(); | |
| TestRunner.PageAgent = target.pageAgent(); | |
| TestRunner.ProfilerAgent = target.profilerAgent(); | |
| TestRunner.RuntimeAgent = target.runtimeAgent(); | |
| TestRunner.TargetAgent = target.targetAgent(); | |
| TestRunner.networkManager = target.model(SDK.NetworkManager); | |
| TestRunner.securityOriginManager = target.model(SDK.SecurityOriginManager); | |
| TestRunner.resourceTreeModel = target.model(SDK.ResourceTreeModel); | |
| TestRunner.debuggerModel = target.model(SDK.DebuggerModel); | |
| TestRunner.runtimeModel = target.model(SDK.RuntimeModel); | |
| TestRunner.domModel = target.model(SDK.DOMModel); | |
| TestRunner.domDebuggerModel = target.model(SDK.DOMDebuggerModel); | |
| TestRunner.cssModel = target.model(SDK.CSSModel); | |
| TestRunner.cpuProfilerModel = target.model(SDK.CPUProfilerModel); | |
| TestRunner.overlayModel = target.model(SDK.OverlayModel); | |
| TestRunner.serviceWorkerManager = target.model(SDK.ServiceWorkerManager); | |
| TestRunner.tracingManager = target.model(SDK.TracingManager); | |
| TestRunner.mainTarget = target; | |
| } | |
| /** | |
| * @param {string} code | |
| * @return {!Promise<*>} | |
| */ | |
| export async function evaluateInPageRemoteObject(code) { | |
| const response = await _evaluateInPage(code); | |
| return TestRunner.runtimeModel.createRemoteObject(response.result); | |
| } | |
| /** | |
| * @param {string} code | |
| * @param {function(*, !Protocol.Runtime.ExceptionDetails=):void} callback | |
| */ | |
| export async function evaluateInPage(code, callback) { | |
| const response = await _evaluateInPage(code); | |
| safeWrap(callback)(response.result.value, response.exceptionDetails); | |
| } | |
| /** @type {number} */ | |
| let _evaluateInPageCounter = 0; | |
| /** | |
| * @param {string} code | |
| * @return {!Promise<undefined|{response: (!SDK.RemoteObject|undefined), | |
| * exceptionDetails: (!Protocol.Runtime.ExceptionDetails|undefined)}>} | |
| */ | |
| export async function _evaluateInPage(code) { | |
| const lines = new Error().stack.split('at '); | |
| // Handles cases where the function is safe wrapped | |
| const testScriptURL = /** @type {string} */ (Root.Runtime.queryParam('test')); | |
| const functionLine = lines.reduce((acc, line) => line.includes(testScriptURL) ? line : acc, lines[lines.length - 2]); | |
| const components = functionLine.trim().split('/'); | |
| const source = components[components.length - 1].slice(0, -1).split(':'); | |
| const fileName = source[0]; | |
| const sourceURL = `test://evaluations/${_evaluateInPageCounter++}/` + fileName; | |
| const lineOffset = parseInt(source[1], 10); | |
| code = '\n'.repeat(lineOffset - 1) + code; | |
| if (code.indexOf('sourceURL=') === -1) { | |
| code += `//# sourceURL=${sourceURL}`; | |
| } | |
| const response = await TestRunner.RuntimeAgent.invoke_evaluate({expression: code, objectGroup: 'console'}); | |
| const error = response[Protocol.Error]; | |
| if (error) { | |
| addResult('Error: ' + error); | |
| completeTest(); | |
| return; | |
| } | |
| return response; | |
| } | |
| /** | |
| * Doesn't append sourceURL to snippets evaluated in inspected page | |
| * to avoid churning test expectations | |
| * @param {string} code | |
| * @param {boolean=} userGesture | |
| * @return {!Promise<*>} | |
| */ | |
| export async function evaluateInPageAnonymously(code, userGesture) { | |
| const response = | |
| await TestRunner.RuntimeAgent.invoke_evaluate({expression: code, objectGroup: 'console', userGesture}); | |
| if (!response[Protocol.Error]) { | |
| return response.result.value; | |
| } | |
| addResult( | |
| 'Error: ' + | |
| (response.exceptionDetails && response.exceptionDetails.text || 'exception from evaluateInPageAnonymously.')); | |
| completeTest(); | |
| } | |
| /** | |
| * @param {string} code | |
| * @return {!Promise<*>} | |
| */ | |
| export function evaluateInPagePromise(code) { | |
| return new Promise(success => evaluateInPage(code, success)); | |
| } | |
| /** | |
| * @param {string} code | |
| * @return {!Promise<*>} | |
| */ | |
| export async function evaluateInPageAsync(code) { | |
| const response = await TestRunner.RuntimeAgent.invoke_evaluate( | |
| {expression: code, objectGroup: 'console', includeCommandLineAPI: false, awaitPromise: true}); | |
| const error = response[Protocol.Error]; | |
| if (!error && !response.exceptionDetails) { | |
| return response.result.value; | |
| } | |
| addResult( | |
| 'Error: ' + | |
| (error || response.exceptionDetails && response.exceptionDetails.text || 'exception while evaluation in page.')); | |
| completeTest(); | |
| } | |
| /** | |
| * @param {string} name | |
| * @param {!Array<*>} args | |
| * @return {!Promise<*>} | |
| */ | |
| export function callFunctionInPageAsync(name, args) { | |
| args = args || []; | |
| return evaluateInPageAsync(name + '(' + args.map(a => JSON.stringify(a)).join(',') + ')'); | |
| } | |
| /** | |
| * @param {string} code | |
| * @param {boolean=} userGesture | |
| */ | |
| export function evaluateInPageWithTimeout(code, userGesture) { | |
| // FIXME: we need a better way of waiting for chromium events to happen | |
| evaluateInPageAnonymously('setTimeout(unescape(\'' + escape(code) + '\'), 1)', userGesture); | |
| } | |
| /** | |
| * @param {function():*} func | |
| * @param {function(*):void} callback | |
| */ | |
| export function evaluateFunctionInOverlay(func, callback) { | |
| const expression = 'internals.evaluateInInspectorOverlay("(" + ' + func + ' + ")()")'; | |
| const mainContext = TestRunner.runtimeModel.executionContexts()[0]; | |
| mainContext | |
| .evaluate( | |
| { | |
| expression: expression, | |
| objectGroup: '', | |
| includeCommandLineAPI: false, | |
| silent: false, | |
| returnByValue: true, | |
| generatePreview: false | |
| }, | |
| /* userGesture */ false, /* awaitPromise*/ false) | |
| .then(result => void callback(result.object.value)); | |
| } | |
| /** | |
| * @param {boolean} passCondition | |
| * @param {string} failureText | |
| */ | |
| export function check(passCondition, failureText) { | |
| if (!passCondition) { | |
| addResult('FAIL: ' + failureText); | |
| } | |
| } | |
| /** | |
| * @param {!Function} callback | |
| */ | |
| export function deprecatedRunAfterPendingDispatches(callback) { | |
| Protocol.test.deprecatedRunAfterPendingDispatches(callback); | |
| } | |
| /** | |
| * This ensures a base tag is set so all DOM references | |
| * are relative to the test file and not the inspected page | |
| * (i.e. http/tests/devtools/resources/inspected-page.html). | |
| * @param {string} html | |
| * @return {!Promise<*>} | |
| */ | |
| export function loadHTML(html) { | |
| if (!html.includes('<base')) { | |
| // <!DOCTYPE...> tag needs to be first | |
| const doctypeRegex = /(<!DOCTYPE.*?>)/i; | |
| const baseTag = `<base href="${url()}">`; | |
| if (html.match(doctypeRegex)) { | |
| html = html.replace(doctypeRegex, '$1' + baseTag); | |
| } else { | |
| html = baseTag + html; | |
| } | |
| } | |
| html = html.replace(/'/g, '\\\'').replace(/\n/g, '\\n'); | |
| return evaluateInPageAnonymously(`document.write(\`${html}\`);document.close();`); | |
| } | |
| /** | |
| * @param {string} path | |
| * @return {!Promise<*>} | |
| */ | |
| export function addScriptTag(path) { | |
| return evaluateInPageAsync(` | |
| (function(){ | |
| let script = document.createElement('script'); | |
| script.src = '${path}'; | |
| document.head.append(script); | |
| return new Promise(f => script.onload = f); | |
| })(); | |
| `); | |
| } | |
| /** | |
| * @param {string} path | |
| * @return {!Promise<*>} | |
| */ | |
| export function addStylesheetTag(path) { | |
| return evaluateInPageAsync(` | |
| (function(){ | |
| const link = document.createElement('link'); | |
| link.rel = 'stylesheet'; | |
| link.href = '${path}'; | |
| link.onload = onload; | |
| document.head.append(link); | |
| let resolve; | |
| const promise = new Promise(r => resolve = r); | |
| function onload() { | |
| // TODO(chenwilliam): It shouldn't be necessary to force | |
| // style recalc here but some tests rely on it. | |
| window.getComputedStyle(document.body).color; | |
| resolve(); | |
| } | |
| return promise; | |
| })(); | |
| `); | |
| } | |
| /** | |
| * @param {string} path | |
| * @return {!Promise<*>} | |
| */ | |
| export function addHTMLImport(path) { | |
| return evaluateInPageAsync(` | |
| (function(){ | |
| const link = document.createElement('link'); | |
| link.rel = 'import'; | |
| link.href = '${path}'; | |
| const promise = new Promise(r => link.onload = r); | |
| document.body.append(link); | |
| return promise; | |
| })(); | |
| `); | |
| } | |
| /** | |
| * NOTE you should manually ensure the path is correct. There | |
| * is no error event triggered if it is incorrect, and this is | |
| * in line with the standard (crbug 365457). | |
| * @param {string} path | |
| * @param {!Object|undefined} options | |
| * @return {!Promise<*>} | |
| */ | |
| export function addIframe(path, options = {}) { | |
| options.id = options.id || ''; | |
| options.name = options.name || ''; | |
| return evaluateInPageAsync(` | |
| (function(){ | |
| const iframe = document.createElement('iframe'); | |
| iframe.src = '${path}'; | |
| iframe.id = '${options.id}'; | |
| iframe.name = '${options.name}'; | |
| document.body.appendChild(iframe); | |
| return new Promise(f => iframe.onload = f); | |
| })(); | |
| `); | |
| } | |
| /** @type {number} */ | |
| let _pendingInits = 0; | |
| /** | |
| * The old test framework executed certain snippets in the inspected page | |
| * context as part of loading a test helper file. | |
| * | |
| * This is deprecated because: | |
| * 1) it makes the testing API less intuitive (need to read the various *TestRunner.js | |
| * files to know which helper functions are available in the inspected page). | |
| * 2) it complicates the test framework's module loading process. | |
| * | |
| * In most cases, this is used to set up inspected page functions (e.g. makeSimpleXHR) | |
| * which should become a *TestRunner method (e.g. NetworkTestRunner.makeSimpleXHR) | |
| * that calls evaluateInPageAnonymously(...). | |
| * @param {string} code | |
| */ | |
| export async function deprecatedInitAsync(code) { | |
| _pendingInits++; | |
| await TestRunner.RuntimeAgent.invoke_evaluate({expression: code, objectGroup: 'console'}); | |
| _pendingInits--; | |
| if (!_pendingInits) { | |
| _resolveOnFinishInits(); | |
| } | |
| } | |
| /** | |
| * @param {string} title | |
| */ | |
| export function markStep(title) { | |
| addResult('\nRunning: ' + title); | |
| } | |
| export function startDumpingProtocolMessages() { | |
| Protocol.test.dumpProtocol = self.testRunner.logToStderr.bind(self.testRunner); | |
| } | |
| /** | |
| * @param {string} url | |
| * @param {string} content | |
| * @param {!SDK.ResourceTreeFrame} frame | |
| */ | |
| export function addScriptForFrame(url, content, frame) { | |
| content += '\n//# sourceURL=' + url; | |
| const executionContext = TestRunner.runtimeModel.executionContexts().find(context => context.frameId === frame.id); | |
| TestRunner.RuntimeAgent.evaluate(content, 'console', false, false, executionContext.id); | |
| } | |
| export const formatters = { | |
| /** | |
| * @param {*} value | |
| * @return {string} | |
| */ | |
| formatAsTypeName(value) { | |
| return '<' + typeof value + '>'; | |
| }, | |
| /** | |
| * @param {*} value | |
| * @return {string} | |
| */ | |
| formatAsTypeNameOrNull(value) { | |
| if (value === null) { | |
| return 'null'; | |
| } | |
| return formatters.formatAsTypeName(value); | |
| }, | |
| /** | |
| * @param {*} value | |
| * @return {string|!Date} | |
| */ | |
| formatAsRecentTime(value) { | |
| if (typeof value !== 'object' || !(value instanceof Date)) { | |
| return formatters.formatAsTypeName(value); | |
| } | |
| const delta = Date.now() - value; | |
| return 0 <= delta && delta < 30 * 60 * 1000 ? '<plausible>' : value; | |
| }, | |
| /** | |
| * @param {string} value | |
| * @return {string} | |
| */ | |
| formatAsURL(value) { | |
| if (!value) { | |
| return value; | |
| } | |
| const lastIndex = value.lastIndexOf('devtools/'); | |
| if (lastIndex < 0) { | |
| return value; | |
| } | |
| return '.../' + value.substr(lastIndex); | |
| }, | |
| /** | |
| * @param {string} value | |
| * @return {string} | |
| */ | |
| formatAsDescription(value) { | |
| if (!value) { | |
| return value; | |
| } | |
| return '"' + value.replace(/^function [gs]et /, 'function ') + '"'; | |
| }, | |
| }; | |
| /** | |
| * @param {!Object} object | |
| * @param {!TestRunner.CustomFormatters=} customFormatters | |
| * @param {string=} prefix | |
| * @param {string=} firstLinePrefix | |
| */ | |
| export function addObject(object, customFormatters, prefix, firstLinePrefix) { | |
| prefix = prefix || ''; | |
| firstLinePrefix = firstLinePrefix || prefix; | |
| addResult(firstLinePrefix + '{'); | |
| const propertyNames = Object.keys(object); | |
| propertyNames.sort(); | |
| for (let i = 0; i < propertyNames.length; ++i) { | |
| const prop = propertyNames[i]; | |
| if (!object.hasOwnProperty(prop)) { | |
| continue; | |
| } | |
| const prefixWithName = ' ' + prefix + prop + ' : '; | |
| const propValue = object[prop]; | |
| if (customFormatters && customFormatters[prop]) { | |
| const formatterName = customFormatters[prop]; | |
| if (formatterName !== 'skip') { | |
| const formatter = formatters[formatterName]; | |
| addResult(prefixWithName + formatter(propValue)); | |
| } | |
| } else { | |
| dump(propValue, customFormatters, ' ' + prefix, prefixWithName); | |
| } | |
| } | |
| addResult(prefix + '}'); | |
| } | |
| /** | |
| * @param {!Array} array | |
| * @param {!TestRunner.CustomFormatters=} customFormatters | |
| * @param {string=} prefix | |
| * @param {string=} firstLinePrefix | |
| */ | |
| export function addArray(array, customFormatters, prefix, firstLinePrefix) { | |
| prefix = prefix || ''; | |
| firstLinePrefix = firstLinePrefix || prefix; | |
| addResult(firstLinePrefix + '['); | |
| for (let i = 0; i < array.length; ++i) { | |
| dump(array[i], customFormatters, prefix + ' '); | |
| } | |
| addResult(prefix + ']'); | |
| } | |
| /** | |
| * @param {!Node} node | |
| */ | |
| export function dumpDeepInnerHTML(node) { | |
| /** | |
| * @param {string} prefix | |
| * @param {!Node} node | |
| */ | |
| function innerHTML(prefix, node) { | |
| const openTag = []; | |
| if (node.nodeType === Node.TEXT_NODE) { | |
| if (!node.parentElement || node.parentElement.nodeName !== 'STYLE') { | |
| addResult(node.nodeValue); | |
| } | |
| return; | |
| } | |
| openTag.push('<' + node.nodeName); | |
| const attrs = node.attributes; | |
| for (let i = 0; attrs && i < attrs.length; ++i) { | |
| openTag.push(attrs[i].name + '=' + attrs[i].value); | |
| } | |
| openTag.push('>'); | |
| addResult(prefix + openTag.join(' ')); | |
| for (let child = node.firstChild; child; child = child.nextSibling) { | |
| innerHTML(prefix + ' ', child); | |
| } | |
| if (node.shadowRoot) { | |
| innerHTML(prefix + ' ', node.shadowRoot); | |
| } | |
| addResult(prefix + '</' + node.nodeName + '>'); | |
| } | |
| innerHTML('', node); | |
| } | |
| /** | |
| * @param {!Node} node | |
| * @return {string} | |
| */ | |
| export function deepTextContent(node) { | |
| if (!node) { | |
| return ''; | |
| } | |
| if (node.nodeType === Node.TEXT_NODE && node.nodeValue) { | |
| return !node.parentElement || node.parentElement.nodeName !== 'STYLE' ? node.nodeValue : ''; | |
| } | |
| let res = ''; | |
| const children = node.childNodes; | |
| for (let i = 0; i < children.length; ++i) { | |
| res += deepTextContent(children[i]); | |
| } | |
| if (node.shadowRoot) { | |
| res += deepTextContent(node.shadowRoot); | |
| } | |
| return res; | |
| } | |
| /** | |
| * @param {*} value | |
| * @param {!TestRunner.CustomFormatters=} customFormatters | |
| * @param {string=} prefix | |
| * @param {string=} prefixWithName | |
| */ | |
| export function dump(value, customFormatters, prefix, prefixWithName) { | |
| prefixWithName = prefixWithName || prefix; | |
| if (prefixWithName && prefixWithName.length > 80) { | |
| addResult(prefixWithName + 'was skipped due to prefix length limit'); | |
| return; | |
| } | |
| if (value === null) { | |
| addResult(prefixWithName + 'null'); | |
| } else if (value && value.constructor && value.constructor.name === 'Array') { | |
| addArray(/** @type {!Array} */ (value), customFormatters, prefix, prefixWithName); | |
| } else if (typeof value === 'object') { | |
| addObject(/** @type {!Object} */ (value), customFormatters, prefix, prefixWithName); | |
| } else if (typeof value === 'string') { | |
| addResult(prefixWithName + '"' + value + '"'); | |
| } else { | |
| addResult(prefixWithName + value); | |
| } | |
| } | |
| /** | |
| * @param {!UI.TreeElement} treeElement | |
| */ | |
| export function dumpObjectPropertyTreeElement(treeElement) { | |
| const expandedSubstring = treeElement.expanded ? '[expanded]' : '[collapsed]'; | |
| addResult(expandedSubstring + ' ' + treeElement.listItemElement.deepTextContent()); | |
| for (let i = 0; i < treeElement.childCount(); ++i) { | |
| const property = treeElement.childAt(i).property; | |
| const key = property.name; | |
| const value = property.value._description; | |
| addResult(' ' + key + ': ' + value); | |
| } | |
| } | |
| /** | |
| * @param {symbol} eventName | |
| * @param {!Common.Object} obj | |
| * @param {function(?):boolean=} condition | |
| * @return {!Promise} | |
| */ | |
| export function waitForEvent(eventName, obj, condition) { | |
| condition = condition || function() { | |
| return true; | |
| }; | |
| return new Promise(resolve => { | |
| obj.addEventListener(eventName, onEventFired); | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| function onEventFired(event) { | |
| if (!condition(event.data)) { | |
| return; | |
| } | |
| obj.removeEventListener(eventName, onEventFired); | |
| resolve(event.data); | |
| } | |
| }); | |
| } | |
| /** | |
| * @param {function(!SDK.Target):boolean} filter | |
| * @return {!Promise<!SDK.Target>} | |
| */ | |
| export function waitForTarget(filter) { | |
| filter = filter || (target => true); | |
| for (const target of SDK.targetManager.targets()) { | |
| if (filter(target)) { | |
| return Promise.resolve(target); | |
| } | |
| } | |
| return new Promise(fulfill => { | |
| const observer = /** @type {!SDK.TargetManager.Observer} */ ({ | |
| targetAdded: function(target) { | |
| if (filter(target)) { | |
| SDK.targetManager.unobserveTargets(observer); | |
| fulfill(target); | |
| } | |
| }, | |
| targetRemoved: function() {}, | |
| }); | |
| SDK.targetManager.observeTargets(observer); | |
| }); | |
| } | |
| /** | |
| * @param {!SDK.Target} targetToRemove | |
| * @return {!Promise<!SDK.Target>} | |
| */ | |
| export function waitForTargetRemoved(targetToRemove) { | |
| return new Promise(fulfill => { | |
| const observer = /** @type {!SDK.TargetManager.Observer} */ ({ | |
| targetRemoved: function(target) { | |
| if (target === targetToRemove) { | |
| SDK.targetManager.unobserveTargets(observer); | |
| fulfill(target); | |
| } | |
| }, | |
| targetAdded: function() {}, | |
| }); | |
| SDK.targetManager.observeTargets(observer); | |
| }); | |
| } | |
| /** | |
| * @param {!SDK.RuntimeModel} runtimeModel | |
| * @return {!Promise} | |
| */ | |
| export function waitForExecutionContext(runtimeModel) { | |
| if (runtimeModel.executionContexts().length) { | |
| return Promise.resolve(runtimeModel.executionContexts()[0]); | |
| } | |
| return runtimeModel.once(SDK.RuntimeModel.Events.ExecutionContextCreated); | |
| } | |
| /** | |
| * @param {!SDK.ExecutionContext} context | |
| * @return {!Promise} | |
| */ | |
| export function waitForExecutionContextDestroyed(context) { | |
| const runtimeModel = context.runtimeModel; | |
| if (runtimeModel.executionContexts().indexOf(context) === -1) { | |
| return Promise.resolve(); | |
| } | |
| return waitForEvent( | |
| SDK.RuntimeModel.Events.ExecutionContextDestroyed, runtimeModel, | |
| destroyedContext => destroyedContext === context); | |
| } | |
| /** | |
| * @param {number} a | |
| * @param {number} b | |
| * @param {string=} message | |
| */ | |
| export function assertGreaterOrEqual(a, b, message) { | |
| if (a < b) { | |
| addResult('FAILED: ' + (message ? message + ': ' : '') + a + ' < ' + b); | |
| } | |
| } | |
| let _pageLoadedCallback; | |
| /** | |
| * @param {string} url | |
| * @param {function():void} callback | |
| */ | |
| export function navigate(url, callback) { | |
| _pageLoadedCallback = safeWrap(callback); | |
| TestRunner.resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, _pageNavigated); | |
| // Note: injected <base> means that url is relative to test | |
| // and not the inspected page | |
| evaluateInPageAnonymously('window.location.replace(\'' + url + '\')'); | |
| } | |
| /** | |
| * @return {!Promise} | |
| */ | |
| export function navigatePromise(url) { | |
| return new Promise(fulfill => navigate(url, fulfill)); | |
| } | |
| export function _pageNavigated() { | |
| TestRunner.resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, _pageNavigated); | |
| _handlePageLoaded(); | |
| } | |
| /** | |
| * @param {function():void} callback | |
| */ | |
| export function hardReloadPage(callback) { | |
| _innerReloadPage(true, undefined, callback); | |
| } | |
| /** | |
| * @param {function():void} callback | |
| */ | |
| export function reloadPage(callback) { | |
| _innerReloadPage(false, undefined, callback); | |
| } | |
| /** | |
| * @param {(string|undefined)} injectedScript | |
| * @param {function():void} callback | |
| */ | |
| export function reloadPageWithInjectedScript(injectedScript, callback) { | |
| _innerReloadPage(false, injectedScript, callback); | |
| } | |
| /** | |
| * @return {!Promise} | |
| */ | |
| export function reloadPagePromise() { | |
| return new Promise(fulfill => reloadPage(fulfill)); | |
| } | |
| /** | |
| * @param {boolean} hardReload | |
| * @param {(string|undefined)} injectedScript | |
| * @param {function():void} callback | |
| */ | |
| export function _innerReloadPage(hardReload, injectedScript, callback) { | |
| _pageLoadedCallback = safeWrap(callback); | |
| TestRunner.resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, pageLoaded); | |
| TestRunner.resourceTreeModel.reloadPage(hardReload, injectedScript); | |
| } | |
| export function pageLoaded() { | |
| TestRunner.resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, pageLoaded); | |
| addResult('Page reloaded.'); | |
| _handlePageLoaded(); | |
| } | |
| export async function _handlePageLoaded() { | |
| await waitForExecutionContext(/** @type {!SDK.RuntimeModel} */ (TestRunner.runtimeModel)); | |
| if (_pageLoadedCallback) { | |
| const callback = _pageLoadedCallback; | |
| _pageLoadedCallback = undefined; | |
| callback(); | |
| } | |
| } | |
| /** | |
| * @param {function():void} callback | |
| */ | |
| export function waitForPageLoad(callback) { | |
| TestRunner.resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, onLoaded); | |
| function onLoaded() { | |
| TestRunner.resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, onLoaded); | |
| callback(); | |
| } | |
| } | |
| /** | |
| * @param {function():void} callback | |
| */ | |
| export function runWhenPageLoads(callback) { | |
| const oldCallback = _pageLoadedCallback; | |
| function chainedCallback() { | |
| if (oldCallback) { | |
| oldCallback(); | |
| } | |
| callback(); | |
| } | |
| _pageLoadedCallback = safeWrap(chainedCallback); | |
| } | |
| /** | |
| * @param {!Array<function(function():void)>} testSuite | |
| */ | |
| export function runTestSuite(testSuite) { | |
| const testSuiteTests = testSuite.slice(); | |
| function runner() { | |
| if (!testSuiteTests.length) { | |
| completeTest(); | |
| return; | |
| } | |
| const nextTest = testSuiteTests.shift(); | |
| addResult(''); | |
| addResult( | |
| 'Running: ' + | |
| /function\s([^(]*)/.exec(nextTest)[1]); | |
| safeWrap(nextTest)(runner); | |
| } | |
| runner(); | |
| } | |
| /** | |
| * @param {!Array<function():Promise<*>>} testSuite | |
| */ | |
| export async function runAsyncTestSuite(testSuite) { | |
| for (const nextTest of testSuite) { | |
| addResult(''); | |
| addResult( | |
| 'Running: ' + | |
| /function\s([^(]*)/.exec(nextTest)[1]); | |
| await safeAsyncWrap(nextTest)(); | |
| } | |
| completeTest(); | |
| } | |
| /** | |
| * @param {*} expected | |
| * @param {*} found | |
| * @param {string} message | |
| */ | |
| export function assertEquals(expected, found, message) { | |
| if (expected === found) { | |
| return; | |
| } | |
| let error; | |
| if (message) { | |
| error = 'Failure (' + message + '):'; | |
| } else { | |
| error = 'Failure:'; | |
| } | |
| throw new Error(error + ' expected <' + expected + '> found <' + found + '>'); | |
| } | |
| /** | |
| * @param {*} found | |
| * @param {string} message | |
| */ | |
| export function assertTrue(found, message) { | |
| assertEquals(true, !!found, message); | |
| } | |
| /** | |
| * @param {!Object} receiver | |
| * @param {string} methodName | |
| * @param {!Function} override | |
| * @param {boolean=} opt_sticky | |
| * @return {!Function} | |
| */ | |
| export function override(receiver, methodName, override, opt_sticky) { | |
| override = safeWrap(override); | |
| const original = receiver[methodName]; | |
| if (typeof original !== 'function') { | |
| throw new Error('Cannot find method to override: ' + methodName); | |
| } | |
| receiver[methodName] = function(var_args) { | |
| try { | |
| return override.apply(this, arguments); | |
| } catch (e) { | |
| throw new Error('Exception in overriden method \'' + methodName + '\': ' + e); | |
| } finally { | |
| if (!opt_sticky) { | |
| receiver[methodName] = original; | |
| } | |
| } | |
| }; | |
| return original; | |
| } | |
| /** | |
| * @param {string} text | |
| * @return {string} | |
| */ | |
| export function clearSpecificInfoFromStackFrames(text) { | |
| let buffer = text.replace(/\(file:\/\/\/(?:[^)]+\)|[\w\/:-]+)/g, '(...)'); | |
| buffer = buffer.replace(/\(http:\/\/(?:[^)]+\)|[\w\/:-]+)/g, '(...)'); | |
| buffer = buffer.replace(/\(test:\/\/(?:[^)]+\)|[\w\/:-]+)/g, '(...)'); | |
| buffer = buffer.replace(/\(<anonymous>:[^)]+\)/g, '(...)'); | |
| buffer = buffer.replace(/VM\d+/g, 'VM'); | |
| return buffer.replace(/\s*at[^()]+\(native\)/g, ''); | |
| } | |
| export function hideInspectorView() { | |
| UI.inspectorView.element.setAttribute('style', 'display:none !important'); | |
| } | |
| /** | |
| * @return {?SDK.ResourceTreeFrame} | |
| */ | |
| export function mainFrame() { | |
| return TestRunner.resourceTreeModel.mainFrame; | |
| } | |
| export class StringOutputStream { | |
| /** | |
| * @param {function(string):void} callback | |
| */ | |
| constructor(callback) { | |
| this._callback = callback; | |
| this._buffer = ''; | |
| } | |
| /** | |
| * @param {string} fileName | |
| * @return {!Promise<boolean>} | |
| */ | |
| async open(fileName) { | |
| return true; | |
| } | |
| /** | |
| * @param {string} chunk | |
| */ | |
| async write(chunk) { | |
| this._buffer += chunk; | |
| } | |
| async close() { | |
| this._callback(this._buffer); | |
| } | |
| } | |
| /** | |
| * @template V | |
| */ | |
| export class MockSetting { | |
| /** | |
| * @param {V} value | |
| */ | |
| constructor(value) { | |
| this._value = value; | |
| } | |
| /** | |
| * @return {V} | |
| */ | |
| get() { | |
| return this._value; | |
| } | |
| /** | |
| * @param {V} value | |
| */ | |
| set(value) { | |
| this._value = value; | |
| } | |
| } | |
| /** | |
| * @return {!Array<!Root.Runtime.Module>} | |
| */ | |
| export function loadedModules() { | |
| return self.runtime._modules.filter(module => module._loadedForTest) | |
| .filter(module => module.name() !== 'help') | |
| .filter(module => module.name().indexOf('test_runner') === -1); | |
| } | |
| /** | |
| * @param {!Array<!Root.Runtime.Module>} relativeTo | |
| * @return {!Array<!Root.Runtime.Module>} | |
| */ | |
| export function dumpLoadedModules(relativeTo) { | |
| const previous = new Set(relativeTo || []); | |
| function moduleSorter(left, right) { | |
| return String.naturalOrderComparator(left._descriptor.name, right._descriptor.name); | |
| } | |
| addResult('Loaded modules:'); | |
| const sortedLoadedModules = loadedModules().sort(moduleSorter); | |
| for (const module of sortedLoadedModules) { | |
| if (previous.has(module)) { | |
| continue; | |
| } | |
| addResult(' ' + module._descriptor.name); | |
| } | |
| return sortedLoadedModules; | |
| } | |
| /** | |
| * @param {string} urlSuffix | |
| * @param {!Workspace.projectTypes=} projectType | |
| * @return {!Promise} | |
| */ | |
| export function waitForUISourceCode(urlSuffix, projectType) { | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @return {boolean} | |
| */ | |
| function matches(uiSourceCode) { | |
| if (projectType && uiSourceCode.project().type() !== projectType) { | |
| return false; | |
| } | |
| if (!projectType && uiSourceCode.project().type() === Workspace.projectTypes.Service) { | |
| return false; | |
| } | |
| if (urlSuffix && !uiSourceCode.url().endsWith(urlSuffix)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| for (const uiSourceCode of Workspace.workspace.uiSourceCodes()) { | |
| if (urlSuffix && matches(uiSourceCode)) { | |
| return Promise.resolve(uiSourceCode); | |
| } | |
| } | |
| return waitForEvent(Workspace.Workspace.Events.UISourceCodeAdded, Workspace.workspace, matches); | |
| } | |
| /** | |
| * @param {!Function} callback | |
| */ | |
| export function waitForUISourceCodeRemoved(callback) { | |
| Workspace.workspace.once(Workspace.Workspace.Events.UISourceCodeRemoved).then(callback); | |
| } | |
| /** | |
| * @param {string=} url | |
| * @return {string} | |
| */ | |
| export function url(url = '') { | |
| const testScriptURL = /** @type {string} */ (Root.Runtime.queryParam('test')); | |
| // This handles relative (e.g. "../file"), root (e.g. "/resource"), | |
| // absolute (e.g. "http://", "data:") and empty (e.g. "") paths | |
| return new URL(url, testScriptURL + '/../').href; | |
| } | |
| /** | |
| * @param {string} str | |
| * @param {string} mimeType | |
| * @return {!Promise.<undefined>} | |
| * @suppressGlobalPropertiesCheck | |
| */ | |
| export function dumpSyntaxHighlight(str, mimeType) { | |
| const node = document.createElement('span'); | |
| node.textContent = str; | |
| const javascriptSyntaxHighlighter = new UI.SyntaxHighlighter(mimeType, false); | |
| return javascriptSyntaxHighlighter.syntaxHighlightNode(node).then(dumpSyntax); | |
| function dumpSyntax() { | |
| const node_parts = []; | |
| for (let i = 0; i < node.childNodes.length; i++) { | |
| if (node.childNodes[i].getAttribute) { | |
| node_parts.push(node.childNodes[i].getAttribute('class')); | |
| } else { | |
| node_parts.push('*'); | |
| } | |
| } | |
| addResult(str + ': ' + node_parts.join(', ')); | |
| } | |
| } | |
| /** | |
| * @param {string} querySelector | |
| */ | |
| export async function dumpInspectedPageElementText(querySelector) { | |
| const value = await evaluateInPageAsync(`document.querySelector('${querySelector}').innerText`); | |
| addResult(value); | |
| } | |
| /** @type {boolean} */ | |
| let _startedTest = false; | |
| /** | |
| * @implements {SDK.TargetManager.Observer} | |
| */ | |
| export class _TestObserver { | |
| /** | |
| * @param {!SDK.Target} target | |
| * @override | |
| */ | |
| targetAdded(target) { | |
| if (target.id() === 'main') { | |
| _setupTestHelpers(target); | |
| } | |
| if (_startedTest) { | |
| return; | |
| } | |
| _startedTest = true; | |
| if (_isStartupTest()) { | |
| return; | |
| } | |
| TestRunner | |
| .loadHTML(` | |
| <head> | |
| <base href="${url()}"> | |
| </head> | |
| <body> | |
| </body> | |
| `).then(() => _executeTestScript()); | |
| } | |
| /** | |
| * @param {!SDK.Target} target | |
| * @override | |
| */ | |
| targetRemoved(target) { | |
| } | |
| } | |
| (async function() { | |
| SDK.targetManager.observeTargets(new _TestObserver()); | |
| if (!_isStartupTest()) { | |
| return; | |
| } | |
| /** | |
| * Startup test initialization: | |
| * 1. Wait for DevTools app UI to load | |
| * 2. Execute test script, the first line will be TestRunner.setupStartupTest(...) which: | |
| * A. Navigate secondary window | |
| * B. After preconditions occur, secondary window calls testRunner.inspectSecondaryWindow() | |
| * 3. Backend executes TestRunner._startupTestSetupFinished() which calls _initializeTarget() | |
| */ | |
| TestRunner._initializeTargetForStartupTest = | |
| override(Main.Main._instanceForTest, '_initializeTarget', () => undefined).bind(Main.Main._instanceForTest); | |
| await addSnifferPromise(Main.Main._instanceForTest, '_showAppUI'); | |
| _executeTestScript(); | |
| })(); | |
| /** @type {!{logToStderr: function(), navigateSecondaryWindow: function(string), notifyDone: function()}|undefined} */ | |
| self.testRunner; | |
| TestRunner.StringOutputStream = StringOutputStream; | |
| TestRunner.MockSetting = MockSetting; | |
| TestRunner.formatters = formatters; | |
| TestRunner.setupStartupTest = setupStartupTest; | |
| TestRunner.flushResults = flushResults; | |
| TestRunner.completeTest = completeTest; | |
| TestRunner.addResult = addResult; | |
| TestRunner.addResults = addResults; | |
| TestRunner.runTests = runTests; | |
| TestRunner.addSniffer = addSniffer; | |
| TestRunner.addSnifferPromise = addSnifferPromise; | |
| TestRunner.showPanel = showPanel; | |
| TestRunner.createKeyEvent = createKeyEvent; | |
| TestRunner.safeWrap = safeWrap; | |
| TestRunner.safeAsyncWrap = safeAsyncWrap; | |
| TestRunner.textContentWithLineBreaks = textContentWithLineBreaks; | |
| TestRunner.textContentWithoutStyles = textContentWithoutStyles; | |
| TestRunner.evaluateInPagePromise = evaluateInPagePromise; | |
| TestRunner.callFunctionInPageAsync = callFunctionInPageAsync; | |
| TestRunner.evaluateInPageWithTimeout = evaluateInPageWithTimeout; | |
| TestRunner.evaluateFunctionInOverlay = evaluateFunctionInOverlay; | |
| TestRunner.check = check; | |
| TestRunner.deprecatedRunAfterPendingDispatches = deprecatedRunAfterPendingDispatches; | |
| TestRunner.loadHTML = loadHTML; | |
| TestRunner.addScriptTag = addScriptTag; | |
| TestRunner.addStylesheetTag = addStylesheetTag; | |
| TestRunner.addHTMLImport = addHTMLImport; | |
| TestRunner.addIframe = addIframe; | |
| TestRunner.markStep = markStep; | |
| TestRunner.startDumpingProtocolMessages = startDumpingProtocolMessages; | |
| TestRunner.addScriptForFrame = addScriptForFrame; | |
| TestRunner.addObject = addObject; | |
| TestRunner.addArray = addArray; | |
| TestRunner.dumpDeepInnerHTML = dumpDeepInnerHTML; | |
| TestRunner.deepTextContent = deepTextContent; | |
| TestRunner.dump = dump; | |
| TestRunner.dumpObjectPropertyTreeElement = dumpObjectPropertyTreeElement; | |
| TestRunner.waitForEvent = waitForEvent; | |
| TestRunner.waitForTarget = waitForTarget; | |
| TestRunner.waitForTargetRemoved = waitForTargetRemoved; | |
| TestRunner.waitForExecutionContext = waitForExecutionContext; | |
| TestRunner.waitForExecutionContextDestroyed = waitForExecutionContextDestroyed; | |
| TestRunner.assertGreaterOrEqual = assertGreaterOrEqual; | |
| TestRunner.navigate = navigate; | |
| TestRunner.navigatePromise = navigatePromise; | |
| TestRunner.hardReloadPage = hardReloadPage; | |
| TestRunner.reloadPage = reloadPage; | |
| TestRunner.reloadPageWithInjectedScript = reloadPageWithInjectedScript; | |
| TestRunner.reloadPagePromise = reloadPagePromise; | |
| TestRunner.pageLoaded = pageLoaded; | |
| TestRunner.waitForPageLoad = waitForPageLoad; | |
| TestRunner.runWhenPageLoads = runWhenPageLoads; | |
| TestRunner.runTestSuite = runTestSuite; | |
| TestRunner.assertEquals = assertEquals; | |
| TestRunner.assertTrue = assertTrue; | |
| TestRunner.override = override; | |
| TestRunner.clearSpecificInfoFromStackFrames = clearSpecificInfoFromStackFrames; | |
| TestRunner.hideInspectorView = hideInspectorView; | |
| TestRunner.mainFrame = mainFrame; | |
| TestRunner.loadedModules = loadedModules; | |
| TestRunner.dumpLoadedModules = dumpLoadedModules; | |
| TestRunner.waitForUISourceCode = waitForUISourceCode; | |
| TestRunner.waitForUISourceCodeRemoved = waitForUISourceCodeRemoved; | |
| TestRunner.url = url; | |
| TestRunner.dumpSyntaxHighlight = dumpSyntaxHighlight; | |
| TestRunner.loadModule = loadModule; | |
| TestRunner.evaluateInPageRemoteObject = evaluateInPageRemoteObject; | |
| TestRunner.evaluateInPage = evaluateInPage; | |
| TestRunner.evaluateInPageAnonymously = evaluateInPageAnonymously; | |
| TestRunner.evaluateInPageAsync = evaluateInPageAsync; | |
| TestRunner.deprecatedInitAsync = deprecatedInitAsync; | |
| TestRunner.runAsyncTestSuite = runAsyncTestSuite; | |
| TestRunner.dumpInspectedPageElementText = dumpInspectedPageElementText; | |
| /** | |
| * @typedef {!Object<string, string>} | |
| */ | |
| TestRunner.CustomFormatters; |