Skip to content
Testing
Skill

/canary-scripting

The Canary sandbox scripting API for browser automation. Use when writing or debugging a Canary script — looking up how to open a page, click, fill, extract text, observe an unknown page with snapshotForAI, evaluate in the page, take a screenshot, persist data between steps, or

From plugin
canary
4945 skills4 agents4 commands

Claude Code

$ npx -y skills add 0xnyn/canary --skill canary-scripting --agent claude-code

SKILL.md

canary-scripting.SKILL.md
name: canary-scripting
description: The Canary sandbox scripting API for browser automation. Use when writing or debugging a Canary script — looking up how to open a page, click, fill, extract text, observe an unknown page with snapshotForAI, evaluate in the page, take a screenshot, persist data between steps, or understand sandbox limits (no imports, timeouts). Trigger phrases — "how do I click in canary", "canary page API", "what's on this page", "explore a page in canary", "snapshotForAI", "saveScreenshot signature", "get text from the page", "why is my canary script timing out", "open a new tab in canary".
license: MIT
metadata:
  author: usecanary
  version: 0.4.4
  category: reference
  tags:
    - canary
    - browser-automation
    - playwright
    - scripting

Canary scripting API

Canary scripts are plain **async JavaScript** run in a QuickJS sandbox with a Playwright-like API. Both `canary-browser run` (one-off) and `canary run --session` (recorded step) execute the same way: top-level `await`, with `browser`, `console`, and the file helpers available as globals.

When to use

  • Writing a script to drive a browser with Canary
  • Looking up a page or locator method (`goto`, `locator`, `evaluate`, `waitForSelector`, `screenshot`)
  • Persisting a page or a file between steps of a session
  • Debugging a timeout, a missing global, or a "page closed" error

Examples

Example 1: open a page and read it

User says: "navigate to a site and get the title in canary" or "how do I read text off the page?" Use a **named** page so it persists across steps, then `goto` and `evaluate`/`locator`. See *Quick start*.

Example 2: click / fill / extract

User says: "click the login button", "fill the search box", "scrape the headlines" `page.locator(selector)` then `.click()` / `.fill(value)` / `.textContent()`; or `page.evaluate(fn)` to pull structured data in one round-trip.

Example 3: screenshot

User says: "take a screenshot" or "what's the saveScreenshot signature?" `const buf = await page.screenshot({ fullPage: true }); await saveScreenshot(buf, "home.png");` — note **buffer first**, and that `saveScreenshot` is a top-level global, not `browser.saveScreenshot`.

Example 4: observe an unknown page

User says: "I don't know the selectors", "what's on this page?", "explore before acting" `(await page.snapshotForAI()).full` → an aria outline of the page. Read it to pick a role/text selector, then act. See *Observing the page*.

Quick start

<!-- canary:snippet ex-quickstart fenced=js -->

const page = await browser.getPage("main");          // named, persistent page
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
console.log(await page.title());

const headings = await page.evaluate(() =>
  [...document.querySelectorAll("h1, h2")].map((h) => h.textContent.trim())
);
console.log(JSON.stringify(headings));

await page.locator("a.more").click();
const buf = await page.screenshot({ fullPage: false });
await saveScreenshot(buf, "page.png");               // saveScreenshot(buffer, name)

<!-- canary:end ex-quickstart -->

Observing the page

<!-- canary:snippet ex-snapshot fenced=js -->

const page = await browser.getPage("main");
const snap = await page.snapshotForAI(); // { full, incremental? }
console.log(page.url(), await page.title());
console.log(snap.full); // aria outline — pick a role/text selector from this
// then act: await page.getByRole("button", { name: "Continue" }).click();
// after changes, page.snapshotForAI({ track: "main" }) returns just the incremental diff

<!-- canary:end ex-snapshot -->

<!-- canary:snippet api-snapshot -->

  • `page.snapshotForAI()` returns `{ full, incremental? }` — `full` is an aria outline of the

page: roles, accessible names, `[ref=eN]` markers on actionable nodes. Read it to pick a semantic selector — `page.getByRole("button", { name: "Continue" })`, `page.getByText("Sign in")` — then act.

  • Options `{ track?, depth?, timeout? }`: re-run `page.snapshotForAI({ track: "main" })` after

the page changes to get just the `incremental` diff; `{ depth: N }` caps the tree on huge pages; `timeout` bounds the walk.

  • `page.locator("aria-ref=e12")` works for an immediate action in the same script only — refs go

stale across steps and after navigations. Prefer re-deriving a semantic selector. <!-- canary:end api-snapshot -->

<!-- canary:snippet rule-observe-first -->

  • Unknown page? Snapshot first, then act: read `(await page.snapshotForAI()).full` to see what

is there, pick a semantic selector from it (`getByRole`, `getByText`), then interact. Never guess selectors blind.

  • Known page or selectors? Skip the snapshot and use direct selectors — faster and more reliable.

<!-- canary:end rule-observe-first -->

  • End each script by logging the state you need for the next decision — stdout is your observation

channel.

The essentials

<!-- canary:snippet api-globals --> Every script gets these globals:

  • `browser` — pre-connected browser handle (see the script API)
  • `console` — `log` / `info` / `warn` / `error`, captured per run
  • `setTimeout` / `clearTimeout` — basic timers
  • `saveScreenshot(buffer, name)` — save a screenshot buffer (async — await it)
  • `writeFile(name, data)` / `readFile(name)` — small-file persistence (async — await them)

<!-- canary:end api-globals -->

<!-- canary:snippet rule-data-passing -->

  • Browser state persists across steps: named pages (and their cookies) stay open between scripts

within a session — reuse the same page name so each step picks up where the last left off.

  • Anonymous `newPage()` tabs are closed when each script ends.
  • To pass values between steps: `writeFile("state.json", JSON.stringify(x))` in one step,

`JSON.parse(await readFile("state.json"))` in the next. <!-- canary:end rule-data-passing -->

  • One **primary named page per step** keeps the per-step report screenshot correct (the full rule

is in [`references/REFERENCE.md`

Read more
@0xnyn
Ships withcanary

QA harness built for Claude Code | E2E testing with screen recordings, console logs, network HARs, and Playwright traces

4945 skills4 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
494
Star history of 0xnyn/canary on GitHub, a chart by star-history.com

Other skills on canary.

canary-automate

canary-automate

Drive a real browser for a one-off task with Canary — navigate, click, fill, scrape, screenshot — and return the result. Nothing…

@0xnyn@0xnynInstall Skill
canary-review

canary-review

Open and triage recorded Canary sessions in the local viewer. Use when the user wants to look at, replay, compare, or triage a…

@0xnyn@0xnynInstall Skill
canary-session

canary-session

Record a verifiable QA session with Canary — explore a flow step by step against one persistent browser, each script a recorded…

@0xnyn@0xnynInstall Skill
canary-verify

canary-verify

Turn a code change into a prioritized browser-QA plan with Canary — read the git diff, infer which user-facing workflows it…

@0xnyn@0xnynInstall Skill

More skills from other plugins.

api-review

api-review

Produce the "Playwright 1.xx API Review" artifact for a range of commits - the public API additions and the breaking or behavior…

@microsoft@microsoftInstall Skill
add-e2e-selectors

add-e2e-selectors

Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e…

grafana2d78k
@grafana@grafanaInstall Skill
promptfoo-evals

promptfoo-evals

Write, refine, run, and QA promptfoo evaluation suites: promptfooconfig.yaml, prompts, providers, vars, tests, assertions,…

promptfoo10d27k
@promptfoo@promptfooInstall Skill
deepeval

deepeval

DeepEval evaluation workflow for AI agents and LLM applications. TRIGGER when the user wants to evaluate or improve an AI agent,…

@confident-ai@confident-aiInstall Skill

Questions about canary-scripting.

Which coding agents can use canary-scripting?
canary-scripting installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.