From 3589e0677e891c0094b7f82ab2ef2da23c40d07d Mon Sep 17 00:00:00 2001 From: Pengoose Date: Tue, 6 May 2025 08:17:20 +0900 Subject: [PATCH 1/8] feat: add test result UI --- packages/trace-viewer/src/ui/uiModeView.css | 24 +++++++++++++++++++-- packages/trace-viewer/src/ui/uiModeView.tsx | 16 ++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/trace-viewer/src/ui/uiModeView.css b/packages/trace-viewer/src/ui/uiModeView.css index de89b911c33b7..d8ac35a06e3b0 100644 --- a/packages/trace-viewer/src/ui/uiModeView.css +++ b/packages/trace-viewer/src/ui/uiModeView.css @@ -91,11 +91,17 @@ flex-direction: row; align-items: center; height: 30px; + overflow: hidden; + gap: 6px; +} + +.status-line > * { + flex-shrink: 0; } .status-line > div { - overflow: hidden; - text-overflow: ellipsis; + display: flex; + align-items: center; } .ui-mode-sidebar input[type=search] { @@ -108,3 +114,17 @@ color: var(--vscode-input-foreground); background-color: var(--vscode-input-background); } + +.status-passed { + color: var(--vscode-debugIcon-restartForeground); + gap: 2px; +} + +.status-failed { + color: var(--vscode-list-errorForeground); + gap: 3px; +} + +.status-skipped { + gap: 3px; +} diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 4375018765aea..b7bd347f73995 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -37,6 +37,7 @@ import { TestListView } from './uiModeTestListView'; import { TraceView } from './uiModeTraceView'; import { SettingsView } from './settingsView'; import { DefaultSettingsView } from './defaultSettingsView'; +import { testStatusIcon } from './testUtils'; let xtermSize = { cols: 80, rows: 24 }; const xtermDataSource: XtermDataSource = { @@ -461,11 +462,18 @@ export const UIModeView: React.FC<{}> = ({ runTests={() => runTests('bounce-if-busy', visibleTestIds)} /> {!isRunningTest && !progress &&
Tests
} - {!isRunningTest && progress &&
-
{progress.passed}/{progress.total} passed ({(progress.passed / progress.total) * 100 | 0}%)
+ {!isRunningTest && progress &&
+ {progress.passed + progress.failed + progress.skipped}/{progress.total} +
{progress.passed}
+
{progress.failed}
+
{progress.skipped}
} - {isRunningTest && progress &&
-
Running {progress.passed}/{runningState.testIds.size} passed ({(progress.passed / runningState.testIds.size) * 100 | 0}%)
+ {isRunningTest && progress &&
+ + {progress.passed + progress.failed + progress.skipped}/{runningState.testIds.size} +
{progress.passed}
+
{progress.failed}
+
{progress.skipped}
} runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}> testServerConnection?.stopTests({})} disabled={!isRunningTest || isLoading}> From e9a2642e614d10ebbfb7cd0180f493da12bfa106 Mon Sep 17 00:00:00 2001 From: Pengoose Date: Tue, 6 May 2025 20:36:27 +0900 Subject: [PATCH 2/8] test: update regression tests --- .../playwright-test/ui-mode-metadata.spec.ts | 6 +- .../ui-mode-test-annotations.spec.ts | 6 +- .../ui-mode-test-attachments.spec.ts | 30 ++++++-- .../ui-mode-test-output.spec.ts | 6 +- .../playwright-test/ui-mode-test-run.spec.ts | 68 +++++++++++++++---- .../ui-mode-test-screencast.spec.ts | 6 +- .../ui-mode-test-setup.spec.ts | 29 ++++++-- .../ui-mode-test-shortcut.spec.ts | 6 +- .../ui-mode-test-watch.spec.ts | 39 +++++++++-- 9 files changed, 162 insertions(+), 34 deletions(-) diff --git a/tests/playwright-test/ui-mode-metadata.spec.ts b/tests/playwright-test/ui-mode-metadata.spec.ts index bfbbba08a54f2..e622332555b53 100644 --- a/tests/playwright-test/ui-mode-metadata.spec.ts +++ b/tests/playwright-test/ui-mode-metadata.spec.ts @@ -41,7 +41,11 @@ test('should render html report git info metadata', async ({ runUITest }) => { }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Toggle output').click(); await expect(page.getByTestId('output')).toContainText('ci.link: https://playwright.dev'); diff --git a/tests/playwright-test/ui-mode-test-annotations.spec.ts b/tests/playwright-test/ui-mode-test-annotations.spec.ts index cc1f0b5f04db8..377cec58c45a8 100644 --- a/tests/playwright-test/ui-mode-test-annotations.spec.ts +++ b/tests/playwright-test/ui-mode-test-annotations.spec.ts @@ -32,7 +32,11 @@ test('should display annotations', async ({ runUITest }) => { `, }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByRole('treeitem', { name: 'suite' }).locator('.codicon-chevron-right').click(); await page.getByText('annotation test').click(); await page.getByText('Annotations', { exact: true }).click(); diff --git a/tests/playwright-test/ui-mode-test-attachments.spec.ts b/tests/playwright-test/ui-mode-test-attachments.spec.ts index 1a9e5b56c2693..292180ee851e5 100644 --- a/tests/playwright-test/ui-mode-test-attachments.spec.ts +++ b/tests/playwright-test/ui-mode-test-attachments.spec.ts @@ -33,7 +33,11 @@ test('should contain text attachment', async ({ runUITest }) => { }); await page.getByText('attach test').click(); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByText('Attachments').click(); await page.locator('.tab-attachments').getByText('text attachment').click(); @@ -69,7 +73,11 @@ test('should contain binary attachment', async ({ runUITest }) => { }); await page.getByText('attach test').click(); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByText('Attachments').click(); const downloadPromise = page.waitForEvent('download'); await page.getByRole('link', { name: 'download' }).click(); @@ -89,7 +97,11 @@ test('should contain string attachment', async ({ runUITest }) => { }); await page.getByText('attach test').click(); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByText('Attachments').click(); await page.getByText('attach "note"', { exact: true }).click(); const downloadPromise = page.waitForEvent('download'); @@ -116,7 +128,11 @@ test('should linkify string attachments', async ({ runUITest, server }) => { }); await page.getByText('attach test').click(); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByText('Attachments').click(); const attachmentsPane = page.locator('.attachments-tab'); @@ -162,7 +178,11 @@ test('should link from attachment step to attachments view', async ({ runUITest await page.getByText('attach test').click(); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByRole('tab', { name: 'Attachments' }).click(); const panel = page.getByRole('tabpanel', { name: 'Attachments' }); diff --git a/tests/playwright-test/ui-mode-test-output.spec.ts b/tests/playwright-test/ui-mode-test-output.spec.ts index 75d4c545f88ed..5b58047baf4a5 100644 --- a/tests/playwright-test/ui-mode-test-output.spec.ts +++ b/tests/playwright-test/ui-mode-test-output.spec.ts @@ -250,7 +250,11 @@ test('should print beforeAll console messages once', async ({ runUITest }, testI await page.getByTitle('Run all').click(); await page.getByText('Console').click(); await page.getByText('print').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await expect(page.locator('.console-tab .console-line-message')).toHaveText([ 'before all log', 'test log', diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts index 3b2351083a8a4..a5df0a898fc03 100644 --- a/tests/playwright-test/ui-mode-test-run.spec.ts +++ b/tests/playwright-test/ui-mode-test-run.spec.ts @@ -81,7 +81,11 @@ test('should run visible', async ({ runUITest }) => { - treeitem "[icon-circle-slash] skipped" `); - await expect(page.getByTestId('status-line')).toHaveText('4/8 passed (50%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('8/8'); + await expect(statusLine.locator('.status-passed')).toHaveText('4'); + await expect(statusLine.locator('.status-failed')).toHaveText('3'); + await expect(statusLine.locator('.status-skipped')).toHaveText('1'); }); test('should show running progress', async ({ runUITest }) => { @@ -96,11 +100,21 @@ test('should show running progress', async ({ runUITest }) => { }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('Running 1/4 passed (25%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/4'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Stop').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/4 passed (25%)'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/4'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Reload').click(); - await expect(page.getByTestId('status-line')).toBeHidden(); + await expect(statusLine.getByTestId('test-count')).toBeHidden(); + await expect(statusLine.locator('.status-passed')).toBeHidden(); + await expect(statusLine.locator('.status-failed')).toBeHidden(); + await expect(statusLine.locator('.status-skipped')).toBeHidden(); }); test('should run on hover', async ({ runUITest }) => { @@ -491,7 +505,11 @@ test('should show time', async ({ runUITest }) => { - treeitem "[icon-circle-slash] skipped" `); - await expect(page.getByTestId('status-line')).toHaveText('4/8 passed (50%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('8/8'); + await expect(statusLine.locator('.status-passed')).toHaveText('4'); + await expect(statusLine.locator('.status-failed')).toHaveText('3'); + await expect(statusLine.locator('.status-skipped')).toHaveText('1'); }); test('should show test.fail as passing', async ({ runUITest }) => { @@ -522,7 +540,11 @@ test('should show test.fail as passing', async ({ runUITest }) => { - treeitem ${/\[icon-check\] should fail \d+m?s/} `); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should ignore repeatEach', async ({ runUITest }) => { @@ -558,7 +580,11 @@ test('should ignore repeatEach', async ({ runUITest }) => { - treeitem ${/\[icon-check\] should pass/} `); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should remove output folder before test run', async ({ runUITest }) => { @@ -593,7 +619,11 @@ test('should remove output folder before test run', async ({ runUITest }) => { - treeitem ${/\[icon-check\] should pass/} `); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Run all').click(); await expect.poll(dumpTestTree(page)).toBe(` @@ -608,7 +638,10 @@ test('should remove output folder before test run', async ({ runUITest }) => { - treeitem ${/\[icon-check\] should pass/} `); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should show proper total when using deps', async ({ runUITest }) => { @@ -660,7 +693,11 @@ test('should show proper total when using deps', async ({ runUITest }) => { - treeitem "[icon-circle-outline] run @chromium chromium" `); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('run @chromium').dblclick(); await expect.poll(dumpTestTree(page)).toBe(` @@ -680,7 +717,10 @@ test('should show proper total when using deps', async ({ runUITest }) => { - button "Watch" `); - await expect(page.getByTestId('status-line')).toHaveText('2/2 passed (100%)'); + await expect(statusLine.getByTestId('test-count')).toHaveText('2/2'); + await expect(statusLine.locator('.status-passed')).toHaveText('2'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should respect --tsconfig option', { @@ -746,7 +786,11 @@ test('should respect --tsconfig option', { - treeitem ${/\[icon-check\] test/} `); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should respect --ignore-snapshots option', { diff --git a/tests/playwright-test/ui-mode-test-screencast.spec.ts b/tests/playwright-test/ui-mode-test-screencast.spec.ts index 23e4f5872e955..eeafe6fd313f1 100644 --- a/tests/playwright-test/ui-mode-test-screencast.spec.ts +++ b/tests/playwright-test/ui-mode-test-screencast.spec.ts @@ -35,7 +35,11 @@ test('should show screenshots', async ({ runUITest }) => { `, }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('2/2 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('2/2'); + await expect(statusLine.locator('.status-passed')).toHaveText('2'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByText('test 1', { exact: true }).click(); await expect(page.getByTestId('actions-tree')).toContainText('expect.toBeVisible'); diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts index c912038571bdd..faa9d48fa2ba7 100644 --- a/tests/playwright-test/ui-mode-test-setup.spec.ts +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -47,7 +47,11 @@ test('should run global setup and teardown', async ({ runUITest }, testInfo) => ` }, undefined, { additionalArgs: ['--output=foo'] }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Toggle output').click(); const output = page.getByTestId('output'); @@ -85,7 +89,11 @@ test('should teardown on sigint', async ({ runUITest, nodeVersion }) => { ` }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Toggle output').click(); await expect(page.getByTestId('output')).toContainText('from-global-setup'); @@ -335,7 +343,11 @@ for (const useWeb of [true, false]) { ` }, null, { useWeb }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await testProcess.kill('SIGINT'); await expect.poll(() => testProcess.outputLines()).toEqual([ 'from-global-teardown0000', @@ -368,7 +380,11 @@ test('should restart webserver on reload', async ({ runUITest }) => { ` }, { DEBUG: 'pw:webserver' }); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Toggle output').click(); await expect(page.getByTestId('output')).toContainText('[WebServer] listening'); @@ -381,5 +397,8 @@ test('should restart webserver on reload', async ({ runUITest }) => { await expect(page.getByTestId('output')).not.toContainText('set reuseExistingServer:true'); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); diff --git a/tests/playwright-test/ui-mode-test-shortcut.spec.ts b/tests/playwright-test/ui-mode-test-shortcut.spec.ts index 9dd73f3e902d1..8c992f1a58618 100644 --- a/tests/playwright-test/ui-mode-test-shortcut.spec.ts +++ b/tests/playwright-test/ui-mode-test-shortcut.spec.ts @@ -37,7 +37,11 @@ test('should run tests', async ({ runUITest }) => { await page.getByPlaceholder('Filter (e.g. text, @tag)').fill('test 3'); await page.keyboard.press('F5'); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByPlaceholder('Filter (e.g. text, @tag)').fill(''); // Only the filtered test was run. diff --git a/tests/playwright-test/ui-mode-test-watch.spec.ts b/tests/playwright-test/ui-mode-test-watch.spec.ts index 867da5cbf540f..3b2f95fd91196 100644 --- a/tests/playwright-test/ui-mode-test-watch.spec.ts +++ b/tests/playwright-test/ui-mode-test-watch.spec.ts @@ -128,7 +128,10 @@ test('should batch watch updates', async ({ runUITest, writeFiles }) => { 'd.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, }); - await expect(page.getByTestId('status-line')).toHaveText('4/4 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.locator('.status-passed')).toHaveText('4'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await expect.poll(dumpTestTree(page)).toBe(` ▼ ✅ a.test.ts 👁 @@ -167,7 +170,11 @@ test('should watch all', async ({ runUITest, writeFiles }) => { 'd.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, }); - await expect(page.getByTestId('status-line')).toHaveText('2/2 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('2/2'); + await expect(statusLine.locator('.status-passed')).toHaveText('2'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await expect.poll(dumpTestTree(page)).toBe(` ▼ ✅ a.test.ts @@ -210,7 +217,11 @@ test('should watch new file', async ({ runUITest, writeFiles }) => { 'b.test.ts': ` import { test } from '@playwright/test'; test('test', () => {});`, }); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await expect.poll(dumpTestTree(page)).toBe(` ▼ ◯ a.test.ts @@ -276,7 +287,11 @@ test('should queue watches', async ({ runUITest, writeFiles, createLatch }) => { await page.getByTitle('Watch all').click(); await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('Running 1/4 passed (25%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/4'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await writeFiles({ 'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, @@ -286,12 +301,18 @@ test('should queue watches', async ({ runUITest, writeFiles, createLatch }) => { // Now watches should not kick in. await new Promise(f => setTimeout(f, 1000)); - await expect(page.getByTestId('status-line')).toHaveText('Running 1/4 passed (25%)'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/4'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); // Allow test to finish and new watch to kick in. latch.open(); - await expect(page.getByTestId('status-line')).toHaveText('3/3 passed (100%)'); + await expect(statusLine.getByTestId('test-count')).toHaveText('3/3'); + await expect(statusLine.locator('.status-passed')).toHaveText('3'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should not watch output', async ({ runUITest }) => { @@ -316,7 +337,11 @@ test('should not watch output', async ({ runUITest }) => { await page.getByTitle('Run all').click(); - await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + const statusLine = page.getByTestId('status-line'); + await expect(statusLine.getByTestId('test-count')).toHaveText('1/1'); + await expect(statusLine.locator('.status-passed')).toHaveText('1'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); expect(commands).toContain('runTests'); expect(commands).not.toContain('listTests'); }); From 863cbb1699a6adb229a4a70e7d7d4b35aa8dbaa5 Mon Sep 17 00:00:00 2001 From: Pengoose Date: Tue, 13 May 2025 15:05:53 +0900 Subject: [PATCH 3/8] feat: add statusLine --- packages/trace-viewer/src/ui/statusLine.css | 44 ++++++++++++++++++ packages/trace-viewer/src/ui/statusLine.tsx | 49 +++++++++++++++++++++ packages/trace-viewer/src/ui/uiModeView.css | 36 --------------- packages/trace-viewer/src/ui/uiModeView.tsx | 30 ++++++------- 4 files changed, 108 insertions(+), 51 deletions(-) create mode 100644 packages/trace-viewer/src/ui/statusLine.css create mode 100644 packages/trace-viewer/src/ui/statusLine.tsx diff --git a/packages/trace-viewer/src/ui/statusLine.css b/packages/trace-viewer/src/ui/statusLine.css new file mode 100644 index 0000000000000..3213d66f93bcd --- /dev/null +++ b/packages/trace-viewer/src/ui/statusLine.css @@ -0,0 +1,44 @@ + +.status-line { + flex: auto; + align-items: center; + white-space: nowrap; + line-height: 22px; + display: flex; + flex-direction: row; + height: 30px; + overflow: hidden; + text-overflow: ellipsis; + padding-left: 5px; + gap: 8px; +} + +.status-line > * { + flex-shrink: 0; +} + +.status-line > div { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + user-select: none; +} + +.status-line-count { + display: flex; + align-items: center; + gap: 8px; +} + +.status-passed { + color: var(--vscode-debugIcon-restartForeground); +} + +.status-failed { + color: var(--vscode-list-errorForeground); +} + +.status-skipped { + color: var(--vscode-foreground); +} diff --git a/packages/trace-viewer/src/ui/statusLine.tsx b/packages/trace-viewer/src/ui/statusLine.tsx new file mode 100644 index 0000000000000..82523d0fee970 --- /dev/null +++ b/packages/trace-viewer/src/ui/statusLine.tsx @@ -0,0 +1,49 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@web/third_party/vscode/codicon.css'; +import '@web/common.css'; +import './statusLine.css'; +import React from 'react'; +import { clsx } from '@web/uiUtils'; +import { testStatusIcon } from './testUtils'; +interface StatusLineProps { + passed: number; + failed: number; + skipped: number; + total: number; + isRunning: boolean; +} + +export const StatusLine: React.FC = ({ passed, failed, skipped, total, isRunning }) => { + const count = passed + failed + skipped; + return ( +
+ + + {count}/{total} + +
+ {passed || 0} +
+
+ {failed || 0} +
+
+ {skipped || 0} +
+
+ ); +}; diff --git a/packages/trace-viewer/src/ui/uiModeView.css b/packages/trace-viewer/src/ui/uiModeView.css index d8ac35a06e3b0..6e122753cc4b5 100644 --- a/packages/trace-viewer/src/ui/uiModeView.css +++ b/packages/trace-viewer/src/ui/uiModeView.css @@ -82,28 +82,6 @@ margin-bottom: 30px; } -.status-line { - flex: auto; - white-space: nowrap; - line-height: 22px; - padding-left: 10px; - display: flex; - flex-direction: row; - align-items: center; - height: 30px; - overflow: hidden; - gap: 6px; -} - -.status-line > * { - flex-shrink: 0; -} - -.status-line > div { - display: flex; - align-items: center; -} - .ui-mode-sidebar input[type=search] { flex: auto; padding: 0 5px; @@ -114,17 +92,3 @@ color: var(--vscode-input-foreground); background-color: var(--vscode-input-background); } - -.status-passed { - color: var(--vscode-debugIcon-restartForeground); - gap: 2px; -} - -.status-failed { - color: var(--vscode-list-errorForeground); - gap: 3px; -} - -.status-skipped { - gap: 3px; -} diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index b7bd347f73995..5c5c0f8fc12c9 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -37,7 +37,7 @@ import { TestListView } from './uiModeTestListView'; import { TraceView } from './uiModeTraceView'; import { SettingsView } from './settingsView'; import { DefaultSettingsView } from './defaultSettingsView'; -import { testStatusIcon } from './testUtils'; +import { StatusLine } from './statusLine'; let xtermSize = { cols: 80, rows: 24 }; const xtermDataSource: XtermDataSource = { @@ -461,20 +461,20 @@ export const UIModeView: React.FC<{}> = ({ testModel={testModel} runTests={() => runTests('bounce-if-busy', visibleTestIds)} /> - {!isRunningTest && !progress &&
Tests
} - {!isRunningTest && progress &&
- {progress.passed + progress.failed + progress.skipped}/{progress.total} -
{progress.passed}
-
{progress.failed}
-
{progress.skipped}
-
} - {isRunningTest && progress &&
- - {progress.passed + progress.failed + progress.skipped}/{runningState.testIds.size} -
{progress.passed}
-
{progress.failed}
-
{progress.skipped}
-
} + {!progress ? () : ( + )} runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}> testServerConnection?.stopTests({})} disabled={!isRunningTest || isLoading}> { From 6356c0693fbf062f6aa4689892158adc4f2ed26e Mon Sep 17 00:00:00 2001 From: Pengoose Date: Tue, 13 May 2025 15:29:36 +0900 Subject: [PATCH 4/8] test: update regression test --- tests/playwright-test/ui-mode-test-run.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts index a5df0a898fc03..1cef41a2745b5 100644 --- a/tests/playwright-test/ui-mode-test-run.spec.ts +++ b/tests/playwright-test/ui-mode-test-run.spec.ts @@ -111,10 +111,10 @@ test('should show running progress', async ({ runUITest }) => { await expect(statusLine.locator('.status-failed')).toHaveText('0'); await expect(statusLine.locator('.status-skipped')).toHaveText('0'); await page.getByTitle('Reload').click(); - await expect(statusLine.getByTestId('test-count')).toBeHidden(); - await expect(statusLine.locator('.status-passed')).toBeHidden(); - await expect(statusLine.locator('.status-failed')).toBeHidden(); - await expect(statusLine.locator('.status-skipped')).toBeHidden(); + await expect(statusLine.getByTestId('test-count')).toHaveText('0/4'); + await expect(statusLine.locator('.status-passed')).toHaveText('0'); + await expect(statusLine.locator('.status-failed')).toHaveText('0'); + await expect(statusLine.locator('.status-skipped')).toHaveText('0'); }); test('should run on hover', async ({ runUITest }) => { From e5e85e0efd5930dc0647fd495c2218c410cf8020 Mon Sep 17 00:00:00 2001 From: Pengoose Date: Wed, 14 May 2025 00:56:45 +0900 Subject: [PATCH 5/8] chore: add status ellipsis --- packages/trace-viewer/src/ui/statusLine.css | 39 +++++++++++++-------- packages/trace-viewer/src/ui/statusLine.tsx | 22 ++++++------ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/trace-viewer/src/ui/statusLine.css b/packages/trace-viewer/src/ui/statusLine.css index 3213d66f93bcd..396699db3fde1 100644 --- a/packages/trace-viewer/src/ui/statusLine.css +++ b/packages/trace-viewer/src/ui/statusLine.css @@ -1,32 +1,41 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ .status-line { - flex: auto; - align-items: center; + display: block; + width: 100%; white-space: nowrap; - line-height: 22px; - display: flex; - flex-direction: row; - height: 30px; overflow: hidden; text-overflow: ellipsis; + line-height: 30px; + height: 30px; padding-left: 5px; - gap: 8px; -} - -.status-line > * { - flex-shrink: 0; + cursor: pointer; } -.status-line > div { - display: flex; +.status-line > span:not(:first-child) { + display: inline-flex; align-items: center; gap: 4px; - cursor: pointer; user-select: none; + margin-left: 8px; } .status-line-count { - display: flex; + display: inline-flex; align-items: center; gap: 8px; } diff --git a/packages/trace-viewer/src/ui/statusLine.tsx b/packages/trace-viewer/src/ui/statusLine.tsx index 82523d0fee970..5bcce65de4bee 100644 --- a/packages/trace-viewer/src/ui/statusLine.tsx +++ b/packages/trace-viewer/src/ui/statusLine.tsx @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import '@web/third_party/vscode/codicon.css'; import '@web/common.css'; import './statusLine.css'; import React from 'react'; import { clsx } from '@web/uiUtils'; import { testStatusIcon } from './testUtils'; + interface StatusLineProps { passed: number; failed: number; @@ -32,18 +34,18 @@ export const StatusLine: React.FC = ({ passed, failed, skipped, return (
- + {count}/{total} -
- {passed || 0} -
-
- {failed || 0} -
-
- {skipped || 0} -
+ + {passed || 0} + + + {failed || 0} + + + {skipped || 0} +
); }; From 9608c92c467cdc7d43584af51c78ea6a3ddd683c Mon Sep 17 00:00:00 2001 From: Pengoose Date: Wed, 14 May 2025 17:08:01 +0900 Subject: [PATCH 6/8] chore: use margin-left per FiltersView --- packages/trace-viewer/src/ui/statusLine.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trace-viewer/src/ui/statusLine.css b/packages/trace-viewer/src/ui/statusLine.css index 396699db3fde1..8676716ab42d0 100644 --- a/packages/trace-viewer/src/ui/statusLine.css +++ b/packages/trace-viewer/src/ui/statusLine.css @@ -22,7 +22,7 @@ text-overflow: ellipsis; line-height: 30px; height: 30px; - padding-left: 5px; + margin-left: 5px; cursor: pointer; } From f3b32091a36c40dd9056110e6bb15a7061e7890a Mon Sep 17 00:00:00 2001 From: Pengoose Date: Thu, 15 May 2025 14:44:01 +0900 Subject: [PATCH 7/8] chore: adjust status-line cursor(pointer > default) --- packages/trace-viewer/src/ui/statusLine.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trace-viewer/src/ui/statusLine.css b/packages/trace-viewer/src/ui/statusLine.css index 8676716ab42d0..e9d1a70f1a029 100644 --- a/packages/trace-viewer/src/ui/statusLine.css +++ b/packages/trace-viewer/src/ui/statusLine.css @@ -23,7 +23,7 @@ line-height: 30px; height: 30px; margin-left: 5px; - cursor: pointer; + cursor: default; } .status-line > span:not(:first-child) { From 84fc89ca9cacdd785d2aafb3da16f0a08bef250b Mon Sep 17 00:00:00 2001 From: Pengoose Date: Thu, 15 May 2025 14:51:58 +0900 Subject: [PATCH 8/8] chore: simplify rendering logic for statusline --- packages/trace-viewer/src/ui/uiModeView.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 5c5c0f8fc12c9..c1d697f3bf99c 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -461,20 +461,13 @@ export const UIModeView: React.FC<{}> = ({ testModel={testModel} runTests={() => runTests('bounce-if-busy', visibleTestIds)} /> - {!progress ? () : ( - )} runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}> testServerConnection?.stopTests({})} disabled={!isRunningTest || isLoading}> {