fix(expect): properly handle custom asymmetric matcher regression (#35149)

This commit is contained in:
Adam Gastineau 2025-03-12 09:05:53 -07:00 committed by GitHub
parent 629f5f2a9a
commit a98075085e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 107 additions and 4 deletions

View File

@ -209,10 +209,12 @@ function setMatcherCallContext(context: MatcherCallContext) {
matcherCallContext = context;
}
function takeMatcherCallContext(): MatcherCallContext {
function takeMatcherCallContext(): MatcherCallContext | undefined {
try {
return matcherCallContext!;
return matcherCallContext;
} finally {
// Any subsequent matcher following the first is assumed to be an unsupported legacy asymmetric matcher.
// Lacking call context in these scenarios is not particularly important.
matcherCallContext = undefined;
}
}
@ -223,13 +225,13 @@ function wrapPlaywrightMatcherToPassNiceThis(matcher: any) {
return function(this: any, ...args: any[]) {
const { isNot, promise, utils } = this;
const context = takeMatcherCallContext();
const timeout = context.expectInfo.timeout ?? context.testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
const timeout = context?.expectInfo.timeout ?? context?.testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
const newThis: ExpectMatcherStateInternal = {
isNot,
promise,
utils,
timeout,
_stepInfo: context.step,
_stepInfo: context?.step,
};
(newThis as any).equals = throwUnsupportedExpectMatcherError;
return matcher.call(newThis, ...args);

View File

@ -1186,3 +1186,104 @@ test('custom asymmetric matchers should work with expect.extend', async ({ runIn
expect(result.passed).toBe(1);
expect(result.output).not.toContain('should not run');
});
test('custom asymmetric matchers should function', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35138' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'expect-test.spec.ts': `
import { test, expect } from '@playwright/test';
expect.extend({
isUndefined(received: unknown, expected: string) {
return { pass: received === undefined, message: () => '' };
}
});
test('example', () => {
expect(undefined).toEqual(expect.isUndefined());
});
test('example2', () => {
expect({
aProperty: undefined,
bProperty: 'foo',
}).toEqual({
aProperty: expect.isUndefined(),
bProperty: 'foo',
cProperty: expect.isUndefined(),
});
});
`,
});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2);
});
test('single custom asymmetric matcher should present the correct error', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35138' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'expect-test.spec.ts': `
import { test, expect } from '@playwright/test';
expect.extend({
isUndefined(received: unknown, expected: string) {
return { pass: received === undefined, message: () => '' };
}
});
test('example', () => {
expect({
aProperty: 'foo'
}).toEqual({
aProperty: expect.isUndefined()
});
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('- \"aProperty\": isUndefined<>');
expect(result.output).toContain('+ \"aProperty\": \"foo\"');
});
test('multiple custom asymmetric matchers should present the correct error', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35138' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'expect-test.spec.ts': `
import { test, expect } from '@playwright/test';
expect.extend({
isUndefined(received: unknown, expected: string) {
return { pass: received === undefined, message: () => '' };
}
});
test('example', () => {
expect({
aProperty: undefined,
bProperty: 'foo',
}).toEqual({
aProperty: expect.isUndefined(),
bProperty: expect.isUndefined()
});
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('- \"bProperty\": isUndefined<>');
expect(result.output).toContain('+ \"bProperty\": \"foo\"');
});
test('multiple custom asymmetric matchers in async expect should present the correct error', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35138' } }, async ({ runInlineTest }) => {
const result = await runInlineTest({
'expect-test.spec.ts': `
import { test, expect } from '@playwright/test';
expect.extend({
isUndefined(received: unknown, expected: string) {
return { pass: received === undefined, message: () => '' };
}
});
test('example', async () => {
await expect.poll(() => ({ aProperty: 'foo', bProperty: undefined })).toEqual({
aProperty: expect.isUndefined(),
bProperty: expect.isUndefined(),
});
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('- \"aProperty\": isUndefined<>');
expect(result.output).toContain('+ \"aProperty\": \"foo\"');
});