# Browser checks

> Write a first browser check, run it against your app with blop test, and read the result.

Write a [browser check](/reference/glossary/#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](/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.

## Prerequisites for browser checks

- Node.js 22.18 or newer, which loads TypeScript checks directly, in the project that owns the checks.
- A [provider](/reference/glossary/#provider) and an API key for it. See [Providers](/checks/providers/).
- Your app running at a URL you control, for example `http://localhost:3000`.

## Write and run your first browser check

1. Install the CLI and `@blopai/test`, the package that runs browser checks:

   ```bash
   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](https://github.com/blop-oss/blop-test)'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`):

   ```bash
   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:

   ```bash
   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`:

   ```bash
   pnpm exec blop test init
   ```

6. Replace its goal with what a user must be able to do:

   ```ts
   // 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:

   ```bash
   pnpm exec blop test list
   ```

8. Run the check against your app:

   ```bash
   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.

## Check the first browser check ran

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/`:

| File | Contents |
|---|---|
| `results.json` | Each check's status, reason, attempts, actions and artifact paths |
| `events.jsonl` | Tool calls and agent events, one per line |
| `report.xml` | JUnit 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](/checks/agent-authored/) and the [result format](/reference/runner/).

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](/troubleshooting/#blop-test-fails-with-unknown-file-extension-ts). If it fails with `No model configured`, set `BLOP_AGENT_MODEL` as in step 4.

## Write goals the agent can act on

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](/troubleshooting/#browser-check-passes-when-the-page-is-broken) and [Examples](/checks/examples/).

## Spec shapes for browser checks

`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`:

```ts
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`](/reference/configuration/). The full DSL is in the [test DSL reference](/reference/test-dsl/).

## Choose which browser checks run

```bash
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`):

| Command | What it does |
|---|---|
| `blop test add [path]` | Add another starter check |
| `blop test init --force` | Overwrite 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](/reference/glossary/#run) of any command and does not start browser checks. Every flag is in the [CLI reference](/reference/cli/#blop-test); environment variables are in [Environment](/reference/environment/).

## Next steps after browser checks

- [Reverify a fix with a browser check](/checks/agent-authored/): evidence, launching from a trace or finding, retries and coverage.
- [Browser checks in CI](/checks/ci/): GitHub Actions, OpenTelemetry export and uploads to Blop Cloud.
- [Production checks](/checks/production/): safeguards before you target production.
