-
Notifications
You must be signed in to change notification settings - Fork 31k
Expand file tree
/
Copy pathinstant-navs-devtools.test.ts
More file actions
247 lines (202 loc) · 8.46 KB
/
instant-navs-devtools.test.ts
File metadata and controls
247 lines (202 loc) · 8.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import { nextTestSetup } from 'e2e-utils'
import { retry, toggleDevToolsIndicatorPopover } from 'next-test-utils'
import { Playwright } from 'next-webdriver'
describe('instant-nav-panel', () => {
const { isNextDev, isTurbopack, next } = nextTestSetup({
files: __dirname,
})
async function waitForPanelRouterTransition() {
// Run all the necessary CSS transitions
// and click-outside event handler adjustment due to cascading update.
// TODO: Consider disabling transitions entirely in Next.js tests.
await new Promise((resolve) =>
setTimeout(
resolve,
// MENU_DURATION_MS + some flakiness buffer
200 + 50
)
)
}
async function waitForInstantModeCookie(browser: Playwright): Promise<void> {
await retry(async () => {
const cookie = await browser.eval(() => document.cookie)
expect(cookie).toMatch(/next-instant-navigation-testing=[^;]+/)
})
}
async function clearInstantModeCookie(browser: Playwright) {
await browser.eval(() => {
document.cookie = 'next-instant-navigation-testing=; path=/; max-age=0'
})
}
async function clickInstantNavMenuItem(browser: Playwright) {
await browser.elementByCss('[data-instant-nav]').click()
}
async function clickViewPageLoad(browser: Playwright) {
await browser
// TODO: Monitor if we need to increase timeouts for all *instant calls
.elementByCss('[data-instant-nav-refresh]', { timeout: 50 })
.click()
await waitForInstantModeCookie(browser)
}
async function clickStartClientNav(browser: Playwright) {
await browser
// TODO: Monitor if we need to increase timeouts for all *instant calls
.elementByCss('[data-instant-nav-client]', { timeout: 50 })
.click()
await waitForInstantModeCookie(browser)
}
async function getInstantNavPanelText(browser: Playwright): Promise<string> {
return browser.elementByCssInstant('.instant-nav-panel').text()
}
async function closePanelViaHeader(browser: Playwright) {
return browser.elementByCss('#_next-devtools-panel-close').click()
}
async function hasInstantNavPanelOpen(browser: Playwright): Promise<void> {
await browser.elementByCssInstant('.instant-nav-panel')
}
async function openInstantNavPanel(browser: Playwright) {
await toggleDevToolsIndicatorPopover(browser)
await waitForPanelRouterTransition()
await clickInstantNavMenuItem(browser)
await retry(
async () => {
await hasInstantNavPanelOpen(browser)
},
5_000,
500
)
await waitForPanelRouterTransition()
}
it('should open panel in waiting state without setting cookie', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
await openInstantNavPanel(browser)
// Panel should show waiting state with Page load and Client navigation sections
await retry(async () => {
const text = await getInstantNavPanelText(browser)
expect(text).toContain('Page load')
expect(text).toContain('Client navigation')
})
// Cookie should NOT be set yet (only set when user clicks Reload or Start)
const cookie = await browser.eval(() => document.cookie)
expect(cookie).not.toContain('next-instant-navigation-testing=')
// Clean up
await clearInstantModeCookie(browser)
})
it('should show page load state after clicking Reload', async () => {
const browser = await next.browser('/target-page/my-post?search=foo')
await clearInstantModeCookie(browser)
await openInstantNavPanel(browser)
await clickViewPageLoad(browser)
await retry(async () => {
const text = await getInstantNavPanelText(browser)
expect(text).toContain('Page load')
expect(text).toContain('pre-rendered static UI')
expect(text).toContain('Continue rendering')
})
})
it('should show client nav state after clicking Start and navigating', async () => {
const targetPage = '/target-page/my-post?search=foo'
const [browser] = await Promise.all([
next.browser('/'),
isNextDev && !isTurbopack
? // warmup target page compilation before clicking Start, to avoid extra flakiness.
next.render(targetPage).catch(() => {})
: null,
])
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
await openInstantNavPanel(browser)
// Click Start to enter client-nav-waiting state
await clickStartClientNav(browser)
// Cookie should now be set
await waitForInstantModeCookie(browser)
// Panel should show client-nav-waiting state
await retry(async () => {
const text = await getInstantNavPanelText(browser)
expect(text).toContain('Client navigation')
expect(text).toContain('Click any link')
})
// Navigate to target page via SPA (use eval to bypass overlay pointer interception)
await browser.eval((page) => {
document.querySelector<HTMLAnchorElement>(`[href="${page}"]`)!.click()
}, targetPage)
// Panel should transition to client-nav state
await retry(async () => {
const text = await getInstantNavPanelText(browser)
expect(text).toContain('Client navigation')
expect(text).toContain('prefetched UI')
expect(text).toContain('Continue rendering')
})
// Clean up
await clearInstantModeCookie(browser)
})
it('should show loading skeletons during SPA navigation after clicking Start', async () => {
const targetPage = '/target-page/my-post?search=foo'
const [browser] = await Promise.all([
next.browser('/'),
isNextDev && !isTurbopack
? // warmup target page compilation before clicking Start, to avoid extra flakiness.
next.render(targetPage).catch(() => {})
: null,
])
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
await openInstantNavPanel(browser)
// Click Start to activate the navigation lock
await clickStartClientNav(browser)
// Navigate to target page via SPA (use eval to bypass overlay pointer interception)
await browser.eval((page) => {
document.querySelector<HTMLAnchorElement>(`[href="${page}"]`)!.click()
}, targetPage)
// Every runtime-dependent segment should be suspended under the lock:
// data-fetching (dynamic content), `await params`, and `await searchParams`.
// Use a longer timeout because dev mode needs to compile the target page.
await browser
.locator('[data-testid="dynamic-skeleton"]')
.waitFor({ state: 'visible', timeout: 30000 })
await browser
.locator('[data-testid="param-skeleton"]')
.waitFor({ state: 'visible' })
await browser
.locator('[data-testid="search-param-skeleton"]')
.waitFor({ state: 'visible' })
// The resolved param value must not have leaked through the lock.
expect(await browser.locator('[data-testid="param-value"]').count()).toBe(0)
// Clean up
await clearInstantModeCookie(browser)
})
it('should auto-open panel on page load when cookie is already set', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
// Open the panel and click Start to set the cookie
await openInstantNavPanel(browser)
await clickStartClientNav(browser)
// Reload — the cookie persists, so the panel should auto-open
await browser.refresh()
await browser.waitForElementByCss('[data-testid="home-title"]')
await retry(async () => {
await hasInstantNavPanelOpen(browser)
})
// Clean up
await clearInstantModeCookie(browser)
})
it('should not set cookie when closing panel from waiting state', async () => {
const browser = await next.browser('/')
await clearInstantModeCookie(browser)
await browser.waitForElementByCss('[data-testid="home-title"]')
await openInstantNavPanel(browser)
// Verify cookie is NOT set (panel opened without activating lock)
const cookie = await browser.eval(() => document.cookie)
expect(cookie).not.toContain('next-instant-navigation-testing=')
// Close panel via X button
await closePanelViaHeader(browser)
// Cookie should still not be set, and no reload should happen
await retry(async () => {
const cookieAfter = await browser.eval(() => document.cookie)
expect(cookieAfter).not.toContain('next-instant-navigation-testing=')
})
})
})