Skip to content

Commit

Permalink
fix: rebuild project tree from scratch when listing tests (#30407)
Browse files Browse the repository at this point in the history
Instead of filtering tests assuming there are no two projects with same
name we always rebuild test tree from scratch and restore previos test
results in the list mode.

Fixes #30396
  • Loading branch information
yury-s committed Apr 18, 2024
1 parent a1b3332 commit 8c181f7
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 34 deletions.
41 changes: 11 additions & 30 deletions packages/playwright/src/isomorphic/teleReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ export class TeleReporterReceiver {
this._reporter = reporter;
}

reset() {
this._rootSuite._entries = [];
this._tests.clear();
}

dispatch(message: JsonEvent): Promise<void> | void {
const { method, params } = message;
if (method === 'onConfigure') {
Expand Down Expand Up @@ -206,35 +211,6 @@ export class TeleReporterReceiver {
projectSuite._project = this._parseProject(project);
for (const suite of project.suites)
this._mergeSuiteInto(suite, projectSuite);

// Remove deleted tests when listing. Empty suites will be auto-filtered
// in the UI layer.
if (this.isListing) {
const testIds = new Set<string>();
const collectIds = (suite: JsonSuite) => {
suite.entries.forEach(entry => {
if ('testId' in entry)
testIds.add(entry.testId);
else
collectIds(entry);
});
};
project.suites.forEach(collectIds);

const filterTests = (suite: TeleSuite) => {
suite._entries = suite._entries.filter(entry => {
if (entry.type === 'test') {
if (testIds.has(entry.id))
return true;
this._tests.delete(entry.id);
return false;
}
filterTests(entry);
return true;
});
};
filterTests(projectSuite);
}
}

private _onBegin() {
Expand Down Expand Up @@ -545,6 +521,11 @@ export class TeleTestCase implements reporterTypes.TestCase {
this._resultsMap.clear();
}

_restoreResults(snapshot: Map<string, TeleTestResult>) {
this.results = [...snapshot.values()];
this._resultsMap = snapshot;
}

_createTestResult(id: string): TeleTestResult {
const result = new TeleTestResult(this.results.length);
this.results.push(result);
Expand Down Expand Up @@ -585,7 +566,7 @@ class TeleTestStep implements reporterTypes.TestStep {
}
}

class TeleTestResult implements reporterTypes.TestResult {
export class TeleTestResult implements reporterTypes.TestResult {
retry: reporterTypes.TestResult['retry'];
parallelIndex: reporterTypes.TestResult['parallelIndex'] = -1;
workerIndex: reporterTypes.TestResult['workerIndex'] = -1;
Expand Down
22 changes: 18 additions & 4 deletions packages/trace-viewer/src/ui/teleSuiteUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver';
import type { TeleTestCase, TeleTestResult } from '@testIsomorphic/teleReceiver';
import { statusEx } from '@testIsomorphic/testTree';
import type { ReporterV2 } from 'playwright/src/reporters/reporterV2';
import type * as reporterTypes from 'playwright/types/testReporter';
Expand All @@ -27,7 +28,7 @@ export type TeleSuiteUpdaterOptions = {
};

export class TeleSuiteUpdater {
rootSuite: reporterTypes.Suite | undefined;
rootSuite: TeleSuite | undefined;
config: reporterTypes.FullConfig | undefined;
readonly loadErrors: reporterTypes.TestError[] = [];
readonly progress: Progress = {
Expand All @@ -41,6 +42,7 @@ export class TeleSuiteUpdater {
private _lastRunReceiver: TeleReporterReceiver | undefined;
private _lastRunTestCount = 0;
private _options: TeleSuiteUpdaterOptions;
private _testResultsSnapshot: Map<string, Map<string, TeleTestResult>> | undefined;

constructor(options: TeleSuiteUpdaterOptions) {
this._receiver = new TeleReporterReceiver(this._createReporter(), {
Expand Down Expand Up @@ -76,7 +78,16 @@ export class TeleSuiteUpdater {

onBegin: (suite: reporterTypes.Suite) => {
if (!this.rootSuite)
this.rootSuite = suite;
this.rootSuite = suite as TeleSuite;
// As soon as new test tree is built add previous results.
if (this._testResultsSnapshot) {
(this.rootSuite.allTests() as TeleTestCase[]).forEach(test => {
const results = this._testResultsSnapshot!.get(test.id);
if (results)
test._restoreResults(results);
});
this._testResultsSnapshot = undefined;
}
this.progress.total = this._lastRunTestCount;
this.progress.passed = 0;
this.progress.failed = 0;
Expand Down Expand Up @@ -123,10 +134,13 @@ export class TeleSuiteUpdater {
}

processListReport(report: any[]) {
this._receiver.isListing = true;
// Save test results and reset all projects, the results will be restored after
// new project structure is built.
if (this.rootSuite)
this._testResultsSnapshot = new Map((this.rootSuite.allTests() as TeleTestCase[]).map(test => [test.id, test._resultsMap]));
this._receiver.reset();
for (const message of report)
this._receiver.dispatch(message);
this._receiver.isListing = false;
}

processTestReportEvent(message: any) {
Expand Down
54 changes: 54 additions & 0 deletions tests/playwright-test/ui-mode-test-tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,60 @@ test('should list tests', async ({ runUITest }) => {
`);
});

test('should list all tests from projects with clashing names', async ({ runUITest }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30396' });
const { page } = await runUITest({
'playwright.config.ts': `
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'proj-uno',
testDir: './foo',
},
{
name: 'proj-dos',
testDir: './foo',
},
{
name: 'proj-uno',
testDir: './bar',
},
{
name: 'proj-dos',
testDir: './bar',
},
]
});
`,
'foo/a.test.ts': `
import { test, expect } from '@playwright/test';
test('one', () => {});
test('two', () => {});
`,
'bar/b.test.ts': `
import { test, expect } from '@playwright/test';
test('three', () => {});
test('four', () => {});
`,
});
await page.getByTestId('test-tree').getByText('b.test.ts').click();
await page.keyboard.press('ArrowRight');
await page.getByTestId('test-tree').getByText('a.test.ts').click();
await page.keyboard.press('ArrowRight');
await expect.poll(dumpTestTree(page)).toBe(`
▼ ◯ bar
▼ ◯ b.test.ts
◯ three
◯ four
▼ ◯ foo
▼ ◯ a.test.ts <=
◯ one
◯ two
`);
});

test('should traverse up/down', async ({ runUITest }) => {
const { page } = await runUITest(basicTestTree);
await page.getByText('a.test.ts').click();
Expand Down

0 comments on commit 8c181f7

Please sign in to comment.