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:
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:
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:
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:
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:
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:
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
transformorzoomon the iframe element is not compensated for, so a scaled preview places its callouts off the mark. -
withinnaming a mark outside the frame is refused rather than quietly scoped to nothing.