diff --git a/packages/trace-viewer/src/ui/uiModeView.css b/packages/trace-viewer/src/ui/uiModeView.css
index 3674e16e20c68..113ac87abc123 100644
--- a/packages/trace-viewer/src/ui/uiModeView.css
+++ b/packages/trace-viewer/src/ui/uiModeView.css
@@ -98,6 +98,10 @@
text-overflow: ellipsis;
}
+.status-line .status-line-failed {
+ color: var(--vscode-testing-iconFailed);
+}
+
.ui-mode-sidebar input[type=search] {
flex: auto;
padding: 0 5px;
diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx
index f1c595027c21f..ed0c67e196c6a 100644
--- a/packages/trace-viewer/src/ui/uiModeView.tsx
+++ b/packages/trace-viewer/src/ui/uiModeView.tsx
@@ -74,6 +74,26 @@ function escapeRegex(text: string) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
+function renderStatusLine(progress: TeleSuiteUpdaterProgress, total: number, isRunning: boolean) {
+ const finished = progress.passed + progress.failed + progress.skipped;
+ const pct = total ? (finished / total) * 100 | 0 : 0;
+ const counts: React.ReactNode[] = [];
+ if (progress.passed > 0)
+ counts.push(`${progress.passed} passed`);
+ if (progress.failed > 0)
+ counts.push({progress.failed} failed);
+ if (progress.skipped > 0)
+ counts.push(`${progress.skipped} skipped`);
+ return
+
+ {isRunning && 'Running '}
+ {finished}/{total} ({pct}%)
+ {counts.length > 0 && ' — '}
+ {counts.map((count, i) => {i > 0 ? ', ' : ''}{count})}
+
+
;
+}
+
export const UIModeView: React.FC<{}> = ({
}) => {
const [filterText, setFilterText] = React.useState('');
@@ -486,12 +506,7 @@ export const UIModeView: React.FC<{}> = ({
runTests={runVisibleTests} />
{!isRunningTest && !progress && Tests
}
- {!isRunningTest && progress &&
-
{progress.passed}/{progress.total} passed ({(progress.passed / progress.total) * 100 | 0}%)
-
}
- {isRunningTest && progress &&
-
Running {progress.passed}/{runningState.testIds.size} passed ({(progress.passed / runningState.testIds.size) * 100 | 0}%)
-
}
+ {progress && renderStatusLine(progress, isRunningTest ? runningState.testIds.size : progress.total, !!isRunningTest)}
testServerConnection?.stopTests({})} disabled={!isRunningTest || isLoading} testId={'stop-button'}>
{
diff --git a/tests/playwright-test/ui-mode-metadata.spec.ts b/tests/playwright-test/ui-mode-metadata.spec.ts
index bfbbba08a54f2..eda38bff3c622 100644
--- a/tests/playwright-test/ui-mode-metadata.spec.ts
+++ b/tests/playwright-test/ui-mode-metadata.spec.ts
@@ -41,7 +41,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
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..2ff20556292bb 100644
--- a/tests/playwright-test/ui-mode-test-annotations.spec.ts
+++ b/tests/playwright-test/ui-mode-test-annotations.spec.ts
@@ -32,7 +32,7 @@ test('should display annotations', async ({ runUITest }) => {
`,
});
await page.getByTitle('Run all').click();
- await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
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 deabe4f6c9196..29f62006ae566 100644
--- a/tests/playwright-test/ui-mode-test-attachments.spec.ts
+++ b/tests/playwright-test/ui-mode-test-attachments.spec.ts
@@ -33,7 +33,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByText('Attachments').click();
await page.locator('.tab-attachments').getByText('text attachment').click();
@@ -69,7 +69,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByText('Attachments').click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'download' }).click();
@@ -89,7 +89,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByText('Attachments').click();
await page.getByText('note', { exact: true }).click();
const downloadPromise = page.waitForEvent('download');
@@ -116,7 +116,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByText('Attachments').click();
const attachmentsPane = page.locator('.attachments-tab');
@@ -162,7 +162,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByRole('tab', { name: 'Attachments' }).click();
const panel = page.getByRole('tabpanel', { name: 'Attachments' });
@@ -193,7 +193,7 @@ test('attachments from inside boxed fixture should be visible', { annotation: {
}, { reporter: 'line' }, {});
await page.getByText('my test').click();
await page.getByTitle('Run all').click();
- await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByRole('treeitem', { name: 'attach "my attachment"' }).getByLabel('Open Attachment').click();
await expect(page.getByRole('tabpanel', { name: 'Attachments' })).toMatchAriaSnapshot(`
diff --git a/tests/playwright-test/ui-mode-test-output.spec.ts b/tests/playwright-test/ui-mode-test-output.spec.ts
index f13c515743911..e6e548b7e2792 100644
--- a/tests/playwright-test/ui-mode-test-output.spec.ts
+++ b/tests/playwright-test/ui-mode-test-output.spec.ts
@@ -254,7 +254,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
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 724a8eb3517e2..0f3f36a31c04f 100644
--- a/tests/playwright-test/ui-mode-test-run.spec.ts
+++ b/tests/playwright-test/ui-mode-test-run.spec.ts
@@ -81,7 +81,7 @@ test('should run visible', async ({ runUITest }) => {
- treeitem "[icon-circle-slash] skipped"
`);
- await expect(page.getByTestId('status-line')).toHaveText('4/8 passed (50%)');
+ await expect(page.getByTestId('status-line')).toHaveText('8/8 (100%) — 4 passed, 3 failed, 1 skipped');
});
test('should show running progress', async ({ runUITest }) => {
@@ -96,9 +96,9 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('Running 1/4 (25%) — 1 passed');
await page.getByTestId('stop-button').click();
- await expect(page.getByTestId('status-line')).toHaveText('1/4 passed (25%)');
+ await expect(page.getByTestId('status-line')).toHaveText('2/4 (50%) — 1 passed, 1 skipped');
await page.getByTitle('Reload').click();
await expect(page.getByTestId('status-line')).toBeHidden();
});
@@ -491,7 +491,7 @@ test('should show time', async ({ runUITest }) => {
- treeitem "[icon-circle-slash] skipped"
`);
- await expect(page.getByTestId('status-line')).toHaveText('4/8 passed (50%)');
+ await expect(page.getByTestId('status-line')).toHaveText('8/8 (100%) — 4 passed, 3 failed, 1 skipped');
});
test('should show test.fail as passing', async ({ runUITest }) => {
@@ -522,7 +522,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
});
test('should ignore repeatEach', async ({ runUITest }) => {
@@ -558,7 +558,7 @@ test('should ignore repeatEach', async ({ runUITest }) => {
- treeitem ${/\[icon-check\] should pass/}
`);
- await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
});
test('should remove output folder before test run', async ({ runUITest }) => {
@@ -593,7 +593,7 @@ 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(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByTitle('Run all').click();
await expect.poll(dumpTestTree(page)).toBe(`
@@ -608,7 +608,7 @@ 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(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
});
test('should show proper total when using deps', async ({ runUITest }) => {
@@ -660,7 +660,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByTitle('run @chromium').dblclick();
await expect.poll(dumpTestTree(page)).toBe(`
@@ -680,7 +680,7 @@ 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(page.getByTestId('status-line')).toHaveText('2/2 (100%) — 2 passed');
});
test('should respect --tsconfig option', {
@@ -746,7 +746,7 @@ test('should respect --tsconfig option', {
- treeitem ${/\[icon-check\] test/}
`);
- await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
});
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 4affc726bc313..7856e4843ecd6 100644
--- a/tests/playwright-test/ui-mode-test-screencast.spec.ts
+++ b/tests/playwright-test/ui-mode-test-screencast.spec.ts
@@ -35,7 +35,7 @@ test('should show screenshots', async ({ runUITest }) => {
`,
});
await page.getByTitle('Run all').click();
- await expect(page.getByTestId('status-line')).toHaveText('2/2 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('2/2 (100%) — 2 passed');
await page.getByText('test 1', { exact: true }).click();
await expect(page.getByTestId('actions-tree')).toContainText('Expect');
diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts
index 0fcac731b0934..5738a3ec22af5 100644
--- a/tests/playwright-test/ui-mode-test-setup.spec.ts
+++ b/tests/playwright-test/ui-mode-test-setup.spec.ts
@@ -47,7 +47,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByTitle('Toggle output').click();
const output = page.getByTestId('output');
@@ -85,7 +85,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByTitle('Toggle output').click();
await expect(page.getByTestId('output')).toContainText('from-global-setup');
@@ -335,7 +335,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await testProcess.kill('SIGINT');
await expect.poll(() => testProcess.outputLines()).toEqual([
'from-global-teardown0000',
@@ -368,7 +368,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByTitle('Toggle output').click();
await expect(page.getByTestId('output')).toContainText('[WebServer] listening');
@@ -381,5 +381,5 @@ 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(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
});
diff --git a/tests/playwright-test/ui-mode-test-shortcut.spec.ts b/tests/playwright-test/ui-mode-test-shortcut.spec.ts
index a7b6852c19303..bc8e85933cccd 100644
--- a/tests/playwright-test/ui-mode-test-shortcut.spec.ts
+++ b/tests/playwright-test/ui-mode-test-shortcut.spec.ts
@@ -37,7 +37,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByPlaceholder('Filter (e.g. text, @tag)').fill('');
// Only the filtered test was run.
diff --git a/tests/playwright-test/ui-mode-test-source.spec.ts b/tests/playwright-test/ui-mode-test-source.spec.ts
index e9985904fa185..a22a142587863 100644
--- a/tests/playwright-test/ui-mode-test-source.spec.ts
+++ b/tests/playwright-test/ui-mode-test-source.spec.ts
@@ -207,7 +207,7 @@ test('should keep showing source when test is pending', async ({ runUITest }, te
await page.getByTitle('Run all').click();
await page.getByTestId('test-tree').getByText('second').click();
- await expect(page.getByTestId('status-line')).toHaveText('Running 1/3 passed (33%)');
+ await expect(page.getByTestId('status-line')).toHaveText('Running 1/3 (33%) — 1 passed');
await expect(page.getByTestId('source-code').locator('.source-tab-file-name')).toHaveText('a.test.ts');
await expect(page.locator('.CodeMirror .source-line-running')).toHaveText(`7 test('second', () => {});`);
});
diff --git a/tests/playwright-test/ui-mode-test-watch.spec.ts b/tests/playwright-test/ui-mode-test-watch.spec.ts
index 851eefd804a93..951919991cba2 100644
--- a/tests/playwright-test/ui-mode-test-watch.spec.ts
+++ b/tests/playwright-test/ui-mode-test-watch.spec.ts
@@ -128,7 +128,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('4/4 (100%) — 4 passed');
await expect.poll(dumpTestTree(page)).toBe(`
▼ ✅ a.test.ts 👁
@@ -168,7 +168,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('2/2 (100%) — 2 passed');
await expect.poll(dumpTestTree(page)).toBe(`
▼ ✅ a.test.ts
@@ -211,7 +211,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ a.test.ts
@@ -277,7 +277,7 @@ 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%)');
+ await expect(page.getByTestId('status-line')).toHaveText('Running 1/4 (25%) — 1 passed');
await writeFiles({
'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
@@ -287,12 +287,12 @@ 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(page.getByTestId('status-line')).toHaveText('Running 1/4 (25%) — 1 passed');
// 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(page.getByTestId('status-line')).toHaveText('3/3 (100%) — 3 passed');
});
test('should not watch output', async ({ runUITest }) => {
@@ -317,7 +317,7 @@ test('should not watch output', async ({ runUITest }) => {
await page.getByTitle('Run all').click();
- await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
expect(commands).toContain('runTests');
expect(commands).not.toContain('listTests');
});
diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts
index 13f69d8964cfb..e890f35e83e8a 100644
--- a/tests/playwright-test/ui-mode-trace.spec.ts
+++ b/tests/playwright-test/ui-mode-trace.spec.ts
@@ -744,7 +744,7 @@ test('should be able to create and dispose APIRequestContext inside Promise.all'
await page.getByText('create api request contexts').dblclick();
await expect(page.getByTestId('workbench-run-status')).toContainText('Passed');
- await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
+ await expect(page.getByTestId('status-line')).toHaveText('1/1 (100%) — 1 passed');
await page.getByText('Errors', { exact: true }).click();
await expect(page.locator('.tab-errors')).toHaveText('No errors');