feat(inspector): collapse completed items (#5484)
This commit is contained in:
parent
dc51536bca
commit
3248c2449c
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,6 @@ body {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codicon {
|
|
||||||
color: var(--toolbar-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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({});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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"',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"]');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue