import { nextTestSetup } from 'e2e-utils' import type * as Playwright from 'playwright' import { createRouterAct } from 'router-act' describe('segment cache (staleness)', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, }) if (isNextDev) { test('disabled in development / deployment', () => {}) return } it('entry expires when its stale time has elapsed', async () => { let page: Playwright.Page const browser = await next.browser('/', { beforePageLoad(p: Playwright.Page) { page = p }, }) const act = createRouterAct(page) await page.clock.install() // Reveal the link to trigger a prefetch const toggle2MinutesLink = await browser.elementByCss( 'input[data-link-accordion="/stale-2-minutes"]' ) const toggle4MinutesLink = await browser.elementByCss( 'input[data-link-accordion="/stale-4-minutes"]' ) await act( async () => { await toggle2MinutesLink.click() await browser.elementByCss('a[href="/stale-2-minutes"]') }, { includes: 'Content with stale time of 2 minutes', } ) await act( async () => { await toggle4MinutesLink.click() await browser.elementByCss('a[href="/stale-4-minutes"]') }, { includes: 'Content with stale time of 4 minutes', } ) // Hide the links await toggle2MinutesLink.click() await toggle4MinutesLink.click() // Fast forward 2 minutes and 1 millisecond await page.clock.fastForward(2 * 60 * 1000 + 1) // Reveal the links again to trigger new prefetch tasks await act( async () => { await toggle2MinutesLink.click() await browser.elementByCss('a[href="/stale-2-minutes"]') }, // The page with a stale time of 2 minutes is requested again // because its stale time elapsed. { includes: 'Content with stale time of 2 minutes', } ) await act( async () => { await toggle4MinutesLink.click() await browser.elementByCss('a[href="/stale-4-minutes"]') }, // The page with a stale time of 4 minutes is *not* requested again // because it's still fresh. 'no-requests' ) }) it('expires runtime prefetches when their stale time has elapsed', async () => { let page: Playwright.Page const browser = await next.browser('/', { beforePageLoad(p: Playwright.Page) { page = p }, }) const act = createRouterAct(page) await page.clock.install() // Reveal the links to trigger a runtime prefetch const toggle2MinutesLink = await browser.elementByCss( 'input[data-link-accordion="/runtime-stale-2-minutes"]' ) const toggle4MinutesLink = await browser.elementByCss( 'input[data-link-accordion="/runtime-stale-4-minutes"]' ) await act( async () => { await toggle2MinutesLink.click() await browser.elementByCss('a[href="/runtime-stale-2-minutes"]') }, { includes: 'Content with stale time of 2 minutes', } ) await act( async () => { await toggle4MinutesLink.click() await browser.elementByCss('a[href="/runtime-stale-4-minutes"]') }, { includes: 'Content with stale time of 4 minutes', } ) // Hide the links await toggle2MinutesLink.click() await toggle4MinutesLink.click() // Fast forward 2 minutes and 1 millisecond await page.clock.fastForward(2 * 60 * 1000 + 1) // Reveal the links again to trigger new prefetch tasks await act( async () => { await toggle2MinutesLink.click() await browser.elementByCss('a[href="/runtime-stale-2-minutes"]') }, // The page with a stale time of 2 minutes is requested again // because its stale time elapsed. { includes: 'Content with stale time of 2 minutes', } ) await act( async () => { await toggle4MinutesLink.click() await browser.elementByCss('a[href="/runtime-stale-4-minutes"]') }, // The page with a stale time of 4 minutes is *not* requested again // because it's still fresh. 'no-requests' ) }) it('reuses dynamic data up to the staleTimes.dynamic threshold', async () => { let page: Playwright.Page const startDate = Date.now() const browser = await next.browser('/', { async beforePageLoad(p: Playwright.Page) { page = p await page.clock.install() await page.clock.setFixedTime(startDate) }, }) const act = createRouterAct(page) // Navigate to the dynamic page await act( async () => { const toggle = await browser.elementByCss( 'input[data-link-accordion="/dynamic"]' ) await toggle.click() const link = await browser.elementByCss('a[href="/dynamic"]') await link.click() }, { includes: 'Dynamic content', } ) expect(await browser.elementById('dynamic-content').text()).toBe( 'Dynamic content' ) await browser.back() // Advance time by 29 seconds. staleTimes.dynamic is configured as 30s, so // if we navigate to the same link again, the old data should be reused // without a new network request. await page.clock.setFixedTime(startDate + 29 * 1000) await act(async () => { const link = await browser.elementByCss('a[href="/dynamic"]') await link.click() // The next page is immediately rendered expect(await browser.elementById('dynamic-content').text()).toBe( 'Dynamic content' ) }, 'no-requests') await browser.back() // Advance an additional second. This time, if we navigate to the link // again, the data is stale, so we issue a new request. await page.clock.setFixedTime(startDate + 30 * 1000) await act( async () => { const link = await browser.elementByCss('a[href="/dynamic"]') await link.click() }, { includes: 'Dynamic content' } ) expect(await browser.elementById('dynamic-content').text()).toBe( 'Dynamic content' ) }) it('caches omitted from the prerender should not affect when the prefetch is expired', async () => { let page: Playwright.Page const browser = await next.browser('/', { beforePageLoad(p: Playwright.Page) { page = p }, }) const act = createRouterAct(page) await page.clock.install() // Reveal the link to trigger a prefetch await act( async () => { await browser .elementByCss('input[data-link-accordion="/seconds"]') .click() await browser.elementByCss('a[href="/seconds"]') }, { // cacheLife("seconds") should be excluded from a static prerender includes: 'Short-lived cached content', block: 'reject', } ) // Hide the link await browser.elementByCss('input[data-link-accordion="/seconds"]').click() // Fast forward 30 seconds and 1 millisecond // (matching the staleness of the "seconds" profile) const timeStep = 30 * 1000 + 1 await page.clock.fastForward(timeStep) // Reveal the link again to trigger new prefetch tasks. // The cache with `cacheLife('seconds'`) should not affect the stale time of the prefetch, // because we omit it from the prerender, so we shouldn't refetch anything yet. await act(async () => { await browser .elementByCss('input[data-link-accordion="/seconds"]') .click() await browser.elementByCss('a[href="/seconds"]') }, 'no-requests') // Hide the link again await browser.elementByCss('input[data-link-accordion="/seconds"]').click() // Fast forward to 5 minutes and 1 millisecond after the prefetch. // (matching the staleness of the "minutes" profile) // Note that we should exclude the timestep we've already done. await page.clock.fastForward(5 * 60 * 1000 + 1 - timeStep) // Reveal the link to trigger a prefetch. // The longer-lived cache we used on the page should make the previous prefetch expire, // so we should issue a new request. await act( async () => { await browser .elementByCss('input[data-link-accordion="/seconds"]') .click() await browser.elementByCss('a[href="/seconds"]') }, { includes: 'Short-lived cached content', block: 'reject', } ) }) })