@@ -17,6 +17,7 @@ import {
1717import {
1818 artifactDirectory ,
1919 beginWalk ,
20+ CONTENT_DEADLINE_MS ,
2021 DESKTOP ,
2122 org ,
2223 type Persona ,
@@ -125,6 +126,21 @@ export async function fullPageScreenshot(page: Page, path: string, viewport: Vie
125126 await page . setViewportSize ( { width : viewport . width , height : viewport . height } )
126127}
127128
129+ /**
130+ * Runs in the page: the length of the page's own text. The rail and page header paint on every
131+ * route, so their text does not count.
132+ */
133+ function pageTextLength ( ) {
134+ const walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT )
135+ let length = 0
136+ while ( walker . nextNode ( ) ) {
137+ const { parentElement, textContent } = walker . currentNode
138+ if ( parentElement ?. closest ( 'aside, nav, header, script, style, noscript' ) ) continue
139+ length += textContent ?. trim ( ) . length ?? 0
140+ }
141+ return length
142+ }
143+
128144/** Long enough for a persona's walk to list every route that failed. */
129145const WALK_ERROR_LIMIT = 16_000
130146
@@ -147,6 +163,12 @@ async function walkOne(persona: Persona, viewport: Viewport, theme: Theme) {
147163 failures . push ( `${ route . name } : ${ getErrorMessage ( error ) } ` )
148164 return null
149165 } )
166+ // A section paints nothing while its first reads start, so a page that went quiet in that
167+ // gap is still given the content deadline to show text before it counts as empty.
168+ const text = await page
169+ . waitForFunction ( pageTextLength , undefined , { timeout : CONTENT_DEADLINE_MS } )
170+ . then ( ( handle ) => handle . jsonValue ( ) )
171+ . catch ( ( ) => 0 )
150172 if ( route . name === 'home' )
151173 await composer ( page )
152174 . waitFor ( { timeout : 20_000 } )
@@ -176,17 +198,6 @@ async function walkOne(persona: Persona, viewport: Viewport, theme: Theme) {
176198 font : getComputedStyle ( document . body ) . fontFamily ,
177199 overflow : document . documentElement . scrollWidth - innerWidth ,
178200 surfaces : document . querySelectorAll ( '[data-platform-ui]' ) . length ,
179- // The rail and page header paint on every route, so only the page's own text counts.
180- text : ( ( ) => {
181- const walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT )
182- let length = 0
183- while ( walker . nextNode ( ) ) {
184- const { parentElement, textContent } = walker . currentNode
185- if ( parentElement ?. closest ( 'aside, nav, header, script, style, noscript' ) ) continue
186- length += textContent ?. trim ( ) . length ?? 0
187- }
188- return length
189- } ) ( ) ,
190201 } ) )
191202 routes . push ( {
192203 persona : persona . key ,
@@ -210,7 +221,7 @@ async function walkOne(persona: Persona, viewport: Viewport, theme: Theme) {
210221 if ( facts . dark !== ( theme === 'dark' ) ) failures . push ( `${ route . name } wrong theme` )
211222 if ( / T i m e s / . test ( facts . font ) ) failures . push ( `${ route . name } has no stylesheet` )
212223 if ( facts . overflow > 1 ) failures . push ( `${ route . name } overflows by ${ facts . overflow } px` )
213- if ( facts . text === 0 ) failures . push ( `${ route . name } rendered an empty shell` )
224+ if ( text === 0 ) failures . push ( `${ route . name } rendered an empty shell` )
214225 if ( facts . surfaces === 0 ) failures . push ( `${ route . name } mounted no design-system surface` )
215226 }
216227 assert . deepEqual ( failures , [ ] )
0 commit comments