diff --git a/packages/playwright/src/runner/loadUtils.ts b/packages/playwright/src/runner/loadUtils.ts index faa1612fb6..5d0be6f943 100644 --- a/packages/playwright/src/runner/loadUtils.ts +++ b/packages/playwright/src/runner/loadUtils.ts @@ -218,11 +218,12 @@ async function filterTestGroups(config: FullConfigInternal, testGroups: TestGrou let filteredTestGroups = testGroups.map(group => ({ tests: group.tests.map(test => test as reporterTypes.TestCase) })); const allTests = new Set(filteredTestGroups.flatMap(group => group.tests)); for (const filter of filters) { + const filterThis = { config: config.config }; if ('filterTestGroups' in filter) { - const result = filter.filterTestGroups(filteredTestGroups, config.config); + const result = filter.filterTestGroups.call(filterThis, filteredTestGroups); filteredTestGroups = result instanceof Promise ? await result : result; } else if ('filterTests' in filter) { - const result = filter.filterTests(filteredTestGroups.flatMap(group => group.tests), config.config); + const result = filter.filterTests.call(filterThis, filteredTestGroups.flatMap(group => group.tests), config.config); const filteredTests = result instanceof Promise ? await result : result; if (!Array.isArray(filteredTests)) throw new Error('Invalid filter result: tests should be an array'); @@ -236,7 +237,7 @@ async function filterTestGroups(config: FullConfigInternal, testGroups: TestGrou filteredTestGroups = filteredTestGroups.map(group => { return { tests: group.tests.filter(test => { - const result = filter(test); + const result = filter.call(filterThis, test); if (typeof result !== 'boolean') throw new Error('Invalid filter result: filter function should return a boolean'); return result; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 9c908caf74..7940922af7 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1885,9 +1885,10 @@ export type TestDetails = { annotation?: TestDetailsAnnotation | TestDetailsAnnotation[]; } -type TestFilterFunction = (test: TestCase) => boolean; -type TestsFilter = { filterTests(tests: TestCase[], config: FullConfig): Promise | TestCase[] }; -type TestGroupsFilter = { filterTestGroups(testGroups: { tests: TestCase[] }[], config: FullConfig): Promise<{ tests: TestCase[] }[]> | { tests: TestCase[] }[] }; +type TestFilterThis = { config: FullConfig }; +type TestFilterFunction = (this: TestFilterThis, test: TestCase) => boolean; +type TestsFilter = { filterTests(this: TestFilterThis, tests: TestCase[]): Promise | TestCase[] }; +type TestGroupsFilter = { filterTestGroups(this: TestFilterThis, testGroups: { tests: TestCase[] }[]): Promise<{ tests: TestCase[] }[]> | { tests: TestCase[] }[] }; export type TestFilter = TestFilterFunction | TestsFilter | TestGroupsFilter; interface SuiteFunction { diff --git a/tests/playwright-test/test-filter.spec.ts b/tests/playwright-test/test-filter.spec.ts index 899f21c902..835fa3694a 100644 --- a/tests/playwright-test/test-filter.spec.ts +++ b/tests/playwright-test/test-filter.spec.ts @@ -138,6 +138,85 @@ test('config.filter async filterTestGroups should work', async ({ runInlineTest ]); }); +test('config.filter function should have access to config', async ({ runInlineTest }) => { + const result = await runInlineTest({ + ...testFiles, + 'playwright.config.ts': ` + module.exports = { + filter(test) { + console.log('\\n%% .config.workers: '+this.config.workers); + return test.title === 'a2-test2' + }, + }; + `, + }, { workers: 2 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + result.outputLines.sort(); + expect(result.outputLines).toEqual([ + // filter function is called once per test... + '.config.workers: 2', + '.config.workers: 2', + '.config.workers: 2', + '.config.workers: 2', + '.config.workers: 2', + '.config.workers: 2', + 'a2-test2', + ]); +}); + +test('config.filter filterTests should have access to config', async ({ runInlineTest }) => { + const result = await runInlineTest({ + ...testFiles, + 'playwright.config.ts': ` + module.exports = { + filter: { + filterTests(tests) { + console.log('\\n%% .config.workers: '+this.config.workers); + return tests.filter((test, index) => index % 2 === 0) + }, + }, + }; + `, + }, { workers: 2 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(3); + result.outputLines.sort(); + expect(result.outputLines).toEqual([ + // filterTests is only called once... + '.config.workers: 2', + 'a1-test1', + 'a2-test1', + 'a3-test1', + ]); +}); + +test('config.filter filterTestGroups should have access to config', async ({ runInlineTest }) => { + const result = await runInlineTest({ + ...testFiles, + 'playwright.config.ts': ` + module.exports = { + filter: { + filterTestGroups(testgroups) { + console.log('\\n%% .config.workers: '+this.config.workers); + return testgroups.filter((testgroup, index) => index % 2 === 1); + }, + }, + }; + `, + }, { workers: 2 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(3); + result.outputLines.sort(); // Due to parallel execution, the order of output lines is not deterministic. + expect(result.outputLines).toEqual([ + // filterTestGroups is only called once... + '.config.workers: 2', + 'a2-test1', + 'a2-test2', + 'a4-test1', + ]); +}); + test('config.filter invalid function should throw', async ({ runInlineTest }) => { const result = await runInlineTest({ ...testFiles, diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 0cfe2a3aa3..8c222213ae 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -77,9 +77,10 @@ export type TestDetails = { annotation?: TestDetailsAnnotation | TestDetailsAnnotation[]; } -type TestFilterFunction = (test: TestCase) => boolean; -type TestsFilter = { filterTests(tests: TestCase[], config: FullConfig): Promise | TestCase[] }; -type TestGroupsFilter = { filterTestGroups(testGroups: { tests: TestCase[] }[], config: FullConfig): Promise<{ tests: TestCase[] }[]> | { tests: TestCase[] }[] }; +type TestFilterThis = { config: FullConfig }; +type TestFilterFunction = (this: TestFilterThis, test: TestCase) => boolean; +type TestsFilter = { filterTests(this: TestFilterThis, tests: TestCase[]): Promise | TestCase[] }; +type TestGroupsFilter = { filterTestGroups(this: TestFilterThis, testGroups: { tests: TestCase[] }[]): Promise<{ tests: TestCase[] }[]> | { tests: TestCase[] }[] }; export type TestFilter = TestFilterFunction | TestsFilter | TestGroupsFilter; interface SuiteFunction {