Build workflows
Screenshots
Capture web pages in a headless browser, on desktop and mobile, and compare a preview against production side by side in a gallery that outlives the run.
screenshot.capture loads pages in a headless browser and keeps a PNG of each one in the account's Screenshots gallery. Each call makes a new set with its own page, so the captures are still there after the run is over, like artifacts.
The screenshot tools are available to every account with nothing to set up, under the screenshot. prefix, in tool steps and in an agent's tools list.
Capturing pages
Pass urls for a list of pages. Each one is captured once for each device in devices:
steps:
- key: shots
kind: tool
tool: screenshot.capture
args:
title: Pricing page
urls: [ "https://example.com/pricing" ]
devices: [ desktop, mobile ]
full_page: true
The devices are desktop (1440×900, the default), laptop (1280×800), tablet, mobile and android. You can also give a size such as 1024x768. The phone and tablet presets render at their real pixel density, with a mobile browser's user agent and touch.
Other options:
full_pagecaptures the whole page rather than the first screen. Very long pages are cut off.wait_untilisload(the default),domcontentloadedornetworkidle.delay_msadds a wait of up to 10 seconds after loading, for animations or late content.timeout_msis how long a page may take to load, up to 90 seconds.
Before and after
To compare sites, pass hosts with paths. hosts maps a name to a base URL. Every path is captured on every host, and the gallery shows them side by side in the order you wrote them:
trigger:
kind: github
connection: github
events: [pull_request]
actions: [opened, synchronize]
steps:
- key: shots
kind: tool
tool: screenshot.capture
args:
title: "PR {{ trigger.pull_request.number }}"
hosts:
before: "https://example.com"
after: "https://pr-{{ trigger.pull_request.number }}.preview.example.com"
paths: [ /, /pricing, /signup ]
devices: [ desktop, mobile ]
- key: comment
kind: tool
tool: github.create_comment
args:
repo: "{{ trigger.repository.full_name }}"
number: "{{ trigger.pull_request.number }}"
body: "{{ steps.shots.output.text }}"
The output's text is a markdown table with a row for each page and device, a column for each host, and a link from each cell to that capture in the gallery. Post it as it is to a pull request or a Slack message.
One call takes at most 12 captures, counted as pages × hosts × devices.
Output
{ slug, title, url, captured, failed, created_at, captures: [...], text }
Each entry in captures has page, variant (the host's name), device, url, final_url (after any redirects), status, error, http_status, title, refused_hosts, link (its place in the gallery) and image_url.
If a page fails to load, that capture is recorded with its error and the others carry on. The step fails only when every capture failed. A page that answers with a 404 or a 500 is still captured, and the gallery and the table mark it with its status.
screenshot.list and screenshot.read find sets that earlier runs took.
What a page can reach
Pages load from the public internet in a fresh browser that is signed out. Every request a page makes goes through a gateway that looks up the address itself, including redirects, scripts, images and frames. The gateway refuses private, loopback and link-local addresses, and any port other than 80 and 443.
- A page URL on a private network is refused before the browser starts.
- A redirect to one fails that capture.
- A script or image the page loads from one is left out, and the host is listed under the capture's
refused_hosts.
Viewing them
The gallery and the image links are only available to signed-in members of the account. Links to them work in a comment or a message, but an image embedded in a GitHub comment won't load, because GitHub can't sign in. Anyone in the account can delete a set from its page, and its images are deleted with it.
Over MCP
Your own agents can use the same tools over MCP as screenshot__capture, screenshot__read and screenshot__list. Screenshots taken this way are recorded against the person the token belongs to.