@@ -67,8 +67,7 @@ export class IframeOrchestrator {
6767 const container = await getContainer ( config )
6868
6969 if ( config . browser . ui ) {
70- container . className = 'absolute origin-top mt-[8px]'
71- container . parentElement ! . setAttribute ( 'data-ready' , 'true' )
70+ container . setAttribute ( 'data-ready' , 'true' )
7271 // in non-isolated mode this will also remove the iframe,
7372 // so we only do this once
7473 if ( container . textContent ) {
@@ -154,9 +153,8 @@ export class IframeOrchestrator {
154153
155154 const config = getConfig ( )
156155 const { width, height } = config . browser . viewport
157- const iframe = this . iframes . get ( ID_ALL ) !
158156
159- await setIframeViewport ( iframe , width , height )
157+ await setIframeViewport ( width , height )
160158 debug ( 'run non-isolated tests' , options . files . join ( ', ' ) )
161159 await this . sendEventToIframe ( {
162160 event : 'execute' ,
@@ -187,13 +185,13 @@ export class IframeOrchestrator {
187185 this . iframes . delete ( file )
188186 }
189187
190- const iframe = await this . prepareIframe (
188+ await this . prepareIframe (
191189 container ,
192190 file ,
193191 startTime ,
194192 otelContext ,
195193 )
196- await setIframeViewport ( iframe , width , height )
194+ await setIframeViewport ( width , height )
197195 // running tests after the "prepare" event
198196 await this . sendEventToIframe ( {
199197 event : 'execute' ,
@@ -312,16 +310,40 @@ export class IframeOrchestrator {
312310 private createTestIframe ( iframeId : string ) {
313311 const iframe = document . createElement ( 'iframe' )
314312 const src = `/?sessionId=${ getBrowserState ( ) . sessionId } &iframeId=${ iframeId } `
313+ const config = getConfig ( )
314+
315315 iframe . setAttribute ( 'loading' , 'eager' )
316316 iframe . setAttribute ( 'src' , src )
317317 iframe . setAttribute ( 'data-vitest' , 'true' )
318-
319- iframe . style . border = 'none'
320- iframe . style . width = '100%'
321- iframe . style . height = '100%'
322318 iframe . setAttribute ( 'allowfullscreen' , 'true' )
323319 iframe . setAttribute ( 'allow' , 'clipboard-write;' )
324320 iframe . setAttribute ( 'name' , 'vitest-iframe' )
321+
322+ iframe . style . setProperty ( 'border' , 'none' )
323+ iframe . style . setProperty ( 'background-color' , '#fff' )
324+ iframe . style . setProperty ( 'width' , 'var(--viewport-width)' )
325+ iframe . style . setProperty ( 'height' , 'var(--viewport-height)' )
326+
327+ // enable scaling only when using the UI, without UI the iframe fills the page
328+ if ( config . browser . ui ) {
329+ if ( config . browser . name !== 'firefox' ) {
330+ iframe . style . setProperty ( 'transform' , 'scale(min(1, calc(100cqw / var(--viewport-width)), calc(100cqh / var(--viewport-height))))' )
331+ }
332+ else {
333+ // Firefox cannot resolve relative units like `cqw` directly inside `atan2()`
334+ // Storing it in a CSS variable first forces Firefox to resolve `100cqw` to an absolute pixel value
335+ iframe . style . setProperty ( '--container-width' , '100cqw' )
336+ iframe . style . setProperty ( '--container-height' , '100cqh' )
337+ // Firefox does not support typed arithmetic (divisions between typed values): https://bugzilla.mozilla.org/show_bug.cgi?id=1264520
338+ // `tan(atan2(a, b))` produces a unit-less `a / b` ratio:
339+ // - `atan2()` accepts two lengths and returns an `<angle>`
340+ // - `tan()` converts it back to a unit-less `<number>`
341+ iframe . style . setProperty ( 'transform' , 'scale(min(1, tan(atan2(var(--container-width), var(--viewport-width))), tan(atan2(var(--container-height), var(--viewport-height)))))' )
342+ }
343+
344+ iframe . style . setProperty ( 'transform-origin' , 'top left' )
345+ }
346+
325347 return iframe
326348 }
327349
@@ -357,7 +379,7 @@ export class IframeOrchestrator {
357379 )
358380 break
359381 }
360- await setIframeViewport ( iframe , width , height )
382+ await setIframeViewport ( width , height )
361383 channel . postMessage ( { event : 'viewport:done' , iframeId : id } satisfies IframeViewportDoneEvent )
362384 break
363385 }
@@ -447,38 +469,25 @@ function generateFileId(file: string) {
447469}
448470
449471async function setIframeViewport (
450- iframe : HTMLIFrameElement ,
451472 width : number ,
452473 height : number ,
453474) {
454475 const ui = getUiAPI ( )
476+
455477 if ( ui ) {
456478 await ui . setIframeViewport ( width , height )
457479 }
458- else if ( getBrowserState ( ) . provider === 'webdriverio' ) {
459- iframe . parentElement ?. setAttribute ( 'data-scale' , '1' )
480+ else {
481+ document . body . style . setProperty ( '--viewport-width' , `${ width } px` )
482+ document . body . style . setProperty ( '--viewport-height' , `${ height } px` )
483+
460484 await client . rpc . triggerCommand (
461485 getBrowserState ( ) . sessionId ,
462486 '__vitest_viewport' ,
463487 undefined ,
464488 [ { width, height } ] ,
465489 )
466490 }
467- else {
468- const scale = Math . min (
469- 1 ,
470- iframe . parentElement ! . parentElement ! . clientWidth / width ,
471- iframe . parentElement ! . parentElement ! . clientHeight / height ,
472- )
473- iframe . parentElement ! . style . cssText = `
474- width: ${ width } px;
475- height: ${ height } px;
476- transform: scale(${ scale } );
477- transform-origin: left top;
478- `
479- iframe . parentElement ?. setAttribute ( 'data-scale' , String ( scale ) )
480- await new Promise ( r => requestAnimationFrame ( r ) )
481- }
482491}
483492
484493function debug ( ...args : unknown [ ] ) {
0 commit comments