Files
next.js/test/e2e/app-dir/app-basepath/index.test.ts
ad65b1bdcf test(app-basepath): increase waitForElementByCss timeout for hard-nav test (#91920)
### 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&currentTab=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]>
2026-03-26 14:20:37 -07:00

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)
})
})