1414# See the License for the specific language governing permissions and
1515# limitations under the License.
1616import os
17+ import base64
1718from typing import Optional , Union
18- from base64 import b64decode
1919
2020from robot .utils import get_link_path
2121from selenium .webdriver .remote .webelement import WebElement
@@ -204,9 +204,22 @@ def _capture_element_screen_to_log(self, element, return_val):
204204 def capture_fullpage_screenshot (self , filename : str = DEFAULT_FILENAME_FULLPAGE ) -> str :
205205 """Takes a screenshot of the entire page, including parts not visible in viewport.
206206
207- This keyword is useful when you need to capture a long page that requires
208- scrolling. It works by temporarily resizing the browser window to show
209- the full page height, taking the screenshot, then restoring the original size.
207+ This keyword captures the full height and width of a web page, even if it extends
208+ beyond the current viewport. The implementation automatically selects the best
209+ available method based on the browser:
210+
211+ *Screenshot Methods (in order of preference):*
212+
213+ 1. **Chrome DevTools Protocol (CDP)**: Used for Chrome, Edge, and Chromium browsers.
214+ Works in both headless and non-headless mode without screen size limitations.
215+
216+ 2. **Firefox Native Method**: Used for Firefox browsers. Captures full page
217+ using the browser's built-in capability.
218+
219+ 3. **Window Resize Method**: Fallback for other browsers. Temporarily resizes
220+ the browser window to match page dimensions. In non-headless mode, this may
221+ be limited by physical screen size, and a warning will be logged if the full
222+ page cannot be captured.
210223
211224 ``filename`` argument specifies where to save the screenshot file.
212225 The directory can be set with `Set Screenshot Directory` keyword or
@@ -241,7 +254,27 @@ def capture_fullpage_screenshot(self, filename: str = DEFAULT_FILENAME_FULLPAGE)
241254 return self ._capture_fullpage_screenshot_to_file (filename )
242255
243256 def _capture_fullpage_screenshot_to_file (self , filename ):
244- """Save fullpage screenshot to file."""
257+ """Save fullpage screenshot to file using best available method."""
258+ # Try CDP first (Chrome/Edge/Chromium) - works in both headless and non-headless
259+ if self ._supports_cdp ():
260+ result = self ._capture_fullpage_via_cdp (filename )
261+ if result :
262+ self .debug ("Full-page screenshot captured using Chrome DevTools Protocol" )
263+ return result
264+
265+ # Try Firefox native method
266+ if self ._supports_native_fullpage ():
267+ result = self ._capture_fullpage_via_firefox (filename )
268+ if result :
269+ self .debug ("Full-page screenshot captured using Firefox native method" )
270+ return result
271+
272+ # Fallback to resize method (works in headless mode for all browsers)
273+ self .debug ("Using window resize method for full-page screenshot" )
274+ return self ._capture_fullpage_via_resize (filename )
275+
276+ def _capture_fullpage_via_resize (self , filename ):
277+ """Fallback method: Save fullpage screenshot by resizing window."""
245278 # Remember current window size so we can restore it later
246279 original_size = self .driver .get_window_size ()
247280
@@ -256,6 +289,17 @@ def _capture_fullpage_screenshot_to_file(self, filename):
256289 import time
257290 time .sleep (0.5 )
258291
292+ # Verify the window actually resized to requested dimensions
293+ # In non-headless mode, browsers may be limited by screen size
294+ actual_size = self .driver .get_window_size ()
295+ if actual_size ['height' ] < full_height * 0.95 : # Allow 5% tolerance for browser chrome
296+ self .warn (
297+ f"Browser window could not be resized to full page height. "
298+ f"Requested: { full_height } px, Actual: { actual_size ['height' ]} px. "
299+ f"Screenshot may not capture the complete page. "
300+ f"Consider running in headless mode for better full-page screenshot support."
301+ )
302+
259303 # Now take the screenshot
260304 path = self ._get_screenshot_path (filename )
261305 self ._create_directory (path )
@@ -269,7 +313,34 @@ def _capture_fullpage_screenshot_to_file(self, filename):
269313 self .driver .set_window_size (original_size ['width' ], original_size ['height' ])
270314
271315 def _capture_fullpage_screen_to_log (self , return_val ):
272- """Get fullpage screenshot as base64 or embed it."""
316+ """Get fullpage screenshot as base64 or embed it using best available method."""
317+ screenshot_as_base64 = None
318+
319+ # Try CDP first (Chrome/Edge/Chromium) - works in both headless and non-headless
320+ if self ._supports_cdp ():
321+ screenshot_as_base64 = self ._capture_fullpage_via_cdp_base64 ()
322+ if screenshot_as_base64 :
323+ self .debug ("Full-page screenshot captured using Chrome DevTools Protocol" )
324+
325+ # Try Firefox native method
326+ if not screenshot_as_base64 and self ._supports_native_fullpage ():
327+ screenshot_as_base64 = self ._capture_fullpage_via_firefox_base64 ()
328+ if screenshot_as_base64 :
329+ self .debug ("Full-page screenshot captured using Firefox native method" )
330+
331+ # Fallback to resize method
332+ if not screenshot_as_base64 :
333+ self .debug ("Using window resize method for full-page screenshot" )
334+ screenshot_as_base64 = self ._capture_fullpage_via_resize_base64 ()
335+
336+ # Embed to log
337+ base64_str = self ._embed_to_log_as_base64 (screenshot_as_base64 , 800 )
338+ if return_val == BASE64 :
339+ return base64_str
340+ return EMBED
341+
342+ def _capture_fullpage_via_resize_base64 (self ):
343+ """Fallback method: Get fullpage screenshot as base64 by resizing window."""
273344 # Remember current window size so we can restore it later
274345 original_size = self .driver .get_window_size ()
275346
@@ -284,17 +355,127 @@ def _capture_fullpage_screen_to_log(self, return_val):
284355 import time
285356 time .sleep (0.5 )
286357
358+ # Verify the window actually resized to requested dimensions
359+ # In non-headless mode, browsers may be limited by screen size
360+ actual_size = self .driver .get_window_size ()
361+ if actual_size ['height' ] < full_height * 0.95 : # Allow 5% tolerance for browser chrome
362+ self .warn (
363+ f"Browser window could not be resized to full page height. "
364+ f"Requested: { full_height } px, Actual: { actual_size ['height' ]} px. "
365+ f"Screenshot may not capture the complete page. "
366+ f"Consider running in headless mode for better full-page screenshot support."
367+ )
368+
287369 # Take the screenshot as base64
288370 screenshot_as_base64 = self .driver .get_screenshot_as_base64 ()
289- base64_str = self ._embed_to_log_as_base64 (screenshot_as_base64 , 800 )
290- if return_val == BASE64 :
291- return base64_str
292- return EMBED
371+ return screenshot_as_base64
293372
294373 finally :
295374 # Put the window back to its original size
296375 self .driver .set_window_size (original_size ['width' ], original_size ['height' ])
297376
377+ def _get_browser_name (self ):
378+ """Get the name of the current browser."""
379+ try :
380+ return self .driver .capabilities .get ('browserName' , '' ).lower ()
381+ except :
382+ return ''
383+
384+ def _supports_cdp (self ):
385+ """Check if browser supports Chrome DevTools Protocol."""
386+ browser_name = self ._get_browser_name ()
387+ return browser_name in ['chrome' , 'chromium' , 'msedge' , 'edge' , 'MicrosoftEdge' ]
388+
389+ def _supports_native_fullpage (self ):
390+ """Check if browser supports native full-page screenshots."""
391+ browser_name = self ._get_browser_name ()
392+ return browser_name == 'firefox'
393+
394+ def _capture_fullpage_via_cdp (self , filename ):
395+ """Capture full-page screenshot using Chrome DevTools Protocol."""
396+ try :
397+ # Get page dimensions
398+ metrics = self .driver .execute_cdp_cmd ('Page.getLayoutMetrics' , {})
399+ width = int (metrics ['contentSize' ]['width' ])
400+ height = int (metrics ['contentSize' ]['height' ])
401+
402+ # Capture screenshot with full page dimensions
403+ screenshot = self .driver .execute_cdp_cmd ('Page.captureScreenshot' , {
404+ 'clip' : {
405+ 'width' : width ,
406+ 'height' : height ,
407+ 'x' : 0 ,
408+ 'y' : 0 ,
409+ 'scale' : 1
410+ },
411+ 'captureBeyondViewport' : True
412+ })
413+
414+ # Save the screenshot
415+ path = self ._get_screenshot_path (filename )
416+ self ._create_directory (path )
417+
418+ with open (path , 'wb' ) as f :
419+ f .write (base64 .b64decode (screenshot ['data' ]))
420+
421+ self ._embed_to_log_as_file (path , 800 )
422+ return path
423+ except Exception as e :
424+ self .debug (f"CDP full-page screenshot failed: { e } . Falling back to resize method." )
425+ return None
426+
427+ def _capture_fullpage_via_cdp_base64 (self ):
428+ """Capture full-page screenshot using CDP and return as base64."""
429+ try :
430+ # Get page dimensions
431+ metrics = self .driver .execute_cdp_cmd ('Page.getLayoutMetrics' , {})
432+ width = int (metrics ['contentSize' ]['width' ])
433+ height = int (metrics ['contentSize' ]['height' ])
434+
435+ # Capture screenshot with full page dimensions
436+ screenshot = self .driver .execute_cdp_cmd ('Page.captureScreenshot' , {
437+ 'clip' : {
438+ 'width' : width ,
439+ 'height' : height ,
440+ 'x' : 0 ,
441+ 'y' : 0 ,
442+ 'scale' : 1
443+ },
444+ 'captureBeyondViewport' : True
445+ })
446+
447+ return screenshot ['data' ]
448+ except Exception as e :
449+ self .debug (f"CDP full-page screenshot failed: { e } . Falling back to resize method." )
450+ return None
451+
452+ def _capture_fullpage_via_firefox (self , filename ):
453+ """Capture full-page screenshot using Firefox native method."""
454+ try :
455+ path = self ._get_screenshot_path (filename )
456+ self ._create_directory (path )
457+
458+ # Firefox has a native full-page screenshot method
459+ screenshot_binary = self .driver .get_full_page_screenshot_as_png ()
460+
461+ with open (path , 'wb' ) as f :
462+ f .write (screenshot_binary )
463+
464+ self ._embed_to_log_as_file (path , 800 )
465+ return path
466+ except Exception as e :
467+ self .debug (f"Firefox native full-page screenshot failed: { e } . Falling back to resize method." )
468+ return None
469+
470+ def _capture_fullpage_via_firefox_base64 (self ):
471+ """Capture full-page screenshot using Firefox and return as base64."""
472+ try :
473+ screenshot_binary = self .driver .get_full_page_screenshot_as_png ()
474+ return base64 .b64encode (screenshot_binary ).decode ('utf-8' )
475+ except Exception as e :
476+ self .debug (f"Firefox native full-page screenshot failed: { e } . Falling back to resize method." )
477+ return None
478+
298479 @property
299480 def _screenshot_root_directory (self ):
300481 return self .ctx .screenshot_root_directory
@@ -445,7 +626,7 @@ def _print_page_as_pdf_to_file(self, filename, options):
445626 return path
446627
447628 def _save_pdf_to_file (self , pdfbase64 , path ):
448- pdfdata = b64decode (pdfbase64 )
629+ pdfdata = base64 . b64decode (pdfbase64 )
449630 with open (path , mode = 'wb' ) as pdf :
450631 pdf .write (pdfdata )
451632
0 commit comments