diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index fe217715e1..2e68f278de 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -85,8 +85,13 @@ class Controller { this._page.exposeBinding('binding', false, (source, data) => { const { method, params } = data; if (method === 'run') { - const { location } = params; - config._internal.cliArgs = [location]; + const { location, testIds } = params; + if (location) + config._internal.cliArgs = [location]; + if (testIds) { + const testIdSet = testIds ? new Set(testIds) : null; + config._internal.testIdMatcher = id => !testIdSet || testIdSet.has(id); + } this._queue = this._queue.then(() => runTests(config, this._runReporter)); return this._queue; } diff --git a/packages/trace-viewer/src/ui/watchMode.css b/packages/trace-viewer/src/ui/watchMode.css index 2ae6eb88d9..363fb486e0 100644 --- a/packages/trace-viewer/src/ui/watchMode.css +++ b/packages/trace-viewer/src/ui/watchMode.css @@ -21,3 +21,21 @@ .watch-mode-sidebar input { flex: auto; } + +.watch-mode-sidebar .toolbar-button:not([disabled]) .codicon-play { + color: var(--vscode-testing-runAction); +} + +.watch-mode-list-item { + flex: auto; +} + +.watch-mode-list-item-title { + flex: auto; + text-overflow: ellipsis; + overflow: hidden +} + +.watch-mode-sidebar .toolbar-button { + margin: 0; +} diff --git a/packages/trace-viewer/src/ui/watchMode.tsx b/packages/trace-viewer/src/ui/watchMode.tsx index b56a3094ae..b4bda5b1ac 100644 --- a/packages/trace-viewer/src/ui/watchMode.tsx +++ b/packages/trace-viewer/src/ui/watchMode.tsx @@ -24,6 +24,7 @@ import type { FullConfig, Suite, TestCase, TestStep } from '../../../playwright- import { SplitView } from '@web/components/splitView'; import type { MultiTraceModel } from './modelUtil'; import './watchMode.css'; +import { ToolbarButton } from '@web/components/toolbarButton'; let rootSuite: Suite | undefined; @@ -63,10 +64,10 @@ export const WatchModeView: React.FC<{}> = ({ const explicitlyOrAutoExpandedFiles = new Set(); const entries = new Map(); const trimmedFilterText = filterText.trim(); - const filterTokens = trimmedFilterText.split(' '); + const filterTokens = trimmedFilterText.toLowerCase().split(' '); for (const fileSuite of fileSuites) { const hasMatch = !trimmedFilterText || fileSuite.allTests().some(test => { - const fullTitle = test.titlePath().join(' '); + const fullTitle = test.titlePath().join(' ').toLowerCase(); return !filterTokens.some(token => !fullTitle.includes(token)); }); if (hasMatch) @@ -76,13 +77,28 @@ export const WatchModeView: React.FC<{}> = ({ if (expandState === true || autoExpandMatches) { explicitlyOrAutoExpandedFiles.add(fileSuite); for (const test of fileSuite.allTests()) { - const fullTitle = test.titlePath().join(' '); + const fullTitle = test.titlePath().join(' ').toLowerCase(); if (!filterTokens.some(token => !fullTitle.includes(token))) entries.set(test, { test, fileSuite }); } } } + const visibleTestIds = new Set(); + for (const { test } of entries.values()) { + if (test) + visibleTestIds.add(test.id); + } + + const runEntry = (entry: Entry) => { + expandedFiles.set(entry.fileSuite, true); + setSelectedTest(entry.test); + setIsRunningTest(true); + runTests(entry.test ? entry.test.location.file + ':' + entry.test.location.line : entry.fileSuite.title, undefined).then(() => { + setIsRunningTest(false); + }); + }; + const selectedEntry = selectedTest ? entries.get(selectedTest) : selectedOrDefaultFileSuite ? entries.get(selectedOrDefaultFileSuite) : undefined; return @@ -93,12 +109,23 @@ export const WatchModeView: React.FC<{}> = ({ setFilterText(e.target.value); }} onKeyDown={e => { + if (e.key === 'Enter') { + setIsRunningTest(true); + runTests(undefined, [...visibleTestIds]).then(() => { + setIsRunningTest(false); + }); + } }}> entry.test ? entry.test!.id : entry.fileSuite.title } - itemRender={(entry: Entry) => entry.test ? entry.test!.titlePath().slice(3).join(' › ') : entry.fileSuite.title } + itemRender={(entry: Entry) => { + return
+
{entry.test ? entry.test!.titlePath().slice(3).join(' › ') : entry.fileSuite.title}
+ runEntry(entry)} disabled={isRunningTest}> +
; + }} itemIcon={(entry: Entry) => { if (entry.test) { if (entry.test.results.length && entry.test.results[0].duration) @@ -113,15 +140,7 @@ export const WatchModeView: React.FC<{}> = ({ }} itemIndent={(entry: Entry) => entry.test ? 1 : 0} selectedItem={selectedEntry} - onAccepted={(entry: Entry) => { - if (entry.test) { - setSelectedTest(entry.test); - setIsRunningTest(true); - runTests(entry.test ? entry.test.location.file + ':' + entry.test.location.line : entry.fileSuite.title).then(() => { - setIsRunningTest(false); - }); - } - }} + onAccepted={runEntry} onLeftArrow={(entry: Entry) => { expandedFiles.set(entry.fileSuite, false); setSelectedTest(undefined); @@ -248,9 +267,9 @@ const receiver = new TeleReporterReceiver({ receiver.dispatch(message); }; -async function runTests(location: string): Promise { +async function runTests(location: string | undefined, testIds: string[] | undefined): Promise { await (window as any).binding({ method: 'run', - params: { location } + params: { location, testIds } }); }