feat(inspector): collapse completed items (#5484)

This commit is contained in:
Pavel Feldman 2021-02-17 17:28:02 -08:00 committed by GitHub
parent dc51536bca
commit 3248c2449c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 55 additions and 67 deletions

View file

@ -35,6 +35,7 @@ export type CallLog = {
messages: string[]; messages: string[];
status: 'in-progress' | 'done' | 'error' | 'paused'; status: 'in-progress' | 'done' | 'error' | 'paused';
error?: string; error?: string;
reveal?: boolean;
}; };
export type SourceHighlight = { export type SourceHighlight = {

View file

@ -81,10 +81,6 @@ body {
display: none !important; display: none !important;
} }
.codicon {
color: var(--toolbar-color);
}
svg { svg {
fill: currentColor; fill: currentColor;
} }

View file

@ -25,9 +25,9 @@ export default {
} as Meta; } as Meta;
const Template: Story<ToolbarProps> = () => <Toolbar> const Template: Story<ToolbarProps> = () => <Toolbar>
<ToolbarButton icon="clone" title="Copy" onClick={() => {}}></ToolbarButton> <ToolbarButton icon='record' title='Record' onClick={() => {}}>Record</ToolbarButton>
<ToolbarButton icon="trashcan" title="Erase" onClick={() => {}}></ToolbarButton> <ToolbarButton icon='question' title='Inspect' onClick={() => {}}>Explore</ToolbarButton>
<ToolbarButton icon="close" title="Close" onClick={() => {}}></ToolbarButton> <ToolbarButton icon='files' title='Copy' onClick={() => {}}></ToolbarButton>
</Toolbar>; </Toolbar>;
export const Primary = Template.bind({}); export const Primary = Template.bind({});

View file

@ -22,6 +22,8 @@
padding: 0; padding: 0;
margin-left: 10px; margin-left: 10px;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
} }
.toolbar-button:disabled { .toolbar-button:disabled {
@ -29,24 +31,10 @@
cursor: default; cursor: default;
} }
.toolbar-button:not(.disabled):hover { .toolbar-button:not(.disabled):not(.toggled):hover {
color: #555; color: #555;
} }
.toolbar-button.toggled { .toolbar-button .codicon {
color: #1ea7fd; margin-right: 4px;
}
.toolbar-button.codicon-record.toggled {
color: #fd1e1e;
}
.toolbar-button.codicon-debug-continue,
.toolbar-button.codicon-debug-step-over {
color: #01bb01;
}
.toolbar-button.codicon-debug-continue:hover,
.toolbar-button.codicon-debug-step-over:hover {
color: #41ca1e;
} }

View file

@ -27,14 +27,15 @@ export interface ToolbarButtonProps {
} }
export const ToolbarButton: React.FC<ToolbarButtonProps> = ({ export const ToolbarButton: React.FC<ToolbarButtonProps> = ({
children,
title = '', title = '',
icon = '', icon = '',
disabled = false, disabled = false,
toggled = false, toggled = false,
onClick = () => {}, onClick = () => {},
}) => { }) => {
let className = `toolbar-button codicon codicon-${icon}`; let className = `toolbar-button ${icon}`;
if (toggled) if (toggled)
className += ' toggled'; className += ' toggled';
return <button className={className} onClick={onClick} title={title} disabled={!!disabled}></button>; return <button className={className} onClick={onClick} title={title} disabled={!!disabled}><span className={`codicon codicon-${icon}`}></span>{ children }</button>;
}; };

View file

@ -50,7 +50,7 @@ export function exampleCallLog(): CallLog[] {
'status': 'paused' 'status': 'paused'
}, },
{ {
'id': 5, 'id': 6,
'messages': [ 'messages': [
'navigating to "https://github.com/microsoft", waiting until "load"', 'navigating to "https://github.com/microsoft", waiting until "load"',
], ],

View file

@ -23,27 +23,37 @@ export interface CallLogProps {
} }
export const CallLogView: React.FC<CallLogProps> = ({ export const CallLogView: React.FC<CallLogProps> = ({
log log,
}) => { }) => {
const messagesEndRef = React.createRef<HTMLDivElement>(); const messagesEndRef = React.createRef<HTMLDivElement>();
const [expandOverrides, setExpandOverrides] = React.useState<Map<number, boolean>>(new Map());
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' }); if (log.find(callLog => callLog.reveal))
messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' });
}, [messagesEndRef]); }, [messagesEndRef]);
return <div className='vbox'> return <div className='vbox'>
<div className='call-log-header' style={{flex: 'none'}}>Log</div> <div className='call-log-header' style={{flex: 'none'}}>Log</div>
<div className='call-log' style={{flex: 'auto'}}> <div className='call-log' style={{flex: 'auto'}}>
{log.map(callLog => { {log.map(callLog => {
const expandOverride = expandOverrides.get(callLog.id);
const isExpanded = typeof expandOverride === 'boolean' ? expandOverride : callLog.status !== 'done';
return <div className={`call-log-call ${callLog.status}`} key={callLog.id}> return <div className={`call-log-call ${callLog.status}`} key={callLog.id}>
<div className='call-log-call-header'> <div className='call-log-call-header'>
<span className={'codicon ' + iconClass(callLog)}></span>{ callLog.title } <span className={`codicon codicon-chevron-${isExpanded ? 'down' : 'right'}`} style={{ cursor: 'pointer' }}onClick={() => {
const newOverrides = new Map(expandOverrides);
newOverrides.set(callLog.id, !isExpanded);
setExpandOverrides(newOverrides);
}}></span>
{ callLog.title }
<span className={'codicon ' + iconClass(callLog)}></span>
</div> </div>
{ callLog.messages.map((message, i) => { { (isExpanded ? callLog.messages : []).map((message, i) => {
return <div className='call-log-message' key={i}> return <div className='call-log-message' key={i}>
{ message.trim() } { message.trim() }
</div>; </div>;
})} })}
{ callLog.error ? <div className='call-log-message error'> { callLog.error ? <div className='call-log-message error' hidden={!isExpanded}>
{ callLog.error } { callLog.error }
</div> : undefined } </div> : undefined }
</div> </div>

View file

@ -42,8 +42,10 @@ export const Main: React.FC = ({
window.playwrightSetPaused = setPaused; window.playwrightSetPaused = setPaused;
window.playwrightUpdateLogs = callLogs => { window.playwrightUpdateLogs = callLogs => {
const newLog = new Map<number, CallLog>(log); const newLog = new Map<number, CallLog>(log);
for (const callLog of callLogs) for (const callLog of callLogs) {
callLog.reveal = !log.has(callLog.id);
newLog.set(callLog.id, callLog); newLog.set(callLog.id, callLog);
}
setLog(newLog); setLog(newLog);
}; };

View file

@ -37,3 +37,21 @@
color: var(--toolbar-color); color: var(--toolbar-color);
margin-left: 16px; margin-left: 16px;
} }
.recorder .toolbar-button.toggled.question {
color: #12a3ff;
}
.recorder .toolbar-button.toggled.record {
color: #fd1e1e;
}
.recorder .toolbar-button:not([disabled]) .codicon-debug-continue,
.recorder .toolbar-button:not([disabled]) .codicon-debug-step-over {
color: #01bb01;
}
.recorder .toolbar-button:not([disabled]):hover .codicon-debug-continue,
.recorder .toolbar-button:not([disabled]):hover .codicon-debug-step-over {
color: #41ca1e;
}

View file

@ -57,15 +57,14 @@ export const Recorder: React.FC<RecorderProps> = ({
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' }); messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' });
}, [messagesEndRef]); }, [messagesEndRef]);
return <div className='recorder'> return <div className='recorder'>
<Toolbar> <Toolbar>
<ToolbarButton icon='record' title='Record' toggled={mode == 'recording'} onClick={() => { <ToolbarButton icon='record' title='Record' toggled={mode == 'recording'} onClick={() => {
window.dispatch({ event: 'setMode', params: { mode: mode === 'recording' ? 'none' : 'recording' }}).catch(() => { }); window.dispatch({ event: 'setMode', params: { mode: mode === 'recording' ? 'none' : 'recording' }}).catch(() => { });
}}></ToolbarButton> }}>Record</ToolbarButton>
<ToolbarButton icon='question' title='Inspect' toggled={mode == 'inspecting'} onClick={() => { <ToolbarButton icon='question' title='Explore' toggled={mode == 'inspecting'} onClick={() => {
window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' }}).catch(() => { }); window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' }}).catch(() => { });
}}></ToolbarButton> }}>Explore</ToolbarButton>
<ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => { <ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => {
copy(source.text); copy(source.text);
}}></ToolbarButton> }}></ToolbarButton>
@ -78,12 +77,12 @@ export const Recorder: React.FC<RecorderProps> = ({
<ToolbarButton icon='debug-step-over' title='Step over' disabled={!paused} onClick={() => { <ToolbarButton icon='debug-step-over' title='Step over' disabled={!paused} onClick={() => {
window.dispatch({ event: 'step' }).catch(() => {}); window.dispatch({ event: 'step' }).catch(() => {});
}}></ToolbarButton> }}></ToolbarButton>
<select className='recorder-chooser' hidden={!sources.length} onChange={event => { <select className='recorder-chooser' hidden={!sources.length} value={file} onChange={event => {
setFile(event.target.selectedOptions[0].value); setFile(event.target.selectedOptions[0].value);
}}>{ }}>{
sources.map(s => { sources.map(s => {
const title = s.file.replace(/.*[/\\]([^/\\]+)/, '$1'); const title = s.file.replace(/.*[/\\]([^/\\]+)/, '$1');
return <option key={s.file} value={s.file} selected={s.file === file}>{title}</option>; return <option key={s.file} value={s.file}>{title}</option>;
}) })
} }
</select> </select>

View file

@ -148,19 +148,6 @@ describe('pause', (suite, { mode }) => {
expect(await sanitizeLog(recorderPage)).toEqual([ expect(await sanitizeLog(recorderPage)).toEqual([
'pause', 'pause',
'click', 'click',
'waiting for selector "button"',
'selector resolved to visible <button>Submit</button>',
'attempting click action',
'waiting for element to be visible, enabled and stable',
'element is visible, enabled and stable',
'scrolling into view if needed',
'done scrolling',
'checking that element receives pointer events at ()',
'element does receive pointer events',
'performing click action',
'click action done',
'waiting for scheduled navigations to finish',
'navigations have finished',
'pause', 'pause',
]); ]);
await recorderPage.click('[title="Resume"]'); await recorderPage.click('[title="Resume"]');
@ -183,21 +170,7 @@ describe('pause', (suite, { mode }) => {
expect(await sanitizeLog(recorderPage)).toEqual([ expect(await sanitizeLog(recorderPage)).toEqual([
'pause', 'pause',
'waitForEvent()', 'waitForEvent()',
'waiting for event \"console\"',
'click', 'click',
'waiting for selector "button"',
'selector resolved to visible <button onclick=\"console.log()\">Submit</button>',
'attempting click action',
'waiting for element to be visible, enabled and stable',
'element is visible, enabled and stable',
'scrolling into view if needed',
'done scrolling',
'checking that element receives pointer events at ()',
'element does receive pointer events',
'performing click action',
'click action done',
'waiting for scheduled navigations to finish',
'navigations have finished',
'pause', 'pause',
]); ]);
await recorderPage.click('[title="Resume"]'); await recorderPage.click('[title="Resume"]');