Skip to content

Browser checks

Write a browser check, run it against your app with blop test, and read what it found. A browser check is a .blop.ts file in your repository. A browser agent works through its goals in a real browser with Browser Harness tools, in a model loop bounded by a timeout, a stall guard and an optional --max-steps cap. Blop never writes or changes checks by itself.

  • Node.js 22.18 or newer, which loads TypeScript checks directly, in the project that owns the checks.
  • A provider and an API key for it. See Providers.
  • Your app running at a URL you control, for example http://localhost:3000.
  1. Install the CLI and @blopai/test, the package that runs browser checks:

    Terminal window
    pnpm add -D @blopai/cli 'git+https://github.com/blop-oss/blop-test.git#master'

    If npm installs a @blopai/cli older than 1.8.0, upgrade it. The install may print peer-dependency or skipped-build-script warnings; step 7 confirms the setup works. To work on @blopai/test itself, follow the blop-test repository’s AGENTS.md instead.

  2. Commit package.json and the lockfile, because @blopai/test installs from Git.

  3. Install Chromium with the Playwright version your @blopai/test uses (currently 1.61.1):

    Terminal window
    pnpm dlx playwright@1.61.1 install chromium
  4. Set the provider, model and key. Blop does not read provider key names such as OPENAI_API_KEY by itself:

    Terminal window
    export BLOP_AGENT_PROVIDER=openai
    export BLOP_AGENT_MODEL=gpt-5
    export BLOP_AGENT_API_KEY="$OPENAI_API_KEY"
  5. Create a starter check at tests/homepage.blop.ts:

    Terminal window
    pnpm exec blop test init
  6. Replace its goal with what a user must be able to do:

    tests/homepage.blop.ts
    import { agentTest, describe } from "@blopai/test";
    describe("homepage", () => {
    agentTest("opens the products page", async ({ agent }) => {
    await agent.goto("/");
    await agent.goal(`
    Click the Products link in the navigation.
    Verify the URL path is /products and the heading "Products" is visible.
    Finish as passed only if both checks succeed; otherwise finish as failed and say which one failed.
    `);
    });
    });
  7. List the checks Blop finds, without opening a browser:

    Terminal window
    pnpm exec blop test list
  8. Run the check against your app:

    Terminal window
    pnpm exec blop test run tests/homepage.blop.ts --base-url http://localhost:3000

--base-url is the app under test. Relative agent.goto("/path") calls resolve against it. It does not redirect an absolute URL written in a check.

The command exits 0 when every check passed, 1 when any check failed or errored, and 2 for a usage error. It writes its reports to .blop/:

FileContents
results.jsonEach check’s status, reason, attempts, actions and artifact paths
events.jsonlTool calls and agent events, one per line
report.xmlJUnit XML for CI

A failed status means the agent finished with a goal unmet. An error status means the check could not finish, for example a missing browser or a rejected API key; fix that and rerun. Neither is proof of an app defect until you read the evidence. See Reverify a fix with a browser check and the result format.

If blop test list or run fails with Unknown file extension ".ts", your Node.js is older than 22.18: upgrade it, or see Troubleshooting. If it fails with No model configured, set BLOP_AGENT_MODEL as in step 4.

  1. Describe outcomes a user sees, not CSS selectors.
  2. Name every verification: URL, heading text, counts, a toast.
  3. End with a strict pass condition: pass only if every verification succeeds, otherwise fail and say why.
  4. Give exact input values, such as Name: "Checkout QA".
  5. Keep one user flow per check; split sign-up, posting and deletion into separate checks.
  6. Say what the agent must not do, such as buying or creating accounts.
  7. Never put real credentials or personal data in a goal.
  8. Avoid words like “works” or “test the homepage”; the agent will pass them optimistically.

More examples: Troubleshooting and Examples.

agent.goto() and agent.goal() do not act immediately. They build an ordered list of steps that the agent works through when the check runs. describe blocks nest, and their names join the check name with >.

A file can also export checks as objects, as default or as a tests array. Object checks accept a per-check baseUrl and timeoutMs:

import { defineAgentTest } from "@blopai/test";
export default defineAgentTest({
name: "staging health",
goal: "Open /health and verify the page shows status ok.",
baseUrl: "https://staging.example.com",
timeoutMs: 60_000,
});

Set defaults for every check in blop.config.ts. The full DSL is in the test DSL reference.

Terminal window
pnpm exec blop test run # every check Blop discovers
pnpm exec blop test run tests/checkout.blop.ts # one file
pnpm exec blop test run tests/ # every check under a directory
pnpm exec blop test run "tests/**/*.blop.ts" # a glob

Each pattern is tried in order as an existing file, then as a directory (searched recursively), then as a glob. With no pattern, Blop uses include from blop.config.ts, or **/*.blop.ts and **/*.blop.tsx. Discovery skips node_modules/, dist/, .blop/ and .git/. A file counts as a browser check when its name ends in .blop. or .agent. and a JavaScript or TypeScript extension, such as homepage.blop.ts or smoke.agent.mjs.

Other commands in the blop test group (also blop flows, where ls means list):

CommandWhat it does
blop test add [path]Add another starter check
blop test init --forceOverwrite an existing file without asking; without a terminal, an existing file exits 2
blop test watch [patterns]Rerun when files change, polling every second
blop test <spec>Same as blop test run <spec>

run and list accept --json for one blop.cli/v1 envelope. blop run -- <command> is different: it records a run of any command and does not start browser checks. Every flag is in the CLI reference; environment variables are in Environment.