feat(poll): expose custom poll interval (#13776)

This commit is contained in:
Pavel Feldman 2022-04-26 20:32:38 -08:00 committed by GitHub
parent 2bc36794d1
commit bc6f8e1f20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 39 additions and 9 deletions

View file

@ -107,13 +107,25 @@ await expect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {
// Custom error message
// Custom error message, optional.
message: 'make sure API eventually succeeds', // custom error message
// Poll for 10 seconds; defaults to 5 seconds. Pass 0 to disable timeout.
timeout: 10000,
}).toBe(200);
```
You can also specify custom polling intervals:
```js
await expect.poll(async () => {
const response = await page.request.get('https://api.example.com');
return response.status();
}, {
// Probe, wait 1s, probe, wait 2s, probe, wait 10s, probe, wait 10s, probe, .... Defaults to [100, 250, 500, 1000].
intervals: [1_000, 2_000, 10_000],
timeout: 60_000
}).toBe(200);
```
## API reference
See the following pages for Playwright-specific assertions:

View file

@ -96,7 +96,7 @@ export const printReceivedStringContainExpectedResult = (
// #endregion
type ExpectMessageOrOptions = undefined | string | { message?: string, timeout?: number };
type ExpectMessageOrOptions = undefined | string | { message?: string, timeout?: number, intervals?: number[] };
function createExpect(actual: unknown, messageOrOptions: ExpectMessageOrOptions, isSoft: boolean, isPoll: boolean, generator?: Generator) {
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(messageOrOptions, isSoft, isPoll, generator));
@ -153,6 +153,7 @@ type ExpectMetaInfo = {
isSoft: boolean;
isPoll: boolean;
pollTimeout?: number;
pollIntervals?: number[];
generator?: Generator;
};
@ -166,6 +167,7 @@ class ExpectMetaInfoProxyHandler {
} else {
this._info.message = messageOrOptions?.message;
this._info.pollTimeout = messageOrOptions?.timeout;
this._info.pollIntervals = messageOrOptions?.intervals;
}
}
@ -233,7 +235,7 @@ class ExpectMetaInfoProxyHandler {
if (this._info.isPoll) {
if ((customMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects')
throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
result = pollMatcher(matcherName, this._info.isNot, currentExpectTimeout({ timeout: this._info.pollTimeout }), this._info.generator!, ...args);
result = pollMatcher(matcherName, this._info.isNot, this._info.pollIntervals, currentExpectTimeout({ timeout: this._info.pollTimeout }), this._info.generator!, ...args);
} else {
result = matcher.call(target, ...args);
}
@ -248,10 +250,11 @@ class ExpectMetaInfoProxyHandler {
}
}
async function pollMatcher(matcherName: any, isNot: boolean, timeout: number, generator: () => any, ...args: any[]) {
async function pollMatcher(matcherName: any, isNot: boolean, pollIntervals: number[] | undefined, timeout: number, generator: () => any, ...args: any[]) {
let matcherError;
const startTime = monotonicTime();
const pollIntervals = [100, 250, 500];
pollIntervals = pollIntervals || [100, 250, 500, 1000];
const lastPollInterval = pollIntervals[pollIntervals.length - 1] || 1000;
while (true) {
const elapsed = monotonicTime() - startTime;
if (timeout !== 0 && elapsed > timeout)
@ -268,7 +271,7 @@ async function pollMatcher(matcherName: any, isNot: boolean, timeout: number, ge
} catch (e) {
matcherError = e;
}
await new Promise(x => setTimeout(x, pollIntervals.shift() ?? 1000));
await new Promise(x => setTimeout(x, pollIntervals!.shift() ?? lastPollInterval));
}
const timeoutMessage = `Timeout ${timeout}ms exceeded while waiting on the predicate`;

View file

@ -2962,7 +2962,7 @@ type MakeMatchers<R, T> = BaseMatchers<R, T> & {
export type Expect = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T>;
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number }) => BaseMatchers<Promise<void>, T> & {
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers<Promise<void>, T> & {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/

View file

@ -19229,7 +19229,7 @@ type MakeMatchers<R, T> = BaseMatchers<R, T> & {
export type Expect = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T>;
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number }) => BaseMatchers<Promise<void>, T> & {
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers<Promise<void>, T> & {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/

View file

@ -183,3 +183,18 @@ test('should support custom matchers', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should respect interval', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.spec.ts': `
const { test } = pwt;
test('should fail', async () => {
let probes = 0;
await test.expect.poll(() => ++probes, { timeout: 1000, intervals: [600] }).toBe(3).catch(() => {});
// Probe at 0s, at 0.6s.
expect(probes).toBe(2);
});
`
});
expect(result.exitCode).toBe(0);
});

View file

@ -323,7 +323,7 @@ type MakeMatchers<R, T> = BaseMatchers<R, T> & {
export type Expect = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T>;
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number }) => BaseMatchers<Promise<void>, T> & {
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers<Promise<void>, T> & {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/