Skip to main content

Quickstart

Run this from your existing project root:
npx openqa init
The interactive wizard asks 4 questions — then everything is set up automatically.

Step 1: Choose your agent

AgentSDKBest for
Claude Code@anthropic-ai/claude-agent-sdkAnthropic models only
OpenCode@opencode-ai/sdkAny 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:
ModelSpeedBest for
claude-haiku-4-5FastDefault, most tests
claude-sonnet-4-6MediumComplex flows
claude-opus-4-7SlowMost demanding tasks
OpenCode models use provider/model format:
ModelProvider
gitlab/duo-chat-haiku-4-5GitLab Duo (default)
github-copilot/gpt-5.4GitHub Copilot
anthropic/claude-haiku-4-5Anthropic
openai/gpt-4oOpenAI
google/gemini-2.0-flashGoogle
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 login
One-time login. No .env needed for local runs.

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.
VariableDefaultDescription
BASE_URLApp URL — sets Playwright baseURL and injected into every agent prompt
APP_USERNAMEUsername — injected into agent prompt for login steps
APP_PASSWORDPassword — injected into prompt; always redacted from logs
OPENQA_VERBOSEtrueSet false to suppress step-by-step agent logs
HEADLESStrueSet false to watch the browser
ANTHROPIC_API_KEYAnthropic API key — CI only (use claude login locally)
OPENAI_API_KEYOpenAI API key — CI only via OpenCode
GOOGLE_API_KEYGoogle 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: