This reference covers saving verified page states, framing the capture and adding marks, callouts, spotlights and redactions. The examples share one page, run in order and write under R’s temporary directory.
Give pz_screenshot() a path in scripts. It writes a PNG
and returns the context invisibly, so the chain can continue. An
existing file is overwritten. Set the viewport and color scheme when
opening, and use reduced_motion = TRUE for stills. Expect
the result of each action before capturing it.
library(paparazzi)
page <- pz_open(
pz_example("tasks"),
width = 1000,
height = 720,
scale = 1,
color_scheme = "light",
reduced_motion = TRUE
)
fern <- pz_loc(".task", has_text = "Repot the fern")
page |>
pz_act_type("Repot the fern", target = "#task-title") |>
pz_act_click("#add-task") |>
pz_expect_visible(fern) |>
pz_expect_count(8, target = ".task") |>
pz_screenshot(tempfile(fileext = ".png"))At the root, the default capture is the viewport. On a scoped context
it is the scope’s box. PNG dimensions are the captured CSS size
multiplied by the device pixel ratio (scale on
pz_open()), so set that explicitly when pixel dimensions
matter.
pz_frame() is a lazy spec: its target is measured when
the screenshot is taken. A selector matching several elements frames
their union; a list of selectors or pz_loc() specs frames
the union across targets. A bare locator passed as frame is
shorthand for a frame around that target.
page |>
pz_screenshot(tempfile(fileext = ".png"), frame = ".task-list") |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame(list("h1", "#new-task"), pad = 24, ratio = 16 / 9)
)pad is CSS pixels: one number for every side, or
c(top, right, bottom, left). ratio is width
divided by height; it grows the shorter side rather than shrinking the
content. anchor places the content within that growth,
offset = c(x, y) nudges the region, and bounds
clamps it to another locator’s box. The rendered page also limits the
region.
page |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame(
"#new-task",
pad = c(16, 24, 16, 24),
ratio = 4 / 3,
anchor = "top",
offset = c(0, 8),
bounds = "main"
)
)With zoom = NULL, a frame fits its target plus padding.
A numeric zoom fixes the crop size to the viewport divided
by that number and places the target inside it. For stills this crops at
the current pixel ratio; it never upscales. when controls
measurement for recordings, covered in Recording and staging;
screenshots always measure at capture time.
pz_stage_frame() sets a default frame for the
screenshots and recordings that follow it in the chain; assign the
staged page (page <- page |> pz_stage_frame(...)) to
keep the default for every chain. Pass its targets unnamed. Each
capture’s unset frame fields inherit the staged values, then the
built-in defaults. A scope takes precedence over a staged target when
the capture has no target of its own.
page |>
pz_stage_frame("#new-task", pad = 16) |>
pz_screenshot(tempfile(fileext = ".png")) |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame(".filters", ratio = 16 / 9)
) |>
pz_screenshot(tempfile(fileext = ".png"), frame = FALSE)frame = FALSE captures the scope or viewport unframed
for that call. pz_stage_frame(NULL) clears an inherited
default for the rest of the chain. Framing applies to stills as well as
video; it is separate from the recording-only animations in
pz_stage().
pz_annotate() draws one mark per match. Types are
"box" (outline), "circle" (ellipse),
"underline" (bottom stroke) and "highlight"
(translucent fill). label = TRUE numbers matches in order;
a string or number repeats the same badge on every match. With no label,
only the mark is drawn. Use pad to give the mark room
around its target.
page |>
pz_annotate("#task-title", type = "box", label = "Title", pad = 4) |>
pz_annotate("#add-task", type = "circle", pad = 6) |>
pz_annotate("h1", type = "underline") |>
pz_annotate(".filters a", type = "highlight", label = TRUE) |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame(
list("h1", "#new-task", ".filters"),
pad = 16,
target_box = "annotated"
)
) |>
pz_annotate_clear()target_box = "annotated" grows the frame to include
marks, callouts and spotlight cutouts on the target, so badges and
bubbles stay in the shot. Redactions and annotations on other elements
don’t count.
Marks stay until cleared. Calls with the same id replace
each other, and calls without one accumulate. Clear annotations between
shots so each image shows only what it’s meant to explain.
pz_stage_annotate() sets style defaults for the
annotations that follow it in the chain; assign the staged page to use
them across chains. Omitted arguments leave a setting alone; explicit
NULL restores its default for the rest of the chain.
Per-call styles override the staged settings. Mark badges use the mark’s
color with white text; label_fill and
label_text_color override those. Staged fill
and text_color style callout bubbles and their badges.
Without a per-call or staged fill, each callout follows its resolved
color. Without a per-call or staged text color, paparazzi
chooses black or white for the higher WCAG contrast ratio against the
fill. Staging fill = NULL or text_color = NULL
restores these defaults. New captions default to the staged
caption_side ("bottom" or
"top").
page |>
pz_stage_annotate(
color = "#2563eb",
fill = "#172554",
text_color = "white",
stroke_width = 2,
distance = 20,
font_family = '"Arial", sans-serif',
font_size = 16
) |>
pz_annotate(
"#add-task",
label = "Add",
id = "button",
label_fill = "#dbeafe",
label_text_color = "#172554"
) |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame("#new-task", pad = 16, target_box = "annotated")
) |>
pz_annotate_clear(id = "button")reveal chooses how a mark enters during an active,
unpaused recording: "fade", "draw",
"pop", "slide", "wipe" or
"none". The default, "auto", uses fade for
boxes and draw for other marks. Clearing plays the reverse. For stills,
drawing and clearing are instant.
pz_annotate_callout() repeats literal text
beside each match. side chooses a cardinal side or a corner
such as "top right"; leaving it unset chooses the cardinal
side with the most room. The bubble stays within the viewport. Set the
viewport before drawing so the text wraps for that size.
page |>
pz_annotate_callout(
"New tasks appear first",
target = "#add-task",
side = "bottom",
leader = c(start = "dot", end = "arrow"),
label = "1",
id = "explanation"
) |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame("#new-task", pad = 12, target_box = "annotated")
) |>
pz_annotate_clear(id = "explanation")leader = TRUE draws an arrow at the target;
FALSE makes a tooltip-style bubble. A named vector sets
start and end decorations to
"none", "arrow", "dot" or
"bar". distance sets the bubble’s gap in CSS
pixels, and stroke_width sets the leader width. Callout
reveal defaults to "pop", with the same
explicit choices as marks.
pz_annotate_spotlight() dims the document except for a
cutout around each match. dim ranges from 0 (transparent)
to 1 (black); pad grows the cutouts. Each call replaces the
page’s one spotlight.
page |>
pz_annotate_spotlight(
list("#task-title", "#add-task"),
pad = 6,
dim = 0.65
) |>
pz_screenshot(tempfile(fileext = ".png"), frame = "main") |>
pz_annotate_clear(id = "spotlight")Marks, badges, spotlight cutouts and redactions follow their targets
and are clipped by the targets’ scrolling containers. Marks keep their
padding on sides where the target is not cut off, so an outline stays
intact even at a container edge. An annotation hides while its target is
scrolled out of view, removed, or hidden with no visible children, so
scroll the target into view and expect it before capturing. A fully
clipped target shows no mark or badge, even if the padding would reach
into view. A callout hides with a fully clipped target; when the target
is partly visible, the leader points at the visible part and the bubble
is never cut off. Clipping follows overflow, not a custom
overflow-clip-margin.
Use pz_annotate_redact(method = "fill") for anything
secret. The opaque cover spans the target’s border box plus
pad; to cover content that overflows the element, redact
the overflowing element or add padding. method = "blur"
suits content that only needs to look unreadable. Add redactions before
starting a recording so the very first frame is covered.
page |>
pz_annotate_redact(
'[data-priority="high"] .task-title',
method = "fill",
pad = 2,
id = "private"
) |>
pz_screenshot(tempfile(fileext = ".png"), frame = ".task-list") |>
pz_annotate_clear(id = "private")Marks, callouts, spotlights and redactions belong to the current
document and are removed on navigation, so in a multi-page recording,
redact each new page before resuming capture. Captions are the
exception: they persist through navigation.
pz_annotate_clear() with no id clears
everything; "spotlight" and "caption" are
reserved ids.
pz_stage_fonts() loads font faces into the page and
returns once they’ve loaded. Declare remote faces with
pz_font_google() or pz_font_bunny() using the
provider’s family name, a weight (400 is regular) and a
style ("normal" or "italic").
Chrome downloads them, so this needs network access. Then name the
family in font_family as a CSS string with a generic
fallback.
page |>
pz_stage_fonts(pz_font_google("Inter"), pz_font_bunny("Lato")) |>
pz_stage_annotate(font_family = '"Inter", sans-serif') |>
pz_annotate("#add-task", label = "Add", font_family = '"Lato", sans-serif') |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame("#new-task", pad = 16, target_box = "annotated")
) |>
pz_annotate_clear()For offline work, pz_font_file() reads a local
.woff2, .woff, .ttf or
.otf file and hands its bytes to the page. This example
borrows the Open Sans file that rmarkdown ships; use your project’s own
licensed font. Staging the same family, weight and style again replaces
that face. Staged fonts last across recordings, and after navigation
they’re loaded into the new page before its first annotation.
font_path <- system.file(
"rmd/h/bootstrap/css/fonts/OpenSans.ttf",
package = "rmarkdown",
mustWork = TRUE
)
page |>
pz_stage_fonts(pz_font_file("Project Sans", font_path)) |>
pz_stage_annotate(font_family = '"Project Sans", sans-serif') |>
pz_annotate_callout("Ready to add", target = "#add-task", side = "bottom") |>
pz_screenshot(
tempfile(fileext = ".png"),
frame = pz_frame("#new-task", pad = 16, target_box = "annotated")
) |>
pz_annotate_clear()
pz_close(page)In a knitted chunk, end the chain with pz_screenshot()
and no path. It returns a knitr image and writes the PNG into the
chunk’s figure directory. That ends the chain, so give any earlier
screenshots a path. Here pz_local_page() closes the page
when the function returns.