diff --git a/docs/src/intro-js.md b/docs/src/intro-js.md index 785e2a2735..4bb54b5967 100644 --- a/docs/src/intro-js.md +++ b/docs/src/intro-js.md @@ -133,20 +133,22 @@ test.only('focus this test', async ({ page }) => { ### Skip a test -You can skip certain test based on the condition. +Mark a test as skipped. ```js js-flavor=js -test('skip this test', async ({ page, browserName }) => { - test.skip(browserName === 'firefox', 'Still working on it'); +test.skip('skip this test', async ({ page }) => { + // This test is not run }); ``` ```js js-flavor=ts -test('skip this test', async ({ page, browserName }) => { - test.skip(browserName === 'firefox', 'Still working on it'); +test.skip('skip this test', async ({ page }) => { + // 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 You can group tests to give them a logical name or to scope before/after hooks to the group. diff --git a/docs/src/test-annotations-js.md b/docs/src/test-annotations-js.md index 4c6148f651..045eeb9cb0 100644 --- a/docs/src/test-annotations-js.md +++ b/docs/src/test-annotations-js.md @@ -33,7 +33,23 @@ test.only('focus this test', async ({ page }) => { ## 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 test('skip this test', async ({ page, browserName }) => { diff --git a/docs/src/test-api/class-test.md b/docs/src/test-api/class-test.md index de716eb1ef..e0a5c4eac3 100644 --- a/docs/src/test-api/class-test.md +++ b/docs/src/test-api/class-test.md @@ -528,13 +528,12 @@ Timeout in milliseconds. 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 const { test, expect } = require('@playwright/test'); -test('broken test', async ({ page }) => { - test.skip(); +test.skip('broken test', async ({ page }) => { // ... }); ``` @@ -542,13 +541,12 @@ test('broken test', async ({ page }) => { ```js js-flavor=ts import { test, expect } from '@playwright/test'; -test('broken test', async ({ page }) => { - test.skip(); +test.skip('broken test', async ({ page }) => { // ... }); ``` -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 const { test, expect } = require('@playwright/test'); @@ -617,15 +615,14 @@ test.beforeEach(async ({ page }) => { ``` ### 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 -- `description` <[void]|[string]> - -Optional description that will be reflected in a test report. +- `testFunctionOrDescription` <[function]\([Fixtures], [TestInfo]\)|[void]|[string]> +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. diff --git a/src/test/test.ts b/src/test/test.ts index f1f9e40918..c7bb409016 100644 --- a/src/test/test.ts +++ b/src/test/test.ts @@ -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); test._only = this._only; test._requireFile = this._requireFile; + test.expectedStatus = this.expectedStatus; return test; } diff --git a/src/test/testType.ts b/src/test/testType.ts index 9370a48dd7..677929d73b 100644 --- a/src/test/testType.ts +++ b/src/test/testType.ts @@ -55,7 +55,7 @@ export class TestTypeImpl { 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(); const suite = currentlyLoadingFileSuite(); if (!suite) @@ -70,6 +70,8 @@ export class TestTypeImpl { if (type === 'only') test._only = true; + if (type === 'skip') + test.expectedStatus = 'skipped'; } 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]) { 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]); + return; + } + if (typeof modifierArgs[0] === 'function') { suite._modifiers.push({ type, fn: modifierArgs[0], location, description: modifierArgs[1] }); } else { diff --git a/src/test/workerRunner.ts b/src/test/workerRunner.ts index 3f043266f1..35e7bd8bd6 100644 --- a/src/test/workerRunner.ts +++ b/src/test/workerRunner.ts @@ -227,7 +227,7 @@ export class WorkerRunner extends EventEmitter { fn: test.fn, repeatEachIndex: this._params.repeatEachIndex, retry: entry.retry, - expectedStatus: 'passed', + expectedStatus: test.expectedStatus, annotations: [], attachments: [], 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]) { + 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]) return; diff --git a/tests/playwright-test/basic.spec.ts b/tests/playwright-test/basic.spec.ts index ea7c131a8d..8a206a2d46 100644 --- a/tests/playwright-test/basic.spec.ts +++ b/tests/playwright-test/basic.spec.ts @@ -366,3 +366,19 @@ test('should help with describe() misuse', async ({ runInlineTest }) => { `});`, ].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'); +}); diff --git a/tests/playwright-test/test-modifiers.spec.ts b/tests/playwright-test/test-modifiers.spec.ts index 7ede14aa7c..690f527fc6 100644 --- a/tests/playwright-test/test-modifiers.spec.ts +++ b/tests/playwright-test/test-modifiers.spec.ts @@ -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.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'); +}); diff --git a/types/test.d.ts b/types/test.d.ts index 9a92bb90ec..dff7639124 100644 --- a/types/test.d.ts +++ b/types/test.d.ts @@ -1177,7 +1177,7 @@ export interface TestInfo { /** * 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 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: * - `'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 * [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail). * @@ -1527,13 +1527,13 @@ export interface TestType { - * test.skip(); + * test.skip('broken test', async ({ page }) => { * // ... * }); * ``` @@ -1541,13 +1541,12 @@ export interface TestType { - * test.skip(); + * test.skip('broken test', async ({ page }) => { * // ... * }); * ``` * - * 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 * const { test, expect } = require('@playwright/test'); @@ -1616,8 +1615,9 @@ export interface TestType {})` 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 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(condition: boolean): void; diff --git a/types/testReporter.d.ts b/types/testReporter.d.ts index 40ef70c7b6..05d204b619 100644 --- a/types/testReporter.d.ts +++ b/types/testReporter.d.ts @@ -113,7 +113,8 @@ export interface TestCase { titlePath(): string[]; /** * 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 * `'skipped'`. * - Tests marked as [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail) are