diff --git a/packages/playwright-test/src/project.ts b/packages/playwright-test/src/project.ts index c9c871d4be..8de0b8486b 100644 --- a/packages/playwright-test/src/project.ts +++ b/packages/playwright-test/src/project.ts @@ -18,6 +18,7 @@ import type { FullProject, Fixtures, FixturesWithLocation } from './types'; import { Suite, TestCase } from './test'; import { FixturePool, isFixtureOption } from './fixtures'; import { TestTypeImpl } from './testType'; +import { calculateSha1 } from 'playwright-core/lib/utils/utils'; export class ProjectImpl { config: FullProject; @@ -64,19 +65,21 @@ export class ProjectImpl { return this.testPools.get(test)!; } - private _cloneEntries(from: Suite, to: Suite, repeatEachIndex: number, filter: (test: TestCase) => boolean): boolean { + private _cloneEntries(from: Suite, to: Suite, repeatEachIndex: number, filter: (test: TestCase) => boolean, relativeTitlePath: string): boolean { for (const entry of from._entries) { if (entry instanceof Suite) { const suite = entry._clone(); to._addSuite(suite); - if (!this._cloneEntries(entry, suite, repeatEachIndex, filter)) { + if (!this._cloneEntries(entry, suite, repeatEachIndex, filter, relativeTitlePath + ' ' + suite.title)) { to._entries.pop(); to.suites.pop(); } } else { const test = entry._clone(); test.retries = this.config.retries; - test._id = `${entry._ordinalInFile}@${entry._requireFile}#run${this.index}-repeat${repeatEachIndex}`; + // We rely upon relative paths being unique. + // See `getClashingTestsPerSuite()` in `runner.ts`. + test._id = `${calculateSha1(relativeTitlePath + ' ' + entry.title)}@${entry._requireFile}#run${this.index}-repeat${repeatEachIndex}`; test.repeatEachIndex = repeatEachIndex; test._projectIndex = this.index; to._addTest(test); @@ -97,7 +100,7 @@ export class ProjectImpl { cloneFileSuite(suite: Suite, repeatEachIndex: number, filter: (test: TestCase) => boolean): Suite | undefined { const result = suite._clone(); - return this._cloneEntries(suite, result, repeatEachIndex, filter) ? result : undefined; + return this._cloneEntries(suite, result, repeatEachIndex, filter, '') ? result : undefined; } private resolveFixtures(testType: TestTypeImpl, configUse: Fixtures): FixturesWithLocation[] { diff --git a/packages/playwright-test/src/test.ts b/packages/playwright-test/src/test.ts index 39f1985e47..fcba982911 100644 --- a/packages/playwright-test/src/test.ts +++ b/packages/playwright-test/src/test.ts @@ -128,17 +128,15 @@ export class TestCase extends Base implements reporterTypes.TestCase { retries = 0; repeatEachIndex = 0; - _ordinalInFile: number; _testType: TestTypeImpl; _id = ''; _workerHash = ''; _pool: FixturePool | undefined; _projectIndex = 0; - constructor(title: string, fn: Function, ordinalInFile: number, testType: TestTypeImpl, location: Location) { + constructor(title: string, fn: Function, testType: TestTypeImpl, location: Location) { super(title); this.fn = fn; - this._ordinalInFile = ordinalInFile; this._testType = testType; this.location = location; } @@ -166,7 +164,7 @@ export class TestCase extends Base implements reporterTypes.TestCase { } _clone(): TestCase { - const test = new TestCase(this.title, this.fn, this._ordinalInFile, this._testType, this.location); + const test = new TestCase(this.title, this.fn, this._testType, this.location); test._only = this._only; test._requireFile = this._requireFile; test.expectedStatus = this.expectedStatus; diff --git a/packages/playwright-test/src/testType.ts b/packages/playwright-test/src/testType.ts index 5277343187..5be9a5e8b6 100644 --- a/packages/playwright-test/src/testType.ts +++ b/packages/playwright-test/src/testType.ts @@ -81,7 +81,7 @@ export class TestTypeImpl { private _createTest(type: 'default' | 'only' | 'skip' | 'fixme', location: Location, title: string, fn: Function) { throwIfRunningInsideJest(); const suite = this._ensureCurrentSuite(location, 'test()'); - const test = new TestCase(title, fn, nextOrdinalInFile(suite._requireFile), this, location); + const test = new TestCase(title, fn, this, location); test._requireFile = suite._requireFile; suite._addTest(test); @@ -243,11 +243,4 @@ function throwIfRunningInsideJest() { } } -const countByFile = new Map(); -function nextOrdinalInFile(file: string) { - const ordinalInFile = countByFile.get(file) || 0; - countByFile.set(file, ordinalInFile + 1); - return ordinalInFile; -} - export const rootTestType = new TestTypeImpl([]); diff --git a/tests/playwright-test/loader.spec.ts b/tests/playwright-test/loader.spec.ts index d596947b88..2280c602e7 100644 --- a/tests/playwright-test/loader.spec.ts +++ b/tests/playwright-test/loader.spec.ts @@ -407,3 +407,52 @@ test('should filter stack even without default Error.prepareStackTrace', async ( expect(stackLines.length).toBe(1); }); +test('should work with cross-imports - 1', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'test1.spec.ts': ` + const { test } = pwt; + test('test 1', async ({}) => { + await new Promise(x => setTimeout(x, 500)); + console.log('running TEST-1'); + }); + `, + 'test2.spec.ts': ` + import * as _ from './test1.spec'; + const { test } = pwt; + test('test 2', async ({}) => { + await new Promise(x => setTimeout(x, 500)); + console.log('running TEST-2'); + }); + ` + }, { workers: 2 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(2); + expect(result.failed).toBe(0); + expect(result.output).toContain('TEST-1'); + expect(result.output).toContain('TEST-2'); +}); + +test('should work with cross-imports - 2', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'test1.spec.ts': ` + const { test } = pwt; + import * as _ from './test2.spec'; + test('test 1', async ({}) => { + await new Promise(x => setTimeout(x, 500)); + console.log('running TEST-1'); + }); + `, + 'test2.spec.ts': ` + const { test } = pwt; + test('test 2', async ({}) => { + await new Promise(x => setTimeout(x, 500)); + console.log('running TEST-2'); + }); + ` + }, { workers: 2, reporter: 'list' }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(2); + expect(result.failed).toBe(0); + expect(result.output).toContain('TEST-1'); + expect(result.output).toContain('TEST-2'); +});