diff --git a/docs/src/test-api/class-test.md b/docs/src/test-api/class-test.md index 8ae5f07dce..05a3275eed 100644 --- a/docs/src/test-api/class-test.md +++ b/docs/src/test-api/class-test.md @@ -66,7 +66,7 @@ Test function that takes one or two arguments: an object with fixtures and optio ## method: Test.afterAll * since: v1.10 -Declares an `afterAll` hook that is executed once per worker after all tests. When called in the scope of a test file, runs after all tests in the file. When called inside a [`method: Test.describe`] group, runs after all tests in the group. If multiple `afterAll` hooks are added, they will run in the order of their registration. +Declares an `afterAll` hook that is executed once per worker after all tests. When called in the scope of a test file, runs after all tests in the file. When called inside a [`method: Test.describe#1`] group, runs after all tests in the group. If multiple `afterAll` hooks are added, they will run in the order of their registration. Note that worker process is restarted on test failures, and `afterAll` hook runs again in the new worker. Learn more about [workers and failures](../test-retries.md). @@ -81,7 +81,7 @@ Hook function that takes one or two arguments: an object with worker fixtures an ## method: Test.afterEach * since: v1.10 -Declares an `afterEach` hook that is executed after each test. When called in the scope of a test file, runs after each test in the file. When called inside a [`method: Test.describe`] group, runs after each test in the group. If multiple `afterEach` hooks are added, they will run in the order of their registration. +Declares an `afterEach` hook that is executed after each test. When called in the scope of a test file, runs after each test in the file. When called inside a [`method: Test.describe#1`] group, runs after each test in the group. If multiple `afterEach` hooks are added, they will run in the order of their registration. You can access all the same [Fixtures] as the test function itself, and also the [TestInfo] object that gives a lot of useful information. For example, you can check whether the test succeeded or failed. @@ -127,7 +127,7 @@ Hook function that takes one or two arguments: an object with fixtures and optio ## method: Test.beforeAll * since: v1.10 -Declares a `beforeAll` hook that is executed once per worker process before all tests. When called in the scope of a test file, runs before all tests in the file. When called inside a [`method: Test.describe`] group, runs before all tests in the group. If multiple `beforeAll` hooks are added, they will run in the order of their registration. +Declares a `beforeAll` hook that is executed once per worker process before all tests. When called in the scope of a test file, runs before all tests in the file. When called inside a [`method: Test.describe#1`] group, runs before all tests in the group. If multiple `beforeAll` hooks are added, they will run in the order of their registration. ```js tab=js-js // example.spec.js @@ -178,7 +178,7 @@ Hook function that takes one or two arguments: an object with worker fixtures an ## method: Test.beforeEach * since: v1.10 -Declares a `beforeEach` hook that is executed before each test. When called in the scope of a test file, runs before each test in the file. When called inside a [`method: Test.describe`] group, runs before each test in the group. If multiple `beforeEach` hooks are added, they will run in the order of their registration. +Declares a `beforeEach` hook that is executed before each test. When called in the scope of a test file, runs before each test in the file. When called inside a [`method: Test.describe#1`] group, runs before each test in the group. If multiple `beforeEach` hooks are added, they will run in the order of their registration. You can access all the same [Fixtures] as the test function itself, and also the [TestInfo] object that gives a lot of useful information. For example, you can navigate the page before starting the test. @@ -221,7 +221,7 @@ Hook function that takes one or two arguments: an object with fixtures and optio -## method: Test.describe +## method: Test.describe#1 * since: v1.10 Declares a group of tests. @@ -250,17 +250,58 @@ test.describe('two tests', () => { }); ``` -### param: Test.describe.title +### param: Test.describe#1.title * since: v1.10 - `title` <[string]> Group title. -### param: Test.describe.callback +### param: Test.describe#1.callback * since: v1.10 - `callback` <[function]> -A callback that is run immediately when calling [`method: Test.describe`]. Any tests added in this callback will belong to the group. +A callback that is run immediately when calling [`method: Test.describe#1`]. Any tests added in this callback will belong to the group. + + +## method: Test.describe#2 +* since: v1.24 + +Declares an anonymous group of tests. This is convenient to give a group of tests a common option with [`method: Test.use`]. + +```js tab=js-js +test.describe(() => { + test.use({ colorScheme: 'dark' }); + + test('one', async ({ page }) => { + // ... + }); + + test('two', async ({ page }) => { + // ... + }); +}); +``` + +```js tab=js-ts +test.describe(() => { + test.use({ colorScheme: 'dark' }); + + test('one', async ({ page }) => { + // ... + }); + + test('two', async ({ page }) => { + // ... + }); +}); +``` + +### param: Test.describe#2.callback +* since: v1.24 +- `callback` <[function]> + +A callback that is run immediately when calling [`method: Test.describe#2`]. Any tests added in this callback will belong to the group. + ## method: Test.describe.configure @@ -494,7 +535,7 @@ A callback that is run immediately when calling [`method: Test.describe.serial.o ## method: Test.describe.skip * since: v1.10 -Declares a skipped test group, similarly to [`method: Test.describe`]. Tests in the skipped group are never run. +Declares a skipped test group, similarly to [`method: Test.describe#1`]. Tests in the skipped group are never run. ```js tab=js-js test.describe.skip('skipped group', () => { @@ -730,7 +771,7 @@ Optional description that will be reflected in a test report. ## method: Test.fail#3 * since: v1.10 -Conditionally mark all tests in a file or [`method: Test.describe`] group as "should fail". +Conditionally mark all tests in a file or [`method: Test.describe#1`] group as "should fail". ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -829,7 +870,7 @@ test('test to be fixed', async ({ page }) => { }); ``` -Mark all tests in a file or [`method: Test.describe`] group as "fixme". +Mark all tests in a file or [`method: Test.describe#1`] group as "fixme". ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -900,7 +941,7 @@ Optional description that will be reflected in a test report. ## method: Test.fixme#4 * since: v1.10 -Conditionally mark all tests in a file or [`method: Test.describe`] group as "fixme". +Conditionally mark all tests in a file or [`method: Test.describe#1`] group as "fixme". ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -1043,7 +1084,7 @@ test.beforeAll(async () => { }); ``` -Changing timeout for all tests in a [`method: Test.describe`] group. +Changing timeout for all tests in a [`method: Test.describe#1`] group. ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -1139,7 +1180,7 @@ test('skipped test', async ({ page }) => { }); ``` -Unconditionally skip all tests in a file or [`method: Test.describe`] group: +Unconditionally skip all tests in a file or [`method: Test.describe#1`] group: ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -1229,7 +1270,7 @@ Optional description that will be reflected in a test report. ## method: Test.skip#4 * since: v1.10 -Conditionally skips all tests in a file or [`method: Test.describe`] group. +Conditionally skips all tests in a file or [`method: Test.describe#1`] group. ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -1338,7 +1379,7 @@ Optional description that will be reflected in a test report. ## method: Test.slow#3 * since: v1.10 -Conditionally mark all tests in a file or [`method: Test.describe`] group as "slow". Slow tests will be given triple the default timeout. +Conditionally mark all tests in a file or [`method: Test.describe#1`] group as "slow". Slow tests will be given triple the default timeout. ```js tab=js-js const { test, expect } = require('@playwright/test'); @@ -1422,7 +1463,7 @@ Step body. ## method: Test.use * since: v1.10 -Specifies options or fixtures to use in a single test file or a [`method: Test.describe`] group. Most useful to set an option, for example set `locale` to configure `context` fixture. `test.use` can be called either in the global scope or inside `test.describe`, it's is an error to call it within `beforeEach` or `beforeAll`. +Specifies options or fixtures to use in a single test file or a [`method: Test.describe#1`] group. Most useful to set an option, for example set `locale` to configure `context` fixture. `test.use` can be called either in the global scope or inside `test.describe`, it's is an error to call it within `beforeEach` or `beforeAll`. ```js tab=js-js const { test, expect } = require('@playwright/test'); diff --git a/docs/src/test-api/class-testinfo.md b/docs/src/test-api/class-testinfo.md index 25503a2589..79946bf3db 100644 --- a/docs/src/test-api/class-testinfo.md +++ b/docs/src/test-api/class-testinfo.md @@ -29,7 +29,7 @@ test('basic test', async ({ page }, testInfo) => { - `type` <[string]> Annotation type, for example `'skip'` or `'fail'`. - `description` ?<[string]> Optional description. -The list of annotations applicable to the current test. Includes annotations from the test, annotations from all [`method: Test.describe`] groups the test belongs to and file-level annotations for the test file. +The list of annotations applicable to the current test. Includes annotations from the test, annotations from all [`method: Test.describe#1`] groups the test belongs to and file-level annotations for the test file. Learn more about [test annotations](../test-annotations.md). diff --git a/docs/src/test-configuration-js.md b/docs/src/test-configuration-js.md index cfd2bdd2c4..91cd5c6bd1 100644 --- a/docs/src/test-configuration-js.md +++ b/docs/src/test-configuration-js.md @@ -60,7 +60,7 @@ npx playwright test --config=tests/my.config.js ## Local configuration -With [`method: Test.use`] you can override some options for a file or a [`method: Test.describe`] block. +With [`method: Test.use`] you can override some options for a file or a [`method: Test.describe#1`] block. ```js tab=js-js // example.spec.js diff --git a/docs/src/test-reporter-api/class-suite.md b/docs/src/test-reporter-api/class-suite.md index 0c0c75691f..6c94c9e823 100644 --- a/docs/src/test-reporter-api/class-suite.md +++ b/docs/src/test-reporter-api/class-suite.md @@ -9,7 +9,7 @@ * File suite #1 * [TestCase] #1 * [TestCase] #2 - * Suite corresponding to a [`method: Test.describe`] group + * Suite corresponding to a [`method: Test.describe#1`] group * [TestCase] #1 in a group * [TestCase] #2 in a group * < more test cases ... > @@ -54,7 +54,7 @@ Child suites. See [Suite] for the hierarchy of suites. * since: v1.10 - type: <[Array]<[TestCase]>> -Test cases in the suite. Note that only test cases defined directly in this suite are in the list. Any test cases defined in nested [`method: Test.describe`] groups are listed +Test cases in the suite. Note that only test cases defined directly in this suite are in the list. Any test cases defined in nested [`method: Test.describe#1`] groups are listed in the child [`property: Suite.suites`]. ## property: Suite.title @@ -65,7 +65,7 @@ Suite title. * Empty for root suite. * Project name for project suite. * File path for file suite. -* Title passed to [`method: Test.describe`] for a group suite. +* Title passed to [`method: Test.describe#1`] for a group suite. ## method: Suite.titlePath * since: v1.10 diff --git a/docs/src/test-reporter-api/class-testcase.md b/docs/src/test-reporter-api/class-testcase.md index 2c9f80723d..b0df65f432 100644 --- a/docs/src/test-reporter-api/class-testcase.md +++ b/docs/src/test-reporter-api/class-testcase.md @@ -10,7 +10,7 @@ - `type` <[string]> Annotation type, for example `'skip'` or `'fail'`. - `description` ?<[string]> Optional description. -The list of annotations applicable to the current test. Includes annotations from the test, annotations from all [`method: Test.describe`] groups the test belongs to and file-level annotations for the test file. +The list of annotations applicable to the current test. Includes annotations from the test, annotations from all [`method: Test.describe#1`] groups the test belongs to and file-level annotations for the test file. Annotations are available during test execution through [`property: TestInfo.annotations`]. diff --git a/packages/playwright-test/src/loader.ts b/packages/playwright-test/src/loader.ts index fc06b6a1f3..308a3ceb38 100644 --- a/packages/playwright-test/src/loader.ts +++ b/packages/playwright-test/src/loader.ts @@ -347,7 +347,9 @@ class ProjectSuiteBuilder { if (entry instanceof Suite) { const suite = entry._clone(); to._addSuite(suite); - if (!this._cloneEntries(entry, suite, repeatEachIndex, filter, relativeTitlePath + ' ' + suite.title)) { + // Ignore empty titles, similar to Suite.titlePath(). + const childTitlePath = relativeTitlePath + (suite.title ? ' ' + suite.title : ''); + if (!this._cloneEntries(entry, suite, repeatEachIndex, filter, childTitlePath)) { to._entries.pop(); to.suites.pop(); } diff --git a/packages/playwright-test/src/test.ts b/packages/playwright-test/src/test.ts index 8c0b08f49e..69339d1144 100644 --- a/packages/playwright-test/src/test.ts +++ b/packages/playwright-test/src/test.ts @@ -81,7 +81,9 @@ export class Suite extends Base implements reporterTypes.Suite { titlePath(): string[] { const titlePath = this.parent ? this.parent.titlePath() : []; - titlePath.push(this.title); + // Ignore anonymous describe blocks. + if (this.title || !this._isDescribe) + titlePath.push(this.title); return titlePath; } diff --git a/packages/playwright-test/src/testType.ts b/packages/playwright-test/src/testType.ts index f467af241d..f840d44a1a 100644 --- a/packages/playwright-test/src/testType.ts +++ b/packages/playwright-test/src/testType.ts @@ -98,16 +98,13 @@ export class TestTypeImpl { } } - private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip', location: Location, title: string, fn: Function) { + private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip', location: Location, title: string | Function, fn?: Function) { throwIfRunningInsideJest(); const suite = this._ensureCurrentSuite(location, 'test.describe()'); + if (typeof title === 'function') { - throw errorWithLocation(location, [ - 'It looks like you are calling describe() without the title. Pass the title as a first argument:', - `test.describe('my test group', () => {`, - ` // Declare tests here`, - `});`, - ].join('\n')); + fn = title; + title = ''; } const child = new Suite(title); @@ -133,7 +130,7 @@ export class TestTypeImpl { } setCurrentlyLoadingFileSuite(child); - fn(); + fn!(); setCurrentlyLoadingFileSuite(suite); } diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index f64ddb29bd..8d3f53fd3d 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -1309,7 +1309,7 @@ export interface TestInfo { project: FullProject; /** * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all - * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) groups the test belongs to + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1) groups the test belongs to * and file-level annotations for the test file. * * Learn more about [test annotations](https://playwright.dev/docs/test-annotations). @@ -1706,7 +1706,48 @@ export interface TestInfo { workerIndex: number;} interface SuiteFunction { + /** + * Declares a group of tests. + * + * ```js + * test.describe('two tests', () => { + * test('one', async ({ page }) => { + * // ... + * }); + * + * test('two', async ({ page }) => { + * // ... + * }); + * }); + * ``` + * + * @param title Group title. + * @param callback A callback that is run immediately when calling [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1). Any tests added in this + * callback will belong to the group. + */ (title: string, callback: () => void): void; + /** + * Declares an anonymous group of tests. This is convenient to give a group of tests a common option with + * [test.use(options)](https://playwright.dev/docs/api/class-test#test-use). + * + * ```js + * test.describe(() => { + * test.use({ colorScheme: 'dark' }); + * + * test('one', async ({ page }) => { + * // ... + * }); + * + * test('two', async ({ page }) => { + * // ... + * }); + * }); + * ``` + * + * @param callback A callback that is run immediately when calling [test.describe(callback)](https://playwright.dev/docs/api/class-test#test-describe-2). Any tests added in this callback + * will belong to the group. + */ + (callback: () => void): void; } interface TestFunction { @@ -1758,7 +1799,7 @@ export interface TestType Promise | any): void; /** * Specifies options or fixtures to use in a single test file or a - * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group. Most useful to set an - * option, for example set `locale` to configure `context` fixture. `test.use` can be called either in the global scope or - * inside `test.describe`, it's is an error to call it within `beforeEach` or `beforeAll`. + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1) group. Most useful to set + * an option, for example set `locale` to configure `context` fixture. `test.use` can be called either in the global scope + * or inside `test.describe`, it's is an error to call it within `beforeEach` or `beforeAll`. * * ```js * import { test, expect } from '@playwright/test'; diff --git a/packages/playwright-test/types/testReporter.d.ts b/packages/playwright-test/types/testReporter.d.ts index c5b59bf1b2..57c2ca83a6 100644 --- a/packages/playwright-test/types/testReporter.d.ts +++ b/packages/playwright-test/types/testReporter.d.ts @@ -26,7 +26,7 @@ export type { FullConfig, TestStatus, TestError } from '@playwright/test'; * - [TestCase] #1 * - [TestCase] #2 * - Suite corresponding to a - * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1) group * - [TestCase] #1 in a group * - [TestCase] #2 in a group * - < more test cases ... > @@ -66,8 +66,8 @@ export interface Suite { /** * Test cases in the suite. Note that only test cases defined directly in this suite are in the list. Any test cases - * defined in nested [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) groups are - * listed in the child [suite.suites](https://playwright.dev/docs/api/class-suite#suite-suites). + * defined in nested [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1) groups + * are listed in the child [suite.suites](https://playwright.dev/docs/api/class-suite#suite-suites). */ tests: Array; @@ -76,7 +76,7 @@ export interface Suite { * - Empty for root suite. * - Project name for project suite. * - File path for file suite. - * - Title passed to [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) for a + * - Title passed to [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1) for a * group suite. */ title: string; @@ -106,7 +106,7 @@ export interface TestCase { expectedStatus: TestStatus; /** * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all - * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) groups the test belongs to + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-1) groups the test belongs to * and file-level annotations for the test file. * * Annotations are available during test execution through diff --git a/tests/playwright-test/basic.spec.ts b/tests/playwright-test/basic.spec.ts index 943634be09..e5ad390082 100644 --- a/tests/playwright-test/basic.spec.ts +++ b/tests/playwright-test/basic.spec.ts @@ -351,19 +351,21 @@ test('should work with test helper', async ({ runInlineTest }) => { ]); }); -test('should help with describe() misuse', async ({ runInlineTest }) => { +test('should support describe() without a title', async ({ runInlineTest }) => { const result = await runInlineTest({ 'a.spec.js': ` - pwt.test.describe(() => {}); + pwt.test.describe('suite1', () => { + pwt.test.describe(() => { + pwt.test.describe('suite2', () => { + pwt.test('my test', () => {}); + }); + }); + }); `, - }); - expect(result.exitCode).toBe(1); - expect(result.output).toContain([ - 'Error: a.spec.js:5:16: It looks like you are calling describe() without the title. Pass the title as a first argument:', - `test.describe('my test group', () => {`, - ` // Declare tests here`, - `});`, - ].join('\n')); + }, { reporter: 'list' }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + expect(stripAnsi(result.output)).toContain('a.spec.js:8:17 › suite1 › suite2 › my test'); }); test('test.{skip,fixme} should define a skipped test', async ({ runInlineTest }) => { diff --git a/tests/playwright-test/types-2.spec.ts b/tests/playwright-test/types-2.spec.ts index e1ed63a3fa..6fa4892a03 100644 --- a/tests/playwright-test/types-2.spec.ts +++ b/tests/playwright-test/types-2.spec.ts @@ -32,6 +32,9 @@ test('basics should work', async ({ runTSC }) => { test.skip('my test', async () => {}); test.fixme('my test', async () => {}); }); + test.describe(() => { + test('my test', () => {}); + }); test.describe.parallel('suite', () => {}); test.describe.parallel.only('suite', () => {}); test.describe.serial('suite', () => {}); diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index a7d7cdd67e..aaa168e9c5 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -98,6 +98,13 @@ class TypesGenerator { handledClasses.add(className); return this.writeComment(docClass.comment) + '\n'; }, (className, methodName, overloadIndex) => { + if (className === 'SuiteFunction' && methodName === '__call') { + console.log(className, methodName, overloadIndex); + const cls = this.documentation.classes.get('Test'); + const method = cls.membersArray.find(m => m.alias === 'describe' && m.overloadIndex === overloadIndex); + return this.memberJSDOC(method, ' ').trimLeft(); + } + const docClass = this.docClassForName(className); let method; if (docClass) { diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 45adcd1b58..44c584ecd3 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -109,6 +109,7 @@ export interface TestInfo { interface SuiteFunction { (title: string, callback: () => void): void; + (callback: () => void): void; } interface TestFunction {