mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
### What?
Increase the `waitForElementByCss('#page-2', ...)` timeout in the `app
dir - basepath › should successfully hard navigate from pages -> app`
test from the default 10 s to 30 s.
### Why?
[Datadog test
runs](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40test.source.file%3Atest%2Fe2e%2Fapp-dir%2Fapp-basepath%2Findex.test.ts%20%40test.name%3A%22app%20dir%20-%20basepath%20should%20successfully%20hard%20navigate%20from%20pages%20-%3E%20app%22&agg_m=count&agg_m_source=base&agg_t=count&cols=%40test.status%2Ctimestamp%2C%40duration%2C%40test.type%2C%40test.is_known_flaky%2C%40test.name¤tTab=overview&eventStack=&fromUser=false&index=citest&start=1773951667400&end=1774556467400&paused=false)
The test navigates from a Pages Router page (`/base/pages-path`) to an
App Router page (`/base/another`) via a hard (non-client-side)
navigation. In dev mode, the destination App Router page is compiled on
demand the first time it is visited. That compilation can take longer
than the default 10 s `waitForElementByCss` timeout, causing the test to
fail spuriously with:
```
page.waitForSelector: Timeout 10000ms exceeded
```
### How?
Pass `30000` (30 s) as the second argument to `waitForElementByCss`.
This matches the pattern used elsewhere in the test suite for dev-mode
tests that are subject to on-demand compilation latency. No runtime code
changes are needed — this is purely a test-side fix.
Co-authored-by: Tobias Koppers <[email protected]>
Co-authored-by: Claude <[email protected]>
213 lines
6.9 KiB
TypeScript
213 lines
6.9 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from 'next-test-utils'
|
|
import type { Request, Response } from 'playwright'
|
|
|
|
describe('app dir - basepath', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
dependencies: {
|
|
sass: 'latest',
|
|
},
|
|
})
|
|
|
|
it('should successfully hard navigate from pages -> app', async () => {
|
|
const browser = await next.browser('/base/pages-path')
|
|
await browser.elementByCss('#to-another').click()
|
|
// Hard nav from pages -> app triggers on-demand compilation in dev; allow extra time
|
|
await browser.waitForElementByCss('#page-2', 30000)
|
|
})
|
|
|
|
it('should support `basePath`', async () => {
|
|
const html = await next.render('/base')
|
|
expect(html).toContain('<h1>Test Page</h1>')
|
|
})
|
|
|
|
it('should support Link with basePath prefixed', async () => {
|
|
const browser = await next.browser('/base')
|
|
expect(
|
|
await browser
|
|
.elementByCss('a[href="/base/another"]')
|
|
.click()
|
|
.waitForElementByCss('#page-2')
|
|
.text()
|
|
).toBe(`Page 2`)
|
|
})
|
|
|
|
it('should prefix segment metadata og image with basePath and pathname', async () => {
|
|
const $ = await next.render$('/base/metadata')
|
|
const ogImageHref = $('meta[property="og:image"]').attr('content')
|
|
expect(ogImageHref).toContain('/base/metadata/opengraph-image.png')
|
|
})
|
|
|
|
it('should prefix manifest with basePath', async () => {
|
|
const $ = await next.render$('/base/metadata')
|
|
const manifestHref = $('link[rel="manifest"]').attr('href')
|
|
expect(manifestHref).toContain('/base/manifest.webmanifest')
|
|
})
|
|
|
|
it('should prefix redirect() with basePath', async () => {
|
|
const browser = await next.browser('/base/redirect')
|
|
await retry(async () => {
|
|
expect(await browser.url()).toBe(`${next.url}/base/another`)
|
|
})
|
|
})
|
|
|
|
it('should render usePathname without the basePath', async () => {
|
|
const pathnames = ['/use-pathname', '/use-pathname-another']
|
|
const validatorPromises = pathnames.map(async (pathname) => {
|
|
const $ = await next.render$('/base' + pathname)
|
|
expect($('#pathname').data('pathname')).toBe(pathname)
|
|
})
|
|
await Promise.all(validatorPromises)
|
|
})
|
|
|
|
it('should handle redirect in dynamic in suspense boundary routes with basePath', async () => {
|
|
const browser = await next.browser('/base/dynamic/source')
|
|
await retry(async () => {
|
|
// Check content is loaded first to avoid flakiness
|
|
expect(await browser.elementByCss('p').text()).toBe(`id:dest`)
|
|
expect(await browser.url()).toBe(`${next.url}/base/dynamic/dest`)
|
|
})
|
|
})
|
|
|
|
it.each(['/base/refresh', '/base/refresh?foo=bar'])(
|
|
`should only make a single RSC call to the current page (%s)`,
|
|
async (path) => {
|
|
let rscRequests = []
|
|
const browser = await next.browser(path, {
|
|
beforePageLoad(page) {
|
|
page.on('request', (request) => {
|
|
const headers = request.headers()
|
|
if (
|
|
headers['rsc'] === '1' &&
|
|
// Prefetches also include `rsc`
|
|
headers['next-router-prefetch'] !== '1'
|
|
) {
|
|
rscRequests.push(request.url())
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
await browser.elementByCss('button').click()
|
|
await browser.waitForIdleNetwork()
|
|
await retry(async () => {
|
|
expect(rscRequests).toEqual([
|
|
expect.stringContaining(`${next.url}${path}`),
|
|
])
|
|
})
|
|
}
|
|
)
|
|
|
|
it.each([
|
|
{ redirectType: 'relative', buttonId: 'redirect-relative' },
|
|
{ redirectType: 'absolute', buttonId: 'redirect-absolute-internal' },
|
|
])(
|
|
`should properly stream an internal server action redirect() with a $redirectType URL`,
|
|
async ({ buttonId }) => {
|
|
const initialPagePath = '/base/client'
|
|
const destinationPagePath = '/base/another'
|
|
|
|
const requests: Request[] = []
|
|
const responses: Response[] = []
|
|
|
|
const browser = await next.browser(initialPagePath)
|
|
|
|
browser.on('request', (req) => {
|
|
const url = req.url()
|
|
|
|
if (
|
|
url.includes(initialPagePath) ||
|
|
url.includes(destinationPagePath)
|
|
) {
|
|
requests.push(req)
|
|
}
|
|
})
|
|
|
|
browser.on('response', (res) => {
|
|
const url = res.url()
|
|
|
|
if (
|
|
url.includes(initialPagePath) ||
|
|
url.includes(destinationPagePath)
|
|
) {
|
|
responses.push(res)
|
|
}
|
|
})
|
|
|
|
await browser.elementById(buttonId).click()
|
|
await retry(async () =>
|
|
expect(await browser.url()).toContain('/base/another')
|
|
)
|
|
|
|
expect(await browser.waitForElementByCss('#page-2').text()).toBe(`Page 2`)
|
|
|
|
// This verifies the redirect & server response happens in a single roundtrip,
|
|
// if the redirect resource was static. In development, these responses are always
|
|
// dynamically generated, so we only expect a single request for build/deploy.
|
|
// TODO(client-segment-cache): re-enable when this optimization is added back
|
|
if (!isNextDev && !process.env.__NEXT_CACHE_COMPONENTS) {
|
|
expect(requests).toHaveLength(1)
|
|
expect(responses).toHaveLength(1)
|
|
}
|
|
|
|
const request = requests[0]
|
|
const response = responses[0]
|
|
|
|
expect(request.url()).toEqual(`${next.url}${initialPagePath}`)
|
|
expect(request.method()).toEqual('POST')
|
|
expect(response.status()).toEqual(303)
|
|
}
|
|
)
|
|
|
|
it('should redirect externally when encountering absolute URLs on the same host outside the basePath', async () => {
|
|
const initialPagePath = '/base/client'
|
|
const destinationPagePath = '/outsideBasePath'
|
|
|
|
const requests: Request[] = []
|
|
const responses: Response[] = []
|
|
|
|
const browser = await next.browser(initialPagePath)
|
|
|
|
browser.on('request', (req) => {
|
|
const url = req.url()
|
|
|
|
if (!url.includes('_next')) {
|
|
requests.push(req)
|
|
}
|
|
})
|
|
|
|
browser.on('response', (res) => {
|
|
const url = res.url()
|
|
|
|
if (!url.includes('_next')) {
|
|
responses.push(res)
|
|
}
|
|
})
|
|
|
|
await browser.elementById('redirect-absolute-external').click()
|
|
await retry(async () =>
|
|
expect(await browser.url()).toContain('/outsideBasePath')
|
|
)
|
|
|
|
// We expect to see two requests, first a POST invoking the server
|
|
// action. And second a GET request resolving the redirect.
|
|
expect(requests).toHaveLength(2)
|
|
expect(responses).toHaveLength(2)
|
|
|
|
const [firstRequest, secondRequest] = requests
|
|
const [firstResponse, secondResponse] = responses
|
|
|
|
expect(firstRequest.url()).toEqual(`${next.url}${initialPagePath}`)
|
|
expect(firstRequest.method()).toEqual('POST')
|
|
|
|
expect(secondRequest.url()).toEqual(`${next.url}${destinationPagePath}`)
|
|
expect(secondRequest.method()).toEqual('GET')
|
|
|
|
expect(firstResponse.status()).toEqual(303)
|
|
// Since this is an external request to a resource outside of NextJS
|
|
// we expect to see a separate request resolving the external URL.
|
|
expect(secondResponse.status()).toEqual(200)
|
|
})
|
|
})
|