# Capture an element inside an iframe

An iframe is a separate document, and a query does not cross into one on its own. This guide names the iframe so everything after it resolves inside — a payment field, an embedded checkout, a block editor's canvas.

Requires shotlist 0.4.0 or later.

## Clip a region inside a frame

`frame` takes a query of its own, which finds the `<iframe>` element. Everything written beside it then resolves in that iframe's document:

screenshots/recipes/order-total.yaml

```
name: order-total

clip:
  frame: { css: 'iframe[title="Checkout"]' }
  css: '.order-total'
  pad: 16
```

Name the iframe by something it carries — a `title`, a `name`, an `id`. Those are attributes on the element in the page you opened, so an ordinary query finds them.

## Mark something inside a frame

A mark takes `frame` the same way, and it does not have to be the region you clipped. Here the capture is a panel in the page and the callout points at a field inside an embedded payment form:

screenshots/recipes/checkout.yaml

```
name: checkout

clip: { css: '.checkout-panel' }

marks:
  card:
    frame: { css: 'iframe[title="Payment"]' }
    label: Card number

callouts:
  - { mark: card, text: Your card is never sent to us, place: right }
```

The rect comes back in page coordinates, so the callout lands where a reader sees the field rather than where the frame thinks it is.

## Click and type inside a frame

Steps take queries, so nothing new is needed:

screenshots/recipes/checkout.yaml

```
setup:
  - fill:
      frame: { css: 'iframe[title="Payment"]' }
      label: Card number
    value: '4242 4242 4242 4242'
  - wait:
      frame: { css: 'iframe[title="Payment"]' }
      text: Card accepted
```

## Cross-origin frames

Add the frame's destination to `site.allow`:

shotlist.config.yaml

```
site:
  allow:
    - checkout.example.com
```

Resolution goes through the browser rather than the page's own JavaScript, so a query can reach into the approved frame even though the page containing it cannot. The destination approval is separate because the frame makes its own network requests rather than inheriting permission from the page around it.

## A frame inside a frame

Needs nothing extra. Name the inner one and the coordinates still come back against the page:

screenshots/recipes/order-total.yaml

```
marks:
  total:
    frame: { css: 'iframe[title="Checkout"]' }
    css: '.total'
```

## When the frame is empty in the picture

An application that renders its own frame — a block editor, a live preview — often replaces it when its content changes. A query resolves against the frame that is there when it runs, and the replacement has not painted yet, so the capture comes out blank while the callout sits in exactly the right place.

Wait for the content, then let it settle:

screenshots/recipes/editor.yaml

```
setup:
  - click: { role: button, name: Add a block }
  - wait:
      frame: { css: 'iframe[name="editor-canvas"]' }
      text: Media Library
  - wait: 1500
    comment: the application rebuilt its frame, and the new one has not painted yet
```

`site.settle` does the same for every recipe in the project, which is the better home for it once more than one shot needs it.

## What it does not handle

- A `transform` or `zoom` on the iframe element is not compensated for, so a scaled preview places its callouts off the mark.
- `within` naming a mark outside the frame is refused rather than quietly scoped to nothing.

## Related

- [Queries reference: `frame`](/docs/reference/queries/#frames)
- [Capture a page after interaction](/docs/how-to/drive-a-page/)
- [Queries that survive a redesign](/docs/explanation/durable-queries/)

Written by Nicola Mustone · Applies to shotlist 0.6.0 · Maintained by Nicola Mustone

Published date unavailable · Updated date unavailable · [View source](https://github.com/SirDarcanos/shotlist.dev/blob/main/src/pages/docs/how-to/iframes.astro) · [Propose a correction](https://github.com/SirDarcanos/shotlist.dev/edit/main/src/pages/docs/how-to/iframes.astro)
