diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 3370eb1a41..7d57ca5d9c 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -511,6 +511,9 @@ var locator = Page.Locator("input"); await Expect(locator).ToBeEditableAsync(); ``` +### option: LocatorAssertions.toBeEditable.editable +* since: v1.26 +- `editable` <[boolean]> ### option: LocatorAssertions.toBeEditable.timeout = %%-js-assertions-timeout-%% * since: v1.18 ### option: LocatorAssertions.toBeEditable.timeout = %%-csharp-java-python-assertions-timeout-%% diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 7f81571ff9..2c8b9604cb 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -1034,6 +1034,8 @@ export class InjectedScript { elementState = progress.injectedScript.elementState(element, 'disabled'); } else if (expression === 'to.be.editable') { elementState = progress.injectedScript.elementState(element, 'editable'); + } else if (expression === 'to.be.readonly') { + elementState = !progress.injectedScript.elementState(element, 'editable'); } else if (expression === 'to.be.empty') { if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') elementState = !(element as HTMLInputElement).value; diff --git a/packages/playwright-test/src/matchers/matchers.ts b/packages/playwright-test/src/matchers/matchers.ts index 1332398220..233cc7eae3 100644 --- a/packages/playwright-test/src/matchers/matchers.ts +++ b/packages/playwright-test/src/matchers/matchers.ts @@ -58,10 +58,11 @@ export function toBeDisabled( export function toBeEditable( this: ReturnType, locator: LocatorEx, - options?: { timeout?: number }, + options?: { editable?: boolean, timeout?: number }, ) { return toBeTruthy.call(this, 'toBeEditable', locator, 'Locator', async (isNot, timeout, customStackTrace) => { - return await locator._expect(customStackTrace, 'to.be.editable', { isNot, timeout }); + const editable = !options || options.editable === undefined || options.editable === true; + return await locator._expect(customStackTrace, editable ? 'to.be.editable' : 'to.be.readonly', { isNot, timeout }); }, options); } diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index c154ee6b16..d134ec7e36 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -3274,6 +3274,8 @@ interface LocatorAssertions { * @param options */ toBeEditable(options?: { + editable?: boolean; + /** * Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`. */ diff --git a/tests/page/expect-boolean.spec.ts b/tests/page/expect-boolean.spec.ts index 4db3e0c731..f339c1e5d7 100644 --- a/tests/page/expect-boolean.spec.ts +++ b/tests/page/expect-boolean.spec.ts @@ -78,10 +78,36 @@ test.describe('toBeChecked', () => { }); }); -test('toBeEditable', async ({ page }) => { - await page.setContent(''); - const locator = page.locator('input'); - await expect(locator).toBeEditable(); +test.describe('toBeEditable', () => { + test('default', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).toBeEditable(); + }); + + test('with not', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).not.toBeEditable(); + }); + + test('with editable:true', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).toBeEditable({ editable: true }); + }); + + test('with editable:false', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).toBeEditable({ editable: false }); + }); + + test('with not and editable:false', async ({ page }) => { + await page.setContent(''); + const locator = page.locator('input'); + await expect(locator).not.toBeEditable({ editable: false }); + }); }); test.describe('toBeEnabled', () => { diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index f9343e26d1..dd8d129116 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -242,6 +242,12 @@ test('should propose only the relevant matchers when custom expect matcher class await test.expect(res as any).toHaveURL('https://example.com'); // @ts-expect-error await test.expect(123).toHaveURL('https://example.com'); + + await test.expect(page.locator('foo')).toBeChecked(); + await test.expect(page.locator('foo')).not.toBeChecked({ checked: true }); + + await test.expect(page.locator('foo')).not.toBeEditable(); + await test.expect(page.locator('foo')).toBeEditable({ editable: false }); }); ` });