Quickstart
Run this from your existing project root:
The interactive wizard asks 4 questions — then everything is set up automatically.
Step 1: Choose your agent
| Agent | SDK | Best for |
|---|
| Claude Code | @anthropic-ai/claude-agent-sdk | Anthropic models only |
| OpenCode | @opencode-ai/sdk | Any provider: Anthropic, OpenAI, Google, and more |
Both agents connect to the same Playwright MCP server over HTTP and expose the same interface. You can swap between them by changing one import.
Step 2: Choose your model
Claude Code models:
| Model | Speed | Best for |
|---|
claude-haiku-4-5 | Fast | Default, most tests |
claude-sonnet-4-6 | Medium | Complex flows |
claude-opus-4-7 | Slow | Most demanding tasks |
OpenCode models use provider/model format:
| Model | Provider |
|---|
gitlab/duo-chat-haiku-4-5 | GitLab Duo (default) |
github-copilot/gpt-5.4 | GitHub Copilot |
anthropic/claude-haiku-4-5 | Anthropic |
openai/gpt-4o | OpenAI |
google/gemini-2.0-flash | Google |
Or enter any custom model name your OpenCode installation supports.
Step 3: Choose your framework
Playwright-BDD — Uses Gherkin .feature files with the Playwright test runner. Recommended.
Cucumber.js — Standard Cucumber with Playwright browser automation.
Step 4: Point to your feature files
Enter the relative path from your project root to your .feature files. For example:
features/ — default
tests/features/
src/test/features/
The harness will be configured to point at them automatically, wherever they live.
What gets created
your-project/
└── .openqa/ ← agent harness lives here
├── .env.example ← copy to .env and fill in your values
├── .env.schema ← committed schema: variable types, defaults, secret redaction
├── playwright.config.ts (or cucumber.js)
├── features/ ← example feature files (edit or replace these)
│ ├── todomvc.feature ← 2 scenarios
│ └── getting-started.feature ← 1 scenario
└── steps/
├── fixtures.ts ← Playwright-BDD only
└── steps.ts ← single AI step definition
Feature files live in .openqa/features/ by default. To move them, update the path in playwright.config.ts (or cucumber.js) — see Writing Feature Files.
Authentication
No API key needed for local development. Just log in once with the CLI and you’re done.
Claude Code (local)
OpenCode (local)
API Key (Claude Code)
API Key (OpenCode)
One-time login. No .env needed for local runs. One-time login — works with GitLab Duo, GitHub Copilot, Anthropic, OpenAI, Google, and more. No .env needed for local runs. cp .openqa/.env.example .openqa/.env
# then add:
ANTHROPIC_API_KEY=your_key_here
Use this for CI or if you prefer an explicit key.cp .openqa/.env.example .openqa/.env
# then add whichever provider key you need:
ANTHROPIC_API_KEY=your_key_here
# OPENAI_API_KEY=your_key_here
# GOOGLE_API_KEY=your_key_here
Use this for CI or if you prefer an explicit key.
Environment Variables
The scaffolded project uses varlock for environment variable management. Variables are defined in .env.schema (committed to git) and values go in .env (gitignored). Secrets are automatically redacted from logs.
| Variable | Default | Description |
|---|
BASE_URL | — | App URL — sets Playwright baseURL and injected into every agent prompt |
APP_USERNAME | — | Username — injected into agent prompt for login steps |
APP_PASSWORD | — | Password — injected into prompt; always redacted from logs |
OPENQA_VERBOSE | true | Set false to suppress step-by-step agent logs |
HEADLESS | true | Set false to watch the browser |
ANTHROPIC_API_KEY | — | Anthropic API key — CI only (use claude login locally) |
OPENAI_API_KEY | — | OpenAI API key — CI only via OpenCode |
GOOGLE_API_KEY | — | Google API key — CI only via OpenCode |
All variables are optional. Copy .env.example to .env and uncomment what you need.
Adding your own variables — declare them in .env.schema, add values to .env:
# .env.schema
# @sensitive=false
ENVIRONMENT = staging
STAGING_USER =
# @sensitive
STAGING_PASSWORD =
Customizing Your Setup
openqa init is a starting point — everything in .openqa/ is yours to edit.
Playwright config — .openqa/playwright.config.ts is a standard Playwright config file. Update timeouts, add projects, enable retries, change reporters — anything the Playwright docs describe will work here.
Step definitions — .openqa/steps/steps.ts is a regular Playwright-BDD step file (or Cucumber.js for the Cucumber framework). Add non-AI steps, Before/After hooks, or custom fixtures alongside the AI step.
Writing Feature Files
Two example feature files are scaffolded into .openqa/features/ — edit or replace them with your own tests.
Use * (asterisk) for natural, AI-friendly steps:
Feature: TodoMVC
Scenario: Add a todo item
* I navigate to "https://demo.playwright.dev/todomvc/"
* I add a new todo item "Buy groceries"
* I should see "Buy groceries" in the todo list
Scenario: Filter completed todos
* I navigate to "https://demo.playwright.dev/todomvc/"
* I add three todo items: "Task 1", "Task 2", and "Task 3"
* I mark the first todo as completed
* I click the Active filter
* I should see 2 active todos
Standard Given/When/Then also works — both are identical under the hood.
Tips for good steps:
- Be specific about URLs:
I navigate to "https://..." not I go to the site
- Quote the exact text you expect:
I should see "Buy groceries"
- Describe intent, not mechanics:
I add a new todo item "..." not I click the input and type "..."
Moving feature files elsewhere — update the path in .openqa/playwright.config.ts (or cucumber.js):
// playwright.config.ts — point to a directory outside .openqa/
const testDir = defineBddConfig({
featuresRoot: '../features',
features: '../features/**/*.feature',
steps: 'steps/*.ts',
});
Run your tests
cd .openqa
npm run test:headed # visible browser
npm run test # headless
npm run test:report # open the HTML report
Changing Model or Provider
Your model is set in one line inside .openqa/steps/steps.ts (Playwright-BDD) or .openqa/steps/steps.js (Cucumber.js). Open that file and change the provider call — that’s it.
Change the Claude Code model:
import { runAgent, claudeCode } from 'openqa';
// Fast (default)
await runAgent(claudeCode('claude-haiku-4-5'), action, page);
// More capable
await runAgent(claudeCode('claude-sonnet-4-6'), action, page);
await runAgent(claudeCode('claude-opus-4-7'), action, page);
Switch to OpenCode — GitLab Duo or GitHub Copilot:
import { runAgent, openCode } from 'openqa'; // swap the import
// GitLab Duo (if you use gitlab.com — login with opencode auth login)
await runAgent(openCode('gitlab/duo-chat-haiku-4-5'), action, page);
// GitHub Copilot (if you use GitHub — login with opencode auth login)
await runAgent(openCode('github-copilot/gpt-5.4'), action, page);
Switch to OpenCode — Anthropic, OpenAI, or Google:
import { runAgent, openCode } from 'openqa';
await runAgent(openCode('anthropic/claude-sonnet-4-6'), action, page);
await runAgent(openCode('openai/gpt-4o'), action, page);
await runAgent(openCode('google/gemini-2.0-flash'), action, page);
Switching between claudeCode and openCode requires changing the import and the provider call. Everything else — feature files, step syntax, test runner commands — stays the same.
Examples
Browse working examples in the repository: