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