feat(poll): expose custom poll interval (#13776)
This commit is contained in:
parent
2bc36794d1
commit
bc6f8e1f20
|
|
@ -107,13 +107,25 @@ await expect.poll(async () => {
|
||||||
const response = await page.request.get('https://api.example.com');
|
const response = await page.request.get('https://api.example.com');
|
||||||
return response.status();
|
return response.status();
|
||||||
}, {
|
}, {
|
||||||
// Custom error message
|
// Custom error message, optional.
|
||||||
message: 'make sure API eventually succeeds', // custom error message
|
message: 'make sure API eventually succeeds', // custom error message
|
||||||
// Poll for 10 seconds; defaults to 5 seconds. Pass 0 to disable timeout.
|
// Poll for 10 seconds; defaults to 5 seconds. Pass 0 to disable timeout.
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
}).toBe(200);
|
}).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
|
## API reference
|
||||||
See the following pages for Playwright-specific assertions:
|
See the following pages for Playwright-specific assertions:
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export const printReceivedStringContainExpectedResult = (
|
||||||
|
|
||||||
// #endregion
|
// #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) {
|
function createExpect(actual: unknown, messageOrOptions: ExpectMessageOrOptions, isSoft: boolean, isPoll: boolean, generator?: Generator) {
|
||||||
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(messageOrOptions, isSoft, isPoll, generator));
|
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(messageOrOptions, isSoft, isPoll, generator));
|
||||||
|
|
@ -153,6 +153,7 @@ type ExpectMetaInfo = {
|
||||||
isSoft: boolean;
|
isSoft: boolean;
|
||||||
isPoll: boolean;
|
isPoll: boolean;
|
||||||
pollTimeout?: number;
|
pollTimeout?: number;
|
||||||
|
pollIntervals?: number[];
|
||||||
generator?: Generator;
|
generator?: Generator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -166,6 +167,7 @@ class ExpectMetaInfoProxyHandler {
|
||||||
} else {
|
} else {
|
||||||
this._info.message = messageOrOptions?.message;
|
this._info.message = messageOrOptions?.message;
|
||||||
this._info.pollTimeout = messageOrOptions?.timeout;
|
this._info.pollTimeout = messageOrOptions?.timeout;
|
||||||
|
this._info.pollIntervals = messageOrOptions?.intervals;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,7 +235,7 @@ class ExpectMetaInfoProxyHandler {
|
||||||
if (this._info.isPoll) {
|
if (this._info.isPoll) {
|
||||||
if ((customMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects')
|
if ((customMatchers 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.`);
|
||||||
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 {
|
} else {
|
||||||
result = matcher.call(target, ...args);
|
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;
|
let matcherError;
|
||||||
const startTime = monotonicTime();
|
const startTime = monotonicTime();
|
||||||
const pollIntervals = [100, 250, 500];
|
pollIntervals = pollIntervals || [100, 250, 500, 1000];
|
||||||
|
const lastPollInterval = pollIntervals[pollIntervals.length - 1] || 1000;
|
||||||
while (true) {
|
while (true) {
|
||||||
const elapsed = monotonicTime() - startTime;
|
const elapsed = monotonicTime() - startTime;
|
||||||
if (timeout !== 0 && elapsed > timeout)
|
if (timeout !== 0 && elapsed > timeout)
|
||||||
|
|
@ -268,7 +271,7 @@ async function pollMatcher(matcherName: any, isNot: boolean, timeout: number, ge
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
matcherError = 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`;
|
const timeoutMessage = `Timeout ${timeout}ms exceeded while waiting on the predicate`;
|
||||||
|
|
|
||||||
2
packages/playwright-test/types/test.d.ts
vendored
2
packages/playwright-test/types/test.d.ts
vendored
|
|
@ -2962,7 +2962,7 @@ type MakeMatchers<R, T> = BaseMatchers<R, T> & {
|
||||||
export type Expect = {
|
export type Expect = {
|
||||||
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
||||||
soft: <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.
|
* If you know how to test something, `.not` lets you test its opposite.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
2
tests/config/experimental.d.ts
vendored
2
tests/config/experimental.d.ts
vendored
|
|
@ -19229,7 +19229,7 @@ type MakeMatchers<R, T> = BaseMatchers<R, T> & {
|
||||||
export type Expect = {
|
export type Expect = {
|
||||||
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
||||||
soft: <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.
|
* If you know how to test something, `.not` lets you test its opposite.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -183,3 +183,18 @@ test('should support custom matchers', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
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);
|
||||||
|
});
|
||||||
|
|
|
||||||
2
utils/generate_types/overrides-test.d.ts
vendored
2
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -323,7 +323,7 @@ type MakeMatchers<R, T> = BaseMatchers<R, T> & {
|
||||||
export type Expect = {
|
export type Expect = {
|
||||||
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
||||||
soft: <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.
|
* If you know how to test something, `.not` lets you test its opposite.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue