@@ -141,17 +141,7 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
141141 const wrappedMatchers : any = { } ;
142142 const extendedMatchers : any = { ...customMatchers } ;
143143 for ( const [ name , matcher ] of Object . entries ( matchers ) ) {
144- wrappedMatchers [ name ] = function ( ...args : any [ ] ) {
145- const { isNot, promise, utils } = this ;
146- const newThis : ExpectMatcherState = {
147- isNot,
148- promise,
149- utils,
150- timeout : currentExpectTimeout ( )
151- } ;
152- ( newThis as any ) . equals = throwUnsupportedExpectMatcherError ;
153- return ( matcher as any ) . call ( newThis , ...args ) ;
154- } ;
144+ wrappedMatchers [ name ] = wrapPlaywrightMatcherToPassNiceThis ( matcher ) ;
155145 const key = qualifiedMatcherName ( qualifier , name ) ;
156146 wrappedMatchers [ key ] = wrappedMatchers [ name ] ;
157147 Object . defineProperty ( wrappedMatchers [ key ] , 'name' , { value : name } ) ;
@@ -203,6 +193,47 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
203193 return expectInstance ;
204194}
205195
196+ // Expect wraps matchers, so there is no way to pass this information to the raw Playwright matcher.
197+ // Rely on sync call sequence to seed each matcher call with the context.
198+ type MatcherCallContext = {
199+ expectInfo : ExpectMetaInfo ;
200+ testInfo : TestInfoImpl | null ;
201+ } ;
202+
203+ let matcherCallContext : MatcherCallContext | undefined ;
204+
205+ function setMatcherCallContext ( context : MatcherCallContext ) {
206+ matcherCallContext = context ;
207+ }
208+
209+ function getMatcherCallContext ( ) : MatcherCallContext {
210+ // TODO: re-implement to allow for "take" semantics.
211+ return matcherCallContext ! ;
212+ }
213+
214+ type ExpectMatcherStateInternal = ExpectMatcherState & {
215+ _context : MatcherCallContext | undefined ;
216+ } ;
217+
218+ const defaultExpectTimeout = 5000 ;
219+
220+ function wrapPlaywrightMatcherToPassNiceThis ( matcher : any ) {
221+ return function ( this : any , ...args : any [ ] ) {
222+ const { isNot, promise, utils } = this ;
223+ const context = getMatcherCallContext ( ) ;
224+ const timeout = context . expectInfo . timeout ?? context ?. testInfo ?. _projectInternal ?. expect ?. timeout ?? defaultExpectTimeout ;
225+ const newThis : ExpectMatcherStateInternal = {
226+ isNot,
227+ promise,
228+ utils,
229+ timeout,
230+ _context : context ,
231+ } ;
232+ ( newThis as any ) . equals = throwUnsupportedExpectMatcherError ;
233+ return matcher . call ( newThis , ...args ) ;
234+ } ;
235+ }
236+
206237function throwUnsupportedExpectMatcherError ( ) {
207238 throw new Error ( 'It looks like you are using custom expect matchers that are not compatible with Playwright. See https://aka.ms/playwright/expect-compatibility' ) ;
208239}
@@ -299,8 +330,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
299330 }
300331 return ( ...args : any [ ] ) => {
301332 const testInfo = currentTestInfo ( ) ;
302- // We assume that the matcher will read the current expect timeout the first thing.
303- setCurrentExpectConfigureTimeout ( this . _info . timeout ) ;
333+ setMatcherCallContext ( { expectInfo : this . _info , testInfo } ) ;
304334 if ( ! testInfo )
305335 return matcher . call ( target , ...args ) ;
306336
@@ -362,7 +392,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
362392async function pollMatcher ( qualifiedMatcherName : string , info : ExpectMetaInfo , prefix : string [ ] , ...args : any [ ] ) {
363393 const testInfo = currentTestInfo ( ) ;
364394 const poll = info . poll ! ;
365- const timeout = poll . timeout ?? currentExpectTimeout ( ) ;
395+ const timeout = poll . timeout ?? info . timeout ?? testInfo ?. _projectInternal ?. expect ?. timeout ?? defaultExpectTimeout ;
366396 const { deadline, timeoutMessage } = testInfo ? testInfo . _deadlineForMatcher ( timeout ) : TestInfoImpl . _defaultDeadlineForMatcher ( timeout ) ;
367397
368398 const result = await pollAgainstDeadline < Error | undefined > ( async ( ) => {
@@ -379,6 +409,7 @@ async function pollMatcher(qualifiedMatcherName: string, info: ExpectMetaInfo, p
379409 let matchers = createMatchers ( value , innerInfo , prefix ) ;
380410 if ( info . isNot )
381411 matchers = matchers . not ;
412+ setMatcherCallContext ( { expectInfo : info , testInfo } ) ;
382413 matchers [ qualifiedMatcherName ] ( ...args ) ;
383414 return { continuePolling : false , result : undefined } ;
384415 } catch ( error ) {
@@ -398,22 +429,6 @@ async function pollMatcher(qualifiedMatcherName: string, info: ExpectMetaInfo, p
398429 }
399430}
400431
401- let currentExpectConfigureTimeout : number | undefined ;
402-
403- function setCurrentExpectConfigureTimeout ( timeout : number | undefined ) {
404- currentExpectConfigureTimeout = timeout ;
405- }
406-
407- function currentExpectTimeout ( ) {
408- if ( currentExpectConfigureTimeout !== undefined )
409- return currentExpectConfigureTimeout ;
410- const testInfo = currentTestInfo ( ) ;
411- let defaultExpectTimeout = testInfo ?. _projectInternal ?. expect ?. timeout ;
412- if ( typeof defaultExpectTimeout === 'undefined' )
413- defaultExpectTimeout = 5000 ;
414- return defaultExpectTimeout ;
415- }
416-
417432function computeArgsSuffix ( matcherName : string , args : any [ ] ) {
418433 let value = '' ;
419434 if ( matcherName === 'toHaveScreenshot' )
0 commit comments