From bc6f8e1f208719d1775370d03fcd1fd7c64f0fed Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 26 Apr 2022 20:32:38 -0800 Subject: [PATCH] feat(poll): expose custom poll interval (#13776) --- docs/src/test-assertions-js.md | 14 +++++++++++++- packages/playwright-test/src/expect.ts | 13 ++++++++----- packages/playwright-test/types/test.d.ts | 2 +- tests/config/experimental.d.ts | 2 +- tests/playwright-test/expect-poll.spec.ts | 15 +++++++++++++++ utils/generate_types/overrides-test.d.ts | 2 +- 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/src/test-assertions-js.md b/docs/src/test-assertions-js.md index 40ea37ca90..439858c087 100644 --- a/docs/src/test-assertions-js.md +++ b/docs/src/test-assertions-js.md @@ -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: diff --git a/packages/playwright-test/src/expect.ts b/packages/playwright-test/src/expect.ts index db3a6396ee..e302bb0e8c 100644 --- a/packages/playwright-test/src/expect.ts +++ b/packages/playwright-test/src/expect.ts @@ -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`; diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index 5ae5425570..7cd8083250 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -2962,7 +2962,7 @@ type MakeMatchers = BaseMatchers & { export type Expect = { (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; - poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number }) => BaseMatchers, T> & { + poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { /** * If you know how to test something, `.not` lets you test its opposite. */ diff --git a/tests/config/experimental.d.ts b/tests/config/experimental.d.ts index f4dd2d1827..e95ad7703d 100644 --- a/tests/config/experimental.d.ts +++ b/tests/config/experimental.d.ts @@ -19229,7 +19229,7 @@ type MakeMatchers = BaseMatchers & { export type Expect = { (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; - poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number }) => BaseMatchers, T> & { + poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { /** * If you know how to test something, `.not` lets you test its opposite. */ diff --git a/tests/playwright-test/expect-poll.spec.ts b/tests/playwright-test/expect-poll.spec.ts index 170c37c1e6..a3e727ce9e 100644 --- a/tests/playwright-test/expect-poll.spec.ts +++ b/tests/playwright-test/expect-poll.spec.ts @@ -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); +}); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 3baa087748..a87cd38270 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -323,7 +323,7 @@ type MakeMatchers = BaseMatchers & { export type Expect = { (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; - poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number }) => BaseMatchers, T> & { + poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { /** * If you know how to test something, `.not` lets you test its opposite. */