@@ -55,7 +55,7 @@ mountBrowserChromeFixture(useBrowserPanelOcclusion);`,
5555 response . end (
5656 path . endsWith ( '.js' )
5757 ? bundle . outputFiles . find ( ( file ) => file . path . endsWith ( '.js' ) ) ?. text
58- : css . css
58+ : ` ${ css . css } \n ${ bundle . outputFiles . find ( ( file ) => file . path . endsWith ( '.css' ) ) ?. text ?? '' } `
5959 )
6060 return
6161 }
@@ -75,7 +75,7 @@ mountBrowserChromeFixture(useBrowserPanelOcclusion);`,
7575 response . end (
7676 path === '/page'
7777 ? '<!doctype html><html><body style="background:#192b40;color:white;font:24px system-ui;padding:25px"><h1>Browser fixture</h1><p>A live page behind the application chrome.</p><button>Page action</button></body></html>'
78- : '<!doctype html><html class="dark"><head><link rel="stylesheet" href="/fixture.css"></head><body style="margin:0;background:#191919 ;color:#eee "><div id="root"></div><script src="/fixture.js"></script></body></html>'
78+ : '<!doctype html><html class="dark"><head><link rel="stylesheet" href="/fixture.css"></head><body style="margin:0;background:var(--bg) ;color:var(--text-primary) "><div id="root"></div><script src="/fixture.js"></script></body></html>'
7979 )
8080 } )
8181 await new Promise < void > ( ( resolve ) => server ?. listen ( 0 , resolve ) )
@@ -116,31 +116,150 @@ mountBrowserChromeFixture(useBrowserPanelOcclusion);`,
116116 return { width : bounds . width , right : bounds . right }
117117 } )
118118 )
119- expect ( geometry . every ( ( tab ) => tab . width >= 64 && tab . width < 160 ) ) . toBe ( true )
119+ expect ( geometry . every ( ( tab ) => tab . width < 160 ) ) . toBe ( true )
120120 expect ( geometry . at ( - 1 ) ?. right ) . toBeLessThan ( 1070 )
121121 await page . screenshot ( { path : testInfo . outputPath ( 'tabs.png' ) } )
122122 } )
123- await test . step ( 'Short labels keep their compact intrinsic width' , async ( ) => {
124- await page . locator ( '#short-tabs' ) . click ( )
125- const widths = await page
126- . locator ( '[data-tab-strip-item]' )
127- . evaluateAll ( ( tabs ) => tabs . map ( ( tab ) => tab . getBoundingClientRect ( ) . width ) )
128- expect ( widths . every ( ( width ) => width >= 64 && width < 96 ) ) . toBe ( true )
129- await page . locator ( '#eight-tabs' ) . click ( )
130- } )
131- await test . step ( 'Crowded tabs preserve controls and scroll' , async ( ) => {
123+ await test . step ( 'Crowded tabs keep readable labels when selected, hovered, and focused' , async ( ) => {
132124 await page . locator ( '#many-tabs' ) . click ( )
133125 await expect ( page . locator ( '[data-tab-strip-item]' ) ) . toHaveCount ( 18 )
134126 const overflow = await page
135127 . locator ( '[data-tab-strip-item]' )
136128 . first ( )
137129 . evaluate ( ( tab ) => ( {
138- width : tab . getBoundingClientRect ( ) . width ,
139130 scrollWidth : tab . parentElement ?. scrollWidth ?? 0 ,
140131 clientWidth : tab . parentElement ?. clientWidth ?? 0 ,
141132 } ) )
142- expect ( overflow . width ) . toBeGreaterThanOrEqual ( 64 )
143133 expect ( overflow . scrollWidth ) . toBeGreaterThan ( overflow . clientWidth )
134+ const active = page . getByRole ( 'tab' , { selected : true } )
135+ const label = active . locator ( '[data-overflow-text]' )
136+ await expect
137+ . poll ( ( ) => label . evaluate ( ( element ) => element . getBoundingClientRect ( ) . width ) )
138+ . toBeGreaterThanOrEqual ( 48 )
139+ const neighbor = page . getByRole ( 'tab' ) . nth ( 2 )
140+ const beforeHover = await neighbor . evaluate ( ( element : HTMLElement ) => ( {
141+ left : element . offsetLeft ,
142+ width : element . offsetWidth ,
143+ } ) )
144+ await neighbor . hover ( )
145+ await expect
146+ . poll ( ( ) =>
147+ neighbor
148+ . locator ( '[data-overflow-text]' )
149+ . evaluate ( ( element ) => element . getBoundingClientRect ( ) . width )
150+ )
151+ . toBeGreaterThanOrEqual ( 48 )
152+ expect (
153+ await neighbor . evaluate ( ( element : HTMLElement ) => ( {
154+ left : element . offsetLeft ,
155+ width : element . offsetWidth ,
156+ } ) )
157+ ) . toEqual ( beforeHover )
158+ await neighbor . click ( )
159+ await expect ( neighbor ) . toHaveAttribute ( 'aria-selected' , 'true' )
160+ await page . keyboard . press ( 'End' )
161+ const last = page . getByRole ( 'tab' ) . last ( )
162+ await expect ( last ) . toBeFocused ( )
163+ await expect ( last ) . toHaveAttribute ( 'aria-selected' , 'true' )
164+ await expect ( last ) . toBeInViewport ( { ratio : 1 } )
165+ await expect
166+ . poll ( ( ) => label . evaluate ( ( element ) => element . getBoundingClientRect ( ) . width ) )
167+ . toBeGreaterThanOrEqual ( 48 )
168+ await page . mouse . move ( 200 , 180 )
169+ await page . screenshot ( {
170+ path : testInfo . outputPath ( 'crowded-tabs.png' ) ,
171+ animations : 'disabled' ,
172+ } )
173+ await page . evaluate ( ( ) => document . documentElement . classList . remove ( 'dark' ) )
174+ await page . screenshot ( {
175+ path : testInfo . outputPath ( 'crowded-tabs-light.png' ) ,
176+ animations : 'disabled' ,
177+ } )
178+ await page . evaluate ( ( ) => document . documentElement . classList . add ( 'dark' ) )
179+ await page . keyboard . press ( 'Home' )
180+ await expect ( page . getByRole ( 'tab' ) . first ( ) ) . toBeInViewport ( { ratio : 1 } )
181+ await page . locator ( '#eight-tabs' ) . click ( )
182+ } )
183+ await test . step ( 'Touch tabs leave room for both attention and close controls' , async ( ) => {
184+ const session = await page . context ( ) . newCDPSession ( page )
185+ try {
186+ await session . send ( 'Emulation.setTouchEmulationEnabled' , { enabled : true } )
187+ expect ( await page . evaluate ( ( ) => matchMedia ( '(any-pointer: coarse)' ) . matches ) ) . toBe ( true )
188+ await page . locator ( '#many-tabs' ) . click ( )
189+ const attention = page . getByRole ( 'tab' ) . nth ( 1 )
190+ await expect
191+ . poll ( ( ) =>
192+ attention
193+ . locator ( '[data-overflow-text]' )
194+ . evaluate ( ( element ) => element . getBoundingClientRect ( ) . width )
195+ )
196+ . toBeGreaterThanOrEqual ( 48 )
197+ const attentionItem = page . locator ( '[data-tab-strip-item="tab-1"]' )
198+ const indicator = attentionItem . locator ( '[data-row-action-indicator]' )
199+ const controls = attentionItem . locator ( '[data-row-action-controls]' )
200+ await expect ( indicator ) . toBeVisible ( )
201+ await expect ( indicator ) . toHaveCSS ( 'opacity' , '1' )
202+ await expect ( controls ) . toHaveCSS ( 'opacity' , '1' )
203+ await attentionItem . getByRole ( 'button' , { name : / ^ C l o s e / } ) . click ( { trial : true } )
204+ expect (
205+ await attentionItem . evaluate ( ( element ) => {
206+ const title = element . querySelector ( '[data-overflow-text]' ) ?. getBoundingClientRect ( )
207+ const indicator = element
208+ . querySelector ( '[data-row-action-indicator]' )
209+ ?. getBoundingClientRect ( )
210+ const close = element . querySelector ( '[aria-label^="Close "]' ) ?. getBoundingClientRect ( )
211+ return (
212+ title &&
213+ indicator &&
214+ close &&
215+ title . right <= indicator . left &&
216+ indicator . right <= close . left &&
217+ close . right <= element . getBoundingClientRect ( ) . right
218+ )
219+ } )
220+ ) . toBe ( true )
221+ await page . screenshot ( { path : testInfo . outputPath ( 'crowded-tabs-touch.png' ) } )
222+ const beforeSelection = await attention . evaluate (
223+ ( element : HTMLElement ) => element . offsetWidth
224+ )
225+ await attention . click ( )
226+ await expect ( attention ) . toHaveAttribute ( 'aria-selected' , 'true' )
227+ expect ( await attention . evaluate ( ( element : HTMLElement ) => element . offsetWidth ) ) . toBe (
228+ beforeSelection
229+ )
230+ await page . locator ( '#toggle-activity' ) . click ( )
231+ expect ( await attention . evaluate ( ( element : HTMLElement ) => element . offsetWidth ) ) . toBe (
232+ beforeSelection
233+ )
234+ await page . locator ( '#toggle-activity' ) . click ( )
235+ await page . locator ( '#medium-tabs' ) . click ( )
236+ await page . getByRole ( 'tab' ) . first ( ) . click ( )
237+ const intrinsicWidth = await attention . evaluate (
238+ ( element : HTMLElement ) => element . offsetWidth
239+ )
240+ expect ( intrinsicWidth ) . toBeGreaterThan ( 144 )
241+ expect ( intrinsicWidth ) . toBeLessThan ( 200 )
242+ await attention . click ( )
243+ expect ( await attention . evaluate ( ( element : HTMLElement ) => element . offsetWidth ) ) . toBe (
244+ intrinsicWidth
245+ )
246+ await page . locator ( '#toggle-activity' ) . click ( )
247+ expect ( await attention . evaluate ( ( element : HTMLElement ) => element . offsetWidth ) ) . toBe (
248+ intrinsicWidth
249+ )
250+ await page . locator ( '#toggle-activity' ) . click ( )
251+ } finally {
252+ await session . send ( 'Emulation.setTouchEmulationEnabled' , { enabled : false } )
253+ await session . detach ( )
254+ }
255+ await page . locator ( '#eight-tabs' ) . click ( )
256+ } )
257+ await test . step ( 'Short labels stay below the maximum tab width' , async ( ) => {
258+ await page . locator ( '#short-tabs' ) . click ( )
259+ const widths = await page
260+ . locator ( '[data-tab-strip-item]' )
261+ . evaluateAll ( ( tabs ) => tabs . map ( ( tab ) => tab . getBoundingClientRect ( ) . width ) )
262+ expect ( widths . every ( ( width ) => width < 120 ) ) . toBe ( true )
144263 await page . locator ( '#eight-tabs' ) . click ( )
145264 } )
146265 await test . step ( 'An open menu recovers when no native page was available for its initial capture' , async ( ) => {
0 commit comments