mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(expect): respect custom message in expect.poll (#32603)
Fixes #32582.
This commit is contained in:
parent
c24ad36f86
commit
268357238a
@ -121,10 +121,10 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
|
|||||||
const [actual, messageOrOptions] = argumentsList;
|
const [actual, messageOrOptions] = argumentsList;
|
||||||
const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
|
const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
|
||||||
const newInfo = { ...info, message };
|
const newInfo = { ...info, message };
|
||||||
if (newInfo.isPoll) {
|
if (newInfo.poll) {
|
||||||
if (typeof actual !== 'function')
|
if (typeof actual !== 'function')
|
||||||
throw new Error('`expect.poll()` accepts only function as a first argument');
|
throw new Error('`expect.poll()` accepts only function as a first argument');
|
||||||
newInfo.generator = actual as any;
|
newInfo.poll.generator = actual as any;
|
||||||
}
|
}
|
||||||
return createMatchers(actual, newInfo, prefix);
|
return createMatchers(actual, newInfo, prefix);
|
||||||
},
|
},
|
||||||
@ -189,10 +189,10 @@ function createExpect(info: ExpectMetaInfo, prefix: string[], customMatchers: Re
|
|||||||
if ('soft' in configuration)
|
if ('soft' in configuration)
|
||||||
newInfo.isSoft = configuration.soft;
|
newInfo.isSoft = configuration.soft;
|
||||||
if ('_poll' in configuration) {
|
if ('_poll' in configuration) {
|
||||||
newInfo.isPoll = !!configuration._poll;
|
newInfo.poll = configuration._poll ? { ...info.poll, generator: () => {} } : undefined;
|
||||||
if (typeof configuration._poll === 'object') {
|
if (typeof configuration._poll === 'object') {
|
||||||
newInfo.pollTimeout = configuration._poll.timeout;
|
newInfo.poll!.timeout = configuration._poll.timeout ?? newInfo.poll!.timeout;
|
||||||
newInfo.pollIntervals = configuration._poll.intervals;
|
newInfo.poll!.intervals = configuration._poll.intervals ?? newInfo.poll!.intervals;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createExpect(newInfo, prefix, customMatchers);
|
return createExpect(newInfo, prefix, customMatchers);
|
||||||
@ -249,11 +249,12 @@ type ExpectMetaInfo = {
|
|||||||
message?: string;
|
message?: string;
|
||||||
isNot?: boolean;
|
isNot?: boolean;
|
||||||
isSoft?: boolean;
|
isSoft?: boolean;
|
||||||
isPoll?: boolean;
|
poll?: {
|
||||||
|
timeout?: number;
|
||||||
|
intervals?: number[];
|
||||||
|
generator: Generator;
|
||||||
|
};
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
pollTimeout?: number;
|
|
||||||
pollIntervals?: number[];
|
|
||||||
generator?: Generator;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
||||||
@ -287,10 +288,10 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||||||
this._info.isNot = !this._info.isNot;
|
this._info.isNot = !this._info.isNot;
|
||||||
return new Proxy(matcher, this);
|
return new Proxy(matcher, this);
|
||||||
}
|
}
|
||||||
if (this._info.isPoll) {
|
if (this._info.poll) {
|
||||||
if ((customAsyncMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects')
|
if ((customAsyncMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects')
|
||||||
throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
|
throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
|
||||||
matcher = (...args: any[]) => pollMatcher(resolvedMatcherName, !!this._info.isNot, this._info.pollIntervals, this._info.pollTimeout ?? currentExpectTimeout(), this._info.generator!, ...args);
|
matcher = (...args: any[]) => pollMatcher(resolvedMatcherName, this._info, this._prefix, ...args);
|
||||||
}
|
}
|
||||||
return (...args: any[]) => {
|
return (...args: any[]) => {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
@ -302,7 +303,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||||||
const customMessage = this._info.message || '';
|
const customMessage = this._info.message || '';
|
||||||
const argsSuffix = computeArgsSuffix(matcherName, args);
|
const argsSuffix = computeArgsSuffix(matcherName, args);
|
||||||
|
|
||||||
const defaultTitle = `expect${this._info.isPoll ? '.poll' : ''}${this._info.isSoft ? '.soft' : ''}${this._info.isNot ? '.not' : ''}.${matcherName}${argsSuffix}`;
|
const defaultTitle = `expect${this._info.poll ? '.poll' : ''}${this._info.isSoft ? '.soft' : ''}${this._info.isNot ? '.not' : ''}.${matcherName}${argsSuffix}`;
|
||||||
const title = customMessage || defaultTitle;
|
const title = customMessage || defaultTitle;
|
||||||
|
|
||||||
// This looks like it is unnecessary, but it isn't - we need to filter
|
// This looks like it is unnecessary, but it isn't - we need to filter
|
||||||
@ -336,7 +337,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||||||
const callback = () => matcher.call(target, ...args);
|
const callback = () => matcher.call(target, ...args);
|
||||||
// toPass and poll matchers can contain other steps, expects and API calls,
|
// toPass and poll matchers can contain other steps, expects and API calls,
|
||||||
// so they behave like a retriable step.
|
// so they behave like a retriable step.
|
||||||
const result = (matcherName === 'toPass' || this._info.isPoll) ?
|
const result = (matcherName === 'toPass' || this._info.poll) ?
|
||||||
zones.run('stepZone', step, callback) :
|
zones.run('stepZone', step, callback) :
|
||||||
zones.run<ExpectZone, any>('expectZone', { title, stepId: step.stepId }, callback);
|
zones.run<ExpectZone, any>('expectZone', { title, stepId: step.stepId }, callback);
|
||||||
if (result instanceof Promise)
|
if (result instanceof Promise)
|
||||||
@ -350,25 +351,32 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pollMatcher(qualifiedMatcherName: any, isNot: boolean, pollIntervals: number[] | undefined, timeout: number, generator: () => any, ...args: any[]) {
|
async function pollMatcher(qualifiedMatcherName: string, info: ExpectMetaInfo, prefix: string[], ...args: any[]) {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
|
const poll = info.poll!;
|
||||||
|
const timeout = poll.timeout ?? currentExpectTimeout();
|
||||||
const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : TestInfoImpl._defaultDeadlineForMatcher(timeout);
|
const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : TestInfoImpl._defaultDeadlineForMatcher(timeout);
|
||||||
|
|
||||||
const result = await pollAgainstDeadline<Error|undefined>(async () => {
|
const result = await pollAgainstDeadline<Error|undefined>(async () => {
|
||||||
if (testInfo && currentTestInfo() !== testInfo)
|
if (testInfo && currentTestInfo() !== testInfo)
|
||||||
return { continuePolling: false, result: undefined };
|
return { continuePolling: false, result: undefined };
|
||||||
|
|
||||||
const value = await generator();
|
const innerInfo: ExpectMetaInfo = {
|
||||||
let expectInstance = expectLibrary(value) as any;
|
...info,
|
||||||
if (isNot)
|
isSoft: false, // soft is outside of poll, not inside
|
||||||
expectInstance = expectInstance.not;
|
poll: undefined,
|
||||||
|
};
|
||||||
|
const value = await poll.generator();
|
||||||
try {
|
try {
|
||||||
expectInstance[qualifiedMatcherName].call(expectInstance, ...args);
|
let matchers = createMatchers(value, innerInfo, prefix);
|
||||||
|
if (info.isNot)
|
||||||
|
matchers = matchers.not;
|
||||||
|
matchers[qualifiedMatcherName](...args);
|
||||||
return { continuePolling: false, result: undefined };
|
return { continuePolling: false, result: undefined };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { continuePolling: true, result: error };
|
return { continuePolling: true, result: error };
|
||||||
}
|
}
|
||||||
}, deadline, pollIntervals ?? [100, 250, 500, 1000]);
|
}, deadline, poll.intervals ?? [100, 250, 500, 1000]);
|
||||||
|
|
||||||
if (result.timedOut) {
|
if (result.timedOut) {
|
||||||
const message = result.result ? [
|
const message = result.result ? [
|
||||||
|
|||||||
@ -263,3 +263,19 @@ test('should propagate string exception from async arrow function', { annotation
|
|||||||
|
|
||||||
expect(result.output).toContain('some error');
|
expect(result.output).toContain('some error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show custom message', {
|
||||||
|
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32582' }
|
||||||
|
}, async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('should fail', async () => {
|
||||||
|
await expect.poll(() => 1, { message: 'custom message', timeout: 500 }).toBe(2);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.output).toContain('Error: custom message');
|
||||||
|
expect(result.output).toContain('Expected: 2');
|
||||||
|
expect(result.output).toContain('Received: 1');
|
||||||
|
});
|
||||||
|
|||||||
@ -987,9 +987,12 @@ expect |expect.poll.toHaveLength @ a.test.ts:14
|
|||||||
pw:api | page.goto(about:blank) @ a.test.ts:7
|
pw:api | page.goto(about:blank) @ a.test.ts:7
|
||||||
test.step | inner step attempt: 0 @ a.test.ts:8
|
test.step | inner step attempt: 0 @ a.test.ts:8
|
||||||
expect | expect.toBe @ a.test.ts:10
|
expect | expect.toBe @ a.test.ts:10
|
||||||
|
expect | expect.toHaveLength @ a.test.ts:6
|
||||||
|
expect | ↪ error: Error: expect(received).toHaveLength(expected)
|
||||||
pw:api | page.goto(about:blank) @ a.test.ts:7
|
pw:api | page.goto(about:blank) @ a.test.ts:7
|
||||||
test.step | inner step attempt: 1 @ a.test.ts:8
|
test.step | inner step attempt: 1 @ a.test.ts:8
|
||||||
expect | expect.toBe @ a.test.ts:10
|
expect | expect.toBe @ a.test.ts:10
|
||||||
|
expect | expect.toHaveLength @ a.test.ts:6
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
@ -1036,9 +1039,12 @@ expect |expect.poll.toBe @ a.test.ts:13
|
|||||||
expect | expect.toHaveText @ a.test.ts:7
|
expect | expect.toHaveText @ a.test.ts:7
|
||||||
test.step | iteration 1 @ a.test.ts:9
|
test.step | iteration 1 @ a.test.ts:9
|
||||||
expect | expect.toBeVisible @ a.test.ts:10
|
expect | expect.toBeVisible @ a.test.ts:10
|
||||||
|
expect | expect.toBe @ a.test.ts:6
|
||||||
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.toHaveText @ a.test.ts:7
|
expect | expect.toHaveText @ a.test.ts:7
|
||||||
test.step | iteration 2 @ a.test.ts:9
|
test.step | iteration 2 @ a.test.ts:9
|
||||||
expect | expect.toBeVisible @ a.test.ts:10
|
expect | expect.toBeVisible @ a.test.ts:10
|
||||||
|
expect | expect.toBe @ a.test.ts:6
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user