docs/CLI.md
This document describes the CLI architecture for Karate v2, including subcommand design and integration with the Rust launcher.
See also: DESIGN.md | Rust Launcher Spec
Karate v2 CLI has a two-tier architecture:
┌─────────────────────────────────────────────────────────────┐
│ Rust Launcher (karate binary) │
│ - Management commands: setup, update, config, doctor │
│ - Delegates runtime commands to JVM │
└─────────────────────────────────────────────────────────────┘
│
▼ delegates
┌─────────────────────────────────────────────────────────────┐
│ Java CLI (karate-core Main.java) │
│ - Runtime commands: run, mock, mcp, init, clean │
│ - Receives args from Rust launcher │
│ - Uses PicoCLI for parsing │
└─────────────────────────────────────────────────────────────┘
Key principle: The Rust launcher handles installation/management; the Java CLI handles test execution and runtime features.
karate <command> [options] [args]
Runtime Commands (implemented in Java):
run Run Karate tests
mock Start mock server
mcp MCP server commands
clean Clean output directories
Management Commands (implemented in Rust):
setup First-run wizard
update Check for updates
config View/edit configuration
init Initialize new project (interactive)
doctor System diagnostics
version Show version info
The Java CLI (io.karatelabs.Main) implements:
| Command | Description | Status |
|---|---|---|
run | Run Karate tests | Priority 1 |
clean | Clean output directories | Priority 2 |
mock | Start mock server | Future |
mcp | MCP server mode | Future |
Note:
initis implemented in Rust (not Java) because it needs to scaffold different project types (Maven, Gradle, standalone) before Java/JVM is involved. See Rust Launcher Spec.
run Commandkarate run [options] [paths...]
karate-pom.json in current directory--pom: Use specified project file--no-pom: Ignore karate-pom.json even if present| Pattern | Description |
|---|---|
*.feature | Gherkin feature files (standard Karate tests) |
*.karate.js | JavaScript scripts with full karate.* API |
JavaScript scripts (*.karate.js) are ideal for:
karate.fork(), karate.exec())# Run feature files
karate run tests/users.feature
# Run JS scripts
karate run tests/setup.karate.js
# Run mixed (discovers both .feature and .karate.js)
karate run tests/
# Run multiple paths from different packages in a single run
# (useful when positional args are awkward, e.g. via -Dkarate.options in Maven)
karate run -P classpath:com/comp/commission -P classpath:com/comp/statement
# Comma-separated form — tight for Maven: -Dkarate.options="--path a,b"
karate run --path classpath:com/comp/commission,classpath:com/comp/statement
You can run specific scenarios by appending :LINE to the feature file path:
# Run scenario at line 10
karate run tests/users.feature:10
# Run multiple scenarios (lines 10 and 25)
karate run tests/users.feature:10:25
# Works with classpath resources too
karate run classpath:features/users.feature:10
Line number matching:
| Line Points To | What Runs |
|---|---|
Scenario: declaration | That specific scenario |
| Any step within a scenario | That scenario |
Scenario Outline: declaration | All examples from that outline |
Examples: table | All examples from that table |
Note: Line number selection bypasses tag filters (@ignore, @env, etc.), allowing you to run specific scenarios regardless of their tags. This is useful for debugging or running individual tests from an IDE.
Use -n, --name to select scenarios by their author-assigned name. Unlike line numbers, names are stable under edits and auto-formatting — IDE plugins can use them as a line-independent identifier for gutter/run-config invocations.
# Run a single scenario by name
karate run -n "Login happy path" tests/users.feature
# The name filter scans all features in the run, so it works across a directory
karate run -n "Login happy path" tests/
# Combine with :LINE to narrow to one row of a Scenario Outline
karate run -n "Parameterized check" tests/outline.feature:9
Matching semantics:
-n " Login " still matches Login.-n with :LINE (intersection semantics — both must match).@ignore, @env) — same as :LINE. Running a specific scenario by name always wins over filters.| Option | Description |
|---|---|
-P, --path <path> | Feature file or directory to run — repeatable and/or comma-separated, combines with positional paths |
-t, --tags <expr> | Tag expression filter (e.g., @smoke, ~@slow) |
-T, --threads <n> | Parallel thread count (default: 1) |
-e, --env <name> | Karate environment (karate.env) |
-n, --name <name> | Scenario name filter (exact, whitespace-trimmed); bypasses tag filters |
-o, --output <dir> | Output directory (default: target/karate-reports) |
-w, --workdir <dir> | Working directory for relative paths |
-g, --configdir <dir> | Directory containing karate-config.js |
-p, --pom <file> | Path to project file (default: karate-pom.json) |
--no-pom | Ignore karate-pom.json even if present |
-C, --clean | Clean output directory before running |
-D, --dryrun | Skip step execution; @setup scenarios still run. See Dry Run. |
--no-color | Disable colored output |
--log-report <level> | Threshold for what gets captured into reports (HTML/JSONL/Cucumber/JUnit). Default: debug. |
--log-console <level> | Threshold for SLF4J/console output via Logback. Default: info (inherited from logback.xml). |
-f, --format <formats> | Output formats (see below) |
--listener <classes> | Comma-separated RunListener class names |
--listener-factory <classes> | Comma-separated RunListenerFactory class names |
Control report output with -f/--format. Comma-separated, use ~ to disable:
| Format | Description | Default |
|---|---|---|
html | Karate HTML reports | On |
cucumber:json | Per-feature Cucumber JSON (for Allure, ReportPortal) | Off |
junit:xml | JUnit XML reports | Off |
karate:jsonl | JSONL event stream (for CI/CD, IDEs) | Off |
Examples:
# Enable Cucumber JSON (HTML still on by default)
karate run -f cucumber:json features/
# Enable multiple formats
karate run -f cucumber:json,junit:xml features/
# Disable HTML, enable Cucumber JSON
karate run -f ~html,cucumber:json features/
# Only JSONL output (disable HTML)
karate run -f ~html,karate:jsonl features/
Log masking is configured per-scenario or globally via configure logging = { mask: ... } in feature files or karate-config.js. There is no CLI flag — masks are declarative and live with the test code that knows what's sensitive.
// karate-config.js — applies to all scenarios in this run
configure logging = {
mask: {
headers: ['Authorization', 'Cookie', 'X-Api-Key'],
jsonPaths: ['$.password', '$..token'],
patterns: [{ regex: '\\bBearer [A-Za-z0-9._-]+\\b', replacement: 'Bearer ***' }],
replacement: '***',
enableForUri: function(uri) { return uri.indexOf('/health') < 0 }
}
}
See DESIGN.md § Logging for the full shape.
# Run with auto-detected karate-pom.json
karate run
# Run specific paths (inherits env, threads, etc. from pom)
karate run src/test/features
# Run with explicit pom file
karate run --pom my-project.json
# Run ignoring karate-pom.json
karate run --no-pom src/test/features
# Run with options
karate run -t @smoke -e dev -T 5 src/test/features
# Run from different working directory
karate run -w /home/user/project src/test/features
karate-pom.jsonThe project file name is karate-pom.json (inspired by Maven's POM concept). When karate run is invoked, it automatically loads karate-pom.json from the current directory (or workdir if specified).
Note:
karate-pom.jsonis designed for non-Java teams using the standalone CLI without Maven/Gradle. Java teams should useRunner.Builderdirectly with system properties for CI/CD overrides.
Note: This is distinct from
karate-config.jswhich handles runtime configuration (baseUrl, auth, etc.). The pom file defines how to run tests (paths, tags, threads, output).
{
"paths": ["src/test/features/", "classpath:features/"],
"tags": ["@smoke", "~@slow"],
"env": "dev",
"threads": 5,
"scenarioName": "Login happy path",
"configDir": "src/test/resources",
"workingDir": "/home/user/project",
"dryRun": false,
"clean": false,
"output": {
"dir": "target/karate-reports",
"html": true,
"junitXml": false,
"cucumberJson": false,
"jsonLines": false
},
"logging": {
"report": "debug",
"console": "info"
},
"listeners": ["com.example.MyListener"],
"listenerFactories": ["com.example.MyListenerFactory"]
}
Important: outputDir and workingDir serve different purposes and are independent:
| Setting | Purpose | Relative To |
|---|---|---|
workingDir | Resolves feature paths, config files, and pom location | Process current directory |
output.dir | Where reports are written | Process current directory (NOT workingDir) |
This means if you set workingDir: "src/test/java" and output.dir: "target/karate-reports", reports go to ./target/karate-reports (not ./src/test/java/target/karate-reports).
Best practice: When the process working directory may vary (CI/CD, IDE integrations), use absolute paths for output.dir:
{
"workingDir": "/home/user/project/src/test/java",
"output": {
"dir": "/home/user/project/target/karate-reports"
}
}
CLI arguments override pom file values:
CLI flags → karate-pom.json → defaults
The pom is always loaded if present, and CLI args override specific values. Use --no-pom to ignore it entirely.
Runner.Builder.parallel() reads a small set of system properties and environment variables just before running, and overrides Builder values with them. This preserves the v1 contract used by Maven and Gradle teams: a Runner class hard-codes its defaults (paths, tags, env), and CI passes -Dkarate.options="--tags @smoke" to run a subset with zero code change.
| Sysprop | Env var fallback | Effect |
|---|---|---|
karate.options | KARATE_OPTIONS | Full option string, parsed with the karate run grammar. Overrides paths, -P, tags, threads, env, name, configdir, output, dryrun, formats, log levels |
karate.env | KARATE_ENV | Override karate environment |
karate.config.dir | KARATE_CONFIG_DIR | Override karate-config.js directory |
karate.output.dir | — | Override report output directory (honored via FileUtils) |
Examples:
# Override a Runner class's hard-coded tags from Maven
mvn test -Dtest=MyKarateTest -Dkarate.options="--tags @smoke"
# Multiple overrides in one string
mvn test -Dtest=MyKarateTest -Dkarate.options="--tags @smoke --env qa --threads 4"
# Maven user override using individual env sysprop (terse)
mvn test -Dtest=MyKarateTest -Dkarate.env=qa
# Non-Java team using the standalone CLI with env var
KARATE_OPTIONS="--tags @smoke" karate run features/
KARATE_ENV=qa karate run features/
karate.options (sysprop)
└─ KARATE_OPTIONS (env) # only if sysprop absent
└─ individual sysprops (karate.env, karate.config.dir)
└─ KARATE_ENV / KARATE_CONFIG_DIR (env) # fallback
└─ karate.output.dir
└─ Runner.Builder values (programmatic)
└─ karate-pom.json
└─ defaults
Rules:
karate.options sysprop and KARATE_OPTIONS env var are set, the sysprop wins.karate.options sets --env qa and -Dkarate.env=dev is also set, karate.options wins (it's applied last).karate.options replace Builder paths entirely (v1 parity) — not additive.karate.options string is logged at WARN and ignored; the run proceeds with Builder defaults rather than crashing.using system property 'karate.options': --tags @smokekarate.options applied: tags=[@smoke], env=qa, threads=4 — one-line summary of what actually took effecttags: ['@wip'] (Builder) → ['@smoke'] (karate.options) — useful when troubleshooting "why is the wrong tag running"invalid karate.options ignored: <message> on parse errorskarate.options can containThe option string uses the same grammar as the karate run CLI. Common flags:
| Flag | Purpose |
|---|---|
positional paths or -P, --path | Feature files/dirs (replaces Builder paths) |
-t, --tags | Tag filter |
-T, --threads | Parallel thread count |
-e, --env | Karate environment |
-n, --name | Scenario name filter (exact, whitespace-trimmed) |
-o, --output | Output directory |
-g, --configdir | Directory containing karate-config.js |
-f, --format | Output formats (html, cucumber:json, junit:xml, karate:jsonl; prefix ~ to disable) |
-D, --dryrun | Dry-run |
--log-report | Threshold for report buffer capture |
--log-console | Threshold for SLF4J/console output |
CLI-lifecycle flags (--no-pom, -p/--pom, -w/--workdir, -C/--clean, -B/--backup-reportdir, --listener, --listener-factory) are intentionally ignored when set via karate.options — they're orchestration concerns specific to invoking the CLI, not Builder state.
The option string is tokenized POSIX-shell-style, so inner double/single quotes survive and whitespace is preserved. For multiple paths that contain spaces:
mvn test -Dtest=UsersTest '-Dkarate.options=-P "src/features/happy path" -P "src/features/edge cases"'
Outer single quotes stop the shell from interpreting the inner doubles; inner doubles group each path into a single token inside the handler.
Refactor Main.java to use PicoCLI subcommands:
@Command(
name = "karate",
subcommands = {
RunCommand.class,
CleanCommand.class,
}
)
public class Main implements Callable<Integer> {
@Parameters(arity = "0..*", hidden = true)
List<String> unknownArgs;
@Override
public Integer call() {
// No subcommand specified
if (unknownArgs != null && !unknownArgs.isEmpty()) {
// Legacy: treat args as paths, delegate to run
return new RunCommand().runWithPaths(unknownArgs);
}
// Look for karate-pom.json in cwd
if (Files.exists(Path.of("karate-pom.json"))) {
return new RunCommand().call();
}
// Show help
CommandLine.usage(this, System.out);
return 0;
}
}
Extract current Main.java logic into RunCommand:
@Command(name = "run", description = "Run Karate tests")
public class RunCommand implements Callable<Integer> {
public static final String DEFAULT_POM_FILE = "karate-pom.json";
@Parameters(description = "Feature files or directories", arity = "0..*")
List<String> paths;
@Option(names = {"-p", "--pom"}, description = "Project file (default: karate-pom.json)")
String pomFile;
@Option(names = {"--no-pom"}, description = "Ignore karate-pom.json")
boolean noPom;
// ... other options ...
@Override
public Integer call() {
// Load pom if present (unless --no-pom)
if (!noPom) {
loadPom();
}
// CLI args override pom values
// ... rest of execution
}
}
@Command(name = "clean", description = "Clean output directories")
public class CleanCommand implements Callable<Integer> {
@Option(names = {"-o", "--output"}, description = "Output directory to clean")
String outputDir;
@Override
public Integer call() {
// Cleans:
// - karate-reports/
// - karate-reports_*/ (backup directories)
// - karate-temp/ (Chrome user data, callSingle cache, etc.)
return 0;
}
}
Karate uses a standardized temp directory for runtime artifacts:
target/ (or build/ for Gradle)
├── karate-reports/ # Test reports (HTML, JSON, etc.)
├── karate-reports_20260125_*/ # Backup directories (when backupReportDir enabled)
└── karate-temp/ # Runtime temp files (cleaned by 'karate clean')
├── chrome-abc12345/ # Chrome/browser user data directories
└── cache/ # callSingle disk cache files
Build directory detection: Karate automatically detects Maven vs Gradle projects:
build.gradle, build.gradle.kts, settings.gradle* → uses build/pom.xml → uses target/build/ or target/ directories-Dkarate.output.dir=custom/pathFor backward compatibility, bare arguments (no subcommand) are treated as paths:
| Invocation | Interpretation |
|---|---|
karate run src/test | Explicit run command |
karate src/test | Legacy → delegates to run |
karate | Auto-load karate-pom.json or show help |
karate -t @smoke src/test | Legacy with options → delegates to run |
karate run and legacy karate <paths>The Rust launcher (see spec.md) delegates runtime commands to the Java CLI:
karate run src/test/features -t @smoke
│
▼ Rust launcher
java -jar ~/.karate/dist/karate-2.0.0.jar run src/test/features -t @smoke
│
▼ Java CLI
Main.java parses and executes
The Rust launcher constructs the classpath in this order:
~/.karate/dist/karate-X.X.X.jar)~/.karate/ext/*.jar).karate/ext/*.jar)--cp flagsThe --cp global flag allows extensions (e.g. IDE integrations) to contribute proprietary JARs:
karate --cp /path/to/karate-ide-v2.jar run features/
karate --cp /path/to/a.jar --cp /path/to/b.jar run features/
Configured via karate-cli.json:
{
"jvm_opts": "-Xmx512m"
}
karate mockStart a mock server:
karate mock --port 8080 mocks/
karate mcpStart MCP server mode for LLM integration:
karate mcp --stdio
Note:
karate initis implemented in Rust. See Rust Launcher Spec for details.
karate perfRun performance tests with Gatling (requires karate-gatling-bundle.jar in ~/.karate/ext/):
karate perf --users 10 --duration 30s features/
See GATLING.md for full documentation.
Modules can register additional subcommands via ServiceLoader:
// io.karatelabs.cli.CommandProvider
public interface CommandProvider {
String getName(); // e.g., "perf"
String getDescription(); // e.g., "Run performance tests"
Object getCommand(); // PicoCLI command instance
}
Registration: Create META-INF/services/io.karatelabs.cli.CommandProvider with implementation class name.
Discovery: Main.java discovers providers on classpath:
ServiceLoader<CommandProvider> providers = ServiceLoader.load(CommandProvider.class);
for (CommandProvider provider : providers) {
spec.addSubcommand(provider.getName(), provider.getCommand());
}
Use case: The karate-gatling module uses this to add the perf subcommand when its JAR is in ~/.karate/ext/.
Currently karate-pom.json is read-only (JSON → config). Implement bidirectional conversion:
KaratePom.toJson() - serialize config back to JSONRunCommand.toPom() - convert CLI args to KaratePom objectkarate config --export - dump effective merged config as JSONkarate config --show - display current effective configurationUse cases:
karate init generating starter karate-pom.json with user's CLI preferences| Code | Meaning |
|---|---|
0 | Success (all tests passed) |
1 | Test failures |
2 | Configuration error |
3 | Runtime error |
After subcommand refactoring:
io.karatelabs/
├── Main.java # Parent command, delegates to subcommands
└── cli/
├── RunCommand.java # karate run
└── CleanCommand.java # karate clean
io.karatelabs.core/
├── KaratePom.java # JSON pom parsing
├── Runner.java # Programmatic API
└── ...
A test script is provided at etc/test-cli.sh. The home/ folder is gitignored for test workspaces.
Build project:
# Standard build
mvn clean install -DskipTests
# Build fatjar (for simpler testing)
mvn clean package -DskipTests -Pfatjar
Test workspace (already set up in home/test-project/):
home/test-project/
├── karate-pom.json # Project file
└── features/
└── hello.feature # Test feature with @smoke, @api tags
Option 1: Using test-cli.sh (recommended)
The script auto-detects fatjar or builds classpath:
# Help
./etc/test-cli.sh --help
# Run tests
./etc/test-cli.sh home/test-project/features
# Run with workdir
./etc/test-cli.sh -w home/test-project features
# Run with explicit pom
./etc/test-cli.sh -p home/test-project/karate-pom.json
Option 2: Using fatjar directly
# Build fatjar first
mvn package -DskipTests -Pfatjar
# Run
java -jar karate-core/target/karate.jar --help
java -jar karate-core/target/karate.jar home/test-project/features
Option 3: Using mvn exec:java
cd karate-core
mvn exec:java -Dexec.mainClass="io.karatelabs.Main" \
-Dexec.args="--help"
The fatjar profile is configured in karate-core/pom.xml:
# Build fatjar
mvn package -DskipTests -Pfatjar
# Output: karate-core/target/karate.jar
| Test | Command | Expected |
|---|---|---|
| Help | --help | Shows usage with all options |
| Version | --version | Shows "Karate 2.0" |
| Run with paths | home/test-project/features | Executes tests, reports to default dir |
| Run with pom | -p home/test-project/karate-pom.json | Loads pom, uses pom paths |
| Run with workdir | -w home/test-project features | Clean relative paths in reports |
| Run with env | -e dev features | Sets karate.env |
| Run with tags | -t @smoke features | Filters by tag |
| Run no pom | --no-pom features | Ignores karate-pom.json |
| Dry run | -D features | Parses but doesn't execute |
| Clean | -C -o home/test-project/target features | Cleans output before run |
After running tests, verify:
# Reports generated
ls -la home/test-project/target/reports/
# Expected files
# - karate-summary.html
# - feature-html/*.html (per-feature reports)
rm -rf home/test-project/target