feat(test-runner): test filter
This commit is contained in:
parent
bc30cc795e
commit
58242e3592
|
|
@ -40,6 +40,12 @@ See [`property: TestConfig.globalTeardown`].
|
||||||
|
|
||||||
See [`property: TestConfig.globalTimeout`].
|
See [`property: TestConfig.globalTimeout`].
|
||||||
|
|
||||||
|
## property: FullConfig.filter
|
||||||
|
* since: v1.49
|
||||||
|
- type: <[null]|[TestFilter]|[Array]<[TestFilter]>>
|
||||||
|
|
||||||
|
See [`property: TestConfig.filter`].
|
||||||
|
|
||||||
## property: FullConfig.grep
|
## property: FullConfig.grep
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: <[RegExp]|[Array]<[RegExp]>>
|
- type: <[RegExp]|[Array]<[RegExp]>>
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,13 @@ export default defineConfig({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## property: TestConfig.filter
|
||||||
|
* since: v1.49
|
||||||
|
- type: ?<[TestFilter]|[Array]<[TestFilter]>>
|
||||||
|
|
||||||
|
Filter tests by passing a function.
|
||||||
|
|
||||||
|
|
||||||
## property: TestConfig.grep
|
## property: TestConfig.grep
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[RegExp]|[Array]<[RegExp]>>
|
- type: ?<[RegExp]|[Array]<[RegExp]>>
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ export class FullConfigInternal {
|
||||||
globalSetup: takeFirst(resolveScript(userConfig.globalSetup, configDir), null),
|
globalSetup: takeFirst(resolveScript(userConfig.globalSetup, configDir), null),
|
||||||
globalTeardown: takeFirst(resolveScript(userConfig.globalTeardown, configDir), null),
|
globalTeardown: takeFirst(resolveScript(userConfig.globalTeardown, configDir), null),
|
||||||
globalTimeout: takeFirst(configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
|
globalTimeout: takeFirst(configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
|
||||||
|
filter: takeFirst(userConfig.filter, undefined),
|
||||||
grep: takeFirst(userConfig.grep, defaultGrep),
|
grep: takeFirst(userConfig.grep, defaultGrep),
|
||||||
grepInvert: takeFirst(userConfig.grepInvert, null),
|
grepInvert: takeFirst(userConfig.grepInvert, null),
|
||||||
maxFailures: takeFirst(configCLIOverrides.debug ? 1 : undefined, configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
|
maxFailures: takeFirst(configCLIOverrides.debug ? 1 : undefined, configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
|
||||||
|
|
@ -289,4 +290,4 @@ const configInternalSymbol = Symbol('configInternalSymbol');
|
||||||
|
|
||||||
export function getProjectId(project: FullProject): string {
|
export function getProjectId(project: FullProject): string {
|
||||||
return (project as any).__projectId!;
|
return (project as any).__projectId!;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -581,6 +581,7 @@ export const baseFullConfig: reporterTypes.FullConfig = {
|
||||||
globalSetup: null,
|
globalSetup: null,
|
||||||
globalTeardown: null,
|
globalTeardown: null,
|
||||||
globalTimeout: 0,
|
globalTimeout: 0,
|
||||||
|
filter: null,
|
||||||
grep: /.*/,
|
grep: /.*/,
|
||||||
grepInvert: null,
|
grepInvert: null,
|
||||||
maxFailures: 0,
|
maxFailures: 0,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { FullConfig, Reporter, TestError } from '../../types/testReporter';
|
import type { FullConfig, Reporter, TestError } from '../../types/testReporter';
|
||||||
|
import type * as reporterTypes from '../../types/testReporter';
|
||||||
import { InProcessLoaderHost, OutOfProcessLoaderHost } from './loaderHost';
|
import { InProcessLoaderHost, OutOfProcessLoaderHost } from './loaderHost';
|
||||||
import { Suite } from '../common/test';
|
import { Suite } from '../common/test';
|
||||||
import type { TestCase } from '../common/test';
|
import type { TestCase } from '../common/test';
|
||||||
|
|
@ -173,22 +174,36 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shard only the top-level projects.
|
// Shard only the top-level projects.
|
||||||
if (config.config.shard) {
|
if (config.config.shard || config.config.filter) {
|
||||||
// Create test groups for top-level projects.
|
// Create test groups for top-level projects.
|
||||||
const testGroups: TestGroup[] = [];
|
const testGroups: TestGroup[] = [];
|
||||||
for (const projectSuite of rootSuite.suites)
|
for (const projectSuite of rootSuite.suites)
|
||||||
testGroups.push(...createTestGroups(projectSuite, config.config.workers));
|
testGroups.push(...createTestGroups(projectSuite, config.config.workers));
|
||||||
|
|
||||||
|
if (config.config.filter) {
|
||||||
|
const filters = Array.isArray(config.config.filter) ? config.config.filter : [config.config.filter];
|
||||||
|
|
||||||
|
const allTests = new Set<reporterTypes.TestCase>(testGroups.flatMap(group => group.tests));
|
||||||
|
|
||||||
|
let filteredTests = [...allTests.values()];
|
||||||
|
for (const filter of filters)
|
||||||
|
filteredTests = filter(filteredTests);
|
||||||
|
|
||||||
|
const filteredTestSet = new Set(filteredTests);
|
||||||
|
for (const group of testGroups)
|
||||||
|
group.tests = group.tests.filter(test => filteredTestSet.has(test));
|
||||||
|
}
|
||||||
|
|
||||||
// Shard test groups.
|
// Shard test groups.
|
||||||
const testGroupsInThisShard = filterForShard(config.config.shard, testGroups);
|
const testGroupsInThisShard = config.config.shard ? filterForShard(config.config.shard, testGroups) : new Set(testGroups);
|
||||||
const testsInThisShard = new Set<TestCase>();
|
const testsInThisRun = new Set<TestCase>();
|
||||||
for (const group of testGroupsInThisShard) {
|
for (const group of testGroupsInThisShard) {
|
||||||
for (const test of group.tests)
|
for (const test of group.tests)
|
||||||
testsInThisShard.add(test);
|
testsInThisRun.add(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update project suites, removing empty ones.
|
// Update project suites, removing empty ones.
|
||||||
filterTestsRemoveEmptySuites(rootSuite, test => testsInThisShard.has(test));
|
filterTestsRemoveEmptySuites(rootSuite, test => testsInThisRun.has(test));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now prepend dependency projects without filtration.
|
// Now prepend dependency projects without filtration.
|
||||||
|
|
|
||||||
14
packages/playwright/types/test.d.ts
vendored
14
packages/playwright/types/test.d.ts
vendored
|
|
@ -18,6 +18,8 @@
|
||||||
import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions, Page, LaunchOptions, ViewportSize, Geolocation, HTTPCredentials, Locator, APIResponse, PageScreenshotOptions } from 'playwright-core';
|
import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions, Page, LaunchOptions, ViewportSize, Geolocation, HTTPCredentials, Locator, APIResponse, PageScreenshotOptions } from 'playwright-core';
|
||||||
export * from 'playwright-core';
|
export * from 'playwright-core';
|
||||||
|
|
||||||
|
import type { TestCase } from './testReporter';
|
||||||
|
|
||||||
export type ReporterDescription = Readonly<
|
export type ReporterDescription = Readonly<
|
||||||
['blob'] | ['blob', { outputDir?: string, fileName?: string }] |
|
['blob'] | ['blob', { outputDir?: string, fileName?: string }] |
|
||||||
['dot'] |
|
['dot'] |
|
||||||
|
|
@ -1034,6 +1036,11 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter tests by passing a function.
|
||||||
|
*/
|
||||||
|
filter?: TestFilter|Array<TestFilter>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to exit with an error if any tests or groups are marked as
|
* Whether to exit with an error if any tests or groups are marked as
|
||||||
* [test.only(title[, details, body])](https://playwright.dev/docs/api/class-test#test-only) or
|
* [test.only(title[, details, body])](https://playwright.dev/docs/api/class-test#test-only) or
|
||||||
|
|
@ -1720,6 +1727,11 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||||
*/
|
*/
|
||||||
configFile?: string;
|
configFile?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [testConfig.filter](https://playwright.dev/docs/api/class-testconfig#test-config-filter).
|
||||||
|
*/
|
||||||
|
filter: null|TestFilter|Array<TestFilter>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [testConfig.forbidOnly](https://playwright.dev/docs/api/class-testconfig#test-config-forbid-only).
|
* See [testConfig.forbidOnly](https://playwright.dev/docs/api/class-testconfig#test-config-forbid-only).
|
||||||
*/
|
*/
|
||||||
|
|
@ -1838,6 +1850,8 @@ export type TestDetails = {
|
||||||
annotation?: TestDetailsAnnotation | TestDetailsAnnotation[];
|
annotation?: TestDetailsAnnotation | TestDetailsAnnotation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TestFilter = (tests: TestCase[]) => TestCase[];
|
||||||
|
|
||||||
interface SuiteFunction {
|
interface SuiteFunction {
|
||||||
/**
|
/**
|
||||||
* Declares a group of tests.
|
* Declares a group of tests.
|
||||||
|
|
|
||||||
37
tests/playwright-test/test-filter.spec.ts
Normal file
37
tests/playwright-test/test-filter.spec.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Copyright Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from './playwright-test-fixtures';
|
||||||
|
|
||||||
|
test('config.filter should work', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
filter: (tests) => tests.filter(test => test.title === 'test1'),
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('test1', async () => { console.log('\\n%% test1'); });
|
||||||
|
test('test2', async () => { console.log('\\n%% test2'); });
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.outputLines).toEqual([
|
||||||
|
'test1',
|
||||||
|
]);
|
||||||
|
});
|
||||||
4
utils/generate_types/overrides-test.d.ts
vendored
4
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -17,6 +17,8 @@
|
||||||
import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions, Page, LaunchOptions, ViewportSize, Geolocation, HTTPCredentials, Locator, APIResponse, PageScreenshotOptions } from 'playwright-core';
|
import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions, Page, LaunchOptions, ViewportSize, Geolocation, HTTPCredentials, Locator, APIResponse, PageScreenshotOptions } from 'playwright-core';
|
||||||
export * from 'playwright-core';
|
export * from 'playwright-core';
|
||||||
|
|
||||||
|
import type { TestCase } from './testReporter';
|
||||||
|
|
||||||
export type ReporterDescription = Readonly<
|
export type ReporterDescription = Readonly<
|
||||||
['blob'] | ['blob', { outputDir?: string, fileName?: string }] |
|
['blob'] | ['blob', { outputDir?: string, fileName?: string }] |
|
||||||
['dot'] |
|
['dot'] |
|
||||||
|
|
@ -75,6 +77,8 @@ export type TestDetails = {
|
||||||
annotation?: TestDetailsAnnotation | TestDetailsAnnotation[];
|
annotation?: TestDetailsAnnotation | TestDetailsAnnotation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TestFilter = (tests: TestCase[]) => TestCase[];
|
||||||
|
|
||||||
interface SuiteFunction {
|
interface SuiteFunction {
|
||||||
(title: string, callback: () => void): void;
|
(title: string, callback: () => void): void;
|
||||||
(callback: () => void): void;
|
(callback: () => void): void;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue