feat(ui): collapse repeating console lines (#34857)
This commit is contained in:
parent
e43d287d8d
commit
bb8e914294
|
|
@ -83,3 +83,16 @@
|
||||||
.console-line .codicon.status-warning::after {
|
.console-line .codicon.status-warning::after {
|
||||||
background-color: var(--vscode-list-warningForeground);
|
background-color: var(--vscode-list-warningForeground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.console-repeat {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #8c959f;
|
||||||
|
color: white;
|
||||||
|
margin-right: 10px;
|
||||||
|
flex: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { PlaceholderPanel } from './placeholderPanel';
|
||||||
export type ConsoleEntry = {
|
export type ConsoleEntry = {
|
||||||
browserMessage?: {
|
browserMessage?: {
|
||||||
body: JSX.Element[];
|
body: JSX.Element[];
|
||||||
|
bodyString: string;
|
||||||
location: string;
|
location: string;
|
||||||
},
|
},
|
||||||
browserError?: channels.SerializedError;
|
browserError?: channels.SerializedError;
|
||||||
|
|
@ -36,6 +37,7 @@ export type ConsoleEntry = {
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
isWarning: boolean;
|
isWarning: boolean;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
repeat: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConsoleTabModel = {
|
type ConsoleTabModel = {
|
||||||
|
|
@ -50,16 +52,38 @@ export function useConsoleTabModel(model: modelUtil.MultiTraceModel | undefined,
|
||||||
if (!model)
|
if (!model)
|
||||||
return { entries: [] };
|
return { entries: [] };
|
||||||
const entries: ConsoleEntry[] = [];
|
const entries: ConsoleEntry[] = [];
|
||||||
for (const event of model.events) {
|
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 });
|
||||||
|
}
|
||||||
|
const logEvents = [...model.events, ...model.stdio].sort((a, b) => {
|
||||||
|
const aTimestamp = 'time' in a ? a.time : a.timestamp;
|
||||||
|
const bTimestamp = 'time' in b ? b.time : b.timestamp;
|
||||||
|
return aTimestamp - bTimestamp;
|
||||||
|
})
|
||||||
|
for (const event of logEvents) {
|
||||||
if (event.type === 'console') {
|
if (event.type === 'console') {
|
||||||
const body = event.args && event.args.length ? format(event.args) : formatAnsi(event.text);
|
const body = event.args && event.args.length ? format(event.args) : formatAnsi(event.text);
|
||||||
const url = event.location.url;
|
const url = event.location.url;
|
||||||
const filename = url ? url.substring(url.lastIndexOf('/') + 1) : '<anonymous>';
|
const filename = url ? url.substring(url.lastIndexOf('/') + 1) : '<anonymous>';
|
||||||
const location = `${filename}:${event.location.lineNumber}`;
|
const location = `${filename}:${event.location.lineNumber}`;
|
||||||
|
|
||||||
entries.push({
|
addEntry({
|
||||||
browserMessage: {
|
browserMessage: {
|
||||||
body,
|
body,
|
||||||
|
bodyString: event.text,
|
||||||
location,
|
location,
|
||||||
},
|
},
|
||||||
isError: event.messageType === 'error',
|
isError: event.messageType === 'error',
|
||||||
|
|
@ -68,29 +92,28 @@ export function useConsoleTabModel(model: modelUtil.MultiTraceModel | undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (event.type === 'event' && event.method === 'pageError') {
|
if (event.type === 'event' && event.method === 'pageError') {
|
||||||
entries.push({
|
addEntry({
|
||||||
browserError: event.params.error,
|
browserError: event.params.error,
|
||||||
isError: true,
|
isError: true,
|
||||||
isWarning: false,
|
isWarning: false,
|
||||||
timestamp: event.time,
|
timestamp: event.time,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
if (event.type === 'stderr' || event.type === 'stdout') {
|
||||||
for (const event of model.stdio) {
|
let html = '';
|
||||||
let html = '';
|
if (event.text)
|
||||||
if (event.text)
|
html = ansi2html(event.text.trim()) || '';
|
||||||
html = ansi2html(event.text.trim()) || '';
|
if (event.base64)
|
||||||
if (event.base64)
|
html = ansi2html(atob(event.base64).trim()) || '';
|
||||||
html = ansi2html(atob(event.base64).trim()) || '';
|
|
||||||
|
|
||||||
entries.push({
|
addEntry({
|
||||||
nodeMessage: { html },
|
nodeMessage: { html },
|
||||||
isError: event.type === 'stderr',
|
isError: event.type === 'stderr',
|
||||||
isWarning: false,
|
isWarning: false,
|
||||||
timestamp: event.timestamp,
|
timestamp: event.timestamp,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
entries.sort((a, b) => a.timestamp - b.timestamp);
|
|
||||||
return { entries };
|
return { entries };
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
|
|
@ -154,6 +177,7 @@ export const ConsoleTab: React.FunctionComponent<{
|
||||||
{timestampElement}
|
{timestampElement}
|
||||||
{statusElement}
|
{statusElement}
|
||||||
{locationText && <span className='console-location'>{locationText}</span>}
|
{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>}
|
{messageBody && <span className='console-line-message'>{messageBody}</span>}
|
||||||
{messageInnerHTML && <span className='console-line-message' dangerouslySetInnerHTML={{ __html: messageInnerHTML }}></span>}
|
{messageInnerHTML && <span className='console-line-message' dangerouslySetInnerHTML={{ __html: messageInnerHTML }}></span>}
|
||||||
{messageStack && <div className='console-stack'>{messageStack}</div>}
|
{messageStack && <div className='console-stack'>{messageStack}</div>}
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,47 @@ test('should show console messages for test', async ({ runUITest }, testInfo) =>
|
||||||
await expect.soft(page.getByText('GREEN', { exact: true })).toHaveCSS('color', 'rgb(0, 188, 0)');
|
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(() => {
|
||||||
|
console.log('page message')
|
||||||
|
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: /page message/
|
||||||
|
- 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) => {
|
test('should format console messages in page', async ({ runUITest }, testInfo) => {
|
||||||
const { page } = await runUITest({
|
const { page } = await runUITest({
|
||||||
'a.spec.ts': `
|
'a.spec.ts': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue