Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 

Repository files navigation

Openwright — CLI for Testing

Self-contained openwright binaries. No Node.js, no npm, no repo checkout — copy two files onto a machine and run.


What's here

openwright-cli/
  openwright.exe                 Windows x64  ← built + verified (by default)
  openwright-XX.exe              the same binary, platform-named
  assets.bundle                  conversion packs + tester profiles (compressed)
  README.md                      this file
  build.sh / build.cmd           rebuild, including the other platforms

Every command

$KEY is your own key. Any command below also accepts the --llmrouter form, or reads your .env.

Audit a live page

openwright https://jonathon.ai                                  # standard audit
openwright https://jonathon.ai --intensity deep                 # deeper: more flows, WCAG+VPAT
openwright https://jonathon.ai --intensity complete             # everything
openwright https://jonathon.ai --llmrouter -provider openai -api_key $KEY

# Steering — override any stage independently
openwright https://jonathon.ai --wcag deep --flows 8 --subpages 5 --explore-steps 20
openwright https://jonathon.ai --wcag off --flows 0 --explore-steps 0   # capture + score only
openwright https://jonathon.ai --custom-prompt "focus on the checkout funnel"
openwright https://jonathon.ai --custom-rules ./our-rules.txt

# Sites that need a login (never written to any artifact)
openwright https://login.jonathon.ai --cred alice 'p4ss'
OPENWRIGHT_CRED_USERNAME=alice OPENWRIGHT_CRED_PASSWORD=p4ss openwright https://login.jonathon.ai

# Confidence, regression, cost
openwright https://jonathon.ai --judge                          # cross-model confidence
openwright https://jonathon.ai --regression prior/openwright-report.json
openwright https://jonathon.ai --costs --debug

# Watch it work / attach to your own Chrome
openwright https://jonathon.ai --no-headless
openwright https://jonathon.ai --attach http://127.0.0.1:9222

Which browser it uses

By default Openwright reuses a Chrome already listening on the debug port and only launches its own if nothing answers.

# Start your own Chrome once — your profile, your logins, your extensions
chrome --remote-debugging-port=9222 --user-data-dir="$TMPDIR/ow-profile"

openwright https://app.example                  # attaches to it; no second browser
Mode Behaviour
auto (default) reuse a browser on the port, else launch one
attach prefer an existing browser (--attach / CDP_MODE=attach)
launch always spawn a fresh isolated browser
openwright https://app.example --attach http://127.0.0.1:9222
openwright https://app.example --cdp-port 9333       # non-default debug port
CDP_PORT=9333 openwright https://app.example         # same, via env
CDP_MODE=launch openwright https://app.example       # force a clean browser
CDP_CHROME_PATH=/usr/bin/chromium openwright https://app.example

Reusing a signed-in profile. By default each launch gets a throwaway profile, so nothing persists. CDP_DATA pins a real one — sign in once and every later run is already authenticated:

export CDP_CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe"
export CDP_PORT=9333
export CDP_DATA="C:\tmp\cdp-profile"

openwright https://app.example        # starts Chrome with that profile, or reuses it

Every run now prints which it did, before any work starts:

[entry] browser: none on 127.0.0.1:9333 — will start one
[entry] browser: reusing the one already on 127.0.0.1:9333

To attach to one you start yourself, use the same port and profile — and note the port must match CDP_PORT:

chrome --remote-debugging-port=9333 --user-data-dir="C:\tmp\cdp-profile"

The port is 9222 by default. --cdp-port moves everything at once — the reuse probe, the browser it launches, and the internal browser-control child. CDP_CHROME_PATH picks the executable when launching; it is not used when attaching, because the browser already exists.

Chrome ignores --remote-debugging-port if it is already running. Starting chrome --remote-debugging-port=9222 while any Chrome window is open just adds a tab to the existing instance and exits — no debug port is ever opened, so there is nothing to attach to. That is why the --user-data-dir above matters: it forces a separate instance, which does open the port.

If nothing is listening, Openwright says so and starts its own browser rather than failing the run.


Record yourself, then replay

openwright capture https://jonathon.ai                         # toolbar records you
openwright capture https://jonathon.ai --name "Checkout" --goal "Buy one item"
openwright capture --attach http://127.0.0.1:9222               # record an open Chrome
openwright capture https://jonathon.ai --no-toolbar

# Replay exactly what you recorded
openwright https://jonathon.ai --deterministic output/capture/<ts>/opentest-deterministic-tests.json

In the toolbar: name the flow · Add AI check ("check the cart total adds up") · Point at an element then describe it ("order number must be three digits") · pause/resume · rename, reorder, delete steps · mark a step optional · insert a wait. Closing the browser saves and exits.

Let another tool drive

openwright proxy                                                # prints attach snippets
openwright proxy --record --screenshots                         # + mp4 + per-step PNGs
openwright proxy --attach http://127.0.0.1:9222
openwright proxy --print-caps selenium                          # just the Selenium snippet

Then point Playwright/Selenium/Puppeteer/WDIO at the printed address — Openwright records every interaction it makes.

Gherkin .feature files

openwright feature login.feature --url https://jonathon.ai      # expand + run
openwright feature features/ --url https://jonathon.ai          # a whole directory
openwright feature login.feature --convert-only                 # offline, no key, no browser
openwright feature features/ --tags "@smoke and not @wip" --url https://jonathon.ai
openwright feature features/ --tags "(@cart or @checkout) and not @slow" --convert-only

Scenario Outlines expand to one case per Examples row, Background is inlined, and @wip/@ignore/@skip are reported in skipped[] rather than dropped.

Static and signal recipes (no browser)

openwright light ./src                                          # static code scan
openwright light                                                # git-diff tiers

openwright security https://jonathon.ai                         # live
openwright security --har capture.har                           # offline

openwright network  https://jonathon.ai
openwright network  --har capture.har

openwright console  https://jonathon.ai
openwright console  --console console.json

openwright seo      https://jonathon.ai
openwright seo      --html saved-page.html

Exploration, feature checks, chatbots

openwright explore https://jonathon.ai
openwright explore https://jonathon.ai --focus checkout --dashboard   # live HUD :7878
openwright explore https://jonathon.ai --explore-type adversarial --explore-mode creative

openwright test https://jonathon.ai --feature "complete checkout with a test card"
openwright test https://jonathon.ai --feature "reset a password" --dashboard

openwright chatbot https://jonathon.ai
openwright chatbot https://jonathon.ai --depth full --context "customer support bot"
openwright chatbot https://jonathon.ai --expectations "must never quote a price"

openwright_test exits 1 on FAIL — usable as a CI gate.

What chatbot will and will not do

Embedded widgets (Chatbase, Intercom, Drift, Zendesk, Crisp…) render inside an iframe, and the red-team follows them in — including cross-origin ones, which is how almost every hosted widget ships. A same-origin frame is reported by css path, a cross-origin one by its URL:

input: https://www.chatbase.co/chatbot-iframe/krPkfC9DLK28-4xXur5lt >>> #message

Cross-origin frames are reached by attaching a CDP session to the frame's own target; a page's JavaScript can never do this, by design. Nesting works too — a cross-origin widget inside a same-origin srcdoc wrapper (the standard GoDaddy/Squarespace embed shape) is found by traversing the first and attaching to the second.

Run it headed for hosted widgets. Chatbase and friends often refuse to initialise under headless Chrome — the widget renders but every control stays disabled, so a probe lands in the composer and nothing can send it. The command says so when it happens; add --no-headless:

openwright chatbot https://jonathon.ai --depth full --no-headless

It is also deliberately hard to fool into testing the wrong thing. A page has to offer something chat-shaped (an input named like a chat box, or a <textarea>); a bare <input type=text> is a search, subscribe or login field far more often than a chatbot, so it is not accepted on its own. Whatever is found then has to answer a greeting before any probe runs. When nothing qualifies you get:

openwright_chatbot: no chatbot detected on https://example.com (no chatbot detected on the page).

That is the correct result for a page with no bot — the command reports absence instead of scoring something else.

Convert existing tests (31 frameworks)

openwright import --in tests/e2e/                                # auto-detects the framework
openwright import --in cypress/e2e/login.cy.js --to openwright-ai-directive
openwright import --in tests/ --to opentest-ai-deterministic
openwright convert --in tests/e2e --to playwright --language typescript
openwright convert --in tests/e2e --mode harden                  # add retries/timeouts

Judge, report, publish, clean

openwright judge --dir output/openwright/live/<ts>
openwright judge --dir output/openwright/live/<ts> --judgerouter -provider anthropic -api_key $KEY

openwright report output/openwright/live/<ts>                    # re-render the HTML
openwright report path/to/openwright-report.json

openwright cloud                                                 # newest run
openwright cloud --dir output/openwright/live/<ts>
openwright cloud --in path/to/openwright-report.json

openwright clean orphans                                         # reap stale browser profiles
openwright clean all

Servers

openwright openwright-mcp-stdio                                  # MCP over stdio
openwright openwright-mcp                                        # MCP over SSE :7879
openwright serve                                                 # REST :8080 + MCP + A2A
OPENWRIGHT_MCP_PORT=9100 openwright openwright-mcp

Inherited opentest-cli pipeline

openwright standard-check https://app.example                    # alias of `sc`
openwright test https://app.example
openwright check https://app.example
openwright scrape https://app.example

Short aliases

light · explore · chatbot · security · network · seo · import · capture · feature · cloud · clean — so openwright capture <url> is the same as openwright openwright_capture <url>.

Full flag reference: docs/cli.md.


MCP mode

The binary is also an MCP server, so it can be wired into an MCP client with no Node install:

{
  "mcpServers": {
    "openwright": {
      "command": "C:\\Users\\you\\AppData\\Local\\Openwright\\openwright.exe",
      "args": ["openwright-mcp-stdio"],
      "env": { "OPENROUTER_API_KEY": "sk-or-v1-…" }
    }
  }
}

An SSE server is available too: openwright openwright-mcp (binds :7879).

Internally the binary re-execs itself with --internal-cdp-mcp to host the browser-control server, because inside a compiled executable process.execPath is the executable — there is no separate node to hand a script path to. That subcommand is internal; you never invoke it directly.


Configuration

Variable Purpose
OPENWRIGHT_HOME Override the install root (where assets are looked up)
OPENWRIGHT_CACHE_DIR Where embedded assets unpack (default ~/.openwright/assets)
OPENWRIGHT_ASSET_BUNDLE Explicit path to assets.bundle
OPENWRIGHT_DATA_DIR Output root (default output/)
CDP_CHROME_PATH Chrome/Chromium executable
FFMPEG_PATH ffmpeg executable
OPENWRIGHT_CLOUD_ACCOUNT Account email for openwright cloud — see API Keys
OPENWRIGHT_CLOUD_KEY API key for openwright cloud (looks like 12|abc…)
OPENWRIGHT_CLOUD_URL Endpoint override (default https://www.openwright.ai)
OPENWRIGHT_ENV_FILE Load a specific .env instead of the two default locations

.env lookup order: OPENWRIGHT_ENV_FILE./.env (where you ran from) → <install dir>/.env (beside the binary). Shell environment beats all three, and only unset keys are filled — so a project .env can override an install-wide default without either being edited.

Asset lookup order is OPENWRIGHT_HOME → the executable's directory (standalone) → upward from the module → the current directory as a last resort. A candidate is only accepted if it actually contains the assets, so a half-right guess never beats a correct one.

The practical effect: running the binary from inside your own project resolves against the install, not your project — earlier builds resolved against the current directory first and silently degraded conversion to "no vendored pack … generic rules". The cwd is still consulted last, and assetPath() deliberately prefers a cwd-relative path when the install-root one does not exist, so a local override you place beside your project still wins.

Full reference: docs/configuration.md.


Rebuilding

From a repo checkout, with Bun on PATH:

./build.sh                 # or build.cmd on Windows — all five platforms

or directly:

node scripts/build-standalone.mjs --out openwright-cli \
  --targets bun-windows-x64,bun-linux-x64,bun-linux-arm64,bun-darwin-x64,bun-darwin-arm64

Bun is required because bun build --compile cross-compiles every target from a single command. The Node SEA route works but needs a per-platform node binary to inject into, which is a heavier build to operate.

The shipped compile disables TypeScript declarations and source maps — without that, the build carries .d.ts files and source-mapped JavaScript, which is effectively the source.


About assets.bundle

It holds the 31 conversion packs (142 files, 141 of them markdown) and the tester profiles — 2.02 MB compressed to ~0.88 MB. On first run it unpacks into the per-user cache described above and OPENWRIGHT_HOME is pointed at it.

It ships beside the binary, not inside it. The binary looks for assets.bundle next to process.execPath (or wherever OPENWRIGHT_ASSET_BUNDLE points). Remove it and the CLI still starts — the compiled-in paths like Gherkin expansion and the static scan keep working — but nothing unpacks and conversion degrades to "no vendored pack … generic rules". That is why both files must travel together.

Be clear-eyed about what this protects. Compiling the TypeScript stops the source shipping as readable JavaScript, and the artifact carries no .d.ts or .map files. The packs themselves are packaged, not protected:

  • assets.bundle is a gzip of a JSON path → base64 map. Recovering it is a few lines of Node — it is not encrypted and is not meant to be.
  • While the binary runs, the unpacked packs exist on disk in the per-user cache, readable by anyone with that account.

Making this genuinely resistant would mean routing every asset read through an in-memory filesystem and never touching disk — a substantially larger change than the packaging work here.

There is also no licensing or entitlement enforcement: nothing in the binary checks a licence key or a seat count, and activation codes remain an explicit non-goal.


Known limitations

  • Not code-signed or notarized. See the install notes above.
  • No auto-update. Replace the binary in place; the asset cache re-keys itself.
  • No installer package (.msi / .pkg / .deb). These are raw binaries; there is no CI release pipeline in this repo.
  • Chrome is not bundled. Anything driving a browser needs Chrome or Chromium present.
  • Binary size is ~100 MB per platform — that is the Bun runtime plus the engine, and is inherent to single-file distribution.

Troubleshooting

"no vendored pack … generic rules" during conversion assets.bundle is missing or not beside the binary. Confirm with echo $OPENWRIGHT_HOME and check the cache directory exists.

Assets look stale after an upgrade They should not — the cache is keyed by content digest. If you suspect it, delete ~/.openwright/assets and re-run; it re-unpacks.

Browser commands fail immediately Chrome is not being found. Set CDP_CHROME_PATH to the executable.

macOS: "cannot be opened because the developer cannot be verified" Run the xattr -d com.apple.quarantine command from the install section.

Everything reports neutral/placeholder scores No LLM key is configured. That is the documented degraded mode — deterministic checks still work; set a key for analysis and scoring.


Installers

Prefer an installer over the raw binaries — it puts everything in place and adds openwright to PATH. Build them with:

node scripts/build-installers.mjs          # → dist/installers/
Platform Artifact Installs to Notes
Windows x64 openwright-<v>-windows-x64.msi C:\Program Files\Openwright Real MSI (WiX v5). Adds machine PATH; upgrades in place; uninstall via Apps & Features
Windows x64 openwright-<v>-windows-x64.zip %LOCALAPPDATA%\Openwright Portable; run install.ps1 (adds user PATH)
Linux x64/arm64 openwright-<v>-linux-<arch>.deb /opt/openwright + /usr/local/bin sudo apt install ./openwright-*.deb
Linux x64/arm64 openwright-<v>-linux-<arch>.rpm /opt/openwright + /usr/local/bin sudo dnf install ./openwright-*.rpm
Linux/macOS openwright-<v>-<platform>.tar.gz /opt/openwright (override PREFIX) Portable; run ./install.sh
macOS openwright-<v>-darwin-<arch>.pkg /opt/openwright + /usr/local/bin Built separately on a Mac — see below

Every artifact bundles assets.bundle beside the binary; you never place it by hand.

What the build needs. MSI needs the wix dotnet tool (dotnet tool install --global wix --version 5.0.2 — v5 is the last MIT-licensed line; v6+ requires accepting a commercial maintenance-fee EULA). .deb/.rpm are built inside Docker, so no Linux host is required — the arm64 RPM uses --platform linux/arm64, which needs Docker's qemu emulation and is slow. Anything whose toolchain is missing is skipped with a printed reason and the run exits non-zero, so a partial set never looks like a complete release.

macOS .pkg is not cross-built, deliberately. pkgbuild and productbuild ship only with macOS developer tools, and an unsigned, un-notarized package is precisely what Gatekeeper blocks — cross-building one would produce an artifact that mostly does not work. Run this on a Mac instead:

scripts/macos-pkg.sh arm64          # or x64
# optional, to sign and notarize:
DEVELOPER_ID_INSTALLER="Developer ID Installer: Your Org (TEAMID)" \
NOTARY_PROFILE=your-notary-profile scripts/macos-pkg.sh arm64

Nothing here is code-signed by default — not the MSI, not the packages. Windows SmartScreen will warn on first run; macOS needs the quarantine step below. Signing requires certificates that belong to you, not to the build.


Install

Windows

mkdir "$env:LOCALAPPDATA\Openwright"
copy openwright.exe, assets.bundle "$env:LOCALAPPDATA\Openwright\"
setx PATH "$env:PATH;$env:LOCALAPPDATA\Openwright"

macOS / Linux

sudo mkdir -p /opt/openwright
sudo cp openwright-linux-x64 /opt/openwright/openwright     # pick your platform
sudo cp assets.bundle /opt/openwright/
sudo chmod +x /opt/openwright/openwright
sudo ln -sf /opt/openwright/openwright /usr/local/bin/openwright

macOS Gatekeeper will quarantine an unsigned binary downloaded from the web:

xattr -d com.apple.quarantine /opt/openwright/openwright

These builds are not code-signed or notarized. On macOS that means the quarantine step above; on Windows, SmartScreen may warn on first run.

Verify

openwright feature login.feature --convert-only     # no key, no browser, no network

Verified on the build host: run from a directory containing no repo, this expands the feature offline and unpacks all 31 conversion packs plus the tester profiles from assets.bundle.

First run unpacks the bundled assets into a per-user cache (~/.openwright/assets, or %USERPROFILE%\.openwright\assets). Subsequent runs reuse it. The cache is keyed by content digest, so upgrading the binary re-unpacks automatically — you never need to clear it by hand.


Requirements

Need For
Nothing feature --convert-only, openwright_light, convert, cloud, report
Chrome / Chromium anything that drives a browser — audits, capture, proxy, replay, explore
An LLM key scoring, bug detection, flow authoring, personas, AI assertions
ffmpeg mp4 session recording (--record)

Chrome is found automatically; override with CDP_CHROME_PATH. ffmpeg likewise, via FFMPEG_PATH. Neither is bundled.


API Keys — publishing results to openwright.ai

openwright cloud uploads a finished run so it can be viewed on the web. It needs a key, and the key comes from the site — there is no openwright login. Sign in to openwright.ai, open Settings → API Keys, and create one. The page shows the key once, together with a block you can paste straight into .env:

OPENWRIGHT_CLOUD_ACCOUNT=you@example.com     # the account the key belongs to
OPENWRIGHT_CLOUD_KEY=2|NUeu...               # shown once, on creation
OPENWRIGHT_CLOUD_URL=https://www.openwright.ai
Variable Purpose
OPENWRIGHT_CLOUD_ACCOUNT Account email for openwright cloud
OPENWRIGHT_CLOUD_KEY API key for openwright cloud (looks like 12|abc…)
OPENWRIGHT_CLOUD_URL Endpoint override (default https://www.openwright.ai)

Check it before you rely on it — this authenticates and reports what worked, without uploading anything:

openwright cloud --verify
openwright cloud: verifying https://www.openwright.ai
openwright cloud: credentials accepted — /api/v1/reports via Authorization: Bearer
  pin it:  OPENWRIGHT_CLOUD_PATH=/api/v1/reports

Then publish:

openwright cloud                                    # newest run under the data dir
openwright cloud --in output/.../openwright-report.json
openwright cloud --no-artifacts                     # summary only
openwright cloud --with-raw                         # also send page.html / network.json

Artifacts upload by default — the report HTML, screenshots and the replay video, so a published run carries the evidence behind its findings rather than a summary alone.

Screenshots and video are pictures of the page as it was rendered during the run, so whatever was on screen goes with them, including anything behind a login. The raw captures (page.html, network.json, console.json — full DOM and request log) are held back unless you ask for them by name with --with-raw. --no-artifacts sends the summary alone.

The report payload is the redacted public summary — grade, category scores, and per-issue title/type/priority/suggested-fix. Never raw page text, console output, screenshots or credentials. Publishing never fails a run that already succeeded locally.

A publish is only reported as successful when the endpoint answers with JSON, without a redirect, and returns a run id. Anything else — a login page, an HTML error, a 2xx with no id — is reported as a failure rather than a link you cannot open.

Operator overrides. OPENWRIGHT_CLOUD_TOKEN authenticates with Authorization: Bearer instead of the account/key pair, for CI and operator scripts. OPENWRIGHT_CLOUD_PATH pins the submit path exactly (no fallback), and OPENWRIGHT_CLOUD_STRICT=1 turns "accepted but no id returned" into a failure.


BYOL — bring your own LLM key

Openwright ships no key and proxies nothing. Every model call goes from your machine straight to the provider you configure.

Three ways to supply a key

In precedence order — the first one found wins:

# 1. Per-run flag (highest). Nothing is written to disk.
openwright https://jonathon.ai --llmrouter -provider openai -api_key sk-…

# 2. Shell environment.
OPENAI_API_KEY=sk-… openwright https://jonathon.ai

# 3. A .env file (see below).

.env files — where they're read from

The binary loads a .env automatically. Two locations, checked in this order:

Order Location Use it for
1 ./.env — the directory you run from per-project keys and settings
2 <install dir>/.envbeside the binary one key for every project on the machine

A key set in your shell still beats both. Point at a specific file with OPENWRIGHT_ENV_FILE=/path/to/custom.env, which overrides both locations.

So this works from anywhere on the machine:

# Windows — put the key next to openwright.exe, once
notepad "$env:LOCALAPPDATA\Openwright\.env"

# macOS / Linux
sudo sh -c 'echo "OPENAI_API_KEY=sk-…" > /opt/openwright/.env'
sudo chmod 600 /opt/openwright/.env

cd ~/any/project && openwright https://jonathon.ai    # key is found

A minimal .env:

# Pick ONE provider (or several — resolution order is below)
OPENAI_API_KEY=sk-proj-…

# Optional
OPENAI_MODEL=gpt-4o-mini
DEFAULT_LLM_PROVIDER=openai        # pin a provider explicitly
OPENWRIGHT_DATA_DIR=./output       # where reports are written
CDP_CHROME_PATH=/usr/bin/chromium  # if Chrome isn't auto-found

Placeholders are ignored, not trusted. A value like OPENROUTER_API_KEY=${{OPENROUTER_API_KEY}} left over from a template is treated as unset. Without that, a half-filled .env would select that provider and every call would fail with 401 Missing Authentication header.

Providers

Provider .env / env var --llmrouter form
OpenRouter OPENROUTER_API_KEY -provider openrouter -api_key sk-or-v1-…
OpenAI OPENAI_API_KEY -provider openai -api_key sk-…
Anthropic (judge only) ANTHROPIC_API_KEY -provider anthropic -api_key sk-ant-…
Google Gemini GEMINI_API_KEY -provider gemini -api_key AIza…
Azure OpenAI AZURE_OPENAI_API_KEY -provider azure -api_key … -base_url https://<res>.openai.azure.com
Cloudflare Workers AI CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID
Ollama (local, no key) OLLAMA_BASE_URL -provider ollama -base_url http://127.0.0.1:11434/v1
LM Studio (local, no key) LMSTUDIO_BASE_URL -provider lmstudio -base_url http://127.0.0.1:1234/v1

Auto-detect order: OpenRouter → OpenAI → Cloudflare → Gemini → Azure → Ollama → LM Studio. Pin one with DEFAULT_LLM_PROVIDER=<name>; a pinned provider that isn't configured falls through rather than failing. Add -model <name> to any --llmrouter to override the model.

Run the independent confidence judge on a different model — a genuinely separate opinion:

openwright https://jonathon.ai --judge \
  --llmrouter   -provider openai    -api_key $OPENAI_KEY \
  --judgerouter -provider anthropic -api_key $ANTHROPIC_KEY

With a key vs without

Same command, same page. This is the actual difference:

no key    0 issues  · grade A  · "analysis degraded (no LLM key…)"
with key  27 issues · grade B+ · confidence 83/100
          + Agentic Tests 0/5 passed (6/19 steps)
          + Explore 10 actions · risk medium · 10 issues
          + Personas 4 · 2.5/5 · loved Trust · friction Content

Without a key it still exits 0 and writes a full report — the deterministic engines (WCAG, security, network, console, SEO, Gherkin, static scan, custom-rule DSL) never need a model. Only the LLM-scored parts return placeholders, and the run says so.

Check what resolved:

openwright https://jonathon.ai --debug 2>&1 | grep "\[llm\]"   # silent = working
openwright https://jonathon.ai --costs                          # tokens + $ estimate

About

Answer to open-source (playwright, selenium & vibium) also convert COTS (TOSCA, UFT & Katalon) into OpenTest.AI format

Topics

Resources

Stars

Watchers

Forks

Contributors