Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
253 lines
9.9 KiB
TypeScript
253 lines
9.9 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import type * as Playwright from 'playwright'
|
|
import { createRouterAct } from 'router-act'
|
|
|
|
describe('segment cache prefetch scheduling', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
if (isNextDev) {
|
|
test('prefetching is disabled', () => {})
|
|
return
|
|
}
|
|
|
|
it('increases the priority of a viewport-initiated prefetch on hover', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/cancellation', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
const checkbox = await browser.elementByCss('input[type="checkbox"]')
|
|
await act(
|
|
async () => {
|
|
// Reveal the links to start prefetching, but block the responses from
|
|
// reaching the client. This will initiate prefetches for the route
|
|
// trees, but it won't start prefetching any segment data yet until the
|
|
// trees have loaded.
|
|
await act(async () => {
|
|
await checkbox.click()
|
|
}, 'block')
|
|
|
|
// Hover over a link to increase its relative priority.
|
|
const link2 = await browser.elementByCss('a[href="/cancellation/2"]')
|
|
await link2.hover()
|
|
|
|
// Hover over a different link to increase its relative priority.
|
|
const link5 = await browser.elementByCss('a[href="/cancellation/5"]')
|
|
await link5.hover()
|
|
},
|
|
// Assert that the segment data is prefetched in the expected order.
|
|
[
|
|
// The last link we hovered over should be the first to prefetch.
|
|
{ includes: 'Content of page 5' },
|
|
// The second-to-last link we hovered over should come next.
|
|
{ includes: 'Content of page 2' },
|
|
// Then all the other links come after that. (We don't need to assert
|
|
// on every single prefetch response. I picked one of them arbitrarily.)
|
|
{ includes: 'Content of page 4' },
|
|
]
|
|
)
|
|
})
|
|
|
|
// TODO: This test no longer works as-written because the metadata is now
|
|
// fetched separately from the route tree. Need to rewrite to assert on
|
|
// something that appears in the route tree and is relatively stable, like a
|
|
// static route name.
|
|
it.skip('prioritizes prefetching the route trees before the segments', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/cancellation', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
const checkbox = await browser.elementByCss('input[type="checkbox"]')
|
|
|
|
await act(async () => {
|
|
// Reveal the links to start prefetching
|
|
await checkbox.click()
|
|
}, [
|
|
// Assert on the order that the prefetches requests are
|
|
// initiated. We don't need to assert on every single prefetch response;
|
|
// this will only check the order of the ones that we've listed.
|
|
//
|
|
// To detect when the route tree is prefetched, we check for a string
|
|
// that is known to be present in the target page's viewport config
|
|
// (which is included in the route tree response). In this test app, the
|
|
// page number is used in the media query of the theme color. E.g. for
|
|
// page 1, the viewport includes:
|
|
//
|
|
// <meta name="theme-color" media="(min-width: 1px)" content="light"/>
|
|
|
|
// First we should prefetch all the route trees:
|
|
{ includes: '(min-width: 7px)' },
|
|
{ includes: '(min-width: 6px)' },
|
|
{ includes: '(min-width: 5px)' },
|
|
{ includes: '(min-width: 4px)' },
|
|
{ includes: '(min-width: 3px)' },
|
|
|
|
// Then we should prefetch the segments:
|
|
{ includes: 'Content of page 7' },
|
|
{ includes: 'Content of page 6' },
|
|
{ includes: 'Content of page 5' },
|
|
{ includes: 'Content of page 4' },
|
|
{ includes: 'Content of page 3' },
|
|
])
|
|
})
|
|
|
|
it('reserve special bandwidth for the most recently hovered link', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/cancellation', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
const checkbox = await browser.elementByCss('input[type="checkbox"]')
|
|
await act(async () => {
|
|
// Reveal the links to start prefetching, but block the responses from
|
|
// reaching the client. This will initiate prefetches for the route
|
|
// trees, but it won't start prefetching any segment data yet until the
|
|
// trees have loaded.
|
|
await act(async () => {
|
|
await checkbox.click()
|
|
}, 'block')
|
|
|
|
// At this point, the network queue is already at max capacity, so
|
|
// revealing more links won't initiate any new requests.
|
|
await act(async () => {
|
|
const showMoreLinksButton = await browser.elementById('show-more-links')
|
|
await showMoreLinksButton.click()
|
|
}, 'no-requests')
|
|
|
|
// However, when hovering over a link, the prefetch task is allowed to
|
|
// exceed the default limit. So hovering over a link does initiate a
|
|
// new request.
|
|
await act(async () => {
|
|
// Hover over a link. This will initiate a request for the route tree,
|
|
// but before we're able to fetch the segments, we'll have already
|
|
// hovered over a different link.
|
|
const link2 = await browser.elementByCss('a[href="/cancellation/2"]')
|
|
await link2.hover()
|
|
|
|
// Immediately hover over a different link.
|
|
const link3 = await browser.elementByCss('a[href="/cancellation/3"]')
|
|
await link3.hover()
|
|
}, [
|
|
// The most recently hovered link is allowed to finish loading its
|
|
// segment data.
|
|
{ includes: 'Content of page 3' },
|
|
// The previously hovered link was downgraded to the default priority,
|
|
// so it should still be blocked.
|
|
{ includes: 'Content of page 2', block: 'reject' },
|
|
])
|
|
}, [
|
|
// Assert that everything else proceeds in the expected order. We don't
|
|
// need to assert on every single prefetch response; I picked just a few
|
|
// of them.
|
|
|
|
// The previously hovered link is the next to finish loading, because
|
|
// even though it was downgraded to the default priority, it was still
|
|
// moved ahead of the other default tasks.
|
|
{ includes: 'Content of page 2' },
|
|
// Next are the links that were revealed by the "Show More" button.
|
|
{ includes: 'Content of page 8' },
|
|
// Then the rest.
|
|
{ includes: 'Content of page 5' },
|
|
])
|
|
})
|
|
|
|
it(
|
|
'cancels a viewport-initiated prefetch if the link leaves the viewport ' +
|
|
'before it finishes',
|
|
async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/cancellation', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
const checkbox = await browser.elementByCss('input[type="checkbox"]')
|
|
|
|
await act(
|
|
async () => {
|
|
// Reveal the links to start prefetching, but block the responses from
|
|
// reaching the client. Because the router limits the number of
|
|
// concurrent prefetches, not all the links will start prefetching —
|
|
// some of them will remain in the queue, waiting for additional
|
|
// network bandwidth. This test demonstrates that those prefetches
|
|
// will be canceled on viewport exit, too.
|
|
await act(async () => {
|
|
await checkbox.click()
|
|
}, 'block')
|
|
|
|
// Before the prefetch finishes, click the checkbox again to hide
|
|
// the link.
|
|
await checkbox.click()
|
|
},
|
|
// When the outer `act` scope finishes, the route tree prefetch will
|
|
// continue. Normally when the router is done prefetching the route
|
|
// tree, it will proceed to prefetching the segments. However, since
|
|
// the link is no longer visible, it should stop prefetching.
|
|
//
|
|
// Assert that no additional network requests are initiated in this
|
|
// outer scope. If this fails, it suggests that the prefetches were not
|
|
// canceled when the links left the viewport.
|
|
'no-requests'
|
|
)
|
|
}
|
|
)
|
|
|
|
it("reschedules a link's prefetch when it re-enters the viewport", async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/cancellation', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
const checkbox = await browser.elementByCss('input[type="checkbox"]')
|
|
|
|
await act(
|
|
async () => {
|
|
// Reveal the links to start prefetching, but block the responses from
|
|
// reaching the client. Because the router limits the number of
|
|
// concurrent prefetches, not all the links will start prefetching —
|
|
// some of them will remain in the queue, waiting for additional
|
|
// network bandwidth. This test demonstrates that those prefetches
|
|
// will be canceled on viewport exit, too.
|
|
await act(async () => {
|
|
await checkbox.click()
|
|
}, 'block')
|
|
|
|
// Before the prefetch finishes, click the checkbox again to hide
|
|
// the link.
|
|
await checkbox.click()
|
|
},
|
|
// When the outer `act` scope finishes, the route tree prefetch will
|
|
// continue. Normally when the router is done prefetching the route
|
|
// tree, it will proceed to prefetching the segments. However, since
|
|
// the link is no longer visible, it should stop prefetching.
|
|
//
|
|
// Assert that no additional network requests are initiated in this
|
|
// outer scope. If this fails, it suggests that the prefetches were not
|
|
// canceled when the links left the viewport.
|
|
'no-requests'
|
|
)
|
|
|
|
// Now we'll reveal the links again to verify that the prefetch tasks are
|
|
// rescheduled, after having been canceled.
|
|
await act(
|
|
async () => {
|
|
await checkbox.click()
|
|
},
|
|
// Don't need to assert on all the prefetch responses. I picked an
|
|
// arbitrary one.
|
|
{ includes: 'Content of page 5' }
|
|
)
|
|
})
|
|
})
|