From b32a9a05e2500d19d70d1025d5cb71872533d364 Mon Sep 17 00:00:00 2001 From: Marcin Szafranek Date: Mon, 14 Apr 2025 21:07:01 +0200 Subject: [PATCH] docs(test-assertions): optimize custom matcher to handle negative assertions efficiently (#35599) --- docs/src/test-assertions-js.md | 9 +++++++-- tests/playwright-test/expect.spec.ts | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/src/test-assertions-js.md b/docs/src/test-assertions-js.md index 162d0351eb..d64a70e6f9 100644 --- a/docs/src/test-assertions-js.md +++ b/docs/src/test-assertions-js.md @@ -258,7 +258,7 @@ In this example we add a custom `toHaveAmount` function. Custom matcher should r ```js title="fixtures.ts" import { expect as baseExpect } from '@playwright/test'; -import type { Page, Locator } from '@playwright/test'; +import type { Locator } from '@playwright/test'; export { test } from '@playwright/test'; @@ -268,13 +268,18 @@ export const expect = baseExpect.extend({ let pass: boolean; let matcherResult: any; try { - await baseExpect(locator).toHaveAttribute('data-amount', String(expected), options); + const expectation = this.isNot ? baseExpect(locator).not : baseExpect(locator); + await expectation.toHaveAttribute('data-amount', String(expected), options); pass = true; } catch (e: any) { matcherResult = e.matcherResult; pass = false; } + if (this.isNot) { + pass =!pass; + } + const message = pass ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + '\n\n' + diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index 465d41191a..187746c607 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -762,13 +762,18 @@ test('should chain expect matchers and expose matcher utils (TSC)', async ({ run let pass: boolean; let matcherResult: any; try { - await baseExpect(baseAmount).toHaveAttribute('data-amount', expected, options); + const expectation = this.isNot ? baseExpect(baseAmount).not : baseExpect(baseAmount); + await expectation.toHaveAttribute('data-amount', expected, options); pass = true; } catch (e: any) { matcherResult = e.matcherResult; pass = false; } + if (this.isNot) { + pass = !pass; + } + const expectOptions = { isNot: this.isNot, }; @@ -842,13 +847,18 @@ test('should chain expect matchers and expose matcher utils', async ({ runInline let pass: boolean; let matcherResult: any; try { - await baseExpect(baseAmount).toHaveAttribute('data-amount', expected, options); + const expectation = this.isNot ? baseExpect(baseAmount).not : baseExpect(baseAmount); + await expectation.toHaveAttribute('data-amount', expected, options); pass = true; } catch (e: any) { matcherResult = e.matcherResult; pass = false; } + if (this.isNot) { + pass = !pass; + } + const expectOptions = { isNot: this.isNot, }; @@ -888,7 +898,7 @@ test('should chain expect matchers and expose matcher utils', async ({ runInline }, { workers: 1 }); const output = stripAnsi(result.output); expect(output).toContain(`await expect(page.locator('div')).toHaveAmount('3', { timeout: 1000 });`); - expect(output).toContain('a.spec.ts:60'); + expect(output).toContain('a.spec.ts:65'); expect(result.failed).toBe(1); expect(result.exitCode).toBe(1); });