@@ -110,13 +110,13 @@ function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[])
110
110
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info, prefix));
111
111
}
112
112
113
-
const getCustomMatchersSymbol = Symbol('get custom matchers');
113
+
const userMatchersSymbol = Symbol('userMatchers');
114
114
115
115
function qualifiedMatcherName(qualifier: string[], matcherName: string) {
116
116
return qualifier.join(':') + '$' + matcherName;
117
117
}
118
118
119
-
function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Record<string, Function>) {
119
+
function createExpect(info: ExpectMetaInfo, prefix: string[], userMatchers: Record<string, Function>) {
120
120
const expectInstance: Expect<{}> = new Proxy(expectLibrary, {
121
121
apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) {
122
122
const [actual, messageOrOptions] = argumentsList;
@@ -130,7 +130,7 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
130
130
return createMatchers(actual, newInfo, prefix);
131
131
},
132
132
133
-
get: function(target: any, property: string | typeof getCustomMatchersSymbol) {
133
+
get: function(target: any, property: string | typeof userMatchersSymbol) {
134
134
if (property === 'configure')
135
135
return configure;
136
136
@@ -139,27 +139,14 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
139
139
const qualifier = [...prefix, createGuid()];
140
140
141
141
const wrappedMatchers: any = {};
142
-
const extendedMatchers: any = { ...customMatchers };
143
142
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
-
};
143
+
wrappedMatchers[name] = wrapPlaywrightMatcherToPassNiceThis(matcher);
155
144
const key = qualifiedMatcherName(qualifier, name);
156
145
wrappedMatchers[key] = wrappedMatchers[name];
157
146
Object.defineProperty(wrappedMatchers[key], 'name', { value: name });
158
-
extendedMatchers[name] = wrappedMatchers[key];
159
147
}
160
148
expectLibrary.extend(wrappedMatchers);
161
-
162
-
return createExpect(info, qualifier, extendedMatchers);
149
+
return createExpect(info, qualifier, { ...userMatchers, ...matchers });
163
150
};
164
151
}
165
152
@@ -169,8 +156,8 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
169
156
};
170
157
}
171
158
172
-
if (property === getCustomMatchersSymbol)
173
-
return customMatchers;
159
+
if (property === userMatchersSymbol)
160
+
return userMatchers;
174
161
175
162
if (property === 'poll') {
176
163
return (actual: unknown, messageOrOptions?: ExpectMessage & { timeout?: number, intervals?: number[] }) => {
@@ -197,12 +184,56 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
197
184
newInfo.poll!.intervals = configuration._poll.intervals ?? newInfo.poll!.intervals;
198
185
}
199
186
}
200
-
return createExpect(newInfo, prefix, customMatchers);
187
+
return createExpect(newInfo, prefix, userMatchers);
201
188
};
202
189
203
190
return expectInstance;
204
191
}
205
192
193
+
// Expect wraps matchers, so there is no way to pass this information to the raw Playwright matcher.
194
+
// Rely on sync call sequence to seed each matcher call with the context.
195
+
type MatcherCallContext = {
196
+
expectInfo: ExpectMetaInfo;
197
+
testInfo: TestInfoImpl | null;
198
+
};
199
+
200
+
let matcherCallContext: MatcherCallContext | undefined;
201
+
202
+
function setMatcherCallContext(context: MatcherCallContext) {
203
+
matcherCallContext = context;
204
+
}
205
+
206
+
function takeMatcherCallContext(): MatcherCallContext {
207
+
try {
208
+
return matcherCallContext!;
209
+
} finally {
210
+
matcherCallContext = undefined;
211
+
}
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 = takeMatcherCallContext();
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
+
206
237
function throwUnsupportedExpectMatcherError() {
207
238
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');
208
239
}
@@ -299,8 +330,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
299
330
}
300
331
return (...args: any[]) => {
301
332
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 });
304
334
if (!testInfo)
305
335
return matcher.call(target, ...args);
306
336
@@ -362,7 +392,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
362
392
async function pollMatcher(qualifiedMatcherName: string, info: ExpectMetaInfo, prefix: string[], ...args: any[]) {
363
393
const testInfo = currentTestInfo();
364
394
const poll = info.poll!;
365
-
const timeout = poll.timeout ?? currentExpectTimeout();
395
+
const timeout = poll.timeout ?? info.timeout ?? testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
366
396
const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : TestInfoImpl._defaultDeadlineForMatcher(timeout);
367
397
368
398
const result = await pollAgainstDeadline<Error|undefined>(async () => {
@@ -398,22 +428,6 @@ async function pollMatcher(qualifiedMatcherName: string, info: ExpectMetaInfo, p
398
428
}
399
429
}
400
430
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
-
417
431
function computeArgsSuffix(matcherName: string, args: any[]) {
418
432
let value = '';
419
433
if (matcherName === 'toHaveScreenshot')
@@ -426,7 +440,7 @@ export const expect: Expect<{}> = createExpect({}, [], {}).extend(customMatchers
426
440
export function mergeExpects(...expects: any[]) {
427
441
let merged = expect;
428
442
for (const e of expects) {
429
-
const internals = e[getCustomMatchersSymbol];
443
+
const internals = e[userMatchersSymbol];
430
444
if (!internals) // non-playwright expects mutate the global expect, so we don't need to do anything special
431
445
continue;
432
446
merged = merged.extend(internals);
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4