feat(ui): collapse repeating console lines

This commit is contained in:
Simon Knott 2025-02-19 13:41:12 +01:00
parent 7f7ab96893
commit f6da75ffa1
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
3 changed files with 78 additions and 3 deletions

View file

@ -83,3 +83,18 @@
.console-line .codicon.status-warning::after {
background-color: var(--vscode-list-warningForeground);
}
.console-repeat {
display: inline-block;
padding: 0 2px;
font-size: 12px;
font-weight: 500;
line-height: 18px;
border: 1px solid transparent;
border-radius: 0.5em;
background-color: #8c959f;
color: white;
margin-right: 10px;
flex: none;
font-weight: 600;
}

View file

@ -27,6 +27,7 @@ import { PlaceholderPanel } from './placeholderPanel';
export type ConsoleEntry = {
browserMessage?: {
body: JSX.Element[];
bodyString: string;
location: string;
},
browserError?: channels.SerializedError;
@ -36,6 +37,7 @@ export type ConsoleEntry = {
isError: boolean;
isWarning: boolean;
timestamp: number;
repeat: number;
};
type ConsoleTabModel = {
@ -50,6 +52,23 @@ export function useConsoleTabModel(model: modelUtil.MultiTraceModel | undefined,
if (!model)
return { entries: [] };
const entries: ConsoleEntry[] = [];
function addEntry(entry: Omit<ConsoleEntry, 'repeat'>) {
const lastEntry = entries[entries.length - 1];
const isSameAsLast =
lastEntry
&& entry.browserMessage?.bodyString === lastEntry.browserMessage?.bodyString
&& entry.browserMessage?.location === lastEntry.browserMessage?.location
&& entry.browserError === lastEntry.browserError
&& entry.nodeMessage?.html === lastEntry.nodeMessage?.html
&& entry.isError === lastEntry.isError
&& entry.isWarning === lastEntry.isWarning
&& entry.timestamp - lastEntry.timestamp < 1000;
if (isSameAsLast) {
lastEntry.repeat++;
} else {
entries.push({ ...entry, repeat: 1 });
}
}
for (const event of model.events) {
if (event.type === 'console') {
const body = event.args && event.args.length ? format(event.args) : formatAnsi(event.text);
@ -57,9 +76,10 @@ export function useConsoleTabModel(model: modelUtil.MultiTraceModel | undefined,
const filename = url ? url.substring(url.lastIndexOf('/') + 1) : '<anonymous>';
const location = `${filename}:${event.location.lineNumber}`;
entries.push({
addEntry({
browserMessage: {
body,
bodyString: event.text,
location,
},
isError: event.messageType === 'error',
@ -68,7 +88,7 @@ export function useConsoleTabModel(model: modelUtil.MultiTraceModel | undefined,
});
}
if (event.type === 'event' && event.method === 'pageError') {
entries.push({
addEntry({
browserError: event.params.error,
isError: true,
isWarning: false,
@ -83,7 +103,7 @@ export function useConsoleTabModel(model: modelUtil.MultiTraceModel | undefined,
if (event.base64)
html = ansi2html(atob(event.base64).trim()) || '';
entries.push({
addEntry({
nodeMessage: { html },
isError: event.type === 'stderr',
isWarning: false,
@ -154,6 +174,7 @@ export const ConsoleTab: React.FunctionComponent<{
{timestampElement}
{statusElement}
{locationText && <span className='console-location'>{locationText}</span>}
{entry.repeat > 1 && <span className='console-repeat'>{entry.repeat}</span>}
{messageBody && <span className='console-line-message'>{messageBody}</span>}
{messageInnerHTML && <span className='console-line-message' dangerouslySetInnerHTML={{ __html: messageInnerHTML }}></span>}
{messageStack && <div className='console-stack'>{messageStack}</div>}

View file

@ -114,6 +114,45 @@ test('should show console messages for test', async ({ runUITest }, testInfo) =>
await expect.soft(page.getByText('GREEN', { exact: true })).toHaveCSS('color', 'rgb(0, 188, 0)');
});
test('should collapse repeated console messages for test', async ({ runUITest }) => {
const { page } = await runUITest({
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('print', async ({ page }) => {
await page.evaluate(() => {
for (let i = 0; i < 10; ++i)
console.log('page message')
});
for (let i = 0; i < 10; ++i)
console.log('node message')
await page.evaluate(async () => {
await new Promise(resolve => {
for (let i = 0; i < 10; ++i)
console.log('page message')
setTimeout(() => {
for (let i = 0; i < 10; ++i)
console.log('page message')
resolve()
}, 1500)
})
});
});
`,
});
await page.getByTitle('Run all').click();
await page.getByRole('tab', { name: 'Console' }).click();
await page.getByText('print').click();
await expect(page.getByRole('tabpanel', { name: 'Console' })).toMatchAriaSnapshot(`
- tabpanel "Console":
- list:
- listitem: /10 page message/
- listitem: /10 node message/
- listitem: /10 page message/
- listitem: /10 page message/
`);
});
test('should format console messages in page', async ({ runUITest }, testInfo) => {
const { page } = await runUITest({
'a.spec.ts': `