diff --git a/src/index.ts b/src/index.ts index 8d81984..9fbb85e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,14 @@ import { getSetTimeoutFn } from "./helpers"; const defaults = { timeout: 4500, - interval: 50 + interval: 50, + minConsecutivePasses: 1 }; +const consecutivePassesDefaultError = new Error( + "Test have not passed the min number of consecutive required runs, might be a flaky test" +); + /** * Waits for the expectation to pass and returns a Promise * @@ -16,7 +21,8 @@ const defaults = { const waitForExpect = function waitForExpect( expectation: () => void | Promise, timeout = defaults.timeout, - interval = defaults.interval + interval = defaults.interval, + minConsecutivePasses = defaults.minConsecutivePasses ) { const setTimeout = getSetTimeoutFn(); @@ -24,8 +30,16 @@ const waitForExpect = function waitForExpect( if (interval < 1) interval = 1; const maxTries = Math.ceil(timeout / interval); let tries = 0; + let consecutivePasses = 0; + let lastError = consecutivePassesDefaultError; return new Promise((resolve, reject) => { - const rejectOrRerun = (error: Error) => { + const getRejectOrRerun = (resetConsecutivePasses: boolean) => ( + error: Error + ) => { + if (resetConsecutivePasses) { + consecutivePasses = 0; + lastError = error; + } if (tries > maxTries) { reject(error); return; @@ -37,10 +51,17 @@ const waitForExpect = function waitForExpect( tries += 1; try { Promise.resolve(expectation()) - .then(() => resolve()) - .catch(rejectOrRerun); + .then(() => { + consecutivePasses += 1; + if (consecutivePasses === minConsecutivePasses) { + resolve(); + return; + } + getRejectOrRerun(false)(lastError); + }) + .catch(getRejectOrRerun(true)); } catch (error) { - rejectOrRerun(error); + getRejectOrRerun(true)(error); } } setTimeout(runExpectation, 0); diff --git a/src/waitForExpect.spec.ts b/src/waitForExpect.spec.ts index a7617a0..e4aa8bf 100644 --- a/src/waitForExpect.spec.ts +++ b/src/waitForExpect.spec.ts @@ -134,3 +134,22 @@ test("it works with a zero interval", async () => { 0 ); }); + +test("it does not pass flaky tests", async () => { + waitForExpect.defaults.minConsecutivePasses = 3; + try { + let counter = 0; + await waitForExpect(() => { + // a flaky test that passes if retried enough times + counter += 1; + if (counter % 10 === 0) { + expect(true).toEqual(true); + } else { + expect(true).toEqual(false); + } + }); + throw Error("waitForExpect should have thrown"); + } catch ({ message }) { + expect(message).not.toEqual("waitForExpect should have thrown"); + } +});