blob: 3f29cdf79378dcd2d04f0ec92e90e38e38a3c4e2 [file] [log] [blame]
// Copyright 2017 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}
*/
/** @typedef {function(!Element, !SDK.ConsoleMessage=):string} */
ConsoleTestRunner.Formatter;
/**
* @param {boolean=} printOriginatingCommand
* @param {boolean=} dumpClassNames
* @param {!ConsoleTestRunner.Formatter=} formatter
*/
ConsoleTestRunner.dumpConsoleMessages = function(printOriginatingCommand, dumpClassNames, formatter) {
TestRunner.addResults(
ConsoleTestRunner.dumpConsoleMessagesIntoArray(printOriginatingCommand, dumpClassNames, formatter));
};
/**
* @param {boolean=} printOriginatingCommand
* @param {boolean=} dumpClassNames
* @param {!ConsoleTestRunner.Formatter=} formatter
* @return {!Array<string>}
*/
ConsoleTestRunner.dumpConsoleMessagesIntoArray = function(printOriginatingCommand, dumpClassNames, formatter) {
formatter = formatter || ConsoleTestRunner.prepareConsoleMessageText;
const result = [];
const consoleView = Console.ConsoleView.instance();
const originalViewportStyle = consoleView._viewport.element.style;
const originalSize = {width: originalViewportStyle.width, height: originalViewportStyle.height};
ConsoleTestRunner.disableConsoleViewport();
if (consoleView._needsFullUpdate) {
consoleView._updateMessageList();
}
const viewMessages = consoleView._visibleViewMessages;
for (let i = 0; i < viewMessages.length; ++i) {
const uiMessage = viewMessages[i];
const message = uiMessage.consoleMessage();
const element = uiMessage.element();
let classNames;
if (dumpClassNames) {
classNames = [''];
for (let node = element.firstChild; node; node = node.traverseNextNode(element)) {
if (node.nodeType === Node.ELEMENT_NODE && node.className) {
let depth = 0;
let depthTest = node;
while (depthTest !== element) {
if (depthTest.nodeType === Node.ELEMENT_NODE && depthTest.className) {
depth++;
}
depthTest = depthTest.parentNodeOrShadowHost();
}
classNames.push(
' '.repeat(depth) +
node.className.replace('platform-linux', 'platform-*')
.replace('platform-mac', 'platform-*')
.replace('platform-windows', 'platform-*'));
}
}
}
if (ConsoleTestRunner.dumpConsoleTableMessage(uiMessage, false, result)) {
if (dumpClassNames) {
result.push(classNames.join('\n'));
}
} else {
let messageText = formatter(element, message);
messageText = messageText.replace(/VM\d+/g, 'VM');
result.push(messageText + (dumpClassNames ? ' ' + classNames.join('\n') : ''));
}
if (printOriginatingCommand && uiMessage.consoleMessage().originatingMessage()) {
result.push('Originating from: ' + uiMessage.consoleMessage().originatingMessage().messageText);
}
}
consoleView._viewport.element.style.width = originalSize.width;
consoleView._viewport.element.style.height = originalSize.height;
return result;
};
/**
* @param {!Element} messageElement
* @return {string}
*/
ConsoleTestRunner.prepareConsoleMessageText = function(messageElement) {
let messageText = messageElement.deepTextContent().replace(/\u200b/g, '');
// Replace scriptIds with generic scriptId string to avoid flakiness.
messageText = messageText.replace(/VM\d+/g, 'VM');
// Remove line and column of evaluate method.
messageText = messageText.replace(/(at eval \(eval at evaluate) \(:\d+:\d+\)/, '$1');
if (messageText.startsWith('Navigated to')) {
const fileName = messageText.split(' ').pop().split('/').pop();
messageText = 'Navigated to ' + fileName;
}
// The message might be extremely long in case of dumping stack overflow message.
messageText = messageText.substring(0, 1024);
return messageText;
};
/**
* @param {!Console.ConsoleViewMessage} viewMessage
* @param {boolean} forceInvalidate
* @param {!Array<string>} results
* @return {boolean}
*/
ConsoleTestRunner.dumpConsoleTableMessage = function(viewMessage, forceInvalidate, results) {
if (forceInvalidate) {
Console.ConsoleView.instance()._viewport.invalidate();
}
const table = viewMessage.element();
const headers = table.querySelectorAll('th > div:first-child');
if (!headers.length) {
return false;
}
let headerLine = '';
for (let i = 0; i < headers.length; i++) {
headerLine += headers[i].textContent + ' | ';
}
addResult('HEADER ' + headerLine);
const rows = table.querySelectorAll('.data-container tr');
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let rowLine = '';
const items = row.querySelectorAll('td > span');
for (let j = 0; j < items.length; j++) {
rowLine += items[j].textContent + ' | ';
}
if (rowLine.trim()) {
addResult('ROW ' + rowLine);
}
}
/**
* @param {string} x
*/
function addResult(x) {
if (results) {
results.push(x);
} else {
TestRunner.addResult(x);
}
}
return true;
};
ConsoleTestRunner.disableConsoleViewport = function() {
ConsoleTestRunner.fixConsoleViewportDimensions(600, 2000);
};
/**
* @param {number} width
* @param {number} height
*/
ConsoleTestRunner.fixConsoleViewportDimensions = function(width, height) {
const viewport = Console.ConsoleView.instance()._viewport;
viewport.element.style.width = width + 'px';
viewport.element.style.height = height + 'px';
viewport.element.style.position = 'absolute';
viewport.invalidate();
};
ConsoleTestRunner.selectMainExecutionContext = function() {
const executionContexts = TestRunner.runtimeModel.executionContexts();
for (const context of executionContexts) {
if (context.isDefault) {
UI.context.setFlavor(SDK.ExecutionContext, context);
return;
}
}
};
/**
* @param {string} code
* @param {!Function=} callback
* @param {boolean=} dontForceMainContext
*/
ConsoleTestRunner.evaluateInConsole = function(code, callback, dontForceMainContext) {
if (!dontForceMainContext) {
ConsoleTestRunner.selectMainExecutionContext();
}
callback = TestRunner.safeWrap(callback);
const consoleView = Console.ConsoleView.instance();
consoleView._prompt._appendCommand(code, true);
ConsoleTestRunner.addConsoleViewSniffer(function(commandResult) {
callback(commandResult.toMessageElement().deepTextContent());
});
};
/**
* @param {string} code
* @param {boolean=} dontForceMainContext
* @return {!Promise}
*/
ConsoleTestRunner.evaluateInConsolePromise = function(code, dontForceMainContext) {
return new Promise(fulfill => ConsoleTestRunner.evaluateInConsole(code, fulfill, dontForceMainContext));
};
/**
* @param {!Function} override
* @param {boolean=} opt_sticky
*/
ConsoleTestRunner.addConsoleViewSniffer = function(override, opt_sticky) {
TestRunner.addSniffer(Console.ConsoleView.prototype, '_consoleMessageAddedForTest', override, opt_sticky);
};
ConsoleTestRunner.waitForPendingViewportUpdates = async function() {
const refreshPromise = Console.ConsoleView.instance()._scheduledRefreshPromiseForTest || Promise.resolve();
await refreshPromise;
};
/**
* @param {string} code
* @param {!Function=} callback
* @param {boolean=} dontForceMainContext
*/
ConsoleTestRunner.evaluateInConsoleAndDump = function(code, callback, dontForceMainContext) {
callback = TestRunner.safeWrap(callback);
/**
* @param {string} text
*/
function mycallback(text) {
text = text.replace(/\bVM\d+/g, 'VM');
TestRunner.addResult(code + ' = ' + text);
callback(text);
}
ConsoleTestRunner.evaluateInConsole(code, mycallback, dontForceMainContext);
};
/**
* @param {string} code
* @param {boolean=} dontForceMainContext
* @return {!Promise}
*/
ConsoleTestRunner.evaluateInConsoleAndDumpPromise = function(code, dontForceMainContext) {
return new Promise(fulfill => ConsoleTestRunner.evaluateInConsoleAndDump(code, fulfill, dontForceMainContext));
};
/**
* @return {number}
*/
ConsoleTestRunner.consoleMessagesCount = function() {
const consoleView = Console.ConsoleView.instance();
return consoleView._consoleMessages.length;
};
/**
* @param {function(!Element):string|undefined} messageFormatter
* @param {!Element} node
* @return {string}
*/
ConsoleTestRunner.formatterIgnoreStackFrameUrls = function(messageFormatter, node) {
/**
* @param {string} string
*/
function isNotEmptyLine(string) {
return string.trim().length > 0;
}
/**
* @param {string} string
*/
function ignoreStackFrameAndMutableData(string) {
let buffer = string.replace(/\u200b/g, '');
buffer = buffer.replace(/VM\d+/g, 'VM');
return buffer.replace(/^\s+at [^\]]+(]?)$/, '$1');
}
messageFormatter = messageFormatter || TestRunner.textContentWithLineBreaks;
const buffer = messageFormatter(node);
return buffer.split('\n').map(ignoreStackFrameAndMutableData).filter(isNotEmptyLine).join('\n');
};
/**
* @param {!Element} element
* @param {!SDK.ConsoleMessage} message
* @return {string}
*/
ConsoleTestRunner.simpleFormatter = function(element, message) {
return message.messageText + ':' + message.line + ':' + message.column;
};
/**
* @param {boolean=} printOriginatingCommand
* @param {boolean=} dumpClassNames
* @param {!ConsoleTestRunner.Formatter=} messageFormatter
*/
ConsoleTestRunner.dumpConsoleMessagesIgnoreErrorStackFrames = function(
printOriginatingCommand, dumpClassNames, messageFormatter) {
TestRunner.addResults(ConsoleTestRunner.dumpConsoleMessagesIntoArray(
printOriginatingCommand, dumpClassNames,
ConsoleTestRunner.formatterIgnoreStackFrameUrls.bind(this, messageFormatter)));
};
ConsoleTestRunner.dumpConsoleMessagesWithStyles = function() {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
const messageText = ConsoleTestRunner.prepareConsoleMessageText(element);
TestRunner.addResult(messageText);
const spans = element.querySelectorAll('.console-message-text *');
for (let j = 0; j < spans.length; ++j) {
TestRunner.addResult('Styled text #' + j + ': ' + (spans[j].style.cssText || 'NO STYLES DEFINED'));
}
}
};
/**
* @param {boolean=} sortMessages
*/
ConsoleTestRunner.dumpConsoleMessagesWithClasses = function(sortMessages) {
const result = [];
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
const contentElement = messageViews[i].contentElement();
const messageText = ConsoleTestRunner.prepareConsoleMessageText(element);
result.push(messageText + ' ' + element.getAttribute('class') + ' > ' + contentElement.getAttribute('class'));
}
if (sortMessages) {
result.sort();
}
TestRunner.addResults(result);
};
ConsoleTestRunner.dumpConsoleClassesBrief = function() {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messageViews.length; ++i) {
const repeatText = messageViews[i].repeatCount() > 1 ? (' x' + messageViews[i].repeatCount()) : '';
TestRunner.addResult(messageViews[i].toMessageElement().className + repeatText);
}
};
ConsoleTestRunner.dumpConsoleCounters = async function() {
const counter = ConsoleCounters.WarningErrorCounter._instanceForTest;
if (counter._updatingForTest) {
await TestRunner.addSnifferPromise(counter, '_updatedForTest');
}
if (counter._titles) {
TestRunner.addResult(counter._titles);
}
ConsoleTestRunner.dumpConsoleClassesBrief();
};
/**
* @param {!Function} callback
* @param {function(!Element):boolean} deepFilter
* @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter
*/
ConsoleTestRunner.expandConsoleMessages = function(callback, deepFilter, sectionFilter) {
Console.ConsoleView.instance()._invalidateViewport();
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
// Initiate round-trips to fetch necessary data for further rendering.
for (let i = 0; i < messageViews.length; ++i) {
messageViews[i].element();
}
TestRunner.deprecatedRunAfterPendingDispatches(expandTreeElements);
function expandTreeElements() {
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
for (let node = element; node; node = node.traverseNextNode(element)) {
if (node.treeElementForTest) {
node.treeElementForTest.expand();
}
if (node._expandStackTraceForTest) {
node._expandStackTraceForTest();
}
if (!node._section) {
continue;
}
if (sectionFilter && !sectionFilter(node._section)) {
continue;
}
node._section.expand();
if (!deepFilter) {
continue;
}
const treeElements = node._section.rootElement().children();
for (let j = 0; j < treeElements.length; ++j) {
for (let treeElement = treeElements[j]; treeElement;
treeElement = treeElement.traverseNextTreeElement(true, null, true)) {
if (deepFilter(treeElement)) {
treeElement.expand();
}
}
}
}
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
}
};
/**
* @param {function(!Element):boolean} deepFilter
* @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter
* @return {!Promise}
*/
ConsoleTestRunner.expandConsoleMessagesPromise = function(deepFilter, sectionFilter) {
return new Promise(fulfill => ConsoleTestRunner.expandConsoleMessages(fulfill, deepFilter, sectionFilter));
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.expandGettersInConsoleMessages = function(callback) {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
const properties = [];
let propertiesCount = 0;
TestRunner.addSniffer(ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', propertyExpandableUpdated);
for (let i = 0; i < messageViews.length; ++i) {
const element = messageViews[i].element();
for (let node = element; node; node = node.traverseNextNode(element)) {
if (node.classList && node.classList.contains('object-value-calculate-value-button')) {
++propertiesCount;
node.click();
properties.push(node.parentElement.parentElement);
}
}
}
function propertyExpandableUpdated() {
--propertiesCount;
if (propertiesCount === 0) {
for (let i = 0; i < properties.length; ++i) {
properties[i].click();
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
} else {
TestRunner.addSniffer(
ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', propertyExpandableUpdated);
}
}
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.expandConsoleMessagesErrorParameters = function(callback) {
const messageViews = Console.ConsoleView.instance()._visibleViewMessages;
// Initiate round-trips to fetch necessary data for further rendering.
for (let i = 0; i < messageViews.length; ++i) {
messageViews[i].element();
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.waitForRemoteObjectsConsoleMessages = function(callback) {
const messages = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < messages.length; ++i) {
messages[i].toMessageElement();
}
TestRunner.deprecatedRunAfterPendingDispatches(callback);
};
/**
* @return {!Promise}
*/
ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise = function() {
return new Promise(resolve => ConsoleTestRunner.waitForRemoteObjectsConsoleMessages(resolve));
};
/**
* @return {!Promise}
*/
ConsoleTestRunner.waitUntilConsoleEditorLoaded = function() {
let fulfill;
const promise = new Promise(x => (fulfill = x));
const prompt = Console.ConsoleView.instance()._prompt;
if (prompt._editor) {
fulfill(prompt._editor);
} else {
TestRunner.addSniffer(Console.ConsolePrompt.prototype, '_editorSetForTest', _ => fulfill(prompt._editor));
}
return promise;
};
/**
* @param {!Function} callback
*/
ConsoleTestRunner.waitUntilMessageReceived = function(callback) {
TestRunner.addSniffer(SDK.consoleModel, 'addMessage', callback, false);
};
/**
* @return {!Promise}
*/
ConsoleTestRunner.waitUntilMessageReceivedPromise = function() {
return new Promise(fulfill => ConsoleTestRunner.waitUntilMessageReceived(fulfill));
};
/**
* @param {number} count
* @param {!Function} callback
*/
ConsoleTestRunner.waitUntilNthMessageReceived = function(count, callback) {
function override() {
if (--count === 0) {
TestRunner.safeWrap(callback)();
} else {
TestRunner.addSniffer(SDK.consoleModel, 'addMessage', override, false);
}
}
TestRunner.addSniffer(SDK.consoleModel, 'addMessage', override, false);
};
/**
* @param {number} count
* @return {!Promise}
*/
ConsoleTestRunner.waitUntilNthMessageReceivedPromise = function(count) {
return new Promise(fulfill => ConsoleTestRunner.waitUntilNthMessageReceived(count, fulfill));
};
/**
* @param {string} namePrefix
*/
ConsoleTestRunner.changeExecutionContext = function(namePrefix) {
const selector = Console.ConsoleView.instance()._consoleContextSelector;
for (const executionContext of selector._items) {
if (selector.titleFor(executionContext).startsWith(namePrefix)) {
UI.context.setFlavor(SDK.ExecutionContext, executionContext);
return;
}
}
TestRunner.addResult('FAILED: context with prefix: ' + namePrefix + ' not found in the context list');
};
/**
* @param {number} expectedCount
* @param {!Function} callback
*/
ConsoleTestRunner.waitForConsoleMessages = function(expectedCount, callback) {
const consoleView = Console.ConsoleView.instance();
checkAndReturn();
function checkAndReturn() {
if (consoleView._visibleViewMessages.length === expectedCount) {
TestRunner.addResult('Message count: ' + expectedCount);
callback();
} else {
TestRunner.addSniffer(consoleView, '_messageAppendedForTests', checkAndReturn);
}
}
};
/**
* @param {number} expectedCount
* @return {!Promise}
*/
ConsoleTestRunner.waitForConsoleMessagesPromise = async function(expectedCount) {
await new Promise(fulfill => ConsoleTestRunner.waitForConsoleMessages(expectedCount, fulfill));
return ConsoleTestRunner.waitForPendingViewportUpdates();
};
/**
* @param {number} fromMessage
* @param {number} fromTextOffset
* @param {number} toMessage
* @param {number} toTextOffset
* @suppressGlobalPropertiesCheck
*/
ConsoleTestRunner.selectConsoleMessages = function(fromMessage, fromTextOffset, toMessage, toTextOffset) {
const consoleView = Console.ConsoleView.instance();
const from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).element(), fromTextOffset);
const to = selectionContainerAndOffset(consoleView.itemElement(toMessage).element(), toTextOffset);
window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset);
/**
* @param {!Node} container
* @param {number} offset
* @return {?{container: !Node, offset: number}}
*/
function selectionContainerAndOffset(container, offset) {
/** @type {?Node} */
let node = container;
if (offset === 0 && container.nodeType !== Node.TEXT_NODE) {
container = /** @type {!Node} */ (container.traverseNextTextNode());
node = container;
}
let charCount = 0;
while ((node = node.traverseNextTextNode(container))) {
const length = node.textContent.length;
if (charCount + length >= offset) {
return {container: node, offset: offset - charCount};
}
charCount += length;
}
return null;
}
};
/**
* @param {!Function} override
* @param {boolean=} opt_sticky
*/
ConsoleTestRunner.addConsoleSniffer = function(override, opt_sticky) {
TestRunner.addSniffer(SDK.ConsoleModel.prototype, 'addMessage', override, opt_sticky);
};
/**
* @param {!Function} func
* @return {!Function}
*/
ConsoleTestRunner.wrapListener = function(func) {
/**
* @this {*}
*/
async function wrapper() {
await Promise.resolve();
func.apply(this, arguments);
}
return wrapper;
};
ConsoleTestRunner.dumpStackTraces = function() {
const viewMessages = Console.ConsoleView.instance()._visibleViewMessages;
for (let i = 0; i < viewMessages.length; ++i) {
const m = viewMessages[i].consoleMessage();
TestRunner.addResult(
'Message[' + i + ']: ' + Bindings.displayNameForURL(m.url || '') + ':' + m.line + ' ' + m.messageText);
const trace = m.stackTrace ? m.stackTrace.callFrames : null;
if (!trace) {
TestRunner.addResult('FAIL: no stack trace attached to message #' + i);
} else {
TestRunner.addResult('Stack Trace:\n');
TestRunner.addResult(' url: ' + trace[0].url);
TestRunner.addResult(' function: ' + trace[0].functionName);
TestRunner.addResult(' line: ' + trace[0].lineNumber);
}
}
};
/**
* @return {!{first: number, last: number, count: number}}
*/
ConsoleTestRunner.visibleIndices = function() {
const consoleView = Console.ConsoleView.instance();
const viewport = consoleView._viewport;
const viewportRect = viewport.element.getBoundingClientRect();
let first = -1;
let last = -1;
let count = 0;
for (let i = 0; i < consoleView._visibleViewMessages.length; i++) {
// Created message elements may have a bounding rect, but not be connected to DOM.
const item = consoleView._visibleViewMessages[i];
if (!item._element || !item._element.isConnected) {
continue;
}
const itemRect = item._element.getBoundingClientRect();
const isVisible = (itemRect.bottom > viewportRect.top + 1) && (itemRect.top <= viewportRect.bottom - 1);
if (isVisible) {
first = first === -1 ? i : first;
last = i;
count++;
}
}
return {first, last, count};
};