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
225 lines
7.4 KiB
TypeScript
225 lines
7.4 KiB
TypeScript
import type * as Playwright from 'playwright'
|
|
import webdriver from 'next-webdriver'
|
|
import { createRouterAct } from 'router-act'
|
|
import { findPort, retry } from 'next-test-utils'
|
|
import { isNextDeploy, isNextDev, isNextStart, nextTestSetup } from 'e2e-utils'
|
|
import { build, start } from './servers.mjs'
|
|
|
|
describe('segment cache (deployment skew)', () => {
|
|
if (isNextDev) {
|
|
test('should not run during dev', () => {})
|
|
return
|
|
}
|
|
|
|
// To debug these tests locally, first build the app:
|
|
// node build.mjs
|
|
//
|
|
// Then start:
|
|
// node start.mjs
|
|
//
|
|
// This will build two versions of the same app on different ports, then
|
|
// start a proxy server that rewrites incoming requests to one or the other
|
|
// based on the request information.
|
|
|
|
if (isNextStart) {
|
|
let cleanup: () => Promise<void>
|
|
let port: number
|
|
|
|
describe('with BUILD_ID', () => {
|
|
beforeAll(async () => {
|
|
build('BUILD_ID')
|
|
const proxyPort = (port = await findPort())
|
|
const nextPort1 = await findPort()
|
|
const nextPort2 = await findPort()
|
|
cleanup = await start(proxyPort, nextPort1, nextPort2, 'BUILD_ID')
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await cleanup()
|
|
})
|
|
|
|
runTests('BUILD_ID', () => port)
|
|
})
|
|
|
|
describe('with NEXT_DEPLOYMENT_ID', () => {
|
|
beforeAll(async () => {
|
|
build('DEPLOYMENT_ID')
|
|
const proxyPort = (port = await findPort())
|
|
const nextPort1 = await findPort()
|
|
const nextPort2 = await findPort()
|
|
cleanup = await start(proxyPort, nextPort1, nextPort2, 'DEPLOYMENT_ID')
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await cleanup()
|
|
})
|
|
|
|
runTests('DEPLOYMENT_ID', () => port)
|
|
})
|
|
}
|
|
|
|
describe('header with deployment id', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
env: {
|
|
// rely on skew protection when deployed
|
|
NEXT_DEPLOYMENT_ID: isNextDeploy ? undefined : 'test-deployment-id',
|
|
},
|
|
disableAutoSkewProtection: true,
|
|
})
|
|
|
|
// Deployment skew is hard to properly e2e deploy test, so this just checks for the header.
|
|
it('header is set on RSC responses', async () => {
|
|
for (const route of ['/dynamic-page', '/static-page']) {
|
|
await next.fetch(route)
|
|
let res = await next.fetch(`${route}?_rsc=`, {
|
|
headers: { rsc: '1' },
|
|
})
|
|
|
|
expect(res.status).toBe(200)
|
|
expect(res.headers.get('content-type')).toBe('text/x-component')
|
|
expect(res.headers.get('x-nextjs-deployment-id')).toBeTruthy()
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
function runTests(mode: 'BUILD_ID' | 'DEPLOYMENT_ID', getPort: () => number) {
|
|
it(
|
|
'does not crash when prefetching a dynamic, non-PPR page ' +
|
|
'on a different deployment',
|
|
async () => {
|
|
// Reproduces a bug that occurred when prefetching a dynamic page
|
|
// from a different deployment, when PPR is disabled. Once PPR is the
|
|
// default, it's OK to rewrite this to use the latest APIs.
|
|
let act
|
|
const browser = await webdriver(getPort(), '/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
// Initiate a prefetch of link to a different deployment
|
|
await act(async () => {
|
|
const checkbox = await browser.elementByCss(
|
|
'[data-link-accordion="/dynamic-page?deployment=2"]'
|
|
)
|
|
await checkbox.click()
|
|
})
|
|
|
|
// Navigate to the target page
|
|
const link = await browser.elementByCss(
|
|
'a[href="/dynamic-page?deployment=2"]'
|
|
)
|
|
await link.click()
|
|
|
|
// Should have performed a full-page navigation to the new deployment.
|
|
const buildId = await browser.elementById('build-id')
|
|
expect(await buildId.text()).toBe('Build ID: 2')
|
|
},
|
|
60 * 1000
|
|
)
|
|
|
|
it(
|
|
'does not crash when prefetching a static page on a different deployment',
|
|
async () => {
|
|
// Same as the previous test, but for a static page
|
|
let act
|
|
const browser = await webdriver(getPort(), '/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
act = createRouterAct(p)
|
|
},
|
|
})
|
|
|
|
// Initiate a prefetch of link to a different deployment
|
|
await act(async () => {
|
|
const checkbox = await browser.elementByCss(
|
|
'[data-link-accordion="/static-page?deployment=2"]'
|
|
)
|
|
await checkbox.click()
|
|
})
|
|
|
|
// Navigate to the target page
|
|
const link = await browser.elementByCss(
|
|
'a[href="/static-page?deployment=2"]'
|
|
)
|
|
await link.click()
|
|
|
|
// Should have performed a full-page navigation to the new deployment.
|
|
const buildId = await browser.elementById('build-id')
|
|
expect(await buildId.text()).toBe('Build ID: 2')
|
|
},
|
|
60 * 1000
|
|
)
|
|
|
|
it(
|
|
'triggers MPA navigation when a server action redirects to a different deployment',
|
|
async () => {
|
|
// Verify that when a server action calls redirect() and the redirect
|
|
// target is served by a different deployment (different build ID), the
|
|
// client falls back to an MPA navigation instead of attempting to apply
|
|
// the foreign RSC payload.
|
|
const browser = await webdriver(getPort(), '/')
|
|
await browser.eval('window.next.router.push("/action-redirect")')
|
|
await browser.waitForElementByCss('#action-page')
|
|
let sawActionRequest = false
|
|
let sawRedirectActionResponse = false
|
|
let actionResponseDeploymentId: string | undefined
|
|
browser.on('request', (request: Playwright.Request) => {
|
|
const headers = request.headers()
|
|
if (request.method() === 'POST' && headers['next-action']) {
|
|
sawActionRequest = true
|
|
}
|
|
})
|
|
browser.on('response', async (response: Playwright.Response) => {
|
|
const request = response.request()
|
|
if (request.method() !== 'POST') {
|
|
return
|
|
}
|
|
|
|
const headers = response.headers()
|
|
if (headers['x-action-redirect']) {
|
|
sawRedirectActionResponse = true
|
|
actionResponseDeploymentId = headers['x-nextjs-deployment-id']
|
|
}
|
|
})
|
|
|
|
// Verify we're on the action redirect page
|
|
const heading = await browser.elementById('action-page')
|
|
expect(await heading.text()).toBe('Action Redirect Page')
|
|
|
|
// Click the button that triggers the server action redirect.
|
|
// In deployment ID mode, the proxy injects a foreign
|
|
// x-nextjs-deployment-id header to simulate skew. In build ID mode,
|
|
// the response omits that header so the client falls back to the
|
|
// build ID carried in the action Flight payload.
|
|
const button = await browser.elementById('redirect-action-button')
|
|
await button.click()
|
|
|
|
await retry(async () => {
|
|
expect(sawActionRequest).toBe(true)
|
|
})
|
|
|
|
await retry(async () => {
|
|
expect(sawRedirectActionResponse).toBe(true)
|
|
})
|
|
|
|
if (mode === 'DEPLOYMENT_ID') {
|
|
expect(actionResponseDeploymentId).toBe('foreign-deployment')
|
|
} else {
|
|
expect(actionResponseDeploymentId).toBeUndefined()
|
|
}
|
|
|
|
// Wait for the navigation to complete.
|
|
// The client detects the mismatch in either the response header or
|
|
// the fallback build ID field and discards the flight data,
|
|
// triggering an MPA navigation (full page load) to the redirect
|
|
// target. The redirect URL (/dynamic-page?deployment=2) goes through
|
|
// the proxy to deployment 2.
|
|
const buildId = await browser.waitForElementByCss('#build-id')
|
|
expect(await buildId.text()).toBe('Build ID: 2')
|
|
},
|
|
60 * 1000
|
|
)
|
|
}
|