Qazy is a Python CLI for agent-driven browser QA. It can start a local app or attach to an existing environment, hand the browser to a runtime such as Claude Code or Codex through Agent Browser, and write markdown results, screenshots, and runtime logs for each run.
Use Qazy when you want a browser agent to walk through user-facing flows such as login, onboarding, checkout, dashboard checks, or smoke tests that are easier to describe in natural language than in deterministic test code.
A skill can tell an agent "open the browser and check login." Qazy is the harness around that prompt so the same check is repeatable, parallel, and CI-friendly:
- Managed dev server — starts your app on a free port, waits for ready, stops it after the run
- Built-in auth — NextAuth or Better Auth cookie injection so the agent skips login boilerplate
- Runtime-agnostic — the same scenario runs under Claude Code or Codex
- Structured outputs — markdown reports, screenshots, runtime logs, and
0/1exit codes - Parallel orchestration — fans scenarios across workers when the target is
parallelSafe(recommended: pair with ephemeralenv for per-run DB/state isolation) - Checked-in scenarios — version-controlled
*.scenario.mdfiles with frontmatter for credentials, start page, and auth, instead of ad hoc prompts - Token accounting —
qazy tokenssummarizes per-run runtime usage
Reach for a skill when exploring; reach for Qazy when you want the same flow to run on every PR.
ephemeralenv gives each run a fresh database, deterministic ports, and seed data with no Docker. Point Qazy's devCommand at it and every run (parallel or not) gets an isolated stack:
ephemeralenv handles the DB/seed lifecycle; Qazy handles port allocation, readiness, the agent run, and teardown.
With Homebrew:
brew tap robbie-wasabi/qazy
brew install qazyFor development from a clone of this repo:
make installTo install the CLI from a clone so qazy is available outside the repo:
pipx install --editable .
pipx ensurepath- Agent Browser on
PATH - at least one runtime CLI on
PATH: Claude Code (claude) or Codex CLI (codex) - either a
qazy.config.jsonc,qazy.config.json, or enough CLI flags to use the built-in defaults
Check the installed runtimes with:
qazy runtimes
qazy runtimes --smokeStart with prompt mode. It runs one check directly from the command line and does not require a *.scenario.md file.
# Run against an app that is already running.
qazy -p "confirm the landing page loads" \
--base-url http://127.0.0.1:3000 \
--no-use-cookie
# Start a local dev server for the check.
qazy -p "confirm the login page loads" \
--dev-command "pnpm dev" \
--start-page /login \
--no-use-cookie
# Run with credentials and let the runtime log in through the browser.
qazy -p "verify the student can sign in and reach the dashboard" \
--start-page /login \
--email student@example.com --password secret123 \
--no-use-cookie
# Run with credentials and Qazy's built-in auth-cookie flow.
qazy -p "verify the student dashboard loads" \
--start-page /dashboard \
--email student@example.com --password secret123 \
--use-cookie
# Use Claude Code.
qazy -p "verify the checkout flow works" \
--runtime claude \
--email student@example.com --password secret123 \
--no-use-cookie
# Use Codex.
qazy -p "verify the checkout flow works" \
--runtime codex \
--email student@example.com --password secret123 \
--no-use-cookieWith no config file, Qazy uses these defaults:
- no target flags: attach to
http://127.0.0.1:3000 --base-url URL: attach toURL--dev-command "...": start a managed app athttp://127.0.0.1:{appPort}withPORT={appPort}
Prompt mode is good for exploration. For repeatable checks, save the instructions in a scenario file.
Create user-scenarios/login.scenario.md:
---
start_page: /login
use_cookie: false
---
# Student Login
## Notes
Use the seeded student account.
## List
- [ ] Sign in and confirm the dashboard loads.
- [ ] Open the profile menu and confirm the signed-in email is shown.Run it:
qazy user-scenarios/login \
--email student@example.com --password secret123Run one scenario, a directory, or a glob:
qazy user-scenarios/login
qazy batch user-scenarios
qazy "user-scenarios/**/*.scenario.md" --parallelFor repeated use, named environments, and checked-in team defaults, add qazy.config.jsonc or qazy.config.json to the target project.
To have an agent inspect the project and create or patch qazy.config.jsonc, run:
qazy setupQazy asks whether to launch Claude Code or Codex, then passes the install prompt to that agent. To skip the chooser:
qazy setup --runtime codex
qazy setup --runtime claudeYou can generate a commented config with every supported option shown:
qazy initA compact strict-JSON config can look like:
{
"version": 1,
"defaultTarget": "local",
"defaultRuntime": "codex",
"resultsDir": ".qazy/results",
"targets": {
"local": {
"mode": "managed",
"baseUrl": "http://localhost:{appPort}",
"devCommand": "pnpm dev",
"ports": {
"appPort": "auto"
},
"env": {
"PORT": "{appPort}"
},
"scenarioDefaults": {
"startPage": "/login",
"useCookie": false
},
"runtimeDefaults": {
"codex": {
"model": "gpt-5.4-mini",
"reasoningEffort": "low"
},
"claude": {
"model": "claude-sonnet-4-5"
}
},
"parallelSafe": true
}
}
}With that config, a scenario can be run with fewer flags:
qazy user-scenarios/login \
--email student@example.com --password secret123Prompt mode runs one ad hoc check from -p or --prompt. Use it for exploration, smoke checks, or trying Qazy against a new app.
Scenario files are checked-in markdown files that make a flow repeatable. Use them for team workflows and regression checks.
Targets tell Qazy where the app is. An attached target points at an already-running app. A managed target starts devCommand, waits for the app to respond, runs the check, then stops the process.
Runtimes are the agent CLIs that drive the browser. Qazy currently supports Claude Code (claude) and Codex CLI (codex).
Authentication can be handled by Qazy's built-in cookie flow with --use-cookie, or by the runtime interacting with the login page in the browser with --no-use-cookie.
Common commands:
qazy -p "verify the student can submit an assignment"
qazy user-scenarios/login
qazy run user-scenarios/login
qazy batch user-scenarios
qazy "user-scenarios/**/*.scenario.md" --parallel
qazy tokens
qazy tokens .qazy/results/my-run/logs/claude-login.log
qazy setup
qazy init
qazy config check
qazy rename-scenarios --write
qazy runtimes
qazy runtimes --smoke
qazy help
qazy --version
qazy help run
qazy help config
qazy help authUseful run options:
--project-rootpoints Qazy at another workspace--config-fileuses a non-default config path--targetpicks a named target--base-urlruns without a config file against an existing app URL--dev-commandstarts a managed app without requiring a config file--runtimechoosesclaudeorcodex--modeland--reasoning-effortforward runtime-specific tuning--email,--password,--start-page,--use-cookie,--no-use-cookieoverride scenario values--headedor--headlesscontrols browser visibility--screenshot-strategyacceptsnone,error,single, orcheckpoints--results-diroverrides the output path--paralleland--max-workerscontrol batch execution
Other command behavior:
qazy tokenssummarizes usage from runtime log filesqazy setuplaunches Claude Code or Codex with Qazy's install prompt to set upqazy.config.jsoncqazy initwritesqazy.config.jsoncwith every supported config field and optional values commented outqazy --versionprints the installed Qazy package versionqazy config checkvalidatesqazy.config.jsoncorqazy.config.json; strict JSON files are also checked for canonical two-space formattingqazy rename-scenariosmigrates legacy scenario layouts to*.scenario.mdqazy runtimeschecks which runtime CLIs are availableqazy runtimes --smokesends a trivial prompt through each installed runtimeqazy help [topic]prints agent-friendly usage guidance without needing this README
Config is optional. Qazy looks for qazy.config.json first, then qazy.config.jsonc in --project-root, but if both are missing Qazy can still run with the built-in defaults described in Quick Start.
qazy init writes qazy.config.jsonc by default. JSONC comments and trailing commas are supported so the generated file can show optional settings inline while remaining loadable by Qazy.
Check a config before a run or in CI:
qazy config check
qazy config check --config-file qazy.config.jsonc
qazy config check --schema-onlyTop-level fields:
version: currently1defaultTarget: target used when--targetis omitteddefaultRuntime: runtime used when--runtimeis omitted, one ofclaudeorcodexresultsDir: default directory for result markdown, screenshots, and logs; Qazy defaults to.qazy/resultsscreenshotStrategy: default screenshot capture policy, one ofnone,error,single, orcheckpoints(defaults toerror); overridden by--screenshot-strategytargets: named target definitions
Target fields:
mode:managedorattachedbaseUrl: may include{appPort}and{mongoPort}placeholdersdevCommand: required formanagedtargetsports:appPortandmongoPort, each fixed or"auto"env: environment variables for managed targetsready: HTTP readiness probe withtype,path, andtimeoutSecondsparallelSafe: required for batch--parallelscenarioDefaults: defaultemail,password,startPage, anduseCookieruntimeDefaults: default runtimemodelandreasoningEffortby runtime name, such ascodexorclaude
Target behavior:
managed: Qazy startsdevCommand, waits for the target to respond, then stops it after the runattached: Qazy never starts a process and usesbaseUrlas-is
Notes:
- Relative
resultsDirvalues resolve from the config file location - If
resultsDiris omitted, results default to<project-root>/.qazy/results/ --runtimeoverridesdefaultRuntime--modeland--reasoning-effortoverrideruntimeDefaults- Runtime and server logs are written to
<resultsDir>/<run-id>/logs/ ready.typecurrently only supportshttp
Frontmatter fields:
emailpasswordstart_pageuse_cookieauth_provider:nextauth(default) orbetter-authauth_cookie_prefix: Better Auth cookie prefix override, defaultbetter-auth
Single-section scenarios are the common case. Multi-section scenarios work by repeating the frontmatter block:
---
email: user1@example.com
password: tester123
start_page: /dashboard
use_cookie: true
---
# Tenant 1
- [ ] Verify tenant 1 state.
---
email: user2@example.com
password: tester123
start_page: /dashboard
use_cookie: true
---
# Tenant 2
- [ ] Verify tenant 2 state.Qazy runs those sections in order against one shared target lifecycle.
Value precedence is:
- CLI overrides
- Scenario frontmatter
- Target
scenarioDefaults - Built-in defaults
Built-in defaults are start_page: /dashboard and use_cookie: true. Built-in cookie auth requires both email and password; browser-driven login with use_cookie: false can run without credentials.
Qazy has built-in credentials-cookie login, controlled by use_cookie plus auth_provider.
use_cookie: true,auth_provider: nextauth(default): Qazy requests/api/auth/csrf, posts form-encoded credentials to/api/auth/callback/credentials, captures thenext-auth.session-tokencookie, and injects it intoagent-browseruse_cookie: true,auth_provider: better-auth: Qazy posts JSON credentials to/api/auth/sign-in/emailwith a matchingOriginheader, captures thebetter-auth.session_tokencookie or__Secure-better-auth.session_tokenon HTTPS, and injects it intoagent-browseruse_cookie: false: Qazy does no pre-authentication. The runtime logs in manually in the browser when credentials are provided. If credentials are omitted, Qazy prints that on startup and instructs the runtime not to search files, environment variables, source code, logs, or config for them.
Credential and provider sources, in precedence order:
- CLI overrides:
--email,--password,--auth-provider,--auth-cookie-prefix - Scenario frontmatter
- Target
scenarioDefaults:email,password,authProvider,authCookiePrefix, and related auth fields
Custom auth flows still work, but they are runtime-driven browser flows rather than built-in Qazy auth. That includes SSO, OAuth redirects, magic links, MFA, and custom login forms.
Screenshot strategies:
none: disable screenshotserror: allow named error screenshots and capture a fallback failure screenshot if neededsingle: save one final screenshot automatically and still allow named error screenshotscheckpoints: allow named screenshots during the run for important states
During a run, Qazy exposes qazy-shot <label> to the runtime so it can save screenshots without calling agent-browser screenshot directly.
Outputs:
- results markdown:
<resultsDir>/<run-id>/, defaulting to<project-root>/.qazy/results/<run-id>/ - screenshots:
<resultsDir>/<run-id>/screenshots/ - runtime and server logs:
<resultsDir>/<run-id>/logs/ - exit code:
0on pass,1on fail or error
Supported runtime adapters today:
- Claude Code (
claude) - Codex CLI (
codex)
Use qazy runtimes to check installation and qazy runtimes --smoke to verify a trivial invocation works in the current environment.
Repo-local example apps live under examples/:
examples/student-portalexamples/task-board
Each example includes a lightweight HTML app, a qazy.config.json, and scenario files you can run directly. See examples/README.md for commands.
Run the mocked unit and CLI suite:
make test-unitRun live runtime integration coverage:
make test-runtime-integrationRun the example-app integration coverage:
make test-example-integrationRun every test tier:
make test-allThe live runtime tests are intentionally skipped unless the relevant runtime CLI is installed locally.
- Built-in auto-auth supports NextAuth and Better Auth credentials-cookie login only
- PASS/FAIL is inferred from runtime output parsed by Qazy
- Ready checks are simple HTTP probes
- Prompt mode is useful for exploration but less repeatable than checked-in scenarios
- Qazy depends on external runtime CLIs and
agent-browser; it does not install or manage them - Qazy is not a replacement for deterministic unit or integration tests
{ "targets": { "local": { "mode": "managed", "devCommand": "pnpm exec ephemeralenv", "ports": { "appPort": "auto" }, "parallelSafe": true } } }