Skip to main content
When a Libretto workflow fails, the browser stays open at the exact point of failure. This is the key difference between debugging with Libretto and debugging with a generic test runner — you don’t lose the state. Your agent can inspect the live page, test new selectors, and fix the issue before re-running to verify.

Example prompt

We have a browser script at ./integration.ts that is supposed to go to Availity and perform an eligibility check for a patient. But I’m getting a broken selector error when I run it. Fix it. Use the Libretto skill.
The agent reproduces the failure, snapshots the broken state, finds the correct selector in the current DOM, patches the code, and re-runs to confirm.

The debugging workflow

1

Run the failing script

npx libretto run ./integration.ts main --session debug-flow --headed
Use --headed so you can watch the browser and see where it stops. Name the session something descriptive — you’ll use it for all subsequent commands.
2

Browser stays open at the failure point

When a workflow fails, Libretto keeps the browser open and returns control to the CLI. The agent sees the error message and knows to inspect the current page state before touching any code.
3

Snapshot the broken state

npx libretto snapshot \
  --session debug-flow \
  --objective "Find the blocking error or broken selector target" \
  --context "The workflow failed while trying to submit the eligibility form. I need to identify the element that can't be found."
Snapshot captures the current screenshot and HTML, then uses LLM analysis to identify what’s on the page, what’s missing, and what the correct selectors should be. This keeps the heavy visual context out of your coding agent’s context window.
4

Test selectors with exec

Once the snapshot identifies candidate selectors, the agent validates them against the live page:
npx libretto exec --session debug-flow \
  "return await page.locator('[data-testid=\"member-id-input\"]').count()"
If the count is 0, the selector doesn’t match. Try alternatives until you find one that returns 1:
npx libretto exec --session debug-flow \
  "return await page.locator('input[name=\"memberId\"]').count()"
5

Fix the code

With the correct selector confirmed, the agent updates the workflow file. Read the code generation rules before editing production code — Playwright locator APIs, no querySelectorAll in page.evaluate(), type-safe output.
6

Re-run to verify

npx libretto run ./integration.ts main --headless
Switch to --headless for the verify loop — it’s faster. If the run succeeds, confirm the actual returned output is correct. If it fails again, the browser stays open and you repeat from step 3.

Using pause() as a breakpoint

If you need to stop a workflow at a specific point — not at failure, but at a known state you want to inspect — add a pause() call:
import { workflow, pause } from "libretto";

export const eligibilityCheck = workflow(async (ctx, input) => {
  const { session, page } = ctx;

  await page.goto("https://availity.com");
  await page.locator("#memberSearch").fill(input.memberId);

  // Pause here to inspect the page before submitting
  await pause(session);

  await page.locator("button[type='submit']").click();
  // ...
});
Run the workflow and it will halt at the pause() call with the browser open. Inspect, test selectors, then resume:
npx libretto resume --session debug-flow
pause() is a no-op when NODE_ENV === "production", so you can leave it in during development and it won’t affect production runs. Remove it once you’re done debugging.

Reading logs for error context

The session log at .libretto/sessions/<session>/logs.jsonl contains structured entries for every step the workflow took. Use jq to find the error:
# Find error entries
jq 'select(.level == "error")' .libretto/sessions/debug-flow/logs.jsonl

# See the last 20 log entries
tail -n 20 .libretto/sessions/debug-flow/logs.jsonl | jq .
Libretto’s instrumentation enriches timeout errors with element context — what was being waited for, its selector, and the page state at the time — so you’re not left reading a raw timeout message.

Common errors

Broken selectors The most common failure. A selector that worked last week no longer matches because the site updated its markup. Fix:
  1. Use snapshot to see the current DOM and identify the updated element
  2. Use exec to test new selectors against the live page
  3. Prefer stable selectors: data-testid attributes, ARIA roles, and visible label text over class names and positional selectors
Popups blocking the workflow A modal, cookie banner, or session-expiry dialog appears unexpectedly and blocks interaction with the underlying page. Consider using attemptWithRecovery() from the Library API to handle known popup patterns:
const result = await attemptWithRecovery(
  ctx,
  async () => {
    await page.locator("#continueButton").click();
  },
  [
    {
      detect: async () =>
        (await page.locator("#sessionExpiredModal").count()) > 0,
      recover: async () => {
        await page.locator("#sessionExpiredModal button").click();
      },
    },
  ],
);
Timeout errors A waitForSelector or locator action times out because the expected element never appeared. Check the snapshot to see what the page actually shows — it may be a loading spinner, an error state, or a redirect you didn’t expect. The structured log entry will include which selector was being waited for.

Tips

Use --headless for the fix/verify loop and --headed when you need to watch what’s happening. Headed mode is slower but lets you see exactly what the browser is doing at each step. Switch back to headless once you’ve confirmed the visual behavior is correct.
Give your debug session a descriptive name with --session. If you run multiple debug sessions, names like debug-eligibility-submit are much easier to work with than the default session name.

One-shot script generation

Build a new automation from scratch without any prior knowledge of the site.

Interactive script building

Show the workflow manually and let your agent turn it into reusable code.

Build docs developers (and LLMs) love