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