Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ultrafunkamsterdam/nodriver/llms.txt
Use this file to discover all available pages before exploring further.
Nodriver provides powerful screenshot capabilities for capturing full pages, viewports, and individual elements. This is useful for visual testing, documentation, and debugging.
Page screenshots
Basic screenshot
Capture the current viewport:
import nodriver as uc
browser = await uc.start()
tab = await browser.get('https://example.com')
# Save screenshot with auto-generated filename
path = await tab.save_screenshot()
print(f"Screenshot saved to: {path}")
From tab.py:1373-1433:
async def save_screenshot(
self,
filename: Optional[PathLike] = "auto",
format: Optional[str] = "jpeg",
full_page: Optional[bool] = False,
) -> str:
"""
Saves a screenshot of the page.
This is not the same as Element.save_screenshot, which saves a
screenshot of a single element only
:param filename: uses this as the save path
:param format: jpeg or png (defaults to jpeg)
:param full_page: when False (default) it captures the current viewport.
when True, it captures the entire page
:return: the path/filename of saved screenshot
"""
import datetime
import urllib.parse
await self.sleep() # update the target's url
path = None
if format.lower() in ["jpg", "jpeg"]:
ext = ".jpg"
format = "jpeg"
elif format.lower() in ["png"]:
ext = ".png"
format = "png"
if not filename or filename == "auto":
parsed = urllib.parse.urlparse(self.target.url)
parts = parsed.path.split("/")
last_part = parts[-1]
last_part = last_part.rsplit("?", 1)[0]
dt_str = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
candidate = f"{parsed.hostname}__{last_part}_{dt_str}"
path = pathlib.Path(candidate + ext)
else:
path = pathlib.Path(filename)
path.parent.mkdir(parents=True, exist_ok=True)
data = await self.send(
cdp.page.capture_screenshot(
format_=format, capture_beyond_viewport=full_page
)
)
Custom filename
# Specify custom filename
await tab.save_screenshot('my_screenshot.jpg')
# Save to specific directory
await tab.save_screenshot('screenshots/homepage.png')
# Full path
from pathlib import Path
await tab.save_screenshot(Path('/tmp/test.jpg'))
Full page screenshot
Capture the entire page, including content below the fold:
# Capture entire page
await tab.save_screenshot(
filename='full_page.jpg',
full_page=True
)
Full page screenshots may take longer for very long pages and consume more memory.
# High quality PNG (larger file size)
await tab.save_screenshot(
filename='screenshot.png',
format='png'
)
# Compressed JPEG (smaller file size)
await tab.save_screenshot(
filename='screenshot.jpg',
format='jpeg' # default
)
Use PNG for screenshots that require transparency or lossless quality. Use JPEG for general purposes to save disk space.
Element screenshots
Capture specific element
Take a screenshot of a single element:
# Find and screenshot element
element = await tab.select('.product-card')
await element.save_screenshot('product.jpg')
# With custom settings
await element.save_screenshot(
filename='element.png',
format='png',
scale=2 # 2x resolution
)
From element.py:843-911:
async def save_screenshot(
self,
filename: typing.Optional[PathLike] = "auto",
format: typing.Optional[str] = "jpeg",
scale: typing.Optional[typing.Union[int, float]] = 1,
):
"""
Saves a screenshot of this element (only)
This is not the same as Tab.save_screenshot, which saves a
"regular" screenshot
When the element is hidden, or has no size, or is otherwise not
capturable, a RuntimeError is raised
:param filename: uses this as the save path
:param format: jpeg or png (defaults to jpeg)
:param scale: the scale of the screenshot, eg: 1 = size as is,
2 = double, 0.5 is half
:return: the path/filename of saved screenshot
"""
import base64
import datetime
import urllib.parse
pos = await self.get_position()
if not pos:
raise RuntimeError(
"could not determine position of element. probably because "
"it's not in view, or hidden"
)
viewport = pos.to_viewport(scale)
path = None
await self.tab.sleep()
if not filename or filename == "auto":
parsed = urllib.parse.urlparse(self.tab.target.url)
parts = parsed.path.split("/")
last_part = parts[-1]
last_part = last_part.rsplit("?", 1)[0]
dt_str = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
candidate = f"{parsed.hostname}__{last_part}_{dt_str}"
ext = ""
if format.lower() in ["jpg", "jpeg"]:
ext = ".jpg"
format = "jpeg"
elif format.lower() in ["png"]:
ext = ".png"
format = "png"
path = pathlib.Path(candidate + ext)
else:
path = pathlib.Path(filename)
path.parent.mkdir(parents=True, exist_ok=True)
data = await self._tab.send(
cdp.page.capture_screenshot(
format, clip=viewport, capture_beyond_viewport=True
)
)
if not data:
from .connection import ProtocolException
raise ProtocolException(
"could not take screenshot. most possible cause is the page "
"has not finished loading yet."
)
data_bytes = base64.b64decode(data)
if not path:
raise RuntimeError("invalid filename or path: '%s'" % filename)
path.write_bytes(data_bytes)
return str(path)
High-resolution element screenshots
# 2x resolution (retina)
element = await tab.select('.logo')
await element.save_screenshot(
filename='logo_2x.png',
format='png',
scale=2
)
# 0.5x resolution (thumbnail)
await element.save_screenshot(
filename='logo_thumb.jpg',
scale=0.5
)
Screenshot multiple elements
# Screenshot all product cards
products = await tab.select_all('.product')
for i, product in enumerate(products):
await product.save_screenshot(f'product_{i}.jpg')
Auto-generated filenames
When you use filename="auto" or omit the filename parameter, nodriver generates descriptive filenames:
# Auto-generated filename
# Format: {hostname}__{path}_{timestamp}.{ext}
await tab.save_screenshot() # e.g., "example.com__index_2024-03-01_14-30-45.jpg"
The filename includes:
- Hostname from the URL
- Last part of the path
- Timestamp (YYYY-MM-DD_HH-MM-SS)
- File extension
Handling screenshot errors
from nodriver.core.connection import ProtocolException
try:
await tab.save_screenshot('screenshot.jpg')
except ProtocolException as e:
print(f"Screenshot failed: {e}")
# Page might not be fully loaded
await tab.wait(2)
await tab.save_screenshot('screenshot.jpg')
except RuntimeError as e:
print(f"Invalid filename or path: {e}")
Screenshots may fail if the page hasn’t finished loading. Always wait for the page to be ready before taking screenshots.
Real-world example from imgur demo
From imgur_upload_image.py:
import nodriver as uc
from pathlib import Path
async def main():
browser = await uc.start()
tab = await browser.get('https://imgur.com')
# Create screenshot of another page
save_path = Path('screenshot.jpg').resolve()
temp_tab = await browser.get(
'https://github.com/ultrafunkamsterdam/undetected-chromedriver',
new_tab=True
)
# Wait for page to load
await temp_tab
# Save screenshot
await temp_tab.save_screenshot(save_path)
print(f"Screenshot saved to {save_path}")
# Close temp tab
await temp_tab.close()
# Now use the screenshot file...
file_input = await tab.select('input[type=file]')
await file_input.send_file(save_path)
if __name__ == '__main__':
uc.loop().run_until_complete(main())
Screenshot workflow for visual testing
Here’s a complete example for visual regression testing:
import nodriver as uc
from pathlib import Path
import hashlib
async def visual_regression_test():
browser = await uc.start()
screenshots_dir = Path('screenshots')
screenshots_dir.mkdir(exist_ok=True)
# Pages to test
pages = [
'https://example.com',
'https://example.com/about',
'https://example.com/contact'
]
for url in pages:
tab = await browser.get(url)
await tab.wait(2) # Let page fully load
# Generate filename from URL
page_name = url.split('/')[-1] or 'home'
# Full page screenshot
screenshot_path = screenshots_dir / f'{page_name}_full.png'
await tab.save_screenshot(
filename=str(screenshot_path),
format='png',
full_page=True
)
# Calculate hash for comparison
with open(screenshot_path, 'rb') as f:
file_hash = hashlib.md5(f.read()).hexdigest()
print(f"{page_name}: {file_hash}")
# Screenshot key elements
header = await tab.select('header')
if header:
await header.save_screenshot(
filename=str(screenshots_dir / f'{page_name}_header.png')
)
footer = await tab.select('footer')
if footer:
await footer.save_screenshot(
filename=str(screenshots_dir / f'{page_name}_footer.png')
)
print(f"Screenshots saved to {screenshots_dir}")
browser.stop()
if __name__ == '__main__':
uc.loop().run_until_complete(visual_regression_test())
Screenshot comparison workflow
from PIL import Image
import numpy as np
async def compare_screenshots():
browser = await uc.start()
tab = await browser.get('https://example.com')
# Take baseline
await tab.save_screenshot('baseline.png')
# Make some changes
await tab.evaluate('document.body.style.backgroundColor = "red"')
# Take comparison
await tab.save_screenshot('comparison.png')
# Compare using PIL
img1 = Image.open('baseline.png')
img2 = Image.open('comparison.png')
arr1 = np.array(img1)
arr2 = np.array(img2)
diff = np.abs(arr1 - arr2)
is_different = np.any(diff > 0)
print(f"Images are different: {is_different}")
browser.stop()
Using CDP for advanced screenshots
For more control, use CDP directly:
from nodriver import cdp
import base64
tab = await browser.get('https://example.com')
# Screenshot with custom viewport
data = await tab.send(
cdp.page.capture_screenshot(
format_='png',
quality=100,
clip=cdp.page.Viewport(
x=0,
y=0,
width=1920,
height=1080,
scale=2
),
capture_beyond_viewport=True,
from_surface=True
)
)
# Save manually
with open('custom_screenshot.png', 'wb') as f:
f.write(base64.b64decode(data))
Best practices
Always wait for the page to be fully loaded:
await tab.get('https://example.com')
await tab.wait(1) # Let JavaScript execute
await tab.save_screenshot()
For visual testing and comparisons, use PNG format to avoid JPEG compression artifacts.
Use a consistent directory structure:
screenshots_dir = Path('screenshots') / datetime.now().strftime('%Y-%m-%d')
screenshots_dir.mkdir(parents=True, exist_ok=True)
Scroll elements into view before taking screenshots:
element = await tab.select('.footer')
await element.scroll_into_view()
await tab.wait(0.5) # Let scroll animation complete
await element.save_screenshot('footer.jpg')
Implement retention policies for screenshot files to avoid filling up disk space.