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_page captures the whole page rather than the first screen. Very long pages are cut off.
  • wait_until is load (the default), domcontentloaded or networkidle.
  • delay_ms adds a wait of up to 10 seconds after loading, for animations or late content.
  • timeout_ms is 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.