diff --git a/packages/trace-viewer/src/ui/consoleTab.css b/packages/trace-viewer/src/ui/consoleTab.css index 50a881cb97..3c31395001 100644 --- a/packages/trace-viewer/src/ui/consoleTab.css +++ b/packages/trace-viewer/src/ui/consoleTab.css @@ -52,12 +52,34 @@ .console-time { float: left; - min-width: 30px; + min-width: 50px; color: var(--vscode-editorCodeLens-foreground); user-select: none; } .console-stack { white-space: pre-wrap; - margin: 3px; + margin-left: 50px; +} + +.console-line .codicon.status-none::after, +.console-line .codicon.status-error::after, +.console-line .codicon.status-warning::after { + display: inline-block; + content: 'a'; + color: transparent; + border-radius: 4px; + width: 8px; + height: 8px; + position: relative; + top: 8px; + left: -7px; +} + +.console-line .codicon.status-error::after { + background-color: var(--vscode-errorForeground); +} + +.console-line .codicon.status-warning::after { + background-color: var(--vscode-list-warningForeground); } diff --git a/packages/trace-viewer/src/ui/consoleTab.tsx b/packages/trace-viewer/src/ui/consoleTab.tsx index c76a9d0783..94a3d7ac9d 100644 --- a/packages/trace-viewer/src/ui/consoleTab.tsx +++ b/packages/trace-viewer/src/ui/consoleTab.tsx @@ -25,13 +25,14 @@ import { msToString } from '@web/uiUtils'; import type * as trace from '@trace/trace'; type ConsoleEntry = { - message?: trace.ConsoleMessageTraceEvent['initializer']; - error?: channels.SerializedError; + browserMessage?: trace.ConsoleMessageTraceEvent['initializer'], + browserError?: channels.SerializedError; nodeMessage?: { text?: string; base64?: string; - isError: boolean; }, + isError: boolean; + isWarning: boolean; timestamp: number; }; @@ -52,13 +53,17 @@ export const ConsoleTab: React.FunctionComponent<{ if (event.method === 'console') { const { guid } = event.params.message; entries.push({ - message: modelUtil.context(event).initializers[guid], + browserMessage: modelUtil.context(event).initializers[guid], + isError: modelUtil.context(event).initializers[guid]?.type === 'error', + isWarning: modelUtil.context(event).initializers[guid]?.type === 'warning', timestamp: event.time, }); } if (event.method === 'pageError') { entries.push({ - error: event.params.error, + browserError: event.params.error, + isError: true, + isWarning: false, timestamp: event.time, }); } @@ -68,8 +73,9 @@ export const ConsoleTab: React.FunctionComponent<{ nodeMessage: { text: event.text, base64: event.base64, - isError: event.type === 'stderr', }, + isError: event.type === 'stderr', + isWarning: false, timestamp: event.timestamp, }); } @@ -86,69 +92,56 @@ export const ConsoleTab: React.FunctionComponent<{ return
!!entry.error || entry.message?.type === 'error' || entry.nodeMessage?.isError || false} - isWarning={entry => entry.message?.type === 'warning'} + isError={entry => entry.isError} + isWarning={entry => entry.isWarning} render={entry => { - const { message, error, nodeMessage } = entry; const timestamp = msToString(entry.timestamp - boundaries.minimum); - if (message) { - const text = message.args ? format(message.args) : message.text; - const url = message.location.url; + const timestampElement = {timestamp}; + const errorSuffix = entry.isError ? ' status-error' : entry.isWarning ? ' status-warning' : ' status-none'; + const statusElement = entry.browserMessage || entry.browserError ? : ; + let locationText: string | undefined; + let messageBody: JSX.Element[] | string | undefined; + let messageInnerHTML: string | undefined; + let messageStack: JSX.Element[] | string | undefined; + + const { browserMessage, browserError, nodeMessage } = entry; + if (browserMessage) { + const text = browserMessage.args ? format(browserMessage.args) : browserMessage.text; + const url = browserMessage.location.url; const filename = url ? url.substring(url.lastIndexOf('/') + 1) : ''; - return
- {timestamp} - {filename}:{message.location.lineNumber} - - {text} -
; + locationText = `${filename}:${browserMessage.location.lineNumber}`; + messageBody = text; } - if (error) { - const { error: errorObject, value } = error; + + if (browserError) { + const { error: errorObject, value } = browserError; if (errorObject) { - return
- {timestamp} - - {errorObject.message} -
{errorObject.stack}
-
; + messageBody = errorObject.message; + messageStack = errorObject.stack; + } else { + messageBody = String(value); } - return
- {timestamp} - - {String(value)} -
; } - if (nodeMessage?.text) { - return
- {timestamp} - - -
; - } - if (nodeMessage?.base64) { - return
- - -
; - } - return null; + + if (nodeMessage?.text) + messageInnerHTML = ansi2htmlMarkup(nodeMessage.text.trim()) || ''; + + if (nodeMessage?.base64) + messageInnerHTML = ansi2htmlMarkup(atob(nodeMessage.base64).trim()) || ''; + + return
+ {timestampElement} + {statusElement} + {locationText && {locationText}} + {messageBody && {messageBody}} + {messageInnerHTML && } + {messageStack &&
{messageStack}
} +
; }} />
; }; -function iconClass(message: trace.ConsoleMessageTraceEvent['initializer']): string { - switch (message.type) { - case 'error': return 'error'; - case 'warning': return 'warning'; - } - return 'blank'; -} - -function stdioClass(isError: boolean): string { - return isError ? 'error' : 'blank'; -} - function format(args: { preview: string, value: any }[]): JSX.Element[] { if (args.length === 1) return [{args[0].preview}]; diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index e65e7ce22d..98daeb1b0b 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -147,12 +147,12 @@ test('should render console', async ({ showTraceViewer, browserName }) => { await expect(traceViewer.consoleLineMessages.last()).toHaveText('Cheers!'); const icons = traceViewer.consoleLines.locator('.codicon'); - await expect(icons.nth(0)).toHaveClass('codicon codicon-blank'); - await expect(icons.nth(1)).toHaveClass('codicon codicon-warning'); - await expect(icons.nth(2)).toHaveClass('codicon codicon-error'); - await expect(icons.nth(3)).toHaveClass('codicon codicon-error'); + await expect.soft(icons.nth(0)).toHaveClass('codicon codicon-browser status-none'); + await expect.soft(icons.nth(1)).toHaveClass('codicon codicon-browser status-warning'); + await expect.soft(icons.nth(2)).toHaveClass('codicon codicon-browser status-error'); + await expect.soft(icons.nth(3)).toHaveClass('codicon codicon-browser status-error'); // Firefox can insert layout error here. - await expect(icons.last()).toHaveClass('codicon codicon-blank'); + await expect.soft(icons.last()).toHaveClass('codicon codicon-browser status-none'); await expect(traceViewer.consoleStacks.first()).toContainText('Error: Unhandled exception'); await traceViewer.selectAction('page.evaluate'); diff --git a/tests/playwright-test/ui-mode-test-output.spec.ts b/tests/playwright-test/ui-mode-test-output.spec.ts index 23f254707a..5590f90ae1 100644 --- a/tests/playwright-test/ui-mode-test-output.spec.ts +++ b/tests/playwright-test/ui-mode-test-output.spec.ts @@ -101,11 +101,11 @@ test('should show console messages for test', async ({ runUITest }, testInfo) => ]); await expect(page.locator('.console-tab .list-view-entry .codicon')).toHaveClass([ - 'codicon codicon-blank', - 'codicon codicon-blank', - 'codicon codicon-error', - 'codicon codicon-error', - 'codicon codicon-blank', + 'codicon codicon-browser status-none', + 'codicon codicon-file status-none', + 'codicon codicon-browser status-error', + 'codicon codicon-file status-error', + 'codicon codicon-file status-none', ]); await expect(page.getByText('RED', { exact: true })).toHaveCSS('color', 'rgb(204, 0, 0)');