Skip to content

Commit 837e086

Browse files
authored
chore(CI): Break pages-dir client-navigation dev tests into smaller parallelizable files/suites (#78787)
Jest can run different suites in parallel, but tests within the same suite must run sequentially in the same worker. You don't want suites that are too small, because each suite requires relatively expensive repository setup, but you also don't want suites that are too large because it inhibits parallelism. This is a big problem for arewerspackyet, where many of these test cases are currently failing. Each test can take up to 4 minutes to time out, so we easily time out the entire 90 minute long test runner job. ## Test plan Tested by running all these tests in parallel with: ``` NEXT_TEST_MODE=dev HEADLESS=true node_modules/.bin/jest test/development/pages-dir/client-navigation ``` It completes in about 2m40s minutes on my machine.
1 parent 2bb2b63 commit 837e086

14 files changed

+1713
-1570
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/* eslint-env jest */
2+
3+
import { waitFor } from 'next-test-utils'
4+
import path from 'path'
5+
import { nextTestSetup } from 'e2e-utils'
6+
7+
describe('Client Navigation', () => {
8+
const { next } = nextTestSetup({
9+
files: path.join(__dirname, 'fixture'),
10+
env: {
11+
TEST_STRICT_NEXT_HEAD: String(true),
12+
},
13+
})
14+
15+
describe('with <a/> tag inside the <Link />', () => {
16+
it('should navigate the page', async () => {
17+
const browser = await next.browser('/nav/about')
18+
const text = await browser
19+
.elementByCss('#home-link')
20+
.click()
21+
.waitForElementByCss('.nav-home')
22+
.elementByCss('p')
23+
.text()
24+
25+
expect(text).toBe('This is the home.')
26+
await browser.close()
27+
})
28+
29+
it('should not navigate if the <a/> tag has a target', async () => {
30+
const browser = await next.browser('/nav')
31+
32+
await browser
33+
.elementByCss('#increase')
34+
.click()
35+
.elementByCss('#target-link')
36+
.click()
37+
38+
await waitFor(1000)
39+
40+
const counterText = await browser.elementByCss('#counter').text()
41+
42+
expect(counterText).toBe('Counter: 1')
43+
await browser.close()
44+
})
45+
46+
it('should not navigate if the click-event is modified', async () => {
47+
const browser = await next.browser('/nav')
48+
49+
await browser.elementByCss('#increase').click()
50+
51+
const key = process.platform === 'darwin' ? 'Meta' : 'Control'
52+
53+
await browser.keydown(key)
54+
55+
await browser.elementByCss('#in-svg-link').click()
56+
57+
await browser.keyup(key)
58+
await waitFor(1000)
59+
60+
const counterText = await browser.elementByCss('#counter').text()
61+
62+
expect(counterText).toBe('Counter: 1')
63+
await browser.close()
64+
})
65+
66+
it('should not reload when link in svg is clicked', async () => {
67+
const browser = await next.browser('/nav')
68+
await browser.eval('window.hello = true')
69+
await browser
70+
.elementByCss('#in-svg-link')
71+
.click()
72+
.waitForElementByCss('.nav-about')
73+
74+
expect(await browser.eval('window.hello')).toBe(true)
75+
await browser.close()
76+
})
77+
})
78+
79+
describe('with unexpected <a/> nested tag', () => {
80+
it('should not redirect if passHref prop is not defined in Link', async () => {
81+
const browser = await next.browser('/nav/pass-href-prop')
82+
const text = await browser
83+
.elementByCss('#without-href')
84+
.click()
85+
.waitForElementByCss('.nav-pass-href-prop')
86+
.elementByCss('p')
87+
.text()
88+
89+
expect(text).toBe('This is the passHref prop page.')
90+
await browser.close()
91+
})
92+
93+
it('should redirect if passHref prop is defined in Link', async () => {
94+
const browser = await next.browser('/nav/pass-href-prop')
95+
const text = await browser
96+
.elementByCss('#with-href')
97+
.click()
98+
.waitForElementByCss('.nav-home')
99+
.elementByCss('p')
100+
.text()
101+
102+
expect(text).toBe('This is the home.')
103+
await browser.close()
104+
})
105+
})
106+
})
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/* eslint-env jest */
2+
3+
import { check } from 'next-test-utils'
4+
import path from 'path'
5+
import { nextTestSetup } from 'e2e-utils'
6+
7+
describe('Client navigation with asPath', () => {
8+
const { next } = nextTestSetup({
9+
files: path.join(__dirname, 'fixture'),
10+
env: {
11+
TEST_STRICT_NEXT_HEAD: String(true),
12+
},
13+
})
14+
15+
describe('inside getInitialProps', () => {
16+
it('should show the correct asPath with a Link with as prop', async () => {
17+
const browser = await next.browser('/nav')
18+
const asPath = await browser
19+
.elementByCss('#as-path-link')
20+
.click()
21+
.waitForElementByCss('.as-path-content')
22+
.elementByCss('.as-path-content')
23+
.text()
24+
25+
expect(asPath).toBe('/as/path')
26+
await browser.close()
27+
})
28+
29+
it('should show the correct asPath with a Link without the as prop', async () => {
30+
const browser = await next.browser('/nav')
31+
const asPath = await browser
32+
.elementByCss('#as-path-link-no-as')
33+
.click()
34+
.waitForElementByCss('.as-path-content')
35+
.elementByCss('.as-path-content')
36+
.text()
37+
38+
expect(asPath).toBe('/nav/as-path')
39+
await browser.close()
40+
})
41+
})
42+
43+
describe('with next/router', () => {
44+
it('should show the correct asPath', async () => {
45+
const browser = await next.browser('/nav')
46+
const asPath = await browser
47+
.elementByCss('#as-path-using-router-link')
48+
.click()
49+
.waitForElementByCss('.as-path-content')
50+
.elementByCss('.as-path-content')
51+
.text()
52+
53+
expect(asPath).toBe('/nav/as-path-using-router')
54+
await browser.close()
55+
})
56+
57+
it('should navigate an absolute url on push', async () => {
58+
const browser = await next.browser(`/absolute-url?port=${next.appPort}`)
59+
await browser.waitForElementByCss('#router-push').click()
60+
await check(
61+
() => browser.eval(() => window.location.origin),
62+
'https://vercel.com'
63+
)
64+
})
65+
66+
it('should navigate an absolute url on replace', async () => {
67+
const browser = await next.browser(`/absolute-url?port=${next.appPort}`)
68+
await browser.waitForElementByCss('#router-replace').click()
69+
await check(
70+
() => browser.eval(() => window.location.origin),
71+
'https://vercel.com'
72+
)
73+
})
74+
75+
it('should navigate an absolute local url on push', async () => {
76+
const browser = await next.browser(`/absolute-url?port=${next.appPort}`)
77+
// @ts-expect-error _didNotNavigate is set intentionally
78+
await browser.eval(() => (window._didNotNavigate = true))
79+
await browser.waitForElementByCss('#router-local-push').click()
80+
const text = await browser
81+
.waitForElementByCss('.nav-about')
82+
.elementByCss('p')
83+
.text()
84+
expect(text).toBe('This is the about page.')
85+
// @ts-expect-error _didNotNavigate is set intentionally
86+
expect(await browser.eval(() => window._didNotNavigate)).toBe(true)
87+
})
88+
89+
it('should navigate an absolute local url on replace', async () => {
90+
const browser = await next.browser(`/absolute-url?port=${next.appPort}`)
91+
// @ts-expect-error _didNotNavigate is set intentionally
92+
await browser.eval(() => (window._didNotNavigate = true))
93+
await browser.waitForElementByCss('#router-local-replace').click()
94+
const text = await browser
95+
.waitForElementByCss('.nav-about')
96+
.elementByCss('p')
97+
.text()
98+
expect(text).toBe('This is the about page.')
99+
// @ts-expect-error _didNotNavigate is set intentionally
100+
expect(await browser.eval(() => window._didNotNavigate)).toBe(true)
101+
})
102+
})
103+
104+
describe('with next/link', () => {
105+
it('should use pushState with same href and different asPath', async () => {
106+
const browser = await next.browser('/nav/as-path-pushstate')
107+
await browser
108+
.elementByCss('#hello')
109+
.click()
110+
.waitForElementByCss('#something-hello')
111+
const queryOne = JSON.parse(
112+
await browser.elementByCss('#router-query').text()
113+
)
114+
expect(queryOne.something).toBe('hello')
115+
await browser
116+
.elementByCss('#same-query')
117+
.click()
118+
.waitForElementByCss('#something-same-query')
119+
const queryTwo = JSON.parse(
120+
await browser.elementByCss('#router-query').text()
121+
)
122+
expect(queryTwo.something).toBe('hello')
123+
await browser.back().waitForElementByCss('#something-hello')
124+
const queryThree = JSON.parse(
125+
await browser.elementByCss('#router-query').text()
126+
)
127+
expect(queryThree.something).toBe('hello')
128+
await browser
129+
.elementByCss('#else')
130+
.click()
131+
.waitForElementByCss('#something-else')
132+
await browser
133+
.elementByCss('#hello2')
134+
.click()
135+
.waitForElementByCss('#nav-as-path-pushstate')
136+
await browser.back().waitForElementByCss('#something-else')
137+
const queryFour = JSON.parse(
138+
await browser.elementByCss('#router-query').text()
139+
)
140+
expect(queryFour.something).toBe(undefined)
141+
})
142+
143+
it('should detect asPath query changes correctly', async () => {
144+
const browser = await next.browser('/nav/as-path-query')
145+
await browser
146+
.elementByCss('#hello')
147+
.click()
148+
.waitForElementByCss('#something-hello-something-hello')
149+
const queryOne = JSON.parse(
150+
await browser.elementByCss('#router-query').text()
151+
)
152+
expect(queryOne.something).toBe('hello')
153+
await browser
154+
.elementByCss('#hello2')
155+
.click()
156+
.waitForElementByCss('#something-hello-something-else')
157+
const queryTwo = JSON.parse(
158+
await browser.elementByCss('#router-query').text()
159+
)
160+
expect(queryTwo.something).toBe('else')
161+
})
162+
})
163+
})
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-env jest */
2+
3+
import { fetchViaHTTP, waitFor } from 'next-test-utils'
4+
import path from 'path'
5+
import { nextTestSetup } from 'e2e-utils'
6+
7+
describe('Client navigation on error pages', () => {
8+
const { next } = nextTestSetup({
9+
files: path.join(__dirname, 'fixture'),
10+
env: {
11+
TEST_STRICT_NEXT_HEAD: String(true),
12+
},
13+
})
14+
15+
it('should not reload when visiting /_error directly', async () => {
16+
const { status } = await fetchViaHTTP(next.appPort, '/_error')
17+
const browser = await next.browser('/_error')
18+
19+
await browser.eval('window.hello = true')
20+
21+
// wait on-demand-entries timeout since it can trigger
22+
// reloading non-stop
23+
for (let i = 0; i < 15; i++) {
24+
expect(await browser.eval('window.hello')).toBe(true)
25+
await waitFor(1000)
26+
}
27+
const html = await browser.eval('document.documentElement.innerHTML')
28+
29+
expect(status).toBe(404)
30+
expect(html).toContain('This page could not be found')
31+
expect(html).toContain('404')
32+
})
33+
34+
describe('with 404 pages', () => {
35+
it('should 404 on not existent page', async () => {
36+
const browser = await next.browser('/non-existent')
37+
expect(await browser.elementByCss('h1').text()).toBe('404')
38+
expect(await browser.elementByCss('h2').text()).toBe(
39+
'This page could not be found.'
40+
)
41+
await browser.close()
42+
})
43+
44+
it('should 404 on wrong casing', async () => {
45+
const browser = await next.browser('/nAv/AbOuT')
46+
expect(await browser.elementByCss('h1').text()).toBe('404')
47+
expect(await browser.elementByCss('h2').text()).toBe(
48+
'This page could not be found.'
49+
)
50+
await browser.close()
51+
})
52+
53+
it('should get url dynamic param', async () => {
54+
const browser = await next.browser('/dynamic/dynamic-part/route')
55+
expect(await browser.elementByCss('p').text()).toBe('dynamic-part')
56+
await browser.close()
57+
})
58+
59+
it('should 404 on wrong casing of url dynamic param', async () => {
60+
const browser = await next.browser('/dynamic/dynamic-part/RoUtE')
61+
expect(await browser.elementByCss('h1').text()).toBe('404')
62+
expect(await browser.elementByCss('h2').text()).toBe(
63+
'This page could not be found.'
64+
)
65+
await browser.close()
66+
})
67+
68+
it('should not 404 for <page>/', async () => {
69+
const browser = await next.browser('/nav/about/')
70+
const text = await browser.elementByCss('p').text()
71+
expect(text).toBe('This is the about page.')
72+
await browser.close()
73+
})
74+
75+
it('should should not contain a page script in a 404 page', async () => {
76+
const browser = await next.browser('/non-existent')
77+
const scripts = await browser.elementsByCss('script[src]')
78+
for (const script of scripts) {
79+
const src = await script.getAttribute('src')
80+
expect(src.includes('/non-existent')).toBeFalsy()
81+
}
82+
await browser.close()
83+
})
84+
})
85+
})

0 commit comments

Comments
 (0)