diff --git a/package-lock.json b/package-lock.json index 9510374360..dda5691907 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9888,7 +9888,7 @@ "requires": { "codemirror": "^5.65.9", "xterm": "^5.1.0", - "xterm-addon-fit": "*" + "xterm-addon-fit": "^0.7.0" } }, "which": { diff --git a/packages/playwright-test/src/isomorphic/teleReceiver.ts b/packages/playwright-test/src/isomorphic/teleReceiver.ts index fab0e58182..48e9716818 100644 --- a/packages/playwright-test/src/isomorphic/teleReceiver.ts +++ b/packages/playwright-test/src/isomorphic/teleReceiver.ts @@ -166,9 +166,7 @@ export class TeleReporterReceiver { private _onTestBegin(testId: string, payload: JsonTestResultStart) { const test = this._tests.get(testId)!; - test.results = []; - test.resultsMap.clear(); - const testResult = test._appendTestResult(payload.id); + const testResult = test._createTestResult(payload.id); testResult.retry = payload.retry; testResult.workerIndex = payload.workerIndex; testResult.parallelIndex = payload.parallelIndex; @@ -398,7 +396,9 @@ export class TeleTestCase implements reporterTypes.TestCase { return status === 'expected' || status === 'flaky' || status === 'skipped'; } - _appendTestResult(id: string): reporterTypes.TestResult { + _createTestResult(id: string): reporterTypes.TestResult { + this.results = []; + this.resultsMap.clear(); const result: TeleTestResult = { retry: this.results.length, parallelIndex: -1, diff --git a/packages/trace-viewer/src/ui/watchMode.css b/packages/trace-viewer/src/ui/watchMode.css index 7f0a16098c..66eb8ce0e4 100644 --- a/packages/trace-viewer/src/ui/watchMode.css +++ b/packages/trace-viewer/src/ui/watchMode.css @@ -68,6 +68,43 @@ margin: 0 5px; } +.list-view { + margin-top: 5px; +} + .list-view-entry:not(.selected):not(.highlighted) .toolbar-button { display: none; } + +.filters { + flex: none; + margin-left: 5px; + line-height: 24px; +} + +.filters > span { + color: var(--vscode-panelTitle-inactiveForeground); + padding-left: 3px; +} + +.filters > div { + display: inline-block; + margin: 0 5px; + user-select: none; + cursor: pointer; +} + +.filters > div:hover, +.filters > div.filters-toggled { + color: var(--vscode-notificationLink-foreground); +} + +.watch-mode-sidebar input[type=search] { + padding: 0 5px; + line-height: 24px; + outline: none; + margin: 0 4px; + border: none; + color: var(--vscode-input-foreground); + background-color: var(--vscode-input-background); +} diff --git a/packages/trace-viewer/src/ui/watchMode.tsx b/packages/trace-viewer/src/ui/watchMode.tsx index d006c34c4f..2def9415fd 100644 --- a/packages/trace-viewer/src/ui/watchMode.tsx +++ b/packages/trace-viewer/src/ui/watchMode.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { TreeView } from '@web/components/treeView'; import type { TreeState } from '@web/components/treeView'; import { TeleReporterReceiver } from '../../../playwright-test/src/isomorphic/teleReceiver'; +import type { TeleTestCase } from '../../../playwright-test/src/isomorphic/teleReceiver'; import type { FullConfig, Suite, TestCase, TestResult, TestStep, Location } from '../../../playwright-test/types/testReporter'; import { SplitView } from '@web/components/splitView'; import { MultiTraceModel } from './modelUtil'; @@ -32,6 +33,7 @@ import type { ContextEntry } from '../entries'; import type * as trace from '@trace/trace'; import type { XtermDataSource } from '@web/components/xtermWrapper'; import { XtermWrapper } from '@web/components/xtermWrapper'; +import { Expandable } from '@web/components/expandable'; let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {}; let updateStepsProgress: () => void = () => {}; @@ -59,6 +61,7 @@ export const WatchModeView: React.FC<{}> = ({ const [isWatchingFiles, setIsWatchingFiles] = React.useState(true); const [visibleTestIds, setVisibleTestIds] = React.useState([]); const [filterText, setFilterText] = React.useState(''); + const [filterExpanded, setFilterExpanded] = React.useState(false); const inputRef = React.useRef(null); React.useEffect(() => { @@ -89,7 +92,7 @@ export const WatchModeView: React.FC<{}> = ({ const testIdSet = new Set(testIds); for (const test of rootSuite.value?.allTests() || []) { if (testIdSet.has(test.id)) - test.results = []; + (test as TeleTestCase)._createTestResult('pending'); } setRootSuite({ ...rootSuite }); } @@ -103,6 +106,24 @@ export const WatchModeView: React.FC<{}> = ({ }); }; + const updateFilter = (name: string, value: string) => { + const result: string[] = []; + const prefix = name + ':'; + for (const t of filterText.split(' ')) { + if (t.startsWith(prefix)) { + if (value) { + result.push(prefix + value); + value = ''; + } + } else { + result.push(t); + } + } + if (value) + result.unshift(prefix + value); + setFilterText(result.join(' ')); + }; + const result = selectedTest?.results[0]; return
@@ -117,16 +138,29 @@ export const WatchModeView: React.FC<{}> = ({
{ setSettingsVisible(!settingsVisible); }}> - - { setFilterText(e.target.value); }} onKeyDown={e => { if (e.key === 'Enter') runTests(visibleTestIds); - }}> - + }}>} + style={{ flex: 'none', marginTop: 8 }} + expanded={filterExpanded} + setExpanded={setFilterExpanded}> +
+ Status: +
updateFilter('s', '')}>all
+ {['failed', 'passed', 'skipped'].map(s =>
updateFilter('s', s)}>{s}
)} +
+ {[...projects.values()].filter(v => v).length > 1 &&
+ Project: +
updateFilter('p', '')}>all
+ {[...projects].filter(([k, v]) => v).map(([k, v]) => k).map(p =>
updateFilter('p', p)}>{p}
)} +
} + } test, children: [], status, + project: projectName }); } }; @@ -573,13 +610,27 @@ function createTree(rootSuite: Suite | undefined, projects: Map function filterTree(rootItem: GroupItem, filterText: string) { const trimmedFilterText = filterText.trim(); const filterTokens = trimmedFilterText.toLowerCase().split(' '); + const textTokens = filterTokens.filter(token => !token.match(/^[sp]:/)); + const statuses = new Set(filterTokens.filter(t => t.startsWith('s:')).map(t => t.substring(2))); + if (statuses.size) + statuses.add('running'); + const projects = new Set(filterTokens.filter(t => t.startsWith('p:')).map(t => t.substring(2))); + + const filter = (testCase: TestCaseItem) => { + const title = testCase.tests[0].titlePath().join(' ').toLowerCase(); + if (!textTokens.every(token => title.includes(token))) + return false; + testCase.children = (testCase.children as TestItem[]).filter(test => !statuses.size || statuses.has(test.status)); + testCase.children = (testCase.children as TestItem[]).filter(test => !projects.size || projects.has(test.project)); + testCase.tests = (testCase.children as TestItem[]).map(c => c.test); + return !!testCase.children.length; + }; const visit = (treeItem: GroupItem) => { const newChildren: (GroupItem | TestCaseItem)[] = []; for (const child of treeItem.children) { if (child.kind === 'case') { - const title = child.tests[0].titlePath().join(' ').toLowerCase(); - if (filterTokens.every(token => title.includes(token))) + if (filter(child)) newChildren.push(child); } else { visit(child); diff --git a/packages/web/src/components/expandable.tsx b/packages/web/src/components/expandable.tsx index 7169510bad..4751381eb6 100644 --- a/packages/web/src/components/expandable.tsx +++ b/packages/web/src/components/expandable.tsx @@ -26,10 +26,10 @@ export const Expandable: React.FunctionComponent
setExpanded(!expanded)} /> {title}
- { expanded &&
{children}
} + { expanded &&
{children}
}
; }; diff --git a/packages/web/src/components/toolbarButton.css b/packages/web/src/components/toolbarButton.css index 70b7e3196c..9686b2f22f 100644 --- a/packages/web/src/components/toolbarButton.css +++ b/packages/web/src/components/toolbarButton.css @@ -41,5 +41,5 @@ } .toolbar-button.toggled { - color: var(--vscode-inputOption-activeBorder); + color: var(--vscode-notificationLink-foreground); }