Go chromedp Screenshot Fails with Context Deadline Exceeded
Find which chromedp action timed out—browser startup, navigation, element lookup, or capture—and fix the cause before changing screenshot code.
context deadline exceeded means the work did not finish before its context deadline. It does not identify which chromedp action failed. First isolate browser startup, navigation, page readiness or selector lookup, and screenshot capture; then fix the stage that returns the error.
Chromedp has separate actions for a viewport screenshot and an element screenshot. An element screenshot must find the element and obtain its geometry before capture, so a timeout can come from readiness or lookup rather than image encoding. [chromedp package documentation] [screenshot implementation]
1. Identify the action that times out
Split the workflow into named actions and wrap each error with its stage and target URL. Log elapsed time as well. This makes the first failing action visible instead of attributing every deadline to the screenshot call.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/chromedp/chromedp"
)
func runStage(ctx context.Context, name string, actions ...chromedp.Action) error {
start := time.Now()
err := chromedp.Run(ctx, actions...)
if err != nil {
return fmt.Errorf("%s after %s: %w", name, time.Since(start), err)
}
log.Printf("%s completed in %s", name, time.Since(start))
return nil
}
func main() {
// Choose a deadline suitable for your browser startup, target page, and
// execution environment. This example's duration is illustrative only.
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
ctx, cancelTimeout := context.WithTimeout(ctx, 45*time.Second)
defer cancelTimeout()
const targetURL = "https://example.com"
var png []byte
if err := runStage(ctx, "navigate to "+targetURL,
chromedp.Navigate(targetURL),
); err != nil {
log.Fatal(err)
}
if err := runStage(ctx, "wait for page heading",
chromedp.WaitVisible("h1", chromedp.ByQuery),
); err != nil {
log.Fatal(err)
}
if err := runStage(ctx, "capture viewport",
chromedp.CaptureScreenshot(&png),
); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("shot.png", png, 0644); err != nil {
log.Fatal(err)
}
}
Add "os" to the imports for os.WriteFile. The snippet uses a single context deadline across the stages, so startup, navigation, waiting, and capture all consume the same budget. For finer diagnosis, use separately bounded contexts or record each stage’s start and remaining deadline; avoid accidentally granting each step a new full budget if the whole request must have a strict overall limit.
The official chromedp example performs navigation and screenshot actions as distinct steps. Its example does not prescribe a fixed timeout; choose one based on your application and environment. [chromedp examples and documentation]
2. Separate the screenshot types
| Action | What it captures | What to check |
|---|---|---|
CaptureScreenshot |
Current browser viewport | Navigation completed and the desired state is visible. |
Screenshot |
The first element matching a selector | The selector matches, the element exists when queried, and geometry can be read. |
FullScreenshot |
Content beyond the viewport | Page size, lazy-loaded content, and resulting capture work and output size. |
These operations are not interchangeable. In particular, a selector-based screenshot adds a selector query and geometry lookup before capture. Test the viewport action against the same page state: if viewport capture succeeds while element capture does not, inspect the selector and element readiness. Full-page capture does more work and can produce a larger image, but the cited sources do not establish it as a general cause of deadline errors. [API documentation] [current screenshot source]
3. Use a readiness condition that matches the task
A successful navigation action does not necessarily mean that the exact content you need is ready. If the screenshot requires a known element, wait for that element rather than waiting for unrelated background activity to stop. Conversely, do not treat a selector wait as proof that every image or dynamic widget has finished loading.
// Viewport capture after a useful page condition.
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.WaitVisible("main article", chromedp.ByQuery),
chromedp.CaptureScreenshot(&png),
)
if err != nil {
return fmt.Errorf("navigate, wait, or capture: %w", err)
}
For an element screenshot, the selector must match an element. Confirm that the element is present and visible within the available time. If the selector is wrong or the element never appears, correct the selector or the page condition; increasing the deadline alone will not make a nonexistent element match.
4. Troubleshoot by the first failing stage
| First failing stage | Likely area to investigate | Next step |
|---|---|---|
| Browser startup | Browser process launch, local environment, or remote endpoint connectivity. | Check whether Chrome/Chromium starts and, for remote Chrome, whether the configured endpoint is reachable. Record the startup error and environment details. |
| Navigation | Target response, redirects, network behavior, or a navigation condition that does not match the page. | Log the target URL and navigation duration. Verify the page independently and wait for the page state the capture actually needs. |
| Selector or readiness wait | Selector mismatch, element not yet present or visible, or a page that never reaches the expected condition. | Check the selector against the rendered page and use a relevant condition. Try viewport capture to separate lookup from capture. |
| Viewport capture | Capture protocol action, browser state, or environment behavior. | Try a minimal local page with the same browser and context, then compare with the target page and deployment. |
| Element capture | Selector query or node geometry step before the capture command. | Verify the first matching element exists and is measurable; compare with viewport capture on the same page state. |
| Any stage, intermittently | Parent context canceled early, overall deadline consumed by previous stages, or environment-specific slowness. | Trace context ownership and remaining time. Ensure the request or job does not cancel its parent while chromedp is running. |
Collect a useful failure report
When the failing stage is still unclear, include:
- Go version and chromedp module version.
- Chrome or Chromium version, operating system, and architecture.
- Whether Chrome runs locally, in a container, or remotely; include relevant host or container details.
- Where the context is created, where it is canceled, and the deadline in effect.
- The first action that returns the error, its elapsed time, target URL, and whether the operation is a viewport, element, or full-page capture.
Older issue reports describe deadline or screenshot symptoms in specific historical setups, including chromedp v0.7.4 with Chrome 90 and Go 1.16, and earlier Chrome versions. They are clues that environment details matter, not evidence of a current general chromedp defect or a universal fix. Reproduce with the versions actually in use. [issue #904] [issue #585] [issue #801]
5. Deadline, reliability, and output considerations
- Choose a deadline for the whole workflow. Browser startup, navigation, waits, and capture share time when they use the same context. There is no universal timeout in the cited example; account for the work and environment rather than copying an arbitrary duration.
- Respect context ownership. A canceled parent context cancels work beneath it. Keep the request or job context alive for the operation and defer cancellation at the appropriate owner.
- Reduce unnecessary waiting. Wait for a task-relevant state. An entire page becoming network-idle may be unnecessary when the capture only needs a known element.
- Compare capture scope. Viewport capture is a useful baseline. Element capture requires lookup and geometry; full-page capture can involve more content and larger output.
- Control output deliberately. The documented viewport call returns PNG bytes. Full-page capture has separate behavior and format considerations; inspect the current API before changing quality or format as a timeout remedy.
- Do not infer cost or performance from the error. The research sources provide no general chromedp timeout rate, recommended duration, or benchmark. Measure startup, navigation, waits, and capture in your own deployment.
Or skip the browser setup
If you need a screenshot endpoint rather than managing a Chrome process and chromedp contexts, ScreenshotNeo is a website screenshot API and MCP server. One GET request takes a URL and returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie banners, newsletter popups, and chat widgets are removed before capture, and each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed; response headers report the page verdict and billing status. Its MCP server provides screenshot tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does this error mean the screenshot format is wrong?
No. The error only says the operation exceeded its context deadline. Identify the first failing action before changing output format.
Why can viewport capture work while an element screenshot times out?
Element capture adds a selector query and geometry lookup. Check that the element exists and is ready to measure.
Should I always wait for network idle?
No universal readiness condition fits every page. Wait for the content or state required by the capture, and avoid waiting on unrelated activity.
Is chromedp currently broken?
The cited older issues do not establish a current general defect. Reproduce the failure with your current Go, chromedp, browser, and deployment versions before drawing that conclusion.


