diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index b78006cae9..c7dfb2e605 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -129,7 +129,7 @@ export type JsonEvent = { export class TeleReporterReceiver { private _rootSuite: TeleSuite; private _pathSeparator: string; - private _reporter: ReporterV2; + private _reporter: Partial; private _tests = new Map(); private _rootDir!: string; private _listOnly = false; @@ -139,7 +139,7 @@ export class TeleReporterReceiver { private _config!: FullConfig; private _stringPool = new StringInternPool(); - constructor(pathSeparator: string, reporter: ReporterV2, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) { + constructor(pathSeparator: string, reporter: Partial, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) { this._rootSuite = new TeleSuite('', 'root'); this._pathSeparator = pathSeparator; this._reporter = reporter; @@ -199,7 +199,7 @@ export class TeleReporterReceiver { this._rootDir = this._reportConfig?.rootDir || config.rootDir; this._listOnly = config.listOnly; this._config = this._parseConfig(config); - this._reporter.onConfigure(this._config); + this._reporter.onConfigure?.(this._config); } private _onProject(project: JsonProject) { diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 49d4e03feb..d1c362a231 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -128,7 +128,7 @@ export const UIModeView: React.FC<{}> = ({ setTestModel({ config, rootSuite, loadErrors }); setProjectFilters(new Map(projectFilters)); if (runningState && newProgress) - setProgress({ ...newProgress, total: runningState.testIds.size }); + setProgress(newProgress); else if (!newProgress) setProgress(undefined); }, [projectFilters, runningState]); @@ -157,7 +157,7 @@ export const UIModeView: React.FC<{}> = ({ const time = ' [' + new Date().toLocaleTimeString() + ']'; xtermDataSource.write('\x1B[2m—'.repeat(Math.max(0, xtermSize.cols - time.length)) + time + '\x1B[22m'); - setProgress({ total: testIds.size, passed: 0, failed: 0, skipped: 0 }); + setProgress({ total: 0, passed: 0, failed: 0, skipped: 0 }); setRunningState({ testIds }); await sendMessage('run', { testIds: [...testIds], projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p) }); @@ -614,6 +614,8 @@ const TraceView: React.FC<{ }; let receiver: TeleReporterReceiver | undefined; +let lastRunReceiver: TeleReporterReceiver | undefined; +let lastRunTestCount: number; let throttleTimer: NodeJS.Timeout | undefined; let throttleData: { config: FullConfig, rootSuite: Suite, loadErrors: TestError[], progress: Progress } | undefined; @@ -638,6 +640,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { let rootSuite: Suite; const loadErrors: TestError[] = []; const progress: Progress = { + total: 0, passed: 0, failed: 0, skipped: 0, @@ -648,11 +651,22 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { onConfigure: (c: FullConfig) => { config = c; + // TeleReportReceiver is merging everything into a single suite, so when we + // run one test, we still get many tests via rootSuite.allTests().length. + // To work around that, have a dedicated per-run receiver that will only have + // suite for a single test run, and hence will have correct total. + lastRunReceiver = new TeleReporterReceiver(pathSeparator, { + onBegin: (suite: Suite) => { + lastRunTestCount = suite.allTests().length; + lastRunReceiver = undefined; + } + }, false); }, onBegin: (suite: Suite) => { if (!rootSuite) rootSuite = suite; + progress.total = lastRunTestCount; progress.passed = 0; progress.failed = 0; progress.skipped = 0; @@ -729,6 +743,9 @@ const dispatchEvent = (method: string, params?: any) => { return; } + // The order of receiver dispatches matters here, we want to assign `lastRunTestCount` + // before we use it. + lastRunReceiver?.dispatch({ method, params })?.catch(() => {}); receiver?.dispatch({ method, params })?.catch(() => {}); }; @@ -764,6 +781,7 @@ const collectTestIds = (treeItem?: TreeItem): Set => { }; type Progress = { + total: number; passed: number; failed: number; skipped: number; diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts index a8e1f8842e..2d41aa18d4 100644 --- a/tests/playwright-test/ui-mode-test-run.spec.ts +++ b/tests/playwright-test/ui-mode-test-run.spec.ts @@ -394,3 +394,51 @@ test('should remove output folder before test run', async ({ runUITest }) => { `); await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); }); + +test('should show proper total when using deps', async ({ runUITest }) => { + const { page } = await runUITest({ + 'playwright.config.ts': ` + import { defineConfig } from "@playwright/test"; + export default defineConfig({ + projects: [ + { name: "setup", grep: /@setup/, }, + { name: "chromium", grep: /@chromium/, dependencies: ["setup"], }, + ], + }); + `, + 'a.test.ts': ` + import { expect, test } from "@playwright/test"; + test("run @setup", async ({ page }) => { + console.log("Test setup executed"); + }); + test("run @chromium", async ({ page }) => { + console.log("Test chromium executed"); + }); + `, + }); + + + await page.getByText('Status:').click(); + await page.getByLabel('setup').setChecked(true); + await page.getByLabel('chromium').setChecked(true); + + await expect.poll(dumpTestTree(page)).toContain(` + ▼ ◯ a.test.ts + `); + + await page.getByTitle('run @setup').dblclick(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ a.test.ts + ✅ run @setup <= + ◯ run @chromium + `); + await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + + await page.getByTitle('run @chromium').dblclick(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ a.test.ts + ✅ run @setup + ✅ run @chromium <= + `); + await expect(page.getByTestId('status-line')).toHaveText('2/2 passed (100%)'); +});