Skip to content

test: improve action test reliability #80587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 62 additions & 50 deletions test/e2e/app-dir/actions/app-action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
getRedboxSource,
} from 'next-test-utils'
import type { Request, Response } from 'playwright'
import fs from 'fs-extra'
import nodeFs from 'fs'
import { join } from 'path'
import fs from 'node:fs/promises'
import { join } from 'node:path'
import { outdent } from 'outdent'
import { setTimeout } from 'node:timers/promises'

const GENERIC_RSC_ERROR =
'Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.'
Expand Down Expand Up @@ -540,10 +540,10 @@ describe('app-dir action handling', () => {
// this triggers a revalidate + redirect in a client component
await browser.elementById('redirect-revalidate-client').click()
await retry(async () => {
expect(await browser.url()).toBe(`${next.url}/revalidate?foo=bar`)

const newJustPutIt = await browser.elementById('justputit').text()
expect(newJustPutIt).not.toBe(initialJustPutit)

expect(await browser.url()).toBe(`${next.url}/revalidate?foo=bar`)
})

// this triggers a revalidate + redirect in a server component
Expand Down Expand Up @@ -593,7 +593,8 @@ describe('app-dir action handling', () => {
beforePageLoad(page) {
page.on('request', (request) => {
const url = new URL(request.url())
if (url.pathname === '/server') {
// Only count POST requests to /server (form submissions)
if (url.pathname === '/server' && request.method() === 'POST') {
requestCount++
}
})
Expand Down Expand Up @@ -963,16 +964,21 @@ describe('app-dir action handling', () => {
if (isNextStart) {
it('should not expose action content in sourcemaps', async () => {
// We check all sourcemaps in the `static` folder for sensitive information given that chunking
const sourcemaps = nodeFs
.readdirSync(join(next.testDir, '.next', 'static'), {
const sourcemaps = await fs
.readdir(join(next.testDir, '.next', 'static'), {
recursive: true,
encoding: 'utf8',
})
.filter((f) => f.endsWith('.js.map'))
.map((f) =>
nodeFs.readFileSync(join(next.testDir, '.next', 'static', f), {
encoding: 'utf8',
})
.then((files) =>
Promise.all(
files
.filter((f) => f.endsWith('.js.map'))
.map((f) =>
fs.readFile(join(next.testDir, '.next', 'static', f), {
encoding: 'utf8',
})
)
)
)

expect(sourcemaps).not.toBeEmpty()
Expand Down Expand Up @@ -1046,23 +1052,22 @@ describe('app-dir action handling', () => {
it('should bundle external libraries if they are on the action layer', async () => {
await next.fetch('/client')
const pageBundle = await fs.readFile(
join(next.testDir, '.next', 'server', 'app', 'client', 'page.js')
join(next.testDir, '.next', 'server', 'app', 'client', 'page.js'),
{ encoding: 'utf8' }
)
if (isTurbopack) {
const chunkPaths = pageBundle
.toString()
.matchAll(/loadChunk\("([^"]*)"\)/g)
// @ts-ignore
const chunkPaths = pageBundle.matchAll(/loadChunk\("([^"]*)"\)/g)
const reads = [...chunkPaths].map(async (match) => {
const bundle = await fs.readFile(
join(next.testDir, '.next', ...match[1].split(/[\\/]/g))
join(next.testDir, '.next', ...match[1].split(/[\\/]/g)),
{ encoding: 'utf8' }
)
return bundle.toString().includes('node_modules/nanoid/index.js')
return bundle.includes('node_modules/nanoid/index.js')
})

expect(await Promise.all(reads)).toContain(true)
} else {
expect(pageBundle.toString()).toContain('node_modules/nanoid/index.js')
expect(pageBundle).toContain('node_modules/nanoid/index.js')
}
})
}
Expand Down Expand Up @@ -1526,7 +1531,7 @@ describe('app-dir action handling', () => {
const browser = await next.browser('/revalidate')
await browser.refresh()

const thankYouNext = await browser.elementByCss('#thankyounext').text()
const original = await browser.elementByCss('#thankyounext').text()

await browser.elementByCss('#another').click()
await retry(async () => {
Expand All @@ -1535,47 +1540,54 @@ describe('app-dir action handling', () => {
)
})

const newThankYouNext = await browser
.elementByCss('#thankyounext')
.text()

// Should be the same number although in serverless
// it might be eventually consistent
// Should be the same number although in serverless it might be
// eventually consistent.
if (!isNextDeploy) {
expect(thankYouNext).toEqual(newThankYouNext)
await retry(async () => {
const another = await browser.elementByCss('#thankyounext').text()
expect(another).toEqual(original)
})
}

await browser.elementByCss('#back').click()

// Should be different
let revalidatedThankYouNext
await retry(async () => {
switch (type) {
case 'tag':
await browser.elementByCss('#revalidate-thankyounext').click()
break
case 'path':
await browser.elementByCss('#revalidate-path').click()
break
default:
throw new Error(`Invalid type: ${type}`)
}
expect(await browser.elementByCss('#title').text()).toBe('revalidate')
})

revalidatedThankYouNext = await browser
.elementByCss('#thankyounext')
.text()
switch (type) {
case 'tag':
await browser.elementByCss('#revalidate-thankyounext').click()
break
case 'path':
await browser.elementByCss('#revalidate-path').click()
break
default:
throw new Error(`Invalid type: ${type}`)
}

expect(thankYouNext).not.toBe(revalidatedThankYouNext)
// Give some time for it to be revalidated.
if (isNextDeploy) {
await setTimeout(5000)
}

// Should be different
let revalidated
await retry(async () => {
revalidated = await browser.elementByCss('#thankyounext').text()
expect(revalidated).not.toBe(original)
})

await browser.elementByCss('#another').click()
await retry(async () => {
expect(await browser.elementByCss('#title').text()).toBe(
'another route'
)
})

// The other page should be revalidated too
await retry(async () => {
const newThankYouNext = await browser
.elementByCss('#thankyounext')
.text()
expect(revalidatedThankYouNext).toBe(newThankYouNext)
const another = await browser.elementByCss('#thankyounext').text()
expect(another).toBe(revalidated)
})
}
)
Expand Down
5 changes: 2 additions & 3 deletions test/e2e/app-dir/actions/app/revalidate-2/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { cookies } from 'next/headers'
import Link from 'next/link'

export default async function Page() {
const randomCookie = (await cookies()).get('random')
const data = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?page',
{
Expand Down Expand Up @@ -33,9 +34,7 @@ export default async function Page() {
</form>
<p>
random cookie:{' '}
<span id="random-cookie">
{JSON.stringify((await cookies()).get('random'))}
</span>
<span id="random-cookie">{JSON.stringify(randomCookie)}</span>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was needed because when PPR was enabled, the components fetch data was being added to the resume data cache (or RDC) which was causing static/dynamic sheering.

</p>
</>
)
Expand Down
8 changes: 3 additions & 5 deletions test/e2e/app-dir/actions/app/revalidate/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { cookies } from 'next/headers'
import RedirectClientComponent from './client'

export default async function Page() {
const cookie = (await cookies()).get('random')
const data = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?page',
{
Expand All @@ -22,7 +23,7 @@ export default async function Page() {

return (
<>
<p>/revalidate</p>
<h1 id="title">revalidate</h1>
<p>
{' '}
revalidate (tags: thankyounext): <span id="thankyounext">
Expand All @@ -39,10 +40,7 @@ export default async function Page() {
<span id="justputit">{data2}</span>
</p>
<p>
random cookie:{' '}
<span id="random-cookie">
{JSON.stringify((await cookies()).get('random'))}
</span>
random cookie: <span id="random-cookie">{JSON.stringify(cookie)}</span>
</p>
<form>
<button
Expand Down
Loading