feat(test runner): test.skip(title, testFunction) syntax (#7922)

This commit is contained in:
Dmitry Gozman 2021-07-29 14:33:37 -07:00 committed by GitHub
parent 2c095294c5
commit 40901e8b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 99 additions and 30 deletions

View File

@ -133,20 +133,22 @@ test.only('focus this test', async ({ page }) => {
### Skip a test ### Skip a test
You can skip certain test based on the condition. Mark a test as skipped.
```js js-flavor=js ```js js-flavor=js
test('skip this test', async ({ page, browserName }) => { test.skip('skip this test', async ({ page }) => {
test.skip(browserName === 'firefox', 'Still working on it'); // This test is not run
}); });
``` ```
```js js-flavor=ts ```js js-flavor=ts
test('skip this test', async ({ page, browserName }) => { test.skip('skip this test', async ({ page }) => {
test.skip(browserName === 'firefox', 'Still working on it'); // This test is not run
}); });
``` ```
You can also skip a test when [some condition is met](./test-annotations.md#conditionally-skip-a-test).
### Group tests ### Group tests
You can group tests to give them a logical name or to scope before/after hooks to the group. You can group tests to give them a logical name or to scope before/after hooks to the group.

View File

@ -33,7 +33,23 @@ test.only('focus this test', async ({ page }) => {
## Skip a test ## Skip a test
You can skip certain tests based on the condition. Mark a test as skipped.
```js js-flavor=js
test.skip('skip this test', async ({ page }) => {
// This test is not run
});
```
```js js-flavor=ts
test.skip('skip this test', async ({ page }) => {
// This test is not run
});
```
## Conditionally skip a test
You can skip certain test based on the condition.
```js js-flavor=js ```js js-flavor=js
test('skip this test', async ({ page, browserName }) => { test('skip this test', async ({ page, browserName }) => {

View File

@ -528,13 +528,12 @@ Timeout in milliseconds.
Skips a test or a group of tests. Skips a test or a group of tests.
Unconditionally skip a test: Unconditionally skip a test, this is similar syntax to [`method: Test.(call)`]:
```js js-flavor=js ```js js-flavor=js
const { test, expect } = require('@playwright/test'); const { test, expect } = require('@playwright/test');
test('broken test', async ({ page }) => { test.skip('broken test', async ({ page }) => {
test.skip();
// ... // ...
}); });
``` ```
@ -542,13 +541,12 @@ test('broken test', async ({ page }) => {
```js js-flavor=ts ```js js-flavor=ts
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test('broken test', async ({ page }) => { test.skip('broken test', async ({ page }) => {
test.skip();
// ... // ...
}); });
``` ```
Conditionally skip a test with an optional description: Conditionally skip a test with an optional description. In this case, call `test.skip()` inside the test function:
```js js-flavor=js ```js js-flavor=js
const { test, expect } = require('@playwright/test'); const { test, expect } = require('@playwright/test');
@ -617,15 +615,14 @@ test.beforeEach(async ({ page }) => {
``` ```
### param: Test.skip.condition ### param: Test.skip.condition
- `condition` <[void]|[boolean]|[function]\([Fixtures]\):[boolean]> - `titleOrCondition` <[string]|[void]|[boolean]|[function]\([Fixtures]\):[boolean]>
Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are skipped when the condition is `true`. When used with `test.skip('test', () => {})` notation, first argument is a test title. Otherwise it is an optional skip condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are skipped when the condition is `true`.
### param: Test.skip.description ### param: Test.skip.description
- `description` <[void]|[string]> - `testFunctionOrDescription` <[function]\([Fixtures], [TestInfo]\)|[void]|[string]>
Optional description that will be reflected in a test report.
When used with `test.skip('test', () => {})` notation, second argument is a test function. Otherwise it is an optional description that will be reflected in a test report.

View File

@ -159,6 +159,7 @@ export class TestCase extends Base implements reporterTypes.TestCase {
const test = new TestCase(this.title, this.fn, this._ordinalInFile, this._testType, this.location); const test = new TestCase(this.title, this.fn, this._ordinalInFile, this._testType, this.location);
test._only = this._only; test._only = this._only;
test._requireFile = this._requireFile; test._requireFile = this._requireFile;
test.expectedStatus = this.expectedStatus;
return test; return test;
} }

View File

@ -55,7 +55,7 @@ export class TestTypeImpl {
this.test = test; this.test = test;
} }
private _createTest(type: 'default' | 'only', location: Location, title: string, fn: Function) { private _createTest(type: 'default' | 'only' | 'skip', location: Location, title: string, fn: Function) {
throwIfRunningInsideJest(); throwIfRunningInsideJest();
const suite = currentlyLoadingFileSuite(); const suite = currentlyLoadingFileSuite();
if (!suite) if (!suite)
@ -70,6 +70,8 @@ export class TestTypeImpl {
if (type === 'only') if (type === 'only')
test._only = true; test._only = true;
if (type === 'skip')
test.expectedStatus = 'skipped';
} }
private _describe(type: 'default' | 'only', location: Location, title: string, fn: Function) { private _describe(type: 'default' | 'only', location: Location, title: string, fn: Function) {
@ -110,6 +112,12 @@ export class TestTypeImpl {
private _modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', location: Location, ...modifierArgs: [arg?: any | Function, description?: string]) { private _modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', location: Location, ...modifierArgs: [arg?: any | Function, description?: string]) {
const suite = currentlyLoadingFileSuite(); const suite = currentlyLoadingFileSuite();
if (suite) { if (suite) {
if (typeof modifierArgs[0] === 'string' && typeof modifierArgs[1] === 'function') {
// Support for test.skip('title', () => {})
this._createTest('skip', location, modifierArgs[0], modifierArgs[1]);
return;
}
if (typeof modifierArgs[0] === 'function') { if (typeof modifierArgs[0] === 'function') {
suite._modifiers.push({ type, fn: modifierArgs[0], location, description: modifierArgs[1] }); suite._modifiers.push({ type, fn: modifierArgs[0], location, description: modifierArgs[1] });
} else { } else {

View File

@ -227,7 +227,7 @@ export class WorkerRunner extends EventEmitter {
fn: test.fn, fn: test.fn,
repeatEachIndex: this._params.repeatEachIndex, repeatEachIndex: this._params.repeatEachIndex,
retry: entry.retry, retry: entry.retry,
expectedStatus: 'passed', expectedStatus: test.expectedStatus,
annotations: [], annotations: [],
attachments: [], attachments: [],
duration: 0, duration: 0,
@ -493,6 +493,16 @@ function buildTestEndPayload(testId: string, testInfo: TestInfo): TestEndPayload
} }
function modifier(testInfo: TestInfo, type: 'skip' | 'fail' | 'fixme' | 'slow', modifierArgs: [arg?: any, description?: string]) { function modifier(testInfo: TestInfo, type: 'skip' | 'fail' | 'fixme' | 'slow', modifierArgs: [arg?: any, description?: string]) {
if (typeof modifierArgs[1] === 'function') {
throw new Error([
'It looks like you are calling test.skip() inside the test and pass a callback.',
'Pass a condition instead and optional description instead:',
`test('my test', async ({ page, isMobile }) => {`,
` test.skip(isMobile, 'This test is not applicable on mobile');`,
`});`,
].join('\n'));
}
if (modifierArgs.length >= 1 && !modifierArgs[0]) if (modifierArgs.length >= 1 && !modifierArgs[0])
return; return;

View File

@ -366,3 +366,19 @@ test('should help with describe() misuse', async ({ runInlineTest }) => {
`});`, `});`,
].join('\n')); ].join('\n'));
}); });
test('test.skip should define a skipped test', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
const { test } = pwt;
const logs = [];
test.skip('foo', () => {
console.log('%%dontseethis');
throw new Error('foo');
});
`,
});
expect(result.exitCode).toBe(0);
expect(result.skipped).toBe(1);
expect(result.output).not.toContain('%%dontseethis');
});

View File

@ -288,3 +288,21 @@ test('test.skip without a callback in describe block should skip hooks', async (
expect(result.report.suites[0].suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]); expect(result.report.suites[0].suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
expect(result.output).not.toContain('%%'); expect(result.output).not.toContain('%%');
}); });
test('test.skip should not define a skipped test inside another test', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
const { test } = pwt;
const logs = [];
test('passes', () => {
test.skip('foo', () => {
console.log('%%dontseethis');
throw new Error('foo');
});
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1);
expect(result.output).toContain('It looks like you are calling test.skip() inside the test and pass a callback');
});

20
types/test.d.ts vendored
View File

@ -1177,7 +1177,7 @@ export interface TestInfo {
/** /**
* Skips the currently running test. This is similar to * Skips the currently running test. This is similar to
* [test.skip([condition, description])](https://playwright.dev/docs/api/class-test#test-skip). * [test.skip(titleOrCondition, testFunctionOrDescription)](https://playwright.dev/docs/api/class-test#test-skip).
* @param condition Optional condition - the test is skipped when the condition is `true`. * @param condition Optional condition - the test is skipped when the condition is `true`.
* @param description Optional description that will be reflected in a test report. * @param description Optional description that will be reflected in a test report.
*/ */
@ -1245,7 +1245,7 @@ export interface TestInfo {
/** /**
* Expected status for the currently running test. This is usually `'passed'`, except for a few cases: * Expected status for the currently running test. This is usually `'passed'`, except for a few cases:
* - `'skipped'` for skipped tests, e.g. with * - `'skipped'` for skipped tests, e.g. with
* [test.skip([condition, description])](https://playwright.dev/docs/api/class-test#test-skip); * [test.skip(titleOrCondition, testFunctionOrDescription)](https://playwright.dev/docs/api/class-test#test-skip);
* - `'failed'` for tests marked as failed with * - `'failed'` for tests marked as failed with
* [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail). * [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail).
* *
@ -1527,13 +1527,13 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
/** /**
* Skips a test or a group of tests. * Skips a test or a group of tests.
* *
* Unconditionally skip a test: * Unconditionally skip a test, this is similar syntax to
* [test.(call)(title, testFunction)](https://playwright.dev/docs/api/class-test#test-call):
* *
* ```js js-flavor=js * ```js js-flavor=js
* const { test, expect } = require('@playwright/test'); * const { test, expect } = require('@playwright/test');
* *
* test('broken test', async ({ page }) => { * test.skip('broken test', async ({ page }) => {
* test.skip();
* // ... * // ...
* }); * });
* ``` * ```
@ -1541,13 +1541,12 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
* ```js js-flavor=ts * ```js js-flavor=ts
* import { test, expect } from '@playwright/test'; * import { test, expect } from '@playwright/test';
* *
* test('broken test', async ({ page }) => { * test.skip('broken test', async ({ page }) => {
* test.skip();
* // ... * // ...
* }); * });
* ``` * ```
* *
* Conditionally skip a test with an optional description: * Conditionally skip a test with an optional description. In this case, call `test.skip()` inside the test function:
* *
* ```js js-flavor=js * ```js js-flavor=js
* const { test, expect } = require('@playwright/test'); * const { test, expect } = require('@playwright/test');
@ -1616,8 +1615,9 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
* }); * });
* ``` * ```
* *
* @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are skipped when the condition is `true`. * @param titleOrCondition When used with `test.skip('test', () => {})` notation, first argument is a test title. Otherwise it is an optional skip condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are
* @param description Optional description that will be reflected in a test report. * skipped when the condition is `true`.
* @param testFunctionOrDescription When used with `test.skip('test', () => {})` notation, second argument is a test function. Otherwise it is an optional description that will be reflected in a test report.
*/ */
skip(): void; skip(): void;
skip(condition: boolean): void; skip(condition: boolean): void;

View File

@ -113,7 +113,8 @@ export interface TestCase {
titlePath(): string[]; titlePath(): string[];
/** /**
* Expected test status. * Expected test status.
* - Tests marked as [test.skip([condition, description])](https://playwright.dev/docs/api/class-test#test-skip) or * - Tests marked as
* [test.skip(titleOrCondition, testFunctionOrDescription)](https://playwright.dev/docs/api/class-test#test-skip) or
* [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme) are expected to be * [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme) are expected to be
* `'skipped'`. * `'skipped'`.
* - Tests marked as [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail) are * - Tests marked as [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail) are