diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index b2b2d79158..e611897478 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -114,6 +114,7 @@ class UIMode { const run = taskRunner.run(context, 0, stop).then(async status => { await reporter.onExit({ status }); this._testRun = undefined; + this._config._internal.testIdMatcher = undefined; return status; }); this._testRun = { run, stop }; diff --git a/packages/trace-viewer/src/ui/watchMode.css b/packages/trace-viewer/src/ui/watchMode.css index c153a03bca..9e8972616f 100644 --- a/packages/trace-viewer/src/ui/watchMode.css +++ b/packages/trace-viewer/src/ui/watchMode.css @@ -43,3 +43,26 @@ .watch-mode-sidebar .toolbar-button { 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; +} diff --git a/packages/trace-viewer/src/ui/watchMode.tsx b/packages/trace-viewer/src/ui/watchMode.tsx index 4d5fffb1e2..1258c5faf6 100644 --- a/packages/trace-viewer/src/ui/watchMode.tsx +++ b/packages/trace-viewer/src/ui/watchMode.tsx @@ -26,15 +26,22 @@ import type { MultiTraceModel } from './modelUtil'; import './watchMode.css'; import { ToolbarButton } from '@web/components/toolbarButton'; import { Toolbar } from '@web/components/toolbar'; +import { toggleTheme } from '@web/theme'; -let updateRootSuite: (rootSuite: Suite) => void = () => {}; -let updateProgress: () => void = () => {}; +let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {}; +let updateStepsProgress: () => void = () => {}; let runWatchedTests = () => {}; export const WatchModeView: React.FC<{}> = ({ }) => { const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined }); - updateRootSuite = (rootSuite: Suite) => setRootSuite({ value: rootSuite }); + const [progress, setProgress] = React.useState({ 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(); const [isRunningTest, setIsRunningTest] = React.useState(false); const [filterText, setFilterText] = React.useState(''); @@ -45,7 +52,7 @@ export const WatchModeView: React.FC<{}> = ({ React.useEffect(() => { inputRef.current?.focus(); - sendMessageNoReply('list'); + resetCollectingRootSuite(); }, []); React.useEffect(() => { @@ -91,18 +98,33 @@ export const WatchModeView: React.FC<{}> = ({ runTests(collectTestIds(selectedTreeItem)); }; - const runTests = (testIds: string[] | undefined) => { + const runTests = (testIds: string[]) => { + setProgress({ total: testIds.length, passed: 0, failed: 0 }); setIsRunningTest(true); sendMessage('run', { testIds }).then(() => { 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 - +
- Test explorer + runTests([...visibleTestIds])} disabled={isRunningTest}> + sendMessageNoReply('stop')} disabled={!isRunningTest}> + +
+ toggleTheme()}> +
+ + { setFilterText(e.target.value); }} @@ -110,8 +132,6 @@ export const WatchModeView: React.FC<{}> = ({ if (e.key === 'Enter') runTests([...visibleTestIds]); }}> - runTests([...visibleTestIds])} disabled={isRunningTest}> - sendMessageNoReply('stop')} disabled={!isRunningTest}> = ({ }} showNoItemsMessage={true}> {(rootSuite.value?.suites.length || 0) > 1 &&
+ +

Projects

+
{ 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); }} itemRender={(suite: Suite) => { @@ -186,17 +212,23 @@ export const WatchModeView: React.FC<{}> = ({ }} />
} + {isRunningTest &&
+ Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed +
} + {!isRunningTest &&
+ Total: {visibleTestIds.size} tests +
}
; }; -export const ProgressView: React.FC<{ +export const StepsView: React.FC<{ testItem: TestItem | undefined, }> = ({ testItem, }) => { const [updateCounter, setUpdateCounter] = React.useState(0); - updateProgress = () => setUpdateCounter(updateCounter + 1); + updateStepsProgress = () => setUpdateCounter(updateCounter + 1); const steps: (TestCase | TestStep)[] = []; for (const result of testItem?.test.results || []) @@ -232,7 +264,7 @@ export const TraceView: React.FC<{ }, [testItem, isRunningTest]); if (isRunningTest) - return ; + return ; if (!model) { return
@@ -255,40 +287,53 @@ declare global { } } -{ +let receiver: TeleReporterReceiver | undefined; + +const resetCollectingRootSuite = () => { let rootSuite: Suite; - const receiver = new TeleReporterReceiver({ + const progress: Progress = { + total: 0, + passed: 0, + failed: 0, + }; + receiver = new TeleReporterReceiver({ onBegin: (config: FullConfig, suite: Suite) => { if (!rootSuite) rootSuite = suite; - updateRootSuite(rootSuite); + progress.passed = 0; + progress.failed = 0; + updateRootSuite(rootSuite, progress); }, onTestBegin: () => { - updateRootSuite(rootSuite); + updateRootSuite(rootSuite, progress); }, - onTestEnd: () => { - updateRootSuite(rootSuite); + onTestEnd: (test: TestCase) => { + if (test.outcome() === 'unexpected') + ++progress.failed; + else + ++progress.passed; + updateRootSuite(rootSuite, progress); }, onStepBegin: () => { - updateProgress(); + updateStepsProgress(); }, onStepEnd: () => { - updateProgress(); + updateStepsProgress(); }, }); + sendMessageNoReply('list'); +}; - - (window as any).dispatch = (message: any) => { - if (message.method === 'fileChanged') - runWatchedTests(); - else - receiver.dispatch(message); - }; -} +(window as any).dispatch = (message: any) => { + if (message.method === 'fileChanged') + runWatchedTests(); + else + receiver?.dispatch(message); +}; const sendMessage = async (method: string, params: any) => { await (window as any).sendMessage({ method, params }); @@ -322,6 +367,12 @@ const collectTestIds = (treeItem?: TreeItem): string[] => { return testIds; }; +type Progress = { + total: number; + passed: number; + failed: number; +}; + type TreeItemBase = { kind: 'file' | 'case' | 'test', id: string; diff --git a/packages/trace-viewer/src/ui/workbench.css b/packages/trace-viewer/src/ui/workbench.css index c27c5168e8..384dd26564 100644 --- a/packages/trace-viewer/src/ui/workbench.css +++ b/packages/trace-viewer/src/ui/workbench.css @@ -20,7 +20,7 @@ justify-content: center; flex: auto; flex-direction: column; - background-color: white; + background-color: var(--vscode-editor-background); position: absolute; top: 0; right: 0; diff --git a/packages/web/src/components/toolbar.css b/packages/web/src/components/toolbar.css index 522c638d46..816c9a9f9f 100644 --- a/packages/web/src/components/toolbar.css +++ b/packages/web/src/components/toolbar.css @@ -34,7 +34,7 @@ padding: 0 10px; line-height: 24px; outline: none; - margin-left: 10px; + margin: 0 4px; } .toolbar select {