feat(test runner): allow top-level test.fixme similar to test.skip (#10250)

```js
test.fixme('my test name', () => {});
```
This commit is contained in:
Dmitry Gozman 2021-11-19 11:40:40 -08:00 committed by GitHub
parent 2a98800ac0
commit 0302e759df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 175 additions and 302 deletions

View File

@ -10,7 +10,7 @@ title: "Annotations"
Playwright Test supports test annotations to deal with failures, flakiness, skip, focus and tag tests:
- [`method: Test.skip#1`] marks the test as irrelevant. Playwright Test does not run such a test. Use this annotation when the test is not applicable in some configuration.
- [`method: Test.fail`] marks the test as failing. Playwright Test will run this test and ensure it does indeed fail. If the test does not fail, Playwright Test will complain.
- [`method: Test.fixme`] marks the test as failing. Playwright Test will not run this test, as opposite to the `fail` annotation. Use `fixme` when running the test is slow or crashy.
- [`method: Test.fixme#1`] marks the test as failing. Playwright Test will not run this test, as opposite to the `fail` annotation. Use `fixme` when running the test is slow or crashy.
- [`method: Test.slow`] marks the test as slow and triples the test timeout.
Annotations can be used on a single test or a group of tests. Annotations can be conditional, in which case they apply when the condition is truthy. Annotations may depend on test fixtures. There could be multiple annotations on the same test, possibly in different configurations.

View File

@ -635,16 +635,46 @@ Optional description that will be reflected in a test report.
## method: Test.fixme
## method: Test.fixme#1
Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them.
Unconditional fixme:
Declares a test to be fixed, similarly to [`method: Test.(call)`]. This test will not be run.
```js js-flavor=js
const { test, expect } = require('@playwright/test');
test('not yet ready', async ({ page }) => {
test.fixme('test to be fixed', async ({ page }) => {
// ...
});
```
```js js-flavor=ts
import { test, expect } from '@playwright/test';
test.fixme('test to be fixed', async ({ page }) => {
// ...
});
```
### param: Test.fixme#1.title
- `title` <[string]>
Test title.
### param: Test.fixme#1.testFunction
- `testFunction` <[function]\([Fixtures], [TestInfo]\)>
Test function that takes one or two arguments: an object with fixtures and optional [TestInfo].
## method: Test.fixme#2
Mark a test as "fixme", with the intention to fix it. Test is immediately aborted when you call [`method: Test.fixme#2`].
```js js-flavor=js
const { test, expect } = require('@playwright/test');
test('test to be fixed', async ({ page }) => {
test.fixme();
// ...
});
@ -653,19 +683,23 @@ test('not yet ready', async ({ page }) => {
```js js-flavor=ts
import { test, expect } from '@playwright/test';
test('not yet ready', async ({ page }) => {
test('test to be fixed', async ({ page }) => {
test.fixme();
// ...
});
```
Conditional fixme a test with an optional description:
Mark all tests in a file or [`method: Test.describe`] group as "fixme".
```js js-flavor=js
const { test, expect } = require('@playwright/test');
test('fixme in WebKit', async ({ page, browserName }) => {
test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
test.fixme();
test('test to be fixed 1', async ({ page }) => {
// ...
});
test('test to be fixed 2', async ({ page }) => {
// ...
});
```
@ -673,23 +707,26 @@ test('fixme in WebKit', async ({ page, browserName }) => {
```js js-flavor=ts
import { test, expect } from '@playwright/test';
test('fixme in WebKit', async ({ page, browserName }) => {
test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
test.fixme();
test('test to be fixed 1', async ({ page }) => {
// ...
});
test('test to be fixed 2', async ({ page }) => {
// ...
});
```
Conditional fixme for all tests in a file or [`method: Test.describe`] group:
## method: Test.fixme#3
Conditionally mark a test as "fixme" with an optional description.
```js js-flavor=js
const { test, expect } = require('@playwright/test');
test.fixme(({ browserName }) => browserName === 'webkit');
test('fixme in WebKit 1', async ({ page }) => {
// ...
});
test('fixme in WebKit 2', async ({ page }) => {
test('broken in WebKit', async ({ page, browserName }) => {
test.fixme(browserName === 'webkit', 'This feature is not implemented on Mac yet');
// ...
});
```
@ -697,45 +734,66 @@ test('fixme in WebKit 2', async ({ page }) => {
```js js-flavor=ts
import { test, expect } from '@playwright/test';
test.fixme(({ browserName }) => browserName === 'webkit');
test('fixme in WebKit 1', async ({ page }) => {
// ...
});
test('fixme in WebKit 2', async ({ page }) => {
test('broken in WebKit', async ({ page, browserName }) => {
test.fixme(browserName === 'webkit', 'This feature is not implemented on Mac yet');
// ...
});
```
`fixme` from a hook:
```js js-flavor=js
const { test, expect } = require('@playwright/test');
### param: Test.fixme#3.condition
- `condition` <[boolean]>
test.beforeEach(async ({ page }) => {
test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
await page.goto('/settings');
});
```
Test or tests are marked as "fixme" when the condition is `true`.
```js js-flavor=ts
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
await page.goto('/settings');
});
```
### param: Test.fixme.condition
- `condition` <[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 marked as "fixme" when the condition is `true`.
### param: Test.fixme.description
### param: Test.fixme#3.description
- `description` <[void]|[string]>
Optional description that will be reflected in a test report.
An optional description that will be reflected in a test report.
## method: Test.fixme#4
Conditionally mark all tests in a file or [`method: Test.describe`] group as "fixme".
```js js-flavor=js
const { test, expect } = require('@playwright/test');
test.fixme(({ browserName }) => browserName === 'webkit');
test('broken in WebKit 1', async ({ page }) => {
// ...
});
test('broken in WebKit 2', async ({ page }) => {
// ...
});
```
```js js-flavor=ts
import { test, expect } from '@playwright/test';
test.fixme(({ browserName }) => browserName === 'webkit');
test('broken in WebKit 1', async ({ page }) => {
// ...
});
test('broken in WebKit 2', async ({ page }) => {
// ...
});
```
### param: Test.fixme#4.condition
- `callback` <[function]\([Fixtures]\):[boolean]>
A function that returns whether to mark as "fixme", based on test fixtures. Test or tests are marked as "fixme" when the return value is `true`.
### param: Test.fixme#4.description
- `description` <[void]|[string]>
An optional description that will be reflected in a test report.

View File

@ -141,7 +141,7 @@ Absolute path to a file where the currently running test is declared.
## method: TestInfo.fixme
Marks the currently running test as "fixme". The test will be skipped, but the intention is to fix it. This is similar to [`method: Test.fixme`].
Marks the currently running test as "fixme". The test will be skipped, but the intention is to fix it. This is similar to [`method: Test.fixme#2`].
### param: TestInfo.fixme.condition
- `condition` <[void]|[boolean]>

View File

@ -18,7 +18,7 @@ Learn more about [test annotations](./test-annotations.md).
- type: <[TestStatus]<"passed"|"failed"|"timedOut"|"skipped">>
Expected test status.
* Tests marked as [`method: Test.skip#1`] or [`method: Test.fixme`] are expected to be `'skipped'`.
* Tests marked as [`method: Test.skip#1`] or [`method: Test.fixme#1`] are expected to be `'skipped'`.
* Tests marked as [`method: Test.fail`] are expected to be `'failed'`.
* Other tests are expected to be `'passed'`.

View File

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

View File

@ -1169,21 +1169,21 @@ export interface TestInfo {
/**
* Marks the currently running test as "fixme". The test will be skipped, but the intention is to fix it. This is similar
* to [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme).
* to [test.fixme()](https://playwright.dev/docs/api/class-test#test-fixme-2).
* @param condition Optional condition - the test is marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
*/
fixme(): void;
/**
* Marks the currently running test as "fixme". The test will be skipped, but the intention is to fix it. This is similar
* to [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme).
* to [test.fixme()](https://playwright.dev/docs/api/class-test#test-fixme-2).
* @param condition Optional condition - the test is marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
*/
fixme(condition: boolean): void;
/**
* Marks the currently running test as "fixme". The test will be skipped, but the intention is to fix it. This is similar
* to [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme).
* to [test.fixme()](https://playwright.dev/docs/api/class-test#test-fixme-2).
* @param condition Optional condition - the test is marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
*/
@ -1659,285 +1659,89 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
*/
skip(callback: (args: TestArgs & WorkerArgs) => boolean, description?: string): void;
/**
* Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them.
*
* Unconditional fixme:
* Declares a test to be fixed, similarly to
* [test.(call)(title, testFunction)](https://playwright.dev/docs/api/class-test#test-call). This test will not be run.
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('not yet ready', async ({ page }) => {
* test.fixme('test to be fixed', async ({ page }) => {
* // ...
* });
* ```
*
* @param title Test title.
* @param testFunction Test function that takes one or two arguments: an object with fixtures and optional [TestInfo].
*/
fixme(title: string, testFunction: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<void> | void): void;
/**
* Mark a test as "fixme", with the intention to fix it. Test is immediately aborted when you call
* [test.fixme()](https://playwright.dev/docs/api/class-test#test-fixme-2).
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('test to be fixed', async ({ page }) => {
* test.fixme();
* // ...
* });
* ```
*
* Conditional fixme a test with an optional description:
* Mark all tests in a file or [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe)
* group as "fixme".
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('fixme in WebKit', async ({ page, browserName }) => {
* test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
* test.fixme();
*
* test('test to be fixed 1', async ({ page }) => {
* // ...
* });
* test('test to be fixed 2', async ({ page }) => {
* // ...
* });
* ```
*
* Conditional fixme for all tests in a file or
* [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.fixme(({ browserName }) => browserName === 'webkit');
*
* test('fixme in WebKit 1', async ({ page }) => {
* // ...
* });
* test('fixme in WebKit 2', async ({ page }) => {
* // ...
* });
* ```
*
* `fixme` from a hook:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.beforeEach(async ({ page }) => {
* test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
* await page.goto('/settings');
* });
* ```
*
* @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
*/
fixme(): void;
/**
* Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them.
*
* Unconditional fixme:
* Conditionally mark a test as "fixme" with an optional description.
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('not yet ready', async ({ page }) => {
* test.fixme();
* test('broken in WebKit', async ({ page, browserName }) => {
* test.fixme(browserName === 'webkit', 'This feature is not implemented on Mac yet');
* // ...
* });
* ```
*
* Conditional fixme a test with an optional description:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('fixme in WebKit', async ({ page, browserName }) => {
* test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
* // ...
* });
* ```
*
* Conditional fixme for all tests in a file or
* [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.fixme(({ browserName }) => browserName === 'webkit');
*
* test('fixme in WebKit 1', async ({ page }) => {
* // ...
* });
* test('fixme in WebKit 2', async ({ page }) => {
* // ...
* });
* ```
*
* `fixme` from a hook:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.beforeEach(async ({ page }) => {
* test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
* await page.goto('/settings');
* });
* ```
*
* @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
* @param condition Test or tests are marked as "fixme" when the condition is `true`.
* @param description An optional description that will be reflected in a test report.
*/
fixme(condition: boolean): void;
fixme(condition: boolean, description?: string): void;
/**
* Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them.
*
* Unconditional fixme:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('not yet ready', async ({ page }) => {
* test.fixme();
* // ...
* });
* ```
*
* Conditional fixme a test with an optional description:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('fixme in WebKit', async ({ page, browserName }) => {
* test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
* // ...
* });
* ```
*
* Conditional fixme for all tests in a file or
* [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group:
* Conditionally mark all tests in a file or
* [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group as "fixme".
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.fixme(({ browserName }) => browserName === 'webkit');
*
* test('fixme in WebKit 1', async ({ page }) => {
* test('broken in WebKit 1', async ({ page }) => {
* // ...
* });
* test('fixme in WebKit 2', async ({ page }) => {
* test('broken in WebKit 2', async ({ page }) => {
* // ...
* });
* ```
*
* `fixme` from a hook:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.beforeEach(async ({ page }) => {
* test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
* await page.goto('/settings');
* });
* ```
*
* @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
* @param callback A function that returns whether to mark as "fixme", based on test fixtures. Test or tests are marked as "fixme" when the return value is `true`.
* @param description An optional description that will be reflected in a test report.
*/
fixme(condition: boolean, description: string): void;
/**
* Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them.
*
* Unconditional fixme:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('not yet ready', async ({ page }) => {
* test.fixme();
* // ...
* });
* ```
*
* Conditional fixme a test with an optional description:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('fixme in WebKit', async ({ page, browserName }) => {
* test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
* // ...
* });
* ```
*
* Conditional fixme for all tests in a file or
* [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.fixme(({ browserName }) => browserName === 'webkit');
*
* test('fixme in WebKit 1', async ({ page }) => {
* // ...
* });
* test('fixme in WebKit 2', async ({ page }) => {
* // ...
* });
* ```
*
* `fixme` from a hook:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.beforeEach(async ({ page }) => {
* test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
* await page.goto('/settings');
* });
* ```
*
* @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
*/
fixme(callback: (args: TestArgs & WorkerArgs) => boolean): void;
/**
* Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them.
*
* Unconditional fixme:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('not yet ready', async ({ page }) => {
* test.fixme();
* // ...
* });
* ```
*
* Conditional fixme a test with an optional description:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test('fixme in WebKit', async ({ page, browserName }) => {
* test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet');
* // ...
* });
* ```
*
* Conditional fixme for all tests in a file or
* [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.fixme(({ browserName }) => browserName === 'webkit');
*
* test('fixme in WebKit 1', async ({ page }) => {
* // ...
* });
* test('fixme in WebKit 2', async ({ page }) => {
* // ...
* });
* ```
*
* `fixme` from a hook:
*
* ```ts
* import { test, expect } from '@playwright/test';
*
* test.beforeEach(async ({ page }) => {
* test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet');
* await page.goto('/settings');
* });
* ```
*
* @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "fixme" when the condition is `true`.
* @param description Optional description that will be reflected in a test report.
*/
fixme(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void;
fixme(callback: (args: TestArgs & WorkerArgs) => boolean, description?: string): void;
/**
* Marks a test or a group of tests as "should fail". Playwright Test runs these tests and ensures that they are actually
* failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed.

View File

@ -123,7 +123,7 @@ export interface TestCase {
/**
* Expected test status.
* - Tests marked as [test.skip(title, testFunction)](https://playwright.dev/docs/api/class-test#test-skip-1) or
* [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme) are expected to be
* [test.fixme(title, testFunction)](https://playwright.dev/docs/api/class-test#test-fixme-1) are expected to be
* `'skipped'`.
* - Tests marked as [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail) are
* expected to be `'failed'`.

View File

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

View File

@ -181,8 +181,16 @@ test('test modifiers should check types', async ({ runTSC }) => {
});
test.skip('skipped', async ({}) => {
});
test.fixme('fixme', async ({}) => {
});
// @ts-expect-error
test.skip('skipped', 'skipped');
// @ts-expect-error
test.fixme('fixme', 'fixme');
// @ts-expect-error
test.skip(true, async () => {});
// @ts-expect-error
test.fixme(true, async () => {});
`,
});
expect(result.exitCode).toBe(0);

View File

@ -241,11 +241,10 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
skip(): void;
skip(condition: boolean, description?: string): void;
skip(callback: (args: TestArgs & WorkerArgs) => boolean, description?: string): void;
fixme(title: string, testFunction: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<void> | void): void;
fixme(): void;
fixme(condition: boolean): void;
fixme(condition: boolean, description: string): void;
fixme(callback: (args: TestArgs & WorkerArgs) => boolean): void;
fixme(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void;
fixme(condition: boolean, description?: string): void;
fixme(callback: (args: TestArgs & WorkerArgs) => boolean, description?: string): void;
fail(): void;
fail(condition: boolean): void;
fail(condition: boolean, description: string): void;