From 610ac6aabb57b97688b81af77d8b3c61d23b161f Mon Sep 17 00:00:00 2001 From: Steven Kitterman Date: Fri, 5 Jul 2024 10:09:17 -0700 Subject: [PATCH 1/2] Network idle watcher for Cypress --- .../cypress/src/network-idle-watcher.test.ts | 76 +++++++++++++++++++ packages/cypress/src/network-idle-watcher.ts | 39 ++++++++++ 2 files changed, 115 insertions(+) create mode 100644 packages/cypress/src/network-idle-watcher.test.ts create mode 100644 packages/cypress/src/network-idle-watcher.ts diff --git a/packages/cypress/src/network-idle-watcher.test.ts b/packages/cypress/src/network-idle-watcher.test.ts new file mode 100644 index 00000000..c77b9dad --- /dev/null +++ b/packages/cypress/src/network-idle-watcher.test.ts @@ -0,0 +1,76 @@ +import { NetworkIdleWatcher } from './network-idle-watcher'; + +jest.useFakeTimers(); + +it('Resolves when there is no network activity', async () => { + const watcher = new NetworkIdleWatcher(); + await expect(watcher.idle()).resolves.toBeDefined(); +}); + +it('Resolves when there is a single request and response', async () => { + const watcher = new NetworkIdleWatcher(); + + watcher.onRequest(); + watcher.onResponse(); + + await expect(watcher.idle()).resolves.toBeDefined(); +}); + +it('Resolves when there are an equal amount of requests and responses', async () => { + const watcher = new NetworkIdleWatcher(); + // in total 4 requests, and 4 responses + watcher.onRequest(); + watcher.onResponse(); + + watcher.onRequest(); + watcher.onResponse(); + + watcher.onRequest(); + watcher.onRequest(); + + watcher.onResponse(); + watcher.onResponse(); + + await expect(watcher.idle()).resolves.toBeDefined(); +}); + +it('Rejects if response never sent for request', async () => { + const watcher = new NetworkIdleWatcher(); + // fire off request + watcher.onRequest(); + const promise = watcher.idle(); + jest.runAllTimers(); + // no response fired off + await expect(promise).rejects.toBeDefined(); +}); + +it("Resolves if response hasn't happened at time of idle(), but comes back before timeout", async () => { + const watcher = new NetworkIdleWatcher(); + // fire off request + watcher.onRequest(); + + const promise = watcher.idle(); + + watcher.onResponse(); + // makes sure we finish the idle watcher as soon as the reponse comes back, and not waiting the full timeout duration + jest.advanceTimersByTime(1); + + await expect(promise).resolves.toBeDefined(); +}); + +it("Rejects if response hasn't happened at time of idle(), and doesn't come back before timeout", async () => { + const watcher = new NetworkIdleWatcher(); + // fire off request + watcher.onRequest(); + + const promise = watcher.idle(); + + // response returns after idle() has been called, but will take too long + setTimeout(() => { + watcher.onResponse(); + }, 10000); + + jest.runAllTimers(); + + await expect(promise).rejects.toBeDefined(); +}); diff --git a/packages/cypress/src/network-idle-watcher.ts b/packages/cypress/src/network-idle-watcher.ts new file mode 100644 index 00000000..b27b50d2 --- /dev/null +++ b/packages/cypress/src/network-idle-watcher.ts @@ -0,0 +1,39 @@ +const TOTAL_TIMEOUT_DURATION = 3000; + +export class NetworkIdleWatcher { + private numInFlightRequests = 0; + + private idleTimer: NodeJS.Timeout | null = null; + + private exitIdleOnResponse: () => void | null = null; + + async idle() { + return new Promise((resolve, reject) => { + if (this.numInFlightRequests === 0) { + resolve(true); + } else { + this.idleTimer = setTimeout(() => { + reject(new Error('some responses have not returned')); + }, TOTAL_TIMEOUT_DURATION); + + // assign a function that resolves... and it can be used... + this.exitIdleOnResponse = () => { + resolve(true); + }; + } + }); + } + + onRequest() { + this.numInFlightRequests += 1; + } + + onResponse() { + this.numInFlightRequests -= 1; + // resolve if the in-flight request amount is now zero + if (this.numInFlightRequests === 0) { + clearTimeout(this.idleTimer); + this.exitIdleOnResponse?.(); + } + } +} From 604ef926dd5c3271149f5f56954c95be90b2e9c7 Mon Sep 17 00:00:00 2001 From: Steven Kitterman Date: Fri, 5 Jul 2024 10:18:00 -0700 Subject: [PATCH 2/2] Clarifying comments --- packages/cypress/src/network-idle-watcher.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/cypress/src/network-idle-watcher.ts b/packages/cypress/src/network-idle-watcher.ts index b27b50d2..1c2b9387 100644 --- a/packages/cypress/src/network-idle-watcher.ts +++ b/packages/cypress/src/network-idle-watcher.ts @@ -1,5 +1,8 @@ const TOTAL_TIMEOUT_DURATION = 3000; +// A Cypress equivalent of Playwright's `page.waitForLoadState()` (https://playwright.dev/docs/api/class-page#page-wait-for-load-state). +// Intentionally simplistic since in Cypress this is just used to make sure there aren't any pending requests hanging around +// after the test has finished. export class NetworkIdleWatcher { private numInFlightRequests = 0; @@ -16,7 +19,7 @@ export class NetworkIdleWatcher { reject(new Error('some responses have not returned')); }, TOTAL_TIMEOUT_DURATION); - // assign a function that resolves... and it can be used... + // assign a function that'll be called as soon as responses are all back this.exitIdleOnResponse = () => { resolve(true); }; @@ -30,7 +33,7 @@ export class NetworkIdleWatcher { onResponse() { this.numInFlightRequests -= 1; - // resolve if the in-flight request amount is now zero + // resolve immediately if the in-flight request amount is now zero if (this.numInFlightRequests === 0) { clearTimeout(this.idleTimer); this.exitIdleOnResponse?.();