fix: rebuild project tree from scratch when listing tests (#30407)
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 https://github.com/microsoft/playwright/issues/30396
This commit is contained in:
parent
a1b3332e54
commit
8c181f7e2b
|
|
@ -146,6 +146,11 @@ export class TeleReporterReceiver {
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this._rootSuite._entries = [];
|
||||||
|
this._tests.clear();
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(message: JsonEvent): Promise<void> | void {
|
dispatch(message: JsonEvent): Promise<void> | void {
|
||||||
const { method, params } = message;
|
const { method, params } = message;
|
||||||
if (method === 'onConfigure') {
|
if (method === 'onConfigure') {
|
||||||
|
|
@ -206,35 +211,6 @@ export class TeleReporterReceiver {
|
||||||
projectSuite._project = this._parseProject(project);
|
projectSuite._project = this._parseProject(project);
|
||||||
for (const suite of project.suites)
|
for (const suite of project.suites)
|
||||||
this._mergeSuiteInto(suite, projectSuite);
|
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() {
|
private _onBegin() {
|
||||||
|
|
@ -545,6 +521,11 @@ export class TeleTestCase implements reporterTypes.TestCase {
|
||||||
this._resultsMap.clear();
|
this._resultsMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_restoreResults(snapshot: Map<string, TeleTestResult>) {
|
||||||
|
this.results = [...snapshot.values()];
|
||||||
|
this._resultsMap = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
_createTestResult(id: string): TeleTestResult {
|
_createTestResult(id: string): TeleTestResult {
|
||||||
const result = new TeleTestResult(this.results.length);
|
const result = new TeleTestResult(this.results.length);
|
||||||
this.results.push(result);
|
this.results.push(result);
|
||||||
|
|
@ -585,7 +566,7 @@ class TeleTestStep implements reporterTypes.TestStep {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TeleTestResult implements reporterTypes.TestResult {
|
export class TeleTestResult implements reporterTypes.TestResult {
|
||||||
retry: reporterTypes.TestResult['retry'];
|
retry: reporterTypes.TestResult['retry'];
|
||||||
parallelIndex: reporterTypes.TestResult['parallelIndex'] = -1;
|
parallelIndex: reporterTypes.TestResult['parallelIndex'] = -1;
|
||||||
workerIndex: reporterTypes.TestResult['workerIndex'] = -1;
|
workerIndex: reporterTypes.TestResult['workerIndex'] = -1;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver';
|
import { TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver';
|
||||||
|
import type { TeleTestCase, TeleTestResult } from '@testIsomorphic/teleReceiver';
|
||||||
import { statusEx } from '@testIsomorphic/testTree';
|
import { statusEx } from '@testIsomorphic/testTree';
|
||||||
import type { ReporterV2 } from 'playwright/src/reporters/reporterV2';
|
import type { ReporterV2 } from 'playwright/src/reporters/reporterV2';
|
||||||
import type * as reporterTypes from 'playwright/types/testReporter';
|
import type * as reporterTypes from 'playwright/types/testReporter';
|
||||||
|
|
@ -27,7 +28,7 @@ export type TeleSuiteUpdaterOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TeleSuiteUpdater {
|
export class TeleSuiteUpdater {
|
||||||
rootSuite: reporterTypes.Suite | undefined;
|
rootSuite: TeleSuite | undefined;
|
||||||
config: reporterTypes.FullConfig | undefined;
|
config: reporterTypes.FullConfig | undefined;
|
||||||
readonly loadErrors: reporterTypes.TestError[] = [];
|
readonly loadErrors: reporterTypes.TestError[] = [];
|
||||||
readonly progress: Progress = {
|
readonly progress: Progress = {
|
||||||
|
|
@ -41,6 +42,7 @@ export class TeleSuiteUpdater {
|
||||||
private _lastRunReceiver: TeleReporterReceiver | undefined;
|
private _lastRunReceiver: TeleReporterReceiver | undefined;
|
||||||
private _lastRunTestCount = 0;
|
private _lastRunTestCount = 0;
|
||||||
private _options: TeleSuiteUpdaterOptions;
|
private _options: TeleSuiteUpdaterOptions;
|
||||||
|
private _testResultsSnapshot: Map<string, Map<string, TeleTestResult>> | undefined;
|
||||||
|
|
||||||
constructor(options: TeleSuiteUpdaterOptions) {
|
constructor(options: TeleSuiteUpdaterOptions) {
|
||||||
this._receiver = new TeleReporterReceiver(this._createReporter(), {
|
this._receiver = new TeleReporterReceiver(this._createReporter(), {
|
||||||
|
|
@ -76,7 +78,16 @@ export class TeleSuiteUpdater {
|
||||||
|
|
||||||
onBegin: (suite: reporterTypes.Suite) => {
|
onBegin: (suite: reporterTypes.Suite) => {
|
||||||
if (!this.rootSuite)
|
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.total = this._lastRunTestCount;
|
||||||
this.progress.passed = 0;
|
this.progress.passed = 0;
|
||||||
this.progress.failed = 0;
|
this.progress.failed = 0;
|
||||||
|
|
@ -123,10 +134,13 @@ export class TeleSuiteUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
processListReport(report: any[]) {
|
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)
|
for (const message of report)
|
||||||
this._receiver.dispatch(message);
|
this._receiver.dispatch(message);
|
||||||
this._receiver.isListing = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processTestReportEvent(message: any) {
|
processTestReportEvent(message: any) {
|
||||||
|
|
|
||||||
|
|
@ -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 }) => {
|
test('should traverse up/down', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest(basicTestTree);
|
const { page } = await runUITest(basicTestTree);
|
||||||
await page.getByText('a.test.ts').click();
|
await page.getByText('a.test.ts').click();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue