From cd9a5946d268563137ac731baf38960db0ca6894 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 20 Sep 2022 17:11:12 -0700 Subject: [PATCH] fix(expect): toHaveAttribute with empty value should not match missing attribute (#17477) Reference #16517 --- .../src/server/injected/injectedScript.ts | 5 +++- tests/page/expect-misc.spec.ts | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index fe757d123e..7b6eba3d9e 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -1099,7 +1099,10 @@ export class InjectedScript { // Single text value. let received: string | undefined; if (expression === 'to.have.attribute') { - received = element.getAttribute(options.expressionArg) || ''; + const value = element.getAttribute(options.expressionArg); + if (value === null) + return { received: null, matches: false }; + received = value; } else if (expression === 'to.have.class') { received = element.classList.toString(); } else if (expression === 'to.have.css') { diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index 3b48c0d49e..87f900295f 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -232,6 +232,36 @@ test.describe('toHaveAttribute', () => { const locator = page.locator('#node'); await expect(locator).toHaveAttribute('id', 'node'); }); + + test('should not match missing attribute', async ({ page }) => { + await page.setContent('
Text content
'); + const locator = page.locator('#node'); + { + const error = await expect(locator).toHaveAttribute('disabled', '', { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('expect.toHaveAttribute with timeout 1000ms'); + } + { + const error = await expect(locator).toHaveAttribute('disabled', /.*/, { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('expect.toHaveAttribute with timeout 1000ms'); + } + await expect(locator).not.toHaveAttribute('disabled', ''); + await expect(locator).not.toHaveAttribute('disabled', /.*/); + }); + + test('should match boolean attribute', async ({ page }) => { + await page.setContent('
Text content
'); + const locator = page.locator('#node'); + await expect(locator).toHaveAttribute('checked', ''); + await expect(locator).toHaveAttribute('checked', /.*/); + { + const error = await expect(locator).not.toHaveAttribute('checked', '', { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('expect.toHaveAttribute with timeout 1000ms'); + } + { + const error = await expect(locator).not.toHaveAttribute('checked', /.*/, { timeout: 1000 }).catch(e => e); + expect(error.message).toContain('expect.toHaveAttribute with timeout 1000ms'); + } + }); }); test.describe('toHaveCSS', () => {