Skip to content

Commit 010973b

Browse files
committed
test: improved test reliability
1 parent e2a237a commit 010973b

File tree

3 files changed

+75
-59
lines changed

3 files changed

+75
-59
lines changed

test/e2e/app-dir/actions/app-action.test.ts

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import {
88
getRedboxSource,
99
} from 'next-test-utils'
1010
import type { Request, Response } from 'playwright'
11-
import fs from 'fs-extra'
12-
import nodeFs from 'fs'
13-
import { join } from 'path'
11+
import fs from 'node:fs/promises'
12+
import { join } from 'node:path'
1413
import { outdent } from 'outdent'
14+
import { setTimeout } from 'node:timers/promises'
1515

1616
const GENERIC_RSC_ERROR =
1717
'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.'
@@ -540,10 +540,10 @@ describe('app-dir action handling', () => {
540540
// this triggers a revalidate + redirect in a client component
541541
await browser.elementById('redirect-revalidate-client').click()
542542
await retry(async () => {
543+
expect(await browser.url()).toBe(`${next.url}/revalidate?foo=bar`)
544+
543545
const newJustPutIt = await browser.elementById('justputit').text()
544546
expect(newJustPutIt).not.toBe(initialJustPutit)
545-
546-
expect(await browser.url()).toBe(`${next.url}/revalidate?foo=bar`)
547547
})
548548

549549
// this triggers a revalidate + redirect in a server component
@@ -593,7 +593,8 @@ describe('app-dir action handling', () => {
593593
beforePageLoad(page) {
594594
page.on('request', (request) => {
595595
const url = new URL(request.url())
596-
if (url.pathname === '/server') {
596+
// Only count POST requests to /server (form submissions)
597+
if (url.pathname === '/server' && request.method() === 'POST') {
597598
requestCount++
598599
}
599600
})
@@ -772,8 +773,15 @@ describe('app-dir action handling', () => {
772773
// This verifies the redirect & server response happens in a single roundtrip,
773774
// if the redirect resource was static. In development, these responses are always
774775
// dynamically generated, so we only expect a single request for build/deploy.
776+
// With PPR enabled, there might be an additional RSC request for the redirect.
775777
if (!isNextDev) {
776-
expect(requests.length).toBe(0)
778+
// When PPR is enabled (either through __NEXT_EXPERIMENTAL_PPR or with node middleware),
779+
// we may see 1 RSC request due to different handling of redirects
780+
const isPPREnabled =
781+
process.env.__NEXT_EXPERIMENTAL_PPR === 'true' ||
782+
process.env.TEST_NODE_MIDDLEWARE
783+
const expectedRequests = isPPREnabled ? 1 : 0
784+
expect(requests.length).toBe(expectedRequests)
777785
}
778786
})
779787

@@ -963,16 +971,21 @@ describe('app-dir action handling', () => {
963971
if (isNextStart) {
964972
it('should not expose action content in sourcemaps', async () => {
965973
// We check all sourcemaps in the `static` folder for sensitive information given that chunking
966-
const sourcemaps = nodeFs
967-
.readdirSync(join(next.testDir, '.next', 'static'), {
974+
const sourcemaps = await fs
975+
.readdir(join(next.testDir, '.next', 'static'), {
968976
recursive: true,
969977
encoding: 'utf8',
970978
})
971-
.filter((f) => f.endsWith('.js.map'))
972-
.map((f) =>
973-
nodeFs.readFileSync(join(next.testDir, '.next', 'static', f), {
974-
encoding: 'utf8',
975-
})
979+
.then((files) =>
980+
Promise.all(
981+
files
982+
.filter((f) => f.endsWith('.js.map'))
983+
.map((f) =>
984+
fs.readFile(join(next.testDir, '.next', 'static', f), {
985+
encoding: 'utf8',
986+
})
987+
)
988+
)
976989
)
977990

978991
expect(sourcemaps).not.toBeEmpty()
@@ -1046,23 +1059,22 @@ describe('app-dir action handling', () => {
10461059
it('should bundle external libraries if they are on the action layer', async () => {
10471060
await next.fetch('/client')
10481061
const pageBundle = await fs.readFile(
1049-
join(next.testDir, '.next', 'server', 'app', 'client', 'page.js')
1062+
join(next.testDir, '.next', 'server', 'app', 'client', 'page.js'),
1063+
{ encoding: 'utf8' }
10501064
)
10511065
if (isTurbopack) {
1052-
const chunkPaths = pageBundle
1053-
.toString()
1054-
.matchAll(/loadChunk\("([^"]*)"\)/g)
1055-
// @ts-ignore
1066+
const chunkPaths = pageBundle.matchAll(/loadChunk\("([^"]*)"\)/g)
10561067
const reads = [...chunkPaths].map(async (match) => {
10571068
const bundle = await fs.readFile(
1058-
join(next.testDir, '.next', ...match[1].split(/[\\/]/g))
1069+
join(next.testDir, '.next', ...match[1].split(/[\\/]/g)),
1070+
{ encoding: 'utf8' }
10591071
)
1060-
return bundle.toString().includes('node_modules/nanoid/index.js')
1072+
return bundle.includes('node_modules/nanoid/index.js')
10611073
})
10621074

10631075
expect(await Promise.all(reads)).toContain(true)
10641076
} else {
1065-
expect(pageBundle.toString()).toContain('node_modules/nanoid/index.js')
1077+
expect(pageBundle).toContain('node_modules/nanoid/index.js')
10661078
}
10671079
})
10681080
}
@@ -1526,7 +1538,7 @@ describe('app-dir action handling', () => {
15261538
const browser = await next.browser('/revalidate')
15271539
await browser.refresh()
15281540

1529-
const thankYouNext = await browser.elementByCss('#thankyounext').text()
1541+
const original = await browser.elementByCss('#thankyounext').text()
15301542

15311543
await browser.elementByCss('#another').click()
15321544
await retry(async () => {
@@ -1535,47 +1547,54 @@ describe('app-dir action handling', () => {
15351547
)
15361548
})
15371549

1538-
const newThankYouNext = await browser
1539-
.elementByCss('#thankyounext')
1540-
.text()
1541-
1542-
// Should be the same number although in serverless
1543-
// it might be eventually consistent
1550+
// Should be the same number although in serverless it might be
1551+
// eventually consistent.
15441552
if (!isNextDeploy) {
1545-
expect(thankYouNext).toEqual(newThankYouNext)
1553+
await retry(async () => {
1554+
const another = await browser.elementByCss('#thankyounext').text()
1555+
expect(another).toEqual(original)
1556+
})
15461557
}
15471558

15481559
await browser.elementByCss('#back').click()
1549-
1550-
// Should be different
1551-
let revalidatedThankYouNext
15521560
await retry(async () => {
1553-
switch (type) {
1554-
case 'tag':
1555-
await browser.elementByCss('#revalidate-thankyounext').click()
1556-
break
1557-
case 'path':
1558-
await browser.elementByCss('#revalidate-path').click()
1559-
break
1560-
default:
1561-
throw new Error(`Invalid type: ${type}`)
1562-
}
1561+
expect(await browser.elementByCss('#title').text()).toBe('revalidate')
1562+
})
15631563

1564-
revalidatedThankYouNext = await browser
1565-
.elementByCss('#thankyounext')
1566-
.text()
1564+
switch (type) {
1565+
case 'tag':
1566+
await browser.elementByCss('#revalidate-thankyounext').click()
1567+
break
1568+
case 'path':
1569+
await browser.elementByCss('#revalidate-path').click()
1570+
break
1571+
default:
1572+
throw new Error(`Invalid type: ${type}`)
1573+
}
15671574

1568-
expect(thankYouNext).not.toBe(revalidatedThankYouNext)
1575+
// Give some time for it to be revalidated.
1576+
if (isNextDeploy) {
1577+
await setTimeout(5000)
1578+
}
1579+
1580+
// Should be different
1581+
let revalidated
1582+
await retry(async () => {
1583+
revalidated = await browser.elementByCss('#thankyounext').text()
1584+
expect(revalidated).not.toBe(original)
15691585
})
15701586

15711587
await browser.elementByCss('#another').click()
1588+
await retry(async () => {
1589+
expect(await browser.elementByCss('#title').text()).toBe(
1590+
'another route'
1591+
)
1592+
})
15721593

15731594
// The other page should be revalidated too
15741595
await retry(async () => {
1575-
const newThankYouNext = await browser
1576-
.elementByCss('#thankyounext')
1577-
.text()
1578-
expect(revalidatedThankYouNext).toBe(newThankYouNext)
1596+
const another = await browser.elementByCss('#thankyounext').text()
1597+
expect(another).toBe(revalidated)
15791598
})
15801599
}
15811600
)

test/e2e/app-dir/actions/app/revalidate-2/page.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cookies } from 'next/headers'
33
import Link from 'next/link'
44

55
export default async function Page() {
6+
const randomCookie = (await cookies()).get('random')
67
const data = await fetch(
78
'https://next-data-api-endpoint.vercel.app/api/random?page',
89
{
@@ -33,9 +34,7 @@ export default async function Page() {
3334
</form>
3435
<p>
3536
random cookie:{' '}
36-
<span id="random-cookie">
37-
{JSON.stringify((await cookies()).get('random'))}
38-
</span>
37+
<span id="random-cookie">{JSON.stringify(randomCookie)}</span>
3938
</p>
4039
</>
4140
)

test/e2e/app-dir/actions/app/revalidate/page.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { cookies } from 'next/headers'
66
import RedirectClientComponent from './client'
77

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

2324
return (
2425
<>
25-
<p>/revalidate</p>
26+
<h1 id="title">revalidate</h1>
2627
<p>
2728
{' '}
2829
revalidate (tags: thankyounext): <span id="thankyounext">
@@ -39,10 +40,7 @@ export default async function Page() {
3940
<span id="justputit">{data2}</span>
4041
</p>
4142
<p>
42-
random cookie:{' '}
43-
<span id="random-cookie">
44-
{JSON.stringify((await cookies()).get('random'))}
45-
</span>
43+
random cookie: <span id="random-cookie">{JSON.stringify(cookie)}</span>
4644
</p>
4745
<form>
4846
<button

0 commit comments

Comments
 (0)