Annotate on failure with the DSL
annotateScreenshot() accepts the annotation DSL
directly since annot-playwright@0.2.0. The DSL is typically
clearer than hand-rolled SVG when the goal is “highlight the
failing element + label it”:
import { test, expect, type BboxAnnotation,} from "@ingcreators/annot-playwright";
test("submit button is reachable", async ({ page, annotator }, testInfo) => { await page.goto("https://example.com/login"); const submit = page.getByRole("button", { name: "Sign in" });
try { await expect(submit).toBeEnabled(); } catch (err) { const box = await submit.boundingBox(); if (box) { const annotated = await annotator.annotateScreenshot(page, { annotations: [ { type: "rect", bbox: box, intent: "error" }, { type: "callout", at: { x: 30, y: 30 }, targetBbox: box, content: "Submit button disabled", }, ] satisfies BboxAnnotation[], }); await testInfo.attach("failure.png", { body: annotated, contentType: "image/png", }); } throw err; }});intent: "error" pulls colours from the Annot design system
(stroke #ef4444, fill 12% alpha, callout text #991b1b) — no
manual hex picking, the failure looks identical to redacts /
error chrome elsewhere in the product.
Comparing the two flavours
Section titled “Comparing the two flavours”The annotationsSvg: string flavour from
the SVG helpers page still works and is
appropriate when:
- You need a shape the DSL doesn’t model (use
{ type: "raw" }or stick with the helpers). - You’re composing many small fragments programmatically (string concatenation is fine; the DSL doesn’t add value).
The DSL flavour is appropriate when:
- You want intent → colour shorthand.
- You want callouts (rect + arrow + text in one entry).
- You’re generating annotations from data + want a typed shape
that survives
JSON.parseround-trips.
The fixture’s annotateScreenshot() accepts either — they’re
not mutually exclusive at the API level, just pick whichever
maps cleanly to the call site.
Combining with locator helpers
Section titled “Combining with locator helpers”The DSL doesn’t resolve locators itself — Playwright tests
already hold a Page and can resolve via
await locator.boundingBox(). If you find yourself writing the
same locator → bbox dance many times, the
draw-by-DOM-locator recipe lifts it into a
helper.
For agent-side workflows where the agent has the locator string
but no Page, the
MCP annot_annotate_url tool
takes locators directly.