From efbb09e807aaf6daa9f17b37395df0462504cddd Mon Sep 17 00:00:00 2001 From: Devin Rousso Date: Fri, 18 Sep 2026 21:52:08 -0600 Subject: [PATCH] feat(test-runner): support locks in `test.describe.configure` locks cannot be declared imperatively for the enclosing suite add a `lock` option to `test.describe.configure` that accumulates suite locks --- docs/src/test-api/class-test.md | 14 ++++++++++++++ docs/src/test-parallel-js.md | 6 ++++++ packages/playwright/src/common/testType.ts | 5 ++++- packages/playwright/types/test.d.ts | 10 +++++++++- tests/playwright-test/test-locks.spec.ts | 6 +++++- utils/generate_types/overrides-test.d.ts | 2 +- 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/src/test-api/class-test.md b/docs/src/test-api/class-test.md index 53b5afa7f4262..314f843e1aa09 100644 --- a/docs/src/test-api/class-test.md +++ b/docs/src/test-api/class-test.md @@ -541,6 +541,14 @@ Learn more about the execution modes [here](../test-parallel.md). test('runs second', async ({ page }) => {}); ``` +* Declaring locks for all tests in a scope. + + ```js + test.describe.configure({ lock: 'user-settings' }); + test('update user settings', async ({ page }) => {}); + test('reset user settings', async ({ page }) => {}); + ``` + * Run multiple describes in parallel, but tests inside each describe in order. ```js @@ -559,6 +567,12 @@ Learn more about the execution modes [here](../test-parallel.md). }); ``` +### option: Test.describe.configure.lock +* since: v1.64 +- `lock` <[string]|[Array]<[string]>> + +Additional named locks for all tests in the enclosing scope, including nested groups. Locks are added to those declared in [`method: Test.describe`] details and previous calls to [`method: Test.describe.configure`]. + ### option: Test.describe.configure.mode * since: v1.10 - `mode` <[TestMode]<"default"|"parallel"|"serial">> diff --git a/docs/src/test-parallel-js.md b/docs/src/test-parallel-js.md index 452dfd9b22720..e59444b570843 100644 --- a/docs/src/test-parallel-js.md +++ b/docs/src/test-parallel-js.md @@ -155,6 +155,12 @@ test('reset the database', { lock: ['database', 'external-api'] }, async () => { }); ``` +You can also add locks to the enclosing file or describe group with [`method: Test.describe.configure`]. Repeated calls add locks without removing existing ones. + +```js +test.describe.configure({ lock: 'user-settings' }); +``` + Playwright acquires all the locks of a test before the test starts and releases them when it finishes. :::note diff --git a/packages/playwright/src/common/testType.ts b/packages/playwright/src/common/testType.ts index 748d48dd8258c..0e3216c6fcb2a 100644 --- a/packages/playwright/src/common/testType.ts +++ b/packages/playwright/src/common/testType.ts @@ -189,12 +189,15 @@ export class TestTypeImpl { suite._hooks.push({ type: name, fn: fn!, title, location }); } - private _configure(location: Location, options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) { + private _configure(location: Location, options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number, lock?: string | string[] }) { throwIfRunningInsideJest(); const suite = this._currentSuite(location, `test.describe.configure()`); if (!suite) return; + if (options.lock !== undefined) + suite._locks.push(...validateTestDetails({ lock: options.lock }, location).locks); + if (options.timeout !== undefined) suite._timeout = options.timeout; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 35d0a0b9a1873..700f520343e8b 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -4414,6 +4414,14 @@ export interface TestType { * test('runs second', async ({ page }) => {}); * ``` * + * - Declaring locks for all tests in a scope. + * + * ```js + * test.describe.configure({ lock: 'user-settings' }); + * test('update user settings', async ({ page }) => {}); + * test('reset user settings', async ({ page }) => {}); + * ``` + * * - Run multiple describes in parallel, but tests inside each describe in order. * * ```js @@ -4434,7 +4442,7 @@ export interface TestType { * * @param options */ - configure: (options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) => void; + configure: (options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number, lock?: string | string[] }) => void; }; /** diff --git a/tests/playwright-test/test-locks.spec.ts b/tests/playwright-test/test-locks.spec.ts index 1c4802ac49616..cf2cf96098e1c 100644 --- a/tests/playwright-test/test-locks.spec.ts +++ b/tests/playwright-test/test-locks.spec.ts @@ -122,7 +122,7 @@ test('should not run tests with the same lock from different projects at the sam expect(conflictingOverlaps(result.outputLines, [['project1', 'project2']])).toEqual([]); }); -test('should support locks declared on a describe group', async ({ runInlineTest }) => { +test('should support locks declared and configured on describe groups', async ({ runInlineTest }) => { const result = await runInlineTest({ 'playwright.config.ts': ` module.exports = { fullyParallel: true }; @@ -131,6 +131,10 @@ test('should support locks declared on a describe group', async ({ runInlineTest import { test } from '@playwright/test'; test.describe('locked suite', { lock: 'shared' }, () => { ${lockedTest('test1', 1000)} + }); + test.describe('configured suite', () => { + test.describe.configure({ lock: 'shared' }); + test.describe.configure({ lock: ['other'] }); ${lockedTest('test2', 1000)} }); `, diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index b95c661759778..47d6aedcadf51 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -154,7 +154,7 @@ export interface TestType { only(title: string, details: TestDetails, callback: () => void): void; }; - configure: (options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) => void; + configure: (options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number, lock?: string | string[] }) => void; }; skip(title: string, body: TestBody): void;