paparazzi drives a headless Chromium browser from R. One chain of actions and expectations can be a browser test, a screenshot script or a recorded demo. It works with static sites, running web apps, Shiny apps on disk and Quarto documents.
pz_open(): a URL, an
app or document path, or a serving handle. Set the viewport when opening
so the first render already uses it.pz_loc() specs. pz_find() returns a new scoped
context; assign it to work inside that scope.pz_act_*() or pz_set_*(), then wait for the
result with a pz_expect_*() call.pz_screenshot(), or wrap the steps in
pz_record() to capture them as video.pz_with_page() closes
it when its code finishes, and pz_local_page() closes it
when the calling test or function exits.This script adds a task to the bundled task tracker, checks that it appears first, and saves a screenshot of the list. Opening the HTML example serves it over HTTP when httpuv is installed, giving it the origin and history behavior of a deployed site.
library(paparazzi)
image <- tempfile(fileext = ".png")
pz_with_page(pz_example("tasks"), function(page) {
page |>
pz_act_type("Repot the fern", target = "#task-title") |>
pz_act_click("#add-task") |>
pz_expect_text(
"Repot the fern",
target = pz_loc(".task-title", which = "first"),
match = "exact"
) |>
pz_screenshot(image, frame = pz_frame(".task-list", pad = 16))
}, width = 1000, height = 720, color_scheme = "light")To record the same interaction, stage the page first and run the
chain inside pz_record(). Assigning the staged page carries
the settings into every later chain, the recording included. Staging
animates the cursor and typing only while a recording runs. This GIF
needs the gifski package.
video <- tempfile(fileext = ".gif")
pz_with_page(pz_example("tasks"), function(page) {
page <- page |> pz_stage(enter = "bottom", pause = 0.5)
page |> pz_record(video, code = {
page |>
pz_act_type("Repot the fern", target = "#task-title") |>
pz_act_click("#add-task") |>
pz_expect_visible(pz_loc(".task", has_text = "Repot the fern")) |>
pz_record_hold(0.5)
}, fps = 8, scale = 0.5)
}, width = 800, height = 600)pz_get_elements() and
pz_inspect(). Target IDs, data attributes and meaningful
classes; add has_text when text identifies a row.row <- pz_find(page, spec) leaves page
unchanged. Use row for row-level steps and
page for whole-page checks.pz_loc()
spec is resolved each time it’s used, so it survives re-renders and
navigation. After the page changes, expect the new content, then call
pz_find() again.width,
height and color_scheme to
pz_open(), plus locale and
timezone when the page formats dates or numbers. Use
reduced_motion = TRUE for stills.pz_open() serves HTML files and directories over HTTP
itself when httpuv is installed; call pz_serve_static()
explicitly for a shared server or a custom root,
pz_serve_quarto() for Quarto sources and
pz_serve_shiny() for apps.pz_screenshot() writes the file and the chain continues.
Without one it returns an image for interactive use or knitted
documents.Read the reference that matches the task.
pz_open() accepts, device settings, HTTP servers,
navigation and cleanup.pz_loc() specs, text and position filters, scoped contexts
and moving between scopes.btw finds this skill whenever paparazzi is attached.
btw::btw_skill_install_package("paparazzi") copies it into
a project’s skills directory. For other agents, copy the whole directory
returned by
system.file("skills/paparazzi", package = "paparazzi"),
including references/.