UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

471 lines (370 loc) 11.3 kB
const startingIndex = Cypress.isBrowser('firefox') ? 1 : 0 const timerNumber = (n) => n + startingIndex // NOTE: basically the same as a cy.wait(...) but uses setTimeout instead of Promise.delay // since firefox will sometimes have `Promise.delay(10)` fire faster than a `setTimeout(..., 1)` const cyWaitTimeout = (n) => cy.wrap(new Promise((resolve) => window.setTimeout(resolve, n))) describe('driver/src/cy/timers', () => { beforeEach(() => { cy.visit('/fixtures/generic.html') }) it('setTimeout is called through', () => { cy .log('setTimeout should be called') .window() .then((win) => { win.setBar = () => { win.bar = 'bar' } const id1 = win.setTimeout(win.setBar, 1) // the timer id is 1 by default since // timers increment and always start at 0 expect(id1).to.eq(timerNumber(1)) cy .window().its('bar').should('eq', 'bar') .log('setTimeout should not be called') .then(() => { win.bar = null const id2 = win.setTimeout(win.setBar, 2) expect(id2).to.eq(timerNumber(2)) const ret = win.clearTimeout(id2) expect(ret).to.be.undefined }) .wait(100) .window().its('bar').should('be.null') }) }) it('setTimeout can pass multiple parameters to the target function', () => { cy .log('setTimeout should call target with two parameters') .window() .then((win) => { win.foo = null win.setFoo = (bar, baz) => { win.foo = bar + baz } win.setTimeout(win.setFoo, 0, 'bar', 'baz') cy .window().its('foo').should('eq', 'barbaz') }) }) it('setInterval is called through', () => { cy .log('setInterval should be called') .window() .then((win) => { win.setBar = () => { win.bar = 'bar' } const id1 = win.setInterval(win.setBar, 1) // the timer id is 1 by default since // timers increment and always start at 0 expect(id1).to.eq(timerNumber(1)) cy .window().its('bar').should('eq', 'bar') .log('setInterval should not be called') .then(() => { win.clearInterval(id1) win.bar = null const id2 = win.setInterval(win.setBar, 2) expect(id2).to.eq(timerNumber(2)) const ret = win.clearInterval(id2) expect(ret).to.be.undefined }) .wait(100) .window().its('bar').should('be.null') }) }) it('setInterval can pass multiple parameters to the target function', () => { cy .log('setInterval should call target with two parameters') .window() .then((win) => { win.foo = null win.setFoo = (bar, baz) => { win.foo = bar + baz } const id1 = win.setInterval(win.setFoo, 1, 'bar', 'baz') cy .window().its('foo').should('eq', 'barbaz') .then(() => { win.clearInterval(id1) }) }) }) it('requestAnimationFrame is called through', () => { cy .log('requestAnimationFrame should be called') .window() .then((win) => { const rafStub = cy .stub() .callsFake(() => { win.bar = 'bar' }) const id1 = win.requestAnimationFrame(rafStub, 'foo', 'bar', 'baz') // the timer id is 1 by default since // timers increment and always start at 0 expect(id1).to.eq(1) cy .window().its('bar').should('eq', 'bar') .log('requestAnimationFrame should not be called') .then(() => { // requestAnimationFrame should have passed through // its high res timestamp from performance.now() expect(rafStub).to.be.calledWithMatch(Number) expect(rafStub.firstCall.args.length).to.eq(1) win.bar = null const id2 = win.requestAnimationFrame(rafStub) expect(id2).to.eq(2) const ret = win.cancelAnimationFrame(id2) expect(ret).to.be.undefined }) .wait(100) .window().its('bar').should('be.null') }) }) it('delays calls to requestAnimationFrame when paused', () => { cy .window() .then((win) => { win.bar = null const rafStub = cy .stub() .callsFake(() => { win.bar = 'bar' }) // prevent timers from firing, add to queue cy.pauseTimers(true) const id1 = win.requestAnimationFrame(rafStub) expect(id1).to.eq(1) cy .wait(100) .log('requestAnimationFrame should NOT have fired when paused') .window().its('bar').should('be.null') .log('requestAnimationFrame should now fire when unpaused') .then(() => { // now go ahead and run all the queued timers cy.pauseTimers(false) expect(win.bar).to.eq('bar') // requestAnimationFrame should have passed through // its high res timestamp from performance.now() expect(rafStub).to.be.calledWithMatch(Number) }) .then(() => { win.bar = 'foo' cy.pauseTimers(true) const id2 = win.requestAnimationFrame(rafStub) expect(id2).to.eq(2) const ret = win.cancelAnimationFrame(id2) expect(ret).to.be.undefined cy.pauseTimers(false) }) .wait(100) .window().its('bar').should('eq', 'foo') }) }) it('delays calls to setTimeout when paused', () => { cy .window() .then((win) => { win.bar = null win.setBar = () => { win.bar = 'bar' } // prevent timers from firing, add to queue cy.pauseTimers(true) const id1 = win.setTimeout(win.setBar, 1) expect(id1).to.eq(timerNumber(1)) cyWaitTimeout(1) .log('setTimeout should NOT have fired when paused') .window().its('bar').should('be.null') .log('setTimeout should now fire when unpaused') .then(() => { // now go ahead and run all the queued timers cy.pauseTimers(false) expect(win.bar).to.eq('bar') }) .then(() => { win.bar = 'foo' cy.pauseTimers(true) const id2 = win.setTimeout(win.setBar, 1) expect(id2).to.eq(timerNumber(2)) const ret = win.clearTimeout(id2) expect(ret).to.be.undefined cy.pauseTimers(false) }) cyWaitTimeout(1) .window().its('bar').should('eq', 'foo') }) }) it('delays timers queued before pausing but have not fired yet', () => { cy .log('setTimeout should be delayed until timers have been unpaused') .window() .then((win) => { win.bar = null win.setBar = () => { win.bar = 'bar' } const id1 = win.setTimeout(win.setBar, 10) // the timer id is 1 by default since // timers increment and always start at 0 expect(id1).to.eq(timerNumber(1)) cy.pauseTimers(true) cyWaitTimeout(10) cy.window().its('bar').should('be.null') .log('setTimeout should be immediately flushed after unpausing') .then(() => { cy.pauseTimers(false) expect(win.bar).to.eq('bar') }) .log('canceling the timeout after timers are paused still cancels') .then(() => { win.bar = null const id2 = win.setInterval(win.setBar, 10) expect(id2).to.eq(timerNumber(2)) cy.pauseTimers(true) // clearing interval on a timer is officially supported by browsers win.clearInterval(id2) }) .wait(100) .window().its('bar').should('be.null') }) }) describe('accepts different types of timer function', () => { it('string', () => { cy .window() .then((win) => { win.stub = cy.stub() win.setTimeout('this.stub()', 1) cyWaitTimeout(1) .then(() => { expect(win.stub).to.be.called }) }) }) const codes = [ ['undefined', undefined], ['boolean', true], ['number', 42], ['array', []], ['object', {}], ] codes.forEach(([name, value]) => { it(name, () => { cy .window() .then((win) => { win.eval = cy.stub() win.setTimeout(value, 1) cyWaitTimeout(1) .then(() => { expect(win.eval).to.be.called }) }) }) }) }) it('can cancel setIntervals paused or unpaused', () => { // 1. setInterval works unpaused and can be canceled after N calls // 2. setInterval stops invoking when paused and when resumed invokes all paused calls cy .log('cancels the interval after 3 calls') .window() .then((win) => { let timerId const cancelAfter3Calls = cy .stub() .onThirdCall() .callsFake(() => { win.clearInterval(timerId) }) timerId = win.setInterval(cancelAfter3Calls, 10) cy .wait(100) .then(() => { expect(cancelAfter3Calls).to.be.calledThrice }) }) .log('cancels the interval after 3 calls even when paused') .window() .then((win) => { let timerId const cancelAfter3Calls = cy .stub() .onThirdCall() .callsFake(() => { // this tests that we're properly overloading clearTimeouts to clear intervals // because this is official supported by all browsers // clearTimeout(N) // clearInterval(N) yup same thing all good win.clearTimeout(timerId) }) cy.pauseTimers(true) timerId = win.setInterval(cancelAfter3Calls, 10) cy .wait(200) .then(() => { cy.pauseTimers(false) expect(cancelAfter3Calls).to.be.calledThrice }) }) // // setInterval(cb, 100) // cb() 100 // cb() 200 // cb() 300 <-- cancel // // when paused... // setInterval(cb, 100) // cb() 100 // cb() 200 // cb() 300 <-- would normally cancel callback but didn't because of pause // cb() 400 <-- return early, do not call // cb() 500 <-- return early, do not call // cb() 600 <-- return early, do not call // ... // }) it('does not fire queued timers if page transitions while paused', () => { // at least in chrome... whenever the page is unloaded // the browser WILL CANCEL outstanding macrotasks automatically // and invoking them does nothing cy .log('the browser should automatically cancel timers from unloaded windows') .window() .then((win) => { const stub1 = cy.stub() // we're setting setTimeout to 500ms because // there were times where the driver's webserver // was sending bytes after 200ms (TTFB) that caused // this test to be flaky. win.setTimeout(stub1, 500) // force the window to sync reload win.location.reload() cy .wait(800) .then(() => { expect(stub1).not.to.be.called }) .log('queued timers are not called on unloaded windows') .window() .then((win) => { const stub2 = cy.stub() cy.pauseTimers(true) win.setTimeout(stub2, 10) cy .wait(100) .then(() => { expect(stub2).not.to.be.called }) .reload() .then(() => { cy.pauseTimers(false) expect(stub2).not.to.be.called }) }) }) }) })