diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index 0d5d226039c4d..79816a8d7ae3e 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -129,7 +129,14 @@ function createExpect(info: ExpectMetaInfo) { if (property === 'extend') { return (matchers: any) => { - expectLibrary.extend(matchers); + const wrappedMatchers: any = {}; + Object.entries(matchers).forEach(([name, matcher]) => { + wrappedMatchers[name] = function (...args: any[]) { + this.timeout = currentExpectTimeout({}); + return (matcher as any).call(this, ...args); + }; + }); + expectLibrary.extend(wrappedMatchers); return expectInstance; }; } diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 4f52130d1022b..725689deef3a3 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6513,6 +6513,7 @@ export type ExpectMatcherState = { isNot: boolean; promise: 'rejects' | 'resolves' | ''; utils: ExpectMatcherUtils; + timeout: number; }; export type MatcherReturnType = { diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index e79a3f9c19de4..64b4f64c13c7c 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -1000,3 +1000,42 @@ test('should respect timeout from configured expect when used outside of the tes expect(stdout).toBe(''); expect(stripAnsi(stderr)).toContain('Timed out 10ms waiting for expect(locator).toBeAttached()'); }); + +test('should expose timeout to custom matchers', async ({ runInlineTest, runTSC }) => { + const files = { + 'playwright.config.ts': ` + export default { + expect: { timeout: 1100 } + }; + `, + 'a.test.ts': ` + import type { ExpectMatcherState, MatcherReturnType } from '@playwright/test'; + import { test, expect as base } from '@playwright/test'; + + const expect = base.extend<{assertTimeout: (this: ExpectMatcherState, page: any, value: number) => MatcherReturnType}>({ + assertTimeout(page: any, value: number) { + const pass = this.timeout === value; + return { + message: () => 'Unexpected timeout: ' + this.timeout, + pass, + name: 'assertTimeout', + }; + } + }); + + test('from config', async ({ page }) => { + expect(page).assertTimeout(1100); + }); + test('from expect.configure', async ({ page }) => { + expect.configure({ timeout: 2200 })(page).assertTimeout(2200); + }); + `, + }; + const { exitCode } = await runTSC(files); + expect(exitCode).toBe(0); + + const result = await runInlineTest(files); + expect(result.exitCode).toBe(0); + expect(result.failed).toBe(0); + expect(result.passed).toBe(2); +}); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 002525f89b771..0d1030f49447d 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -361,6 +361,7 @@ export type ExpectMatcherState = { isNot: boolean; promise: 'rejects' | 'resolves' | ''; utils: ExpectMatcherUtils; + timeout: number; }; export type MatcherReturnType = {