---
title: "Beyond the Viewport: Capturing Full-Size Screenshots with Rust and Chrome"
description: "Tired of partial webpage screenshots? Discover how to capture entire, full-size web pages from header to footer using Rust and the powerful Chrome DevTools Protocol (CDP). This in-depth guide, leveraging the `thirtyfour` crate, reveals the secret to bypassing standard WebDriver viewport limitations with `Page.captureScreenshot`'s `captureBeyondViewport` parameter, making it perfect for visual regression testing, generating complete website previews, or archiving web content without missing a single pixel."
slug: "cdp-full-page-screenshots"
created: 2026-02-01T19:36:00Z
updated: 2026-02-01T19:36:00Z
tags:
  - "rust"
  - "cdp"
  - "devtools"
  - "chrome"
hero_image: "/images/cdp-full-page-screenshots-hero.jpg"
ai_assisted: true
---

![](/images/cdp-full-page-screenshots-hero.jpg)

If you’ve ever tried to automate website screenshots using Selenium or WebDriver, you’ve likely hit the "cutoff" wall. By default, most drivers only capture what’s currently visible in the browser window. If your page is 5,000 pixels long, but your window is only 1,080, you’re missing the best part of the story.

In this post, we’re going to look at how to use the **Chrome DevTools Protocol (CDP)** via the [thirtyfour](https://crates.io/crates/thirtyfour) crate to capture every single pixel of a webpage, from header to footer.

## Why standard screenshots fail
Standard WebDriver commands are designed for cross-browser compatibility. Because not every browser handles "full-page" rendering the same way, the lowest common denominator is the **Viewport**. 

To get the full page in Chrome, we need to go "under the hood" and talk to Chrome directly using CDP.

## The Secret Sauce: `Page.captureScreenshot`

Chrome provides a specific command called `Page.captureScreenshot`. The real hero here is a parameter called `captureBeyondViewport`. When set to `true`, Chrome ignores the window constraints and renders the full height of the document.

### The Implementation

First, we define our data structures to match the Chrome DevTools schema. Note the `#[serde(rename_all = "camelCase")]` attribute. This is vital because Rust's `snake_case` will be rejected by Chrome's API.

```rust
use std::error::Error;
use base64::{Engine, prelude::BASE64_STANDARD};
use serde::{Deserialize, Serialize};
use thirtyfour::extensions::cdp::ChromeDevTools;

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScreenshotParams {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub quality: Option<u8>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub clip: Option<Viewport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub from_surface: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub capture_beyond_viewport: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub optimize_for_speed: Option<bool>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Viewport {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
    pub scale: u32,
}

pub const FULL_SIZE_SCREENSHOT: ScreenshotParams = ScreenshotParams {
    capture_beyond_viewport: Some(true),
    from_surface: Some(true),
    clip: None,
    format: None,
    optimize_for_speed: None,
    quality: None,
};
```

### Executing the Command
Once our structs are ready, we execute the command. Chrome returns the image as a **Base64 encoded string**, so we need to decode that into a raw byte vector (`Vec<u8>`) so we can save it to disk or process it.

```rust
pub async fn screenshot(devtools: &ChromeDevTools) -> Result<Vec<u8>, Box<dyn Error>> {
    // 1. Serialize our parameters to JSON
    let params = serde_json::to_value(&FULL_SIZE_SCREENSHOT).unwrap();

    // 2. Call the CDP method
    let response = devtools
        .execute_cdp_with_params("Page.captureScreenshot", params)
        .await?;

    // 3. Extract the Base64 data from the response
    let base_64_png = response.get("data")
        .and_then(|d| d.as_str())
        .ok_or("Failed to find image data in response")?;

    // 4. Decode it into raw PNG bytes
    let png = BASE64_STANDARD.decode(base_64_png)?;
    Ok(png)
}
```

### How to use it in your project
Integrating this into your `thirtyfour` workflow is straightforward. Simply wrap your driver handle in a `ChromeDevTools` instance:
```rust
// ... setup your thirtyfour WebDriver ...
let devtools = ChromeDevTools::new(driver.handle());

// Navigate to a long page
driver.goto("[https://www.rust-lang.org](https://www.rust-lang.org)").await?;

// Capture everything!
let image_bytes = screenshot(&devtools).await?;
std::fs::write("rust_homepage.png", image_bytes)?;
```

## Summary
By reaching past the standard WebDriver API and using CDP, we gain much finer control over how Chrome behaves. This approach is perfect for:

 - Visual regression testing.
 - Generating website previews.
 - Archiving landing pages.

Just a heads-up: capturing extremely long pages (like a social media feed) can result in massive PNG files, so keep an eye on your memory usage!

**Happy Hacking!**