mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 21:54:47 +08:00
Bug: 829119 Change-Id: Idbc1dd0294174b457989893d11e66237a04c3ea5 Reviewed-on: https://chromium-review.googlesource.com/1005367 Reviewed-by: Dmitry Gozman <dgozman@chromium.org> Commit-Queue: Nate Chapin <japhet@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#551465} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 908b82feaae2e824e32a0ec1df505fe5ba2ac671
1433 lines
41 KiB
JavaScript
1433 lines
41 KiB
JavaScript
// 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 */
|
|
|
|
/** @type {!{logToStderr: function(), navigateSecondaryWindow: function(string), notifyDone: function()}|undefined} */
|
|
self.testRunner;
|
|
|
|
/**
|
|
* Only tests in /LayoutTests/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>}
|
|
*/
|
|
TestRunner.setupStartupTest = function(path) {
|
|
const absoluteURL = TestRunner.url(path);
|
|
self.testRunner.navigateSecondaryWindow(absoluteURL);
|
|
return new Promise(f => TestRunner._startupTestSetupFinished = () => {
|
|
TestRunner._initializeTargetForStartupTest();
|
|
delete TestRunner._startupTestSetupFinished;
|
|
f();
|
|
});
|
|
};
|
|
|
|
TestRunner._executeTestScript = function() {
|
|
const testScriptURL = /** @type {string} */ (Runtime.queryParam('test'));
|
|
fetch(testScriptURL)
|
|
.then(data => data.text())
|
|
.then(testScript => {
|
|
if (TestRunner._isDebugTest()) {
|
|
TestRunner.addResult = console.log;
|
|
TestRunner.completeTest = () => 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) {
|
|
TestRunner.addResult('TEST ENDED EARLY DUE TO UNCAUGHT ERROR:');
|
|
TestRunner.addResult(err && err.stack || err);
|
|
TestRunner.addResult('=== DO NOT COMMIT THIS INTO -expected.txt ===');
|
|
TestRunner.completeTest();
|
|
}
|
|
})();
|
|
})
|
|
.catch(error => {
|
|
TestRunner.addResult(`Unable to execute test script because of error: ${error}`);
|
|
TestRunner.completeTest();
|
|
});
|
|
};
|
|
|
|
/** @type {!Array<string>} */
|
|
TestRunner._results = [];
|
|
|
|
TestRunner.completeTest = function() {
|
|
TestRunner.flushResults();
|
|
self.testRunner.notifyDone();
|
|
};
|
|
|
|
/**
|
|
* @suppressGlobalPropertiesCheck
|
|
*/
|
|
TestRunner.flushResults = function() {
|
|
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 < TestRunner._results.length; i++) {
|
|
outputElement.appendChild(document.createTextNode(TestRunner._results[i]));
|
|
outputElement.appendChild(document.createElement('br'));
|
|
}
|
|
TestRunner._results = [];
|
|
};
|
|
|
|
/**
|
|
* @param {*} text
|
|
*/
|
|
TestRunner.addResult = function(text) {
|
|
TestRunner._results.push(String(text));
|
|
};
|
|
|
|
/**
|
|
* @param {!Array<string>} textArray
|
|
*/
|
|
TestRunner.addResults = function(textArray) {
|
|
if (!textArray)
|
|
return;
|
|
for (let i = 0, size = textArray.length; i < size; ++i)
|
|
TestRunner.addResult(textArray[i]);
|
|
};
|
|
|
|
/**
|
|
* @param {!Array<function()>} tests
|
|
*/
|
|
TestRunner.runTests = function(tests) {
|
|
nextTest();
|
|
|
|
function nextTest() {
|
|
const test = tests.shift();
|
|
if (!test) {
|
|
TestRunner.completeTest();
|
|
return;
|
|
}
|
|
TestRunner.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
|
|
*/
|
|
TestRunner.addSniffer = function(receiver, methodName, override, opt_sticky) {
|
|
override = TestRunner.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<*>}
|
|
*/
|
|
TestRunner.addSnifferPromise = function(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);
|
|
TestRunner.completeTest();
|
|
}
|
|
return result;
|
|
};
|
|
});
|
|
};
|
|
|
|
/** @type {function():void} */
|
|
TestRunner._resolveOnFinishInits;
|
|
|
|
/**
|
|
* @param {string} module
|
|
* @return {!Promise<undefined>}
|
|
*/
|
|
TestRunner.loadModule = async function(module) {
|
|
const promise = new Promise(resolve => TestRunner._resolveOnFinishInits = resolve);
|
|
await self.runtime.loadModulePromise(module);
|
|
if (!TestRunner._pendingInits)
|
|
return;
|
|
return promise;
|
|
};
|
|
|
|
/**
|
|
* @param {string} panel
|
|
* @return {!Promise.<?UI.Panel>}
|
|
*/
|
|
TestRunner.showPanel = function(panel) {
|
|
return UI.viewManager.showView(panel);
|
|
};
|
|
|
|
/**
|
|
* @param {string} key
|
|
* @param {boolean=} ctrlKey
|
|
* @param {boolean=} altKey
|
|
* @param {boolean=} shiftKey
|
|
* @param {boolean=} metaKey
|
|
* @return {!KeyboardEvent}
|
|
*/
|
|
TestRunner.createKeyEvent = function(key, ctrlKey, altKey, shiftKey, metaKey) {
|
|
return new KeyboardEvent('keydown', {
|
|
key: key,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
ctrlKey: !!ctrlKey,
|
|
altKey: !!altKey,
|
|
shiftKey: !!shiftKey,
|
|
metaKey: !!metaKey
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {!Function|undefined} func
|
|
* @param {!Function=} onexception
|
|
* @return {!Function}
|
|
*/
|
|
TestRunner.safeWrap = function(func, onexception) {
|
|
/**
|
|
* @this {*}
|
|
*/
|
|
function result() {
|
|
if (!func)
|
|
return;
|
|
const wrapThis = this;
|
|
try {
|
|
return func.apply(wrapThis, arguments);
|
|
} catch (e) {
|
|
TestRunner.addResult('Exception while running: ' + func + '\n' + (e.stack || e));
|
|
if (onexception)
|
|
TestRunner.safeWrap(onexception)();
|
|
else
|
|
TestRunner.completeTest();
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* @param {!Node} node
|
|
* @return {string}
|
|
*/
|
|
TestRunner.textContentWithLineBreaks = function(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}
|
|
*/
|
|
TestRunner.textContentWithoutStyles = function(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
|
|
*/
|
|
TestRunner._setupTestHelpers = function(target) {
|
|
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.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<*>}
|
|
*/
|
|
TestRunner.evaluateInPageRemoteObject = async function(code) {
|
|
const response = await TestRunner._evaluateInPage(code);
|
|
return TestRunner.runtimeModel.createRemoteObject(response.result);
|
|
};
|
|
|
|
/**
|
|
* @param {string} code
|
|
* @param {function(*, !Protocol.Runtime.ExceptionDetails=):void} callback
|
|
*/
|
|
TestRunner.evaluateInPage = async function(code, callback) {
|
|
const response = await TestRunner._evaluateInPage(code);
|
|
TestRunner.safeWrap(callback)(response.result.value, response.exceptionDetails);
|
|
};
|
|
|
|
/** @type {number} */
|
|
TestRunner._evaluateInPageCounter = 0;
|
|
|
|
/**
|
|
* @param {string} code
|
|
* @return {!Promise<{response: !SDK.RemoteObject,
|
|
* exceptionDetails: (!Protocol.Runtime.ExceptionDetails|undefined)}>}
|
|
*/
|
|
TestRunner._evaluateInPage = async function(code) {
|
|
const lines = new Error().stack.split('at ');
|
|
|
|
// Handles cases where the function is safe wrapped
|
|
const testScriptURL = /** @type {string} */ (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/${TestRunner._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) {
|
|
TestRunner.addResult('Error: ' + error);
|
|
TestRunner.completeTest();
|
|
return;
|
|
}
|
|
return response;
|
|
};
|
|
|
|
/**
|
|
* Doesn't append sourceURL to snippets evaluated in inspected page
|
|
* to avoid churning test expectations
|
|
* @param {string} code
|
|
* @return {!Promise<*>}
|
|
*/
|
|
TestRunner.evaluateInPageAnonymously = async function(code) {
|
|
const response = await TestRunner.RuntimeAgent.invoke_evaluate({expression: code, objectGroup: 'console'});
|
|
if (!response[Protocol.Error])
|
|
return response.result.value;
|
|
TestRunner.addResult(
|
|
'Error: ' +
|
|
(response.exceptionDetails && response.exceptionDetails.text || 'exception from evaluateInPageAnonymously.'));
|
|
TestRunner.completeTest();
|
|
};
|
|
|
|
/**
|
|
* @param {string} code
|
|
* @return {!Promise<*>}
|
|
*/
|
|
TestRunner.evaluateInPagePromise = function(code) {
|
|
return new Promise(success => TestRunner.evaluateInPage(code, success));
|
|
};
|
|
|
|
/**
|
|
* @param {string} code
|
|
* @return {!Promise<*>}
|
|
*/
|
|
TestRunner.evaluateInPageAsync = async function(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;
|
|
TestRunner.addResult(
|
|
'Error: ' +
|
|
(error || response.exceptionDetails && response.exceptionDetails.text || 'exception while evaluation in page.'));
|
|
TestRunner.completeTest();
|
|
};
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise<*>}
|
|
*/
|
|
TestRunner.callFunctionInPageAsync = function(name, args) {
|
|
args = args || [];
|
|
return TestRunner.evaluateInPageAsync(name + '(' + args.map(a => JSON.stringify(a)).join(',') + ')');
|
|
};
|
|
|
|
/**
|
|
* @param {string} code
|
|
*/
|
|
TestRunner.evaluateInPageWithTimeout = function(code) {
|
|
// FIXME: we need a better way of waiting for chromium events to happen
|
|
TestRunner.evaluateInPageAnonymously('setTimeout(unescape(\'' + escape(code) + '\'), 1)');
|
|
};
|
|
|
|
/**
|
|
* @param {function():*} func
|
|
* @param {function(*):void} callback
|
|
*/
|
|
TestRunner.evaluateFunctionInOverlay = function(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
|
|
*/
|
|
TestRunner.check = function(passCondition, failureText) {
|
|
if (!passCondition)
|
|
TestRunner.addResult('FAIL: ' + failureText);
|
|
};
|
|
|
|
/**
|
|
* @param {!Function} callback
|
|
*/
|
|
TestRunner.deprecatedRunAfterPendingDispatches = function(callback) {
|
|
const targets = SDK.targetManager.targets();
|
|
const promises = targets.map(target => new Promise(resolve => target._deprecatedRunAfterPendingDispatches(resolve)));
|
|
Promise.all(promises).then(TestRunner.safeWrap(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<*>}
|
|
*/
|
|
TestRunner.loadHTML = function(html) {
|
|
if (!html.includes('<base')) {
|
|
// <!DOCTYPE...> tag needs to be first
|
|
const doctypeRegex = /(<!DOCTYPE.*?>)/i;
|
|
const baseTag = `<base href="${TestRunner.url()}">`;
|
|
if (html.match(doctypeRegex))
|
|
html = html.replace(doctypeRegex, '$1' + baseTag);
|
|
else
|
|
html = baseTag + html;
|
|
}
|
|
html = html.replace(/'/g, '\\\'').replace(/\n/g, '\\n');
|
|
return TestRunner.evaluateInPageAnonymously(`document.write(\`${html}\`);document.close();`);
|
|
};
|
|
|
|
/**
|
|
* @param {string} path
|
|
* @return {!Promise<*>}
|
|
*/
|
|
TestRunner.addScriptTag = function(path) {
|
|
return TestRunner.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<*>}
|
|
*/
|
|
TestRunner.addStylesheetTag = function(path) {
|
|
return TestRunner.evaluateInPageAsync(`
|
|
(function(){
|
|
let link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.type = 'text/css';
|
|
link.href = '${path}';
|
|
link.onload = onload;
|
|
document.head.append(link);
|
|
let resolve;
|
|
let 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<*>}
|
|
*/
|
|
TestRunner.addHTMLImport = function(path) {
|
|
return TestRunner.evaluateInPageAsync(`
|
|
(function(){
|
|
let link = document.createElement('link');
|
|
link.rel = 'import';
|
|
link.href = '${path}';
|
|
let promise = new Promise(r => link.onload = r);
|
|
document.body.append(link);
|
|
return promise;
|
|
})();
|
|
`);
|
|
};
|
|
|
|
/**
|
|
* @param {string} path
|
|
* @param {!Object|undefined} options
|
|
* @return {!Promise<*>}
|
|
*/
|
|
TestRunner.addIframe = function(path, options = {}) {
|
|
options.id = options.id || '';
|
|
options.name = options.name || '';
|
|
return TestRunner.evaluateInPageAsync(`
|
|
(function(){
|
|
let 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} */
|
|
TestRunner._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
|
|
*/
|
|
TestRunner.deprecatedInitAsync = async function(code) {
|
|
TestRunner._pendingInits++;
|
|
await TestRunner.RuntimeAgent.invoke_evaluate({expression: code, objectGroup: 'console'});
|
|
TestRunner._pendingInits--;
|
|
if (!TestRunner._pendingInits)
|
|
TestRunner._resolveOnFinishInits();
|
|
};
|
|
|
|
/**
|
|
* @param {string} title
|
|
*/
|
|
TestRunner.markStep = function(title) {
|
|
TestRunner.addResult('\nRunning: ' + title);
|
|
};
|
|
|
|
TestRunner.startDumpingProtocolMessages = function() {
|
|
// TODO(chenwilliam): stop abusing Closure interface which is why
|
|
// we need to opt out of type checking here
|
|
const untypedConnection = /** @type {*} */ (Protocol.InspectorBackend.Connection);
|
|
untypedConnection.prototype._dumpProtocolMessage = self.testRunner.logToStderr.bind(self.testRunner);
|
|
Protocol.InspectorBackend.Options.dumpInspectorProtocolMessages = 1;
|
|
};
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @param {string} content
|
|
* @param {!SDK.ResourceTreeFrame} frame
|
|
*/
|
|
TestRunner.addScriptForFrame = function(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);
|
|
};
|
|
|
|
TestRunner.formatters = {};
|
|
|
|
/**
|
|
* @param {*} value
|
|
* @return {string}
|
|
*/
|
|
TestRunner.formatters.formatAsTypeName = function(value) {
|
|
return '<' + typeof value + '>';
|
|
};
|
|
|
|
/**
|
|
* @param {*} value
|
|
* @return {string}
|
|
*/
|
|
TestRunner.formatters.formatAsTypeNameOrNull = function(value) {
|
|
if (value === null)
|
|
return 'null';
|
|
return TestRunner.formatters.formatAsTypeName(value);
|
|
};
|
|
|
|
/**
|
|
* @param {*} value
|
|
* @return {string|!Date}
|
|
*/
|
|
TestRunner.formatters.formatAsRecentTime = function(value) {
|
|
if (typeof value !== 'object' || !(value instanceof Date))
|
|
return TestRunner.formatters.formatAsTypeName(value);
|
|
const delta = Date.now() - value;
|
|
return 0 <= delta && delta < 30 * 60 * 1000 ? '<plausible>' : value;
|
|
};
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @return {string}
|
|
*/
|
|
TestRunner.formatters.formatAsURL = function(value) {
|
|
if (!value)
|
|
return value;
|
|
const lastIndex = value.lastIndexOf('devtools/');
|
|
if (lastIndex < 0)
|
|
return value;
|
|
return '.../' + value.substr(lastIndex);
|
|
};
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @return {string}
|
|
*/
|
|
TestRunner.formatters.formatAsDescription = function(value) {
|
|
if (!value)
|
|
return value;
|
|
return '"' + value.replace(/^function [gs]et /, 'function ') + '"';
|
|
};
|
|
|
|
/**
|
|
* @typedef {!Object<string, string>}
|
|
*/
|
|
TestRunner.CustomFormatters;
|
|
|
|
/**
|
|
* @param {!Object} object
|
|
* @param {!TestRunner.CustomFormatters=} customFormatters
|
|
* @param {string=} prefix
|
|
* @param {string=} firstLinePrefix
|
|
*/
|
|
TestRunner.addObject = function(object, customFormatters, prefix, firstLinePrefix) {
|
|
prefix = prefix || '';
|
|
firstLinePrefix = firstLinePrefix || prefix;
|
|
TestRunner.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 = TestRunner.formatters[formatterName];
|
|
TestRunner.addResult(prefixWithName + formatter(propValue));
|
|
}
|
|
} else {
|
|
TestRunner.dump(propValue, customFormatters, ' ' + prefix, prefixWithName);
|
|
}
|
|
}
|
|
TestRunner.addResult(prefix + '}');
|
|
};
|
|
|
|
/**
|
|
* @param {!Array} array
|
|
* @param {!TestRunner.CustomFormatters=} customFormatters
|
|
* @param {string=} prefix
|
|
* @param {string=} firstLinePrefix
|
|
*/
|
|
TestRunner.addArray = function(array, customFormatters, prefix, firstLinePrefix) {
|
|
prefix = prefix || '';
|
|
firstLinePrefix = firstLinePrefix || prefix;
|
|
TestRunner.addResult(firstLinePrefix + '[');
|
|
for (let i = 0; i < array.length; ++i)
|
|
TestRunner.dump(array[i], customFormatters, prefix + ' ');
|
|
TestRunner.addResult(prefix + ']');
|
|
};
|
|
|
|
/**
|
|
* @param {!Node} node
|
|
*/
|
|
TestRunner.dumpDeepInnerHTML = function(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')
|
|
TestRunner.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('>');
|
|
TestRunner.addResult(prefix + openTag.join(' '));
|
|
for (let child = node.firstChild; child; child = child.nextSibling)
|
|
innerHTML(prefix + ' ', child);
|
|
if (node.shadowRoot)
|
|
innerHTML(prefix + ' ', node.shadowRoot);
|
|
TestRunner.addResult(prefix + '</' + node.nodeName + '>');
|
|
}
|
|
innerHTML('', node);
|
|
};
|
|
|
|
/**
|
|
* @param {!Node} node
|
|
* @return {string}
|
|
*/
|
|
TestRunner.deepTextContent = function(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 += TestRunner.deepTextContent(children[i]);
|
|
if (node.shadowRoot)
|
|
res += TestRunner.deepTextContent(node.shadowRoot);
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* @param {*} value
|
|
* @param {!TestRunner.CustomFormatters=} customFormatters
|
|
* @param {string=} prefix
|
|
* @param {string=} prefixWithName
|
|
*/
|
|
TestRunner.dump = function(value, customFormatters, prefix, prefixWithName) {
|
|
prefixWithName = prefixWithName || prefix;
|
|
if (prefixWithName && prefixWithName.length > 80) {
|
|
TestRunner.addResult(prefixWithName + 'was skipped due to prefix length limit');
|
|
return;
|
|
}
|
|
if (value === null)
|
|
TestRunner.addResult(prefixWithName + 'null');
|
|
else if (value && value.constructor && value.constructor.name === 'Array')
|
|
TestRunner.addArray(/** @type {!Array} */ (value), customFormatters, prefix, prefixWithName);
|
|
else if (typeof value === 'object')
|
|
TestRunner.addObject(/** @type {!Object} */ (value), customFormatters, prefix, prefixWithName);
|
|
else if (typeof value === 'string')
|
|
TestRunner.addResult(prefixWithName + '"' + value + '"');
|
|
else
|
|
TestRunner.addResult(prefixWithName + value);
|
|
};
|
|
|
|
/**
|
|
* @param {!UI.TreeElement} treeElement
|
|
*/
|
|
TestRunner.dumpObjectPropertyTreeElement = function(treeElement) {
|
|
const expandedSubstring = treeElement.expanded ? '[expanded]' : '[collapsed]';
|
|
TestRunner.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;
|
|
TestRunner.addResult(' ' + key + ': ' + value);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {symbol} event
|
|
* @param {!Common.Object} obj
|
|
* @param {function(?):boolean=} condition
|
|
* @return {!Promise}
|
|
*/
|
|
TestRunner.waitForEvent = function(event, obj, condition) {
|
|
condition = condition || function() {
|
|
return true;
|
|
};
|
|
return new Promise(resolve => {
|
|
obj.addEventListener(event, onEventFired);
|
|
|
|
/**
|
|
* @param {!Common.Event} event
|
|
*/
|
|
function onEventFired(event) {
|
|
if (!condition(event.data))
|
|
return;
|
|
obj.removeEventListener(event, onEventFired);
|
|
resolve(event.data);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {function(!SDK.Target):boolean} filter
|
|
* @return {!Promise<!SDK.Target>}
|
|
*/
|
|
TestRunner.waitForTarget = function(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.RuntimeModel} runtimeModel
|
|
* @return {!Promise}
|
|
*/
|
|
TestRunner.waitForExecutionContext = function(runtimeModel) {
|
|
if (runtimeModel.executionContexts().length)
|
|
return Promise.resolve(runtimeModel.executionContexts()[0]);
|
|
return runtimeModel.once(SDK.RuntimeModel.Events.ExecutionContextCreated);
|
|
};
|
|
|
|
/**
|
|
* @param {!SDK.ExecutionContext} context
|
|
* @return {!Promise}
|
|
*/
|
|
TestRunner.waitForExecutionContextDestroyed = function(context) {
|
|
const runtimeModel = context.runtimeModel;
|
|
if (runtimeModel.executionContexts().indexOf(context) === -1)
|
|
return Promise.resolve();
|
|
return TestRunner.waitForEvent(
|
|
SDK.RuntimeModel.Events.ExecutionContextDestroyed, runtimeModel,
|
|
destroyedContext => destroyedContext === context);
|
|
};
|
|
|
|
/**
|
|
* @param {number} a
|
|
* @param {number} b
|
|
* @param {string=} message
|
|
*/
|
|
TestRunner.assertGreaterOrEqual = function(a, b, message) {
|
|
if (a < b)
|
|
TestRunner.addResult('FAILED: ' + (message ? message + ': ' : '') + a + ' < ' + b);
|
|
};
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner.navigate = function(url, callback) {
|
|
TestRunner._pageLoadedCallback = TestRunner.safeWrap(callback);
|
|
TestRunner.resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, TestRunner._pageNavigated);
|
|
// Note: injected <base> means that url is relative to test
|
|
// and not the inspected page
|
|
TestRunner.evaluateInPageAnonymously('window.location.replace(\'' + url + '\')');
|
|
};
|
|
|
|
/**
|
|
* @return {!Promise}
|
|
*/
|
|
TestRunner.navigatePromise = function(url) {
|
|
return new Promise(fulfill => TestRunner.navigate(url, fulfill));
|
|
};
|
|
|
|
TestRunner._pageNavigated = function() {
|
|
TestRunner.resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, TestRunner._pageNavigated);
|
|
TestRunner._handlePageLoaded();
|
|
};
|
|
|
|
/**
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner.hardReloadPage = function(callback) {
|
|
TestRunner._innerReloadPage(true, undefined, callback);
|
|
};
|
|
|
|
/**
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner.reloadPage = function(callback) {
|
|
TestRunner._innerReloadPage(false, undefined, callback);
|
|
};
|
|
|
|
/**
|
|
* @param {(string|undefined)} injectedScript
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner.reloadPageWithInjectedScript = function(injectedScript, callback) {
|
|
TestRunner._innerReloadPage(false, injectedScript, callback);
|
|
};
|
|
|
|
/**
|
|
* @return {!Promise}
|
|
*/
|
|
TestRunner.reloadPagePromise = function() {
|
|
return new Promise(fulfill => TestRunner.reloadPage(fulfill));
|
|
};
|
|
|
|
/**
|
|
* @param {boolean} hardReload
|
|
* @param {(string|undefined)} injectedScript
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner._innerReloadPage = function(hardReload, injectedScript, callback) {
|
|
TestRunner._pageLoadedCallback = TestRunner.safeWrap(callback);
|
|
TestRunner.resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, TestRunner.pageLoaded);
|
|
TestRunner.resourceTreeModel.reloadPage(hardReload, injectedScript);
|
|
};
|
|
|
|
TestRunner.pageLoaded = function() {
|
|
TestRunner.resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, TestRunner.pageLoaded);
|
|
TestRunner.addResult('Page reloaded.');
|
|
TestRunner._handlePageLoaded();
|
|
};
|
|
|
|
TestRunner._handlePageLoaded = async function() {
|
|
await TestRunner.waitForExecutionContext(/** @type {!SDK.RuntimeModel} */ (TestRunner.runtimeModel));
|
|
if (TestRunner._pageLoadedCallback) {
|
|
const callback = TestRunner._pageLoadedCallback;
|
|
delete TestRunner._pageLoadedCallback;
|
|
callback();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner.waitForPageLoad = function(callback) {
|
|
TestRunner.resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, onLoaded);
|
|
|
|
function onLoaded() {
|
|
TestRunner.resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, onLoaded);
|
|
callback();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {function():void} callback
|
|
*/
|
|
TestRunner.runWhenPageLoads = function(callback) {
|
|
const oldCallback = TestRunner._pageLoadedCallback;
|
|
function chainedCallback() {
|
|
if (oldCallback)
|
|
oldCallback();
|
|
callback();
|
|
}
|
|
TestRunner._pageLoadedCallback = TestRunner.safeWrap(chainedCallback);
|
|
};
|
|
|
|
/**
|
|
* @param {!Array<function(function():void)>} testSuite
|
|
*/
|
|
TestRunner.runTestSuite = function(testSuite) {
|
|
const testSuiteTests = testSuite.slice();
|
|
|
|
function runner() {
|
|
if (!testSuiteTests.length) {
|
|
TestRunner.completeTest();
|
|
return;
|
|
}
|
|
const nextTest = testSuiteTests.shift();
|
|
TestRunner.addResult('');
|
|
TestRunner.addResult(
|
|
'Running: ' +
|
|
/function\s([^(]*)/.exec(nextTest)[1]);
|
|
TestRunner.safeWrap(nextTest)(runner);
|
|
}
|
|
runner();
|
|
};
|
|
|
|
/**
|
|
* @param {*} expected
|
|
* @param {*} found
|
|
* @param {string} message
|
|
*/
|
|
TestRunner.assertEquals = function(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
|
|
*/
|
|
TestRunner.assertTrue = function(found, message) {
|
|
TestRunner.assertEquals(true, !!found, message);
|
|
};
|
|
|
|
/**
|
|
* @param {!Object} receiver
|
|
* @param {string} methodName
|
|
* @param {!Function} override
|
|
* @param {boolean=} opt_sticky
|
|
* @return {!Function}
|
|
*/
|
|
TestRunner.override = function(receiver, methodName, override, opt_sticky) {
|
|
override = TestRunner.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}
|
|
*/
|
|
TestRunner.clearSpecificInfoFromStackFrames = function(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, '');
|
|
};
|
|
|
|
TestRunner.hideInspectorView = function() {
|
|
UI.inspectorView.element.setAttribute('style', 'display:none !important');
|
|
};
|
|
|
|
/**
|
|
* @return {?SDK.ResourceTreeFrame}
|
|
*/
|
|
TestRunner.mainFrame = function() {
|
|
return TestRunner.resourceTreeModel.mainFrame;
|
|
};
|
|
|
|
|
|
TestRunner.StringOutputStream = class {
|
|
/**
|
|
* @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
|
|
*/
|
|
TestRunner.MockSetting = class {
|
|
/**
|
|
* @param {V} value
|
|
*/
|
|
constructor(value) {
|
|
this._value = value;
|
|
}
|
|
|
|
/**
|
|
* @return {V}
|
|
*/
|
|
get() {
|
|
return this._value;
|
|
}
|
|
|
|
/**
|
|
* @param {V} value
|
|
*/
|
|
set(value) {
|
|
this._value = value;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @return {!Array<!Runtime.Module>}
|
|
*/
|
|
TestRunner.loadedModules = function() {
|
|
return self.runtime._modules.filter(module => module._loadedForTest)
|
|
.filter(module => module.name().indexOf('test_runner') === -1);
|
|
};
|
|
|
|
/**
|
|
* @param {!Array<!Runtime.Module>} relativeTo
|
|
* @return {!Array<!Runtime.Module>}
|
|
*/
|
|
TestRunner.dumpLoadedModules = function(relativeTo) {
|
|
const previous = new Set(relativeTo || []);
|
|
function moduleSorter(left, right) {
|
|
return String.naturalOrderComparator(left._descriptor.name, right._descriptor.name);
|
|
}
|
|
|
|
TestRunner.addResult('Loaded modules:');
|
|
const loadedModules = TestRunner.loadedModules().sort(moduleSorter);
|
|
for (const module of loadedModules) {
|
|
if (previous.has(module))
|
|
continue;
|
|
TestRunner.addResult(' ' + module._descriptor.name);
|
|
}
|
|
return loadedModules;
|
|
};
|
|
|
|
/**
|
|
* @param {!SDK.Target} target
|
|
* @return {boolean}
|
|
*/
|
|
TestRunner.isDedicatedWorker = function(target) {
|
|
return target && !target.hasBrowserCapability() && target.hasJSCapability() && target.hasLogCapability();
|
|
};
|
|
|
|
/**
|
|
* @param {!SDK.Target} target
|
|
* @return {boolean}
|
|
*/
|
|
TestRunner.isServiceWorker = function(target) {
|
|
return target && !target.hasBrowserCapability() && !target.hasJSCapability() && target.hasNetworkCapability() &&
|
|
target.hasTargetCapability();
|
|
};
|
|
|
|
/**
|
|
* @param {!SDK.Target} target
|
|
* @return {string}
|
|
*/
|
|
TestRunner.describeTargetType = function(target) {
|
|
if (TestRunner.isDedicatedWorker(target))
|
|
return 'worker';
|
|
if (TestRunner.isServiceWorker(target))
|
|
return 'service-worker';
|
|
if (!target.parentTarget())
|
|
return 'page';
|
|
return 'frame';
|
|
};
|
|
|
|
/**
|
|
* @param {string} urlSuffix
|
|
* @param {!Workspace.projectTypes=} projectType
|
|
* @return {!Promise}
|
|
*/
|
|
TestRunner.waitForUISourceCode = function(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 TestRunner.waitForEvent(Workspace.Workspace.Events.UISourceCodeAdded, Workspace.workspace, matches);
|
|
};
|
|
|
|
/**
|
|
* @param {!Function} callback
|
|
*/
|
|
TestRunner.waitForUISourceCodeRemoved = function(callback) {
|
|
Workspace.workspace.once(Workspace.Workspace.Events.UISourceCodeRemoved).then(callback);
|
|
};
|
|
|
|
/**
|
|
* @param {string=} url
|
|
* @return {string}
|
|
*/
|
|
TestRunner.url = function(url = '') {
|
|
const testScriptURL = /** @type {string} */ (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
|
|
*/
|
|
TestRunner.dumpSyntaxHighlight = function(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('*');
|
|
}
|
|
|
|
TestRunner.addResult(str + ': ' + node_parts.join(', '));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {string} messageType
|
|
*/
|
|
TestRunner._consoleOutputHook = function(messageType) {
|
|
TestRunner.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).
|
|
*/
|
|
TestRunner._printDevToolsConsole = function() {
|
|
if (TestRunner._isDebugTest())
|
|
return;
|
|
console.log = TestRunner._consoleOutputHook.bind(TestRunner, 'log');
|
|
console.error = TestRunner._consoleOutputHook.bind(TestRunner, 'error');
|
|
console.info = TestRunner._consoleOutputHook.bind(TestRunner, 'info');
|
|
};
|
|
|
|
/**
|
|
* @param {string} querySelector
|
|
*/
|
|
TestRunner.dumpInspectedPageElementText = async function(querySelector) {
|
|
const value = await TestRunner.evaluateInPageAsync(`document.querySelector('${querySelector}').innerText`);
|
|
TestRunner.addResult(value);
|
|
};
|
|
|
|
/** @type {boolean} */
|
|
TestRunner._startedTest = false;
|
|
|
|
/**
|
|
* @implements {SDK.TargetManager.Observer}
|
|
*/
|
|
TestRunner._TestObserver = class {
|
|
/**
|
|
* @param {!SDK.Target} target
|
|
* @override
|
|
*/
|
|
targetAdded(target) {
|
|
if (TestRunner._startedTest)
|
|
return;
|
|
TestRunner._startedTest = true;
|
|
TestRunner._setupTestHelpers(target);
|
|
if (TestRunner._isStartupTest())
|
|
return;
|
|
TestRunner
|
|
.loadHTML(`
|
|
<head>
|
|
<base href="${TestRunner.url()}">
|
|
</head>
|
|
<body>
|
|
</body>
|
|
`).then(() => TestRunner._executeTestScript());
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.Target} target
|
|
* @override
|
|
*/
|
|
targetRemoved(target) {
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
TestRunner._isDebugTest = function() {
|
|
return !self.testRunner || !!Runtime.queryParam('debugFrontend');
|
|
};
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
TestRunner._isStartupTest = function() {
|
|
return Runtime.queryParam('test').includes('/startup/');
|
|
};
|
|
|
|
(async function() {
|
|
/**
|
|
* @param {string|!Event} message
|
|
* @param {string} source
|
|
* @param {number} lineno
|
|
* @param {number} colno
|
|
* @param {!Error} error
|
|
*/
|
|
function completeTestOnError(message, source, lineno, colno, error) {
|
|
TestRunner.addResult('TEST ENDED IN ERROR: ' + error.stack);
|
|
TestRunner.completeTest();
|
|
}
|
|
|
|
self['onerror'] = completeTestOnError;
|
|
TestRunner._printDevToolsConsole();
|
|
SDK.targetManager.observeTargets(new TestRunner._TestObserver());
|
|
if (!TestRunner._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 =
|
|
TestRunner.override(Main.Main._instanceForTest, '_initializeTarget', () => undefined)
|
|
.bind(Main.Main._instanceForTest);
|
|
await TestRunner.addSnifferPromise(Main.Main._instanceForTest, '_showAppUI');
|
|
TestRunner._executeTestScript();
|
|
})();
|