mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-02 10:34:27 +03:00
chore: rearrange settings (#21467)
This commit is contained in:
parent
c9eac69f2b
commit
9e7abb2a76
@ -48,7 +48,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||
itemKey={(action: ActionTraceEvent) => action.callId}
|
||||
itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined}
|
||||
itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, revealConsole)}
|
||||
showNoItemsMessage={true}
|
||||
noItemsMessage='No actions'
|
||||
></ListView>;
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ export const MetadataView: React.FunctionComponent<{
|
||||
return <></>;
|
||||
return <div className='vbox'>
|
||||
<div className='call-section' style={{ paddingTop: 2 }}>Time</div>
|
||||
{model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>}
|
||||
{!!model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>}
|
||||
<div className='call-line'>duration:<span className='call-value number' title={msToString(model.endTime - model.startTime)}>{msToString(model.endTime - model.startTime)}</span></div>
|
||||
<div className='call-section'>Browser</div>
|
||||
<div className='call-line'>engine:<span className='call-value string' title={model.browserName}>{model.browserName}</span></div>
|
||||
|
@ -22,7 +22,7 @@
|
||||
padding: 20px 0 5px;
|
||||
cursor: text;
|
||||
user-select: none;
|
||||
border-bottom: 1px solid var(--vscode-panel-border);
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.timeline-divider {
|
||||
|
@ -153,45 +153,47 @@ export const Timeline: React.FunctionComponent<{
|
||||
onSelected(entry);
|
||||
};
|
||||
|
||||
return <div ref={ref} className='timeline-view' onMouseMove={onMouseMove} onMouseOver={onMouseMove} onMouseLeave={onMouseLeave} onClick={onClick}>
|
||||
<div className='timeline-grid'>{
|
||||
offsets.map((offset, index) => {
|
||||
return <div key={index} className='timeline-divider' style={{ left: offset.position + 'px' }}>
|
||||
<div className='timeline-time'>{msToString(offset.time - boundaries.minimum)}</div>
|
||||
</div>;
|
||||
})
|
||||
}</div>
|
||||
<div className='timeline-lane timeline-labels'>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||
style={{
|
||||
left: bar.leftPosition,
|
||||
maxWidth: 100,
|
||||
}}
|
||||
>
|
||||
{bar.label}
|
||||
</div>;
|
||||
})
|
||||
}</div>
|
||||
<div className='timeline-lane timeline-bars' ref={barsRef}>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-bar ' + (bar.action ? 'action ' : '') + (bar.event ? 'event ' : '') + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||
style={{
|
||||
left: bar.leftPosition + 'px',
|
||||
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
||||
top: barTop(bar) + 'px',
|
||||
}}
|
||||
title={bar.title}
|
||||
></div>;
|
||||
})
|
||||
}</div>
|
||||
<FilmStrip model={model} boundaries={boundaries} previewPoint={previewPoint} />
|
||||
<div className='timeline-marker timeline-marker-hover' style={{
|
||||
display: (previewPoint !== undefined) ? 'block' : 'none',
|
||||
left: (previewPoint?.x || 0) + 'px',
|
||||
}}></div>
|
||||
return <div style={{ flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
|
||||
<div ref={ref} className='timeline-view' onMouseMove={onMouseMove} onMouseOver={onMouseMove} onMouseLeave={onMouseLeave} onClick={onClick}>
|
||||
<div className='timeline-grid'>{
|
||||
offsets.map((offset, index) => {
|
||||
return <div key={index} className='timeline-divider' style={{ left: offset.position + 'px' }}>
|
||||
<div className='timeline-time'>{msToString(offset.time - boundaries.minimum)}</div>
|
||||
</div>;
|
||||
})
|
||||
}</div>
|
||||
<div className='timeline-lane timeline-labels'>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||
style={{
|
||||
left: bar.leftPosition,
|
||||
maxWidth: 100,
|
||||
}}
|
||||
>
|
||||
{bar.label}
|
||||
</div>;
|
||||
})
|
||||
}</div>
|
||||
<div className='timeline-lane timeline-bars' ref={barsRef}>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-bar ' + (bar.action ? 'action ' : '') + (bar.event ? 'event ' : '') + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||
style={{
|
||||
left: bar.leftPosition + 'px',
|
||||
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
||||
top: barTop(bar) + 'px',
|
||||
}}
|
||||
title={bar.title}
|
||||
></div>;
|
||||
})
|
||||
}</div>
|
||||
<FilmStrip model={model} boundaries={boundaries} previewPoint={previewPoint} />
|
||||
<div className='timeline-marker timeline-marker-hover' style={{
|
||||
display: (previewPoint !== undefined) ? 'block' : 'none',
|
||||
left: (previewPoint?.x || 0) + 'px',
|
||||
}}></div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
}
|
||||
|
||||
.watch-mode-sidebar input {
|
||||
.watch-mode-sidebar input[type=search] {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
@ -44,16 +44,11 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.watch-mode-sidebar .toolbar h3.title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
.watch-mode-sidebar .section-title {
|
||||
font-size: 11px;
|
||||
min-width: 3ch;
|
||||
margin: 0 10px;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.watch-mode-sidebar .spacer {
|
||||
|
@ -33,23 +33,97 @@ import type * as trace from '@trace/trace';
|
||||
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
||||
let updateStepsProgress: () => void = () => {};
|
||||
let runWatchedTests = () => {};
|
||||
let runVisibleTests = () => {};
|
||||
|
||||
export const WatchModeView: React.FC<{}> = ({
|
||||
}) => {
|
||||
const [projectNames, setProjectNames] = React.useState<string[]>([]);
|
||||
const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined });
|
||||
const [isRunningTest, setIsRunningTest] = React.useState<boolean>(false);
|
||||
const [progress, setProgress] = React.useState<Progress>({ total: 0, passed: 0, failed: 0 });
|
||||
const [selectedTestItem, setSelectedTestItem] = React.useState<TestItem | undefined>(undefined);
|
||||
const [settingsVisible, setSettingsVisible] = React.useState<boolean>(false);
|
||||
|
||||
updateRootSuite = (rootSuite: Suite, { passed, failed }: Progress) => {
|
||||
setRootSuite({ value: rootSuite });
|
||||
progress.passed = passed;
|
||||
progress.failed = failed;
|
||||
setProgress({ ...progress });
|
||||
};
|
||||
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
||||
const [isRunningTest, setIsRunningTest] = React.useState<boolean>(false);
|
||||
const [filterText, setFilterText] = React.useState<string>('');
|
||||
const [projectNames, setProjectNames] = React.useState<string[]>([]);
|
||||
const [expandedItems, setExpandedItems] = React.useState<Map<string, boolean>>(new Map());
|
||||
|
||||
const runTests = (testIds: string[]) => {
|
||||
setProgress({ total: testIds.length, passed: 0, failed: 0 });
|
||||
setIsRunningTest(true);
|
||||
sendMessage('run', { testIds }).then(() => {
|
||||
setIsRunningTest(false);
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (projectNames.length === 0 && rootSuite.value?.suites.length)
|
||||
setProjectNames([rootSuite.value?.suites[0].title]);
|
||||
}, [projectNames, rootSuite]);
|
||||
|
||||
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||
<TraceView testItem={selectedTestItem}></TraceView>
|
||||
<div className='vbox watch-mode-sidebar'>
|
||||
<Toolbar>
|
||||
<div className='section-title' style={{ cursor: 'pointer' }} onClick={() => setSettingsVisible(false)}>Tests</div>
|
||||
<ToolbarButton icon='play' title='Run' onClick={runVisibleTests} disabled={isRunningTest}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
||||
<ToolbarButton icon='refresh' title='Reload' onClick={resetCollectingRootSuite} disabled={isRunningTest}></ToolbarButton>
|
||||
<div className='spacer'></div>
|
||||
<ToolbarButton icon='gear' title='Toggle color mode' toggled={settingsVisible} onClick={() => { setSettingsVisible(!settingsVisible); }}></ToolbarButton>
|
||||
</Toolbar>
|
||||
{ !settingsVisible && <TestList
|
||||
projectNames={projectNames}
|
||||
rootSuite={rootSuite}
|
||||
isRunningTest={isRunningTest}
|
||||
runTests={runTests}
|
||||
onTestItemSelected={setSelectedTestItem} />}
|
||||
{ settingsVisible && <div className='vbox'>
|
||||
<div className='hbox' style={{ flex: 'none' }}>
|
||||
<div className='section-title' style={{ marginTop: 10 }}>Projects</div>
|
||||
<div className='spacer'></div>
|
||||
<ToolbarButton icon='close' title='Close settings' toggled={false} onClick={() => setSettingsVisible(false)}></ToolbarButton>
|
||||
</div>
|
||||
{(rootSuite.value?.suites || []).map(suite => {
|
||||
return <div style={{ display: 'flex', alignItems: 'center', lineHeight: '24px' }}>
|
||||
<input id={`project-${suite.title}`} type='checkbox' checked={projectNames.includes(suite.title)} onClick={() => {
|
||||
const copy = [...projectNames];
|
||||
if (copy.includes(suite.title))
|
||||
copy.splice(copy.indexOf(suite.title), 1);
|
||||
else
|
||||
copy.push(suite.title);
|
||||
setProjectNames(copy);
|
||||
}} style={{ margin: '0 5px 0 10px' }} />
|
||||
<label htmlFor={`project-${suite.title}`}>
|
||||
{suite.title}
|
||||
</label>
|
||||
</div>;
|
||||
})}
|
||||
<div className='section-title'>Appearance</div>
|
||||
<div style={{ marginLeft: 3 }}>
|
||||
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}>Toggle color mode</ToolbarButton>
|
||||
</div>
|
||||
</div>}
|
||||
{isRunningTest && <div className='status-line'>
|
||||
Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed
|
||||
</div>}
|
||||
</div>
|
||||
</SplitView>;
|
||||
};
|
||||
|
||||
export const TestList: React.FC<{
|
||||
projectNames: string[],
|
||||
rootSuite: { value: Suite | undefined },
|
||||
runTests: (testIds: string[]) => void,
|
||||
isRunningTest: boolean,
|
||||
onTestItemSelected: (test: TestItem | undefined) => void,
|
||||
}> = ({ projectNames, rootSuite, runTests, isRunningTest, onTestItemSelected }) => {
|
||||
const [filterText, setFilterText] = React.useState<string>('');
|
||||
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
||||
const [expandedItems, setExpandedItems] = React.useState<Map<string, boolean>>(new Map());
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -57,11 +131,6 @@ export const WatchModeView: React.FC<{}> = ({
|
||||
resetCollectingRootSuite();
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (projectNames.length === 0 && rootSuite.value?.suites.length)
|
||||
setProjectNames([rootSuite.value?.suites[0].title]);
|
||||
}, [projectNames, rootSuite]);
|
||||
|
||||
const { filteredItems, treeItemMap, visibleTestIds } = React.useMemo(() => {
|
||||
const treeItems = createTree(rootSuite.value, projectNames);
|
||||
const filteredItems = filterTree(treeItems, filterText);
|
||||
@ -78,6 +147,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||
return { treeItemMap, visibleTestIds, filteredItems };
|
||||
}, [filterText, rootSuite, projectNames]);
|
||||
|
||||
runVisibleTests = () => runTests([...visibleTestIds]);
|
||||
|
||||
const { listItems } = React.useMemo(() => {
|
||||
const listItems = flattenTree(filteredItems, expandedItems, !!filterText.trim());
|
||||
@ -95,6 +165,8 @@ export const WatchModeView: React.FC<{}> = ({
|
||||
return { selectedTreeItem, selectedTestItem };
|
||||
}, [selectedTreeItemId, treeItemMap]);
|
||||
|
||||
onTestItemSelected(selectedTestItem);
|
||||
|
||||
const runTreeItem = (treeItem: TreeItem) => {
|
||||
expandedItems.set(treeItem.id, true);
|
||||
setSelectedTreeItemId(treeItem.id);
|
||||
@ -105,128 +177,79 @@ export const WatchModeView: React.FC<{}> = ({
|
||||
runTests(collectTestIds(selectedTreeItem));
|
||||
};
|
||||
|
||||
const runTests = (testIds: string[]) => {
|
||||
setProgress({ total: testIds.length, passed: 0, failed: 0 });
|
||||
setIsRunningTest(true);
|
||||
sendMessage('run', { testIds }).then(() => {
|
||||
setIsRunningTest(false);
|
||||
});
|
||||
};
|
||||
|
||||
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||
<TraceView testItem={selectedTestItem} isRunningTest={isRunningTest}></TraceView>
|
||||
<div className='vbox watch-mode-sidebar'>
|
||||
<Toolbar>
|
||||
<h3 className='title'>Test explorer</h3>
|
||||
<ToolbarButton icon='play' title='Run' onClick={() => runTests([...visibleTestIds])} disabled={isRunningTest}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
||||
<ToolbarButton icon='refresh' title='Reload' onClick={resetCollectingRootSuite} disabled={isRunningTest}></ToolbarButton>
|
||||
<div className='spacer'></div>
|
||||
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
|
||||
</Toolbar>
|
||||
<Toolbar>
|
||||
<input ref={inputRef} type='search' placeholder='Filter (e.g. text, @tag)' spellCheck={false} value={filterText}
|
||||
onChange={e => {
|
||||
setFilterText(e.target.value);
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter')
|
||||
runTests([...visibleTestIds]);
|
||||
}}></input>
|
||||
</Toolbar>
|
||||
<ListView
|
||||
items={listItems}
|
||||
itemKey={(treeItem: TreeItem) => treeItem.id }
|
||||
itemRender={(treeItem: TreeItem) => {
|
||||
return <div className='hbox watch-mode-list-item'>
|
||||
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
|
||||
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={isRunningTest}></ToolbarButton>
|
||||
</div>;
|
||||
return <div className='vbox'>
|
||||
<Toolbar>
|
||||
<input ref={inputRef} type='search' placeholder='Filter (e.g. text, @tag)' spellCheck={false} value={filterText}
|
||||
onChange={e => {
|
||||
setFilterText(e.target.value);
|
||||
}}
|
||||
itemIcon={(treeItem: TreeItem) => {
|
||||
if (treeItem.kind === 'case' && treeItem.children?.length === 1)
|
||||
treeItem = treeItem.children[0];
|
||||
if (treeItem.kind === 'test') {
|
||||
const ok = treeItem.test.outcome() === 'expected';
|
||||
const failed = treeItem.test.results.length && treeItem.test.outcome() !== 'expected';
|
||||
const running = treeItem.test.results.some(r => r.duration === -1);
|
||||
if (running)
|
||||
return 'codicon-loading';
|
||||
if (ok)
|
||||
return 'codicon-check';
|
||||
if (failed)
|
||||
return 'codicon-error';
|
||||
} else {
|
||||
return treeItem.expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
|
||||
}
|
||||
}}
|
||||
itemIndent={(treeItem: TreeItem) => treeItem.kind === 'file' ? 0 : treeItem.kind === 'case' ? 1 : 2}
|
||||
selectedItem={selectedTreeItem}
|
||||
onAccepted={runTreeItem}
|
||||
onLeftArrow={(treeItem: TreeItem) => {
|
||||
if (treeItem.children && treeItem.expanded) {
|
||||
expandedItems.set(treeItem.id, false);
|
||||
setExpandedItems(new Map(expandedItems));
|
||||
} else {
|
||||
setSelectedTreeItemId(treeItem.parent?.id);
|
||||
}
|
||||
}}
|
||||
onRightArrow={(treeItem: TreeItem) => {
|
||||
if (treeItem.children) {
|
||||
expandedItems.set(treeItem.id, true);
|
||||
setExpandedItems(new Map(expandedItems));
|
||||
}
|
||||
setRootSuite({ ...rootSuite });
|
||||
}}
|
||||
onSelected={(treeItem: TreeItem) => {
|
||||
setSelectedTreeItemId(treeItem.id);
|
||||
}}
|
||||
onIconClicked={(treeItem: TreeItem) => {
|
||||
if (treeItem.kind === 'test')
|
||||
return;
|
||||
if (treeItem.expanded)
|
||||
expandedItems.set(treeItem.id, false);
|
||||
else
|
||||
expandedItems.set(treeItem.id, true);
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter')
|
||||
runVisibleTests();
|
||||
}}></input>
|
||||
</Toolbar>
|
||||
<ListView
|
||||
items={listItems}
|
||||
itemKey={(treeItem: TreeItem) => treeItem.id }
|
||||
itemRender={(treeItem: TreeItem) => {
|
||||
return <div className='hbox watch-mode-list-item'>
|
||||
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
|
||||
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={isRunningTest}></ToolbarButton>
|
||||
</div>;
|
||||
}}
|
||||
itemIcon={(treeItem: TreeItem) => {
|
||||
if (treeItem.kind === 'case' && treeItem.children?.length === 1)
|
||||
treeItem = treeItem.children[0];
|
||||
if (treeItem.kind === 'test') {
|
||||
const ok = treeItem.test.outcome() === 'expected';
|
||||
const failed = treeItem.test.results.length && treeItem.test.outcome() !== 'expected';
|
||||
const running = treeItem.test.results.some(r => r.duration === -1);
|
||||
if (running)
|
||||
return 'codicon-loading';
|
||||
if (ok)
|
||||
return 'codicon-check';
|
||||
if (failed)
|
||||
return 'codicon-error';
|
||||
} else {
|
||||
return treeItem.expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
|
||||
}
|
||||
}}
|
||||
itemIndent={(treeItem: TreeItem) => treeItem.kind === 'file' ? 0 : treeItem.kind === 'case' ? 1 : 2}
|
||||
selectedItem={selectedTreeItem}
|
||||
onAccepted={runTreeItem}
|
||||
onLeftArrow={(treeItem: TreeItem) => {
|
||||
if (treeItem.children && treeItem.expanded) {
|
||||
expandedItems.set(treeItem.id, false);
|
||||
setExpandedItems(new Map(expandedItems));
|
||||
}}
|
||||
showNoItemsMessage={true}></ListView>
|
||||
{(rootSuite.value?.suites.length || 0) > 1 && <div style={{ flex: 'none', borderTop: '1px solid var(--vscode-panel-border)' }}>
|
||||
<Toolbar>
|
||||
<h3 className='title'>Projects</h3>
|
||||
</Toolbar>
|
||||
<ListView
|
||||
items={rootSuite.value!.suites}
|
||||
onSelected={(suite: Suite) => {
|
||||
const copy = [...projectNames];
|
||||
if (copy.includes(suite.title))
|
||||
copy.splice(copy.indexOf(suite.title), 1);
|
||||
else
|
||||
copy.push(suite.title);
|
||||
setProjectNames(copy);
|
||||
}}
|
||||
itemRender={(suite: Suite) => {
|
||||
return <label style={{ display: 'flex', pointerEvents: 'none' }}>
|
||||
<input type='checkbox' checked={projectNames.includes(suite.title)} />
|
||||
{suite.title}
|
||||
</label>;
|
||||
}}
|
||||
/>
|
||||
</div>}
|
||||
{isRunningTest && <div className='status-line'>
|
||||
Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed
|
||||
</div>}
|
||||
{!isRunningTest && <div className='status-line'>
|
||||
Total: {visibleTestIds.size} tests
|
||||
</div>}
|
||||
</div>
|
||||
</SplitView>;
|
||||
} else {
|
||||
setSelectedTreeItemId(treeItem.parent?.id);
|
||||
}
|
||||
}}
|
||||
onRightArrow={(treeItem: TreeItem) => {
|
||||
if (treeItem.children) {
|
||||
expandedItems.set(treeItem.id, true);
|
||||
setExpandedItems(new Map(expandedItems));
|
||||
}
|
||||
}}
|
||||
onSelected={(treeItem: TreeItem) => {
|
||||
setSelectedTreeItemId(treeItem.id);
|
||||
}}
|
||||
onIconClicked={(treeItem: TreeItem) => {
|
||||
if (treeItem.kind === 'test')
|
||||
return;
|
||||
if (treeItem.expanded)
|
||||
expandedItems.set(treeItem.id, false);
|
||||
else
|
||||
expandedItems.set(treeItem.id, true);
|
||||
setExpandedItems(new Map(expandedItems));
|
||||
}}
|
||||
noItemsMessage='No tests' />;
|
||||
</div>;
|
||||
};
|
||||
|
||||
export const TraceView: React.FC<{
|
||||
testItem: TestItem | undefined,
|
||||
isRunningTest: boolean,
|
||||
}> = ({ testItem, isRunningTest }) => {
|
||||
}> = ({ testItem }) => {
|
||||
const [model, setModel] = React.useState<MultiTraceModel | undefined>();
|
||||
const [stepsProgress, setStepsProgress] = React.useState(0);
|
||||
updateStepsProgress = () => setStepsProgress(stepsProgress + 1);
|
||||
@ -249,20 +272,9 @@ export const TraceView: React.FC<{
|
||||
setModel(undefined);
|
||||
}
|
||||
})();
|
||||
}, [testItem, isRunningTest, stepsProgress]);
|
||||
}, [testItem, stepsProgress]);
|
||||
|
||||
if (!model) {
|
||||
return <div className='vbox'>
|
||||
<div className='drop-target'>
|
||||
<div>Run test to see the trace</div>
|
||||
<div style={{ paddingTop: 20 }}>
|
||||
<div>Double click a test or hit Enter</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <Workbench model={model} />;
|
||||
return <Workbench model={model}/>;
|
||||
};
|
||||
|
||||
declare global {
|
||||
|
@ -31,25 +31,26 @@ import './workbench.css';
|
||||
import { MetadataView } from './metadataView';
|
||||
|
||||
export const Workbench: React.FunctionComponent<{
|
||||
model: MultiTraceModel,
|
||||
model?: MultiTraceModel,
|
||||
}> = ({ model }) => {
|
||||
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
||||
const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>('logs');
|
||||
const activeAction = highlightedAction || selectedAction;
|
||||
const activeAction = model ? highlightedAction || selectedAction : undefined;
|
||||
|
||||
const { errors, warnings } = activeAction ? modelUtil.stats(activeAction) : { errors: 0, warnings: 0 };
|
||||
const consoleCount = errors + warnings;
|
||||
const networkCount = activeAction ? modelUtil.resourcesForAction(activeAction).length : 0;
|
||||
const sdkLanguage = model?.sdkLanguage || 'javascript';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={activeAction} sdkLanguage={model.sdkLanguage} /> },
|
||||
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} /> },
|
||||
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={activeAction} /> },
|
||||
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={activeAction} /> },
|
||||
];
|
||||
|
||||
if (model.hasSource)
|
||||
if (model?.hasSource)
|
||||
tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> });
|
||||
|
||||
return <div className='vbox'>
|
||||
@ -60,27 +61,33 @@ export const Workbench: React.FunctionComponent<{
|
||||
/>
|
||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||
<SplitView sidebarSize={300} orientation='vertical'>
|
||||
<SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} />
|
||||
<SnapshotTab action={activeAction} sdkLanguage={sdkLanguage} testIdAttributeName={model?.testIdAttributeName || 'data-testid'} />
|
||||
<TabbedPane tabs={tabs} selectedTab={selectedPropertiesTab} setSelectedTab={setSelectedPropertiesTab}/>
|
||||
</SplitView>
|
||||
<TabbedPane tabs={
|
||||
[
|
||||
{ id: 'actions', title: 'Actions', count: 0, render: () => <ActionList
|
||||
sdkLanguage={model.sdkLanguage}
|
||||
actions={model.actions}
|
||||
selectedAction={selectedAction}
|
||||
onSelected={action => {
|
||||
setSelectedAction(action);
|
||||
}}
|
||||
onHighlighted={action => {
|
||||
setHighlightedAction(action);
|
||||
}}
|
||||
revealConsole={() => setSelectedPropertiesTab('console')}
|
||||
/> },
|
||||
{ id: 'metadata',
|
||||
{
|
||||
id: 'actions',
|
||||
title: 'Actions',
|
||||
count: 0,
|
||||
component: <ActionList
|
||||
sdkLanguage={sdkLanguage}
|
||||
actions={model?.actions || []}
|
||||
selectedAction={model ? selectedAction : undefined}
|
||||
onSelected={action => {
|
||||
setSelectedAction(action);
|
||||
}}
|
||||
onHighlighted={action => {
|
||||
setHighlightedAction(action);
|
||||
}}
|
||||
revealConsole={() => setSelectedPropertiesTab('console')}
|
||||
/>
|
||||
},
|
||||
{
|
||||
id: 'metadata',
|
||||
title: 'Metadata',
|
||||
count: 0,
|
||||
render: () => <MetadataView model={model} />
|
||||
component: <MetadataView model={model}/>
|
||||
},
|
||||
]
|
||||
} selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/>
|
||||
|
@ -101,7 +101,6 @@ svg {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.codicon-close,
|
||||
.codicon-error {
|
||||
color: var(--red);
|
||||
}
|
||||
|
@ -34,25 +34,24 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.list-view-entry.highlighted,
|
||||
.list-view-entry.selected {
|
||||
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||
.list-view-entry.highlighted:not(.selected) {
|
||||
background-color: var(--vscode-list-inactiveSelectionBackground) !important;
|
||||
}
|
||||
|
||||
.list-view-entry.selected {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.list-view-entry.highlighted {
|
||||
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||
}
|
||||
|
||||
.list-view-content:focus .list-view-entry.selected:not(.error) {
|
||||
.list-view-content:focus .list-view-entry.selected {
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
outline: 1px solid var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.list-view-content:focus .list-view-entry.selected * {
|
||||
color: var(--vscode-list-activeSelectionForeground) !important;
|
||||
}
|
||||
|
||||
.list-view-content:focus .list-view-entry.error.selected {
|
||||
outline: 1px solid var(--vscode-inputValidation-errorBorder);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export type ListViewProps = {
|
||||
onRightArrow?: (item: any) => void,
|
||||
onHighlighted?: (item: any | undefined) => void,
|
||||
onIconClicked?: (item: any) => void,
|
||||
showNoItemsMessage?: boolean,
|
||||
noItemsMessage?: string,
|
||||
dataTestId?: string,
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ export const ListView: React.FC<ListViewProps> = ({
|
||||
onRightArrow,
|
||||
onHighlighted,
|
||||
onIconClicked,
|
||||
showNoItemsMessage,
|
||||
noItemsMessage,
|
||||
dataTestId,
|
||||
}) => {
|
||||
const itemListRef = React.createRef<HTMLDivElement>();
|
||||
@ -102,7 +102,7 @@ export const ListView: React.FC<ListViewProps> = ({
|
||||
}}
|
||||
ref={itemListRef}
|
||||
>
|
||||
{showNoItemsMessage && items.length === 0 && <div className='list-view-empty'>No items</div>}
|
||||
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
|
||||
{items.map((item, index) => <ListItemView
|
||||
key={itemKey ? itemKey(item) : String(index)}
|
||||
hasIcons={!!itemIcon}
|
||||
|
@ -22,31 +22,37 @@ export interface TabbedPaneTabModel {
|
||||
id: string;
|
||||
title: string | JSX.Element;
|
||||
count?: number;
|
||||
render: () => React.ReactElement;
|
||||
component?: React.ReactElement;
|
||||
render?: () => React.ReactElement;
|
||||
}
|
||||
|
||||
export const TabbedPane: React.FunctionComponent<{
|
||||
tabs: TabbedPaneTabModel[],
|
||||
leftToolbar?: React.ReactElement[],
|
||||
rightToolbar?: React.ReactElement[],
|
||||
selectedTab: string,
|
||||
setSelectedTab: (tab: string) => void
|
||||
}> = ({ tabs, selectedTab, setSelectedTab }) => {
|
||||
}> = ({ tabs, selectedTab, setSelectedTab, leftToolbar, rightToolbar }) => {
|
||||
return <div className='tabbed-pane'>
|
||||
<div className='vbox'>
|
||||
<Toolbar>{
|
||||
tabs.map(tab => (
|
||||
<Toolbar>{[
|
||||
...leftToolbar || [],
|
||||
...tabs.map(tab => (
|
||||
<TabbedPaneTab
|
||||
id={tab.id}
|
||||
title={tab.title}
|
||||
count={tab.count}
|
||||
selected={selectedTab === tab.id}
|
||||
onSelect={setSelectedTab}
|
||||
></TabbedPaneTab>
|
||||
))
|
||||
}</Toolbar>
|
||||
></TabbedPaneTab>)),
|
||||
...rightToolbar || [],
|
||||
]}</Toolbar>
|
||||
{
|
||||
tabs.map(tab => {
|
||||
if (tab.component)
|
||||
return <div key={tab.id} className='tab-content' style={{ display: selectedTab === tab.id ? 'inherit' : 'none' }}>{tab.component}</div>;
|
||||
if (selectedTab === tab.id)
|
||||
return <div key={tab.id} className='tab-content'>{tab.render()}</div>;
|
||||
return <div key={tab.id} className='tab-content'>{tab.component || tab.render!()}</div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
@ -45,7 +45,7 @@ class TraceViewerPage {
|
||||
|
||||
constructor(public page: Page) {
|
||||
this.actionTitles = page.locator('.action-title');
|
||||
this.callLines = page.locator('.call-line');
|
||||
this.callLines = page.locator('.call-tab .call-line');
|
||||
this.consoleLines = page.locator('.console-line');
|
||||
this.consoleLineMessages = page.locator('.console-line-message');
|
||||
this.consoleStacks = page.locator('.console-stack');
|
||||
|
Loading…
Reference in New Issue
Block a user