@@ -326,6 +326,8 @@ func (s *BrowserService) Render(ctx context.Context, url string, optionFuncs ...
326326 scrollForElements (opts .timeBetweenScrolls ),
327327 waitForDuration (time .Second ),
328328 waitForReady (browserCtx , opts .timeout ),
329+ resizeViewportForFullHeight (opts ), // Resize after all content is loaded and ready
330+ waitForReady (browserCtx , opts .timeout ), // Wait for readiness again after viewport resize
329331 opts .printer .action (fileChan , opts ),
330332 }
331333 span .AddEvent ("actions created" )
@@ -728,7 +730,7 @@ type pngPrinter struct {
728730 fullHeight bool
729731}
730732
731- func (p * pngPrinter ) action (dst chan []byte , _ * renderingOptions ) chromedp.Action {
733+ func (p * pngPrinter ) action (dst chan []byte , opts * renderingOptions ) chromedp.Action {
732734 return chromedp .ActionFunc (func (ctx context.Context ) error {
733735 tracer := tracer (ctx )
734736 ctx , span := tracer .Start (ctx , "pngPrinter.action" ,
@@ -739,7 +741,10 @@ func (p *pngPrinter) action(dst chan []byte, _ *renderingOptions) chromedp.Actio
739741
740742 output , err := page .CaptureScreenshot ().
741743 WithFormat (page .CaptureScreenshotFormatPng ).
742- WithCaptureBeyondViewport (p .fullHeight ).
744+ // We don't want to use this option: it doesn't take a full window screenshot,
745+ // rather it takes a screenshot including content that bleeds outside the viewport (e.g. something 110vh tall).
746+ // Instead, we change the viewport height to match the content height.
747+ WithCaptureBeyondViewport (false ).
743748 Do (ctx )
744749 if err != nil {
745750 span .SetStatus (codes .Error , err .Error ())
@@ -826,6 +831,54 @@ func setCookies(cookies []*network.SetCookieParams) chromedp.Action {
826831 })
827832}
828833
834+ func resizeViewportForFullHeight (opts * renderingOptions ) chromedp.Action {
835+ return chromedp .ActionFunc (func (ctx context.Context ) error {
836+ // Only resize for PNG printers with fullHeight enabled
837+ pngPrinter , ok := opts .printer .(* pngPrinter )
838+ if ! ok || ! pngPrinter .fullHeight {
839+ return nil // Skip for non-PNG or non-fullHeight screenshots
840+ }
841+
842+ tracer := tracer (ctx )
843+ ctx , span := tracer .Start (ctx , "resizeViewportForFullHeight" )
844+ defer span .End ()
845+
846+ var scrollHeight int
847+ err := chromedp .Evaluate (`document.body.scrollHeight` , & scrollHeight ).Do (ctx )
848+ if err != nil {
849+ span .SetStatus (codes .Error , "failed to get scroll height: " + err .Error ())
850+ return fmt .Errorf ("failed to get scroll height: %w" , err )
851+ }
852+
853+ // Only resize if the page is actually taller than the current viewport
854+ if scrollHeight > opts .viewportHeight {
855+ span .AddEvent ("resizing viewport for full height capture" ,
856+ trace .WithAttributes (
857+ attribute .Int ("originalHeight" , opts .viewportHeight ),
858+ attribute .Int ("newHeight" , scrollHeight ),
859+ ))
860+
861+ // Determine orientation from options
862+ orientation := chromedp .EmulatePortrait
863+ if opts .landscape {
864+ orientation = chromedp .EmulateLandscape
865+ }
866+
867+ err = chromedp .EmulateViewport (int64 (opts .viewportWidth ), int64 (scrollHeight ), orientation ).Do (ctx )
868+ if err != nil {
869+ span .SetStatus (codes .Error , "failed to resize viewport: " + err .Error ())
870+ return fmt .Errorf ("failed to resize viewport for full height: %w" , err )
871+ }
872+
873+ span .SetStatus (codes .Ok , "viewport resized successfully" )
874+ } else {
875+ span .AddEvent ("no viewport resize needed" , trace .WithAttributes (attribute .Int ("pageHeight" , scrollHeight )))
876+ }
877+
878+ return nil
879+ })
880+ }
881+
829882func scrollForElements (timeBetweenScrolls time.Duration ) chromedp.Action {
830883 return chromedp .ActionFunc (func (ctx context.Context ) error {
831884 tracer := tracer (ctx )
0 commit comments