mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: split ui mode toolbar into lines, show progress (#21403)
This commit is contained in:
parent
d8d410db6e
commit
bae9173208
@ -114,6 +114,7 @@ class UIMode {
|
|||||||
const run = taskRunner.run(context, 0, stop).then(async status => {
|
const run = taskRunner.run(context, 0, stop).then(async status => {
|
||||||
await reporter.onExit({ status });
|
await reporter.onExit({ status });
|
||||||
this._testRun = undefined;
|
this._testRun = undefined;
|
||||||
|
this._config._internal.testIdMatcher = undefined;
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
this._testRun = { run, stop };
|
this._testRun = { run, stop };
|
||||||
|
@ -43,3 +43,26 @@
|
|||||||
.watch-mode-sidebar .toolbar-button {
|
.watch-mode-sidebar .toolbar-button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.watch-mode-sidebar .toolbar h3.title {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 11px;
|
||||||
|
min-width: 3ch;
|
||||||
|
margin: 0 10px;
|
||||||
|
-webkit-margin-before: 0;
|
||||||
|
-webkit-margin-after: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watch-mode-sidebar .spacer {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watch-mode-sidebar .status-line {
|
||||||
|
flex: none;
|
||||||
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
|
line-height: 22px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
@ -26,15 +26,22 @@ import type { MultiTraceModel } from './modelUtil';
|
|||||||
import './watchMode.css';
|
import './watchMode.css';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { Toolbar } from '@web/components/toolbar';
|
import { Toolbar } from '@web/components/toolbar';
|
||||||
|
import { toggleTheme } from '@web/theme';
|
||||||
|
|
||||||
let updateRootSuite: (rootSuite: Suite) => void = () => {};
|
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
||||||
let updateProgress: () => void = () => {};
|
let updateStepsProgress: () => void = () => {};
|
||||||
let runWatchedTests = () => {};
|
let runWatchedTests = () => {};
|
||||||
|
|
||||||
export const WatchModeView: React.FC<{}> = ({
|
export const WatchModeView: React.FC<{}> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined });
|
const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined });
|
||||||
updateRootSuite = (rootSuite: Suite) => setRootSuite({ value: rootSuite });
|
const [progress, setProgress] = React.useState<Progress>({ total: 0, passed: 0, failed: 0 });
|
||||||
|
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 [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
||||||
const [isRunningTest, setIsRunningTest] = React.useState<boolean>(false);
|
const [isRunningTest, setIsRunningTest] = React.useState<boolean>(false);
|
||||||
const [filterText, setFilterText] = React.useState<string>('');
|
const [filterText, setFilterText] = React.useState<string>('');
|
||||||
@ -45,7 +52,7 @@ export const WatchModeView: React.FC<{}> = ({
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
sendMessageNoReply('list');
|
resetCollectingRootSuite();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -91,18 +98,33 @@ export const WatchModeView: React.FC<{}> = ({
|
|||||||
runTests(collectTestIds(selectedTreeItem));
|
runTests(collectTestIds(selectedTreeItem));
|
||||||
};
|
};
|
||||||
|
|
||||||
const runTests = (testIds: string[] | undefined) => {
|
const runTests = (testIds: string[]) => {
|
||||||
|
setProgress({ total: testIds.length, passed: 0, failed: 0 });
|
||||||
setIsRunningTest(true);
|
setIsRunningTest(true);
|
||||||
sendMessage('run', { testIds }).then(() => {
|
sendMessage('run', { testIds }).then(() => {
|
||||||
setIsRunningTest(false);
|
setIsRunningTest(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let selectedTestItem: TestItem | undefined;
|
||||||
|
if (selectedTreeItem?.kind === 'test')
|
||||||
|
selectedTestItem = selectedTreeItem;
|
||||||
|
else if (selectedTreeItem?.kind === 'case' && selectedTreeItem.children?.length === 1)
|
||||||
|
selectedTestItem = selectedTreeItem.children[0]! as TestItem;
|
||||||
|
|
||||||
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
<TraceView testItem={selectedTreeItem?.kind === 'test' ? selectedTreeItem : undefined} isRunningTest={isRunningTest}></TraceView>
|
<TraceView testItem={selectedTestItem} isRunningTest={isRunningTest}></TraceView>
|
||||||
<div className='vbox watch-mode-sidebar'>
|
<div className='vbox watch-mode-sidebar'>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<input ref={inputRef} type='search' placeholder='Filter tests' spellCheck={false} value={filterText}
|
<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 => {
|
onChange={e => {
|
||||||
setFilterText(e.target.value);
|
setFilterText(e.target.value);
|
||||||
}}
|
}}
|
||||||
@ -110,8 +132,6 @@ export const WatchModeView: React.FC<{}> = ({
|
|||||||
if (e.key === 'Enter')
|
if (e.key === 'Enter')
|
||||||
runTests([...visibleTestIds]);
|
runTests([...visibleTestIds]);
|
||||||
}}></input>
|
}}></input>
|
||||||
<ToolbarButton icon='play' title='Run' onClick={() => runTests([...visibleTestIds])} disabled={isRunningTest}></ToolbarButton>
|
|
||||||
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<ListView
|
<ListView
|
||||||
items={listItems}
|
items={listItems}
|
||||||
@ -171,11 +191,17 @@ export const WatchModeView: React.FC<{}> = ({
|
|||||||
}}
|
}}
|
||||||
showNoItemsMessage={true}></ListView>
|
showNoItemsMessage={true}></ListView>
|
||||||
{(rootSuite.value?.suites.length || 0) > 1 && <div style={{ flex: 'none', borderTop: '1px solid var(--vscode-panel-border)' }}>
|
{(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
|
<ListView
|
||||||
items={rootSuite.value!.suites}
|
items={rootSuite.value!.suites}
|
||||||
onSelected={(suite: Suite) => {
|
onSelected={(suite: Suite) => {
|
||||||
const copy = [...projectNames];
|
const copy = [...projectNames];
|
||||||
copy.includes(suite.title) ? copy.splice(copy.indexOf(suite.title), 1) : copy.push(suite.title);
|
if (copy.includes(suite.title))
|
||||||
|
copy.splice(copy.indexOf(suite.title), 1);
|
||||||
|
else
|
||||||
|
copy.push(suite.title);
|
||||||
setProjectNames(copy);
|
setProjectNames(copy);
|
||||||
}}
|
}}
|
||||||
itemRender={(suite: Suite) => {
|
itemRender={(suite: Suite) => {
|
||||||
@ -186,17 +212,23 @@ export const WatchModeView: React.FC<{}> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>}
|
</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>
|
</div>
|
||||||
</SplitView>;
|
</SplitView>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProgressView: React.FC<{
|
export const StepsView: React.FC<{
|
||||||
testItem: TestItem | undefined,
|
testItem: TestItem | undefined,
|
||||||
}> = ({
|
}> = ({
|
||||||
testItem,
|
testItem,
|
||||||
}) => {
|
}) => {
|
||||||
const [updateCounter, setUpdateCounter] = React.useState(0);
|
const [updateCounter, setUpdateCounter] = React.useState(0);
|
||||||
updateProgress = () => setUpdateCounter(updateCounter + 1);
|
updateStepsProgress = () => setUpdateCounter(updateCounter + 1);
|
||||||
|
|
||||||
const steps: (TestCase | TestStep)[] = [];
|
const steps: (TestCase | TestStep)[] = [];
|
||||||
for (const result of testItem?.test.results || [])
|
for (const result of testItem?.test.results || [])
|
||||||
@ -232,7 +264,7 @@ export const TraceView: React.FC<{
|
|||||||
}, [testItem, isRunningTest]);
|
}, [testItem, isRunningTest]);
|
||||||
|
|
||||||
if (isRunningTest)
|
if (isRunningTest)
|
||||||
return <ProgressView testItem={testItem}></ProgressView>;
|
return <StepsView testItem={testItem}></StepsView>;
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
return <div className='vbox'>
|
return <div className='vbox'>
|
||||||
@ -255,40 +287,53 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
let receiver: TeleReporterReceiver | undefined;
|
||||||
|
|
||||||
|
const resetCollectingRootSuite = () => {
|
||||||
let rootSuite: Suite;
|
let rootSuite: Suite;
|
||||||
const receiver = new TeleReporterReceiver({
|
const progress: Progress = {
|
||||||
|
total: 0,
|
||||||
|
passed: 0,
|
||||||
|
failed: 0,
|
||||||
|
};
|
||||||
|
receiver = new TeleReporterReceiver({
|
||||||
onBegin: (config: FullConfig, suite: Suite) => {
|
onBegin: (config: FullConfig, suite: Suite) => {
|
||||||
if (!rootSuite)
|
if (!rootSuite)
|
||||||
rootSuite = suite;
|
rootSuite = suite;
|
||||||
updateRootSuite(rootSuite);
|
progress.passed = 0;
|
||||||
|
progress.failed = 0;
|
||||||
|
updateRootSuite(rootSuite, progress);
|
||||||
},
|
},
|
||||||
|
|
||||||
onTestBegin: () => {
|
onTestBegin: () => {
|
||||||
updateRootSuite(rootSuite);
|
updateRootSuite(rootSuite, progress);
|
||||||
},
|
},
|
||||||
|
|
||||||
onTestEnd: () => {
|
onTestEnd: (test: TestCase) => {
|
||||||
updateRootSuite(rootSuite);
|
if (test.outcome() === 'unexpected')
|
||||||
|
++progress.failed;
|
||||||
|
else
|
||||||
|
++progress.passed;
|
||||||
|
updateRootSuite(rootSuite, progress);
|
||||||
},
|
},
|
||||||
|
|
||||||
onStepBegin: () => {
|
onStepBegin: () => {
|
||||||
updateProgress();
|
updateStepsProgress();
|
||||||
},
|
},
|
||||||
|
|
||||||
onStepEnd: () => {
|
onStepEnd: () => {
|
||||||
updateProgress();
|
updateStepsProgress();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
sendMessageNoReply('list');
|
||||||
|
};
|
||||||
|
|
||||||
|
(window as any).dispatch = (message: any) => {
|
||||||
(window as any).dispatch = (message: any) => {
|
if (message.method === 'fileChanged')
|
||||||
if (message.method === 'fileChanged')
|
runWatchedTests();
|
||||||
runWatchedTests();
|
else
|
||||||
else
|
receiver?.dispatch(message);
|
||||||
receiver.dispatch(message);
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendMessage = async (method: string, params: any) => {
|
const sendMessage = async (method: string, params: any) => {
|
||||||
await (window as any).sendMessage({ method, params });
|
await (window as any).sendMessage({ method, params });
|
||||||
@ -322,6 +367,12 @@ const collectTestIds = (treeItem?: TreeItem): string[] => {
|
|||||||
return testIds;
|
return testIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Progress = {
|
||||||
|
total: number;
|
||||||
|
passed: number;
|
||||||
|
failed: number;
|
||||||
|
};
|
||||||
|
|
||||||
type TreeItemBase = {
|
type TreeItemBase = {
|
||||||
kind: 'file' | 'case' | 'test',
|
kind: 'file' | 'case' | 'test',
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: white;
|
background-color: var(--vscode-editor-background);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin-left: 10px;
|
margin: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar select {
|
.toolbar select {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user