docs/DESIGN.md
Start here. This is the primary reference for LLMs and maintainers working on the Karate codebase.
See also: PRINCIPLES.md | CLI.md | JS_ENGINE.md | TODOS.md
Suite → FeatureRuntime → ScenarioRuntime → StepExecutor
↓
┌────────────────┼────────────────┐
▼ ▼ ▼
Match Engine Http Client Other Actions
karate/
├── karate-js/ # JavaScript engine, Gherkin parser, lexer
│ └── io.karatelabs.js # JsEngine, JsValue, GherkinParser, JsParser
├── karate-core/ # Runtime, HTTP, matching, mocks, reports, templating
│ └── io.karatelabs.* # See packages below
├── karate-junit6/ # JUnit 6 integration
├── karate-gatling/ # Performance testing (Gatling integration)
└── docs/ # Design docs (this file)
| Package | Purpose |
|---|---|
io.karatelabs | Suite, Runner, FeatureRuntime, ScenarioRuntime, StepExecutor |
io.karatelabs.http | ApacheHttpClient, HttpClientFactory, MockServer, WebSocket |
io.karatelabs.match | Match engine (EQUALS, CONTAINS, WITHIN, etc.) |
io.karatelabs.output | Reports (HTML, Cucumber JSON, JUnit XML, JSONL), LogContext |
io.karatelabs.template | Thymeleaf-based HTML templating |
io.karatelabs.driver | Browser automation (CDP, W3C WebDriver) |
| Class | Role |
|---|---|
Suite | Top-level orchestrator, config, parallel execution |
FeatureRuntime | Feature execution, scenario iteration, callOnce caching |
ScenarioRuntime | Scenario execution, variable scope, implements KarateJsContext |
StepExecutor | Keyword dispatch (def, match, url, method, etc.) |
KarateJs | JS engine bridge, karate.* API methods |
KarateJsBase | Shared state and infrastructure for KarateJs |
KarateJsUtils | Stateless utility methods (karate.filter, karate.map, etc.) |
HttpClientFactory | Factory for HTTP clients (extensible for Gatling pooling) |
Runner | Fluent API entry point for test execution |
CommandProvider | SPI for CLI subcommand registration |
def, set, remove, text, json, xml, csv, yaml, string, xmlstring, copy, table, replacematch (all operators), assert, printurl, path, param, params, header, headers, cookie, cookies, form field, form fields, request, retry until, method, status, multipart file/field/fields/files/entitycall, callonce, eval, docconfigure (ssl, proxy, timeouts, headers, cookies, auth, retry, report, etc.)| Tag | Description |
|---|---|
@ignore | Skip execution (but still callable via call read(...)) |
@env=<name> | Run only when karate.env matches |
@envnot=<name> | Skip when karate.env matches |
@setup | Data provider for dynamic outlines |
@fail | Expect failure (invert result) |
@lock=name | Named mutual exclusion (same name = sequential) |
@lock=* | Exclusive execution (no other scenarios run concurrently) |
@report=false | Scenario runs and counts toward suite totals, but its step detail is suppressed from HTML / Cucumber JSON / JUnit XML / JSONL outputs. Failures surface only a redacted message; full detail still hits runtime logs. Inherits into any features called from this scenario. Use for runs where step content (HTTP bodies, error messages) may include secrets that mustn't reach CI artifacts. |
@skipped | Synthetic — engine auto-adds in the result's tags when a scenario was aborted (via karate.abort() or suite abort). Informational only; surfaces in the HTML report's tag chips. skippedCount is additive — skipped scenarios still count as passed (no breaking change to existing counts). |
| Method | Scope | Use Case |
|---|---|---|
callonce | Feature-scoped | Shared setup within a feature |
karate.callSingle() | Suite-scoped | Global setup (e.g., auth token). Supports disk caching via configure callSingleCache |
Runner.path("features/users.feature:10:25") — selects scenarios by line. Bypasses all tag filters including @ignore. Essential for IDE integrations.
Runner.Builder.scenarioName("Login happy path") (CLI: -n/--name) — selects scenarios by exact name, trimmed on both sides. Same tag-bypass semantics as the line filter; intersects with :LINE when both are set (for Scenario Outline row targeting). Stable under edits — IDE plugins use this as a line-independent key. Source: FeatureRuntime.matchesScenarioName.
Runner.Builder.dryRun(true) or CLI -D/--dryrun skips step execution and still produces a full report. Intended for fast feature-file validation, outline-expansion sanity checks, and CI smoke passes that don't need real I/O.
Under dry-run:
@setup scenario is recorded as passed with 0ms duration — no HTTP, no match, no def, no side effects.karate-base.js, karate-config.js, and env-specific config JS are not evaluated for non-@setup scenarios.beforeScenario / afterScenario hooks are skipped for non-@setup scenarios.@setup scenarios execute fully, so dynamic outlines (Examples: | karate.setup().data |) still resolve their rows.Escape hatch — karate.dryRun. A boolean readable from any step, useful inside @setup to short-circuit expensive fixture creation:
@setup
Scenario:
* def rows = karate.dryRun ? [{ name: 'placeholder' }] : fetchFromDb()
Source: ScenarioRuntime.isDryRunSkip(), KarateJs.isDryRun().
Runner.Builder.parallel() applies CI overrides before execution (v1 parity). Reads karate.options (with KARATE_OPTIONS env fallback), plus karate.env and karate.config.dir, and overrides Builder values in place. The option string uses the karate run CLI grammar. Applied before startDebugServerIfRequired, so IDE debug launches inherit the merged state via buildDebugArgs. See CLI.md. Source: KarateOptionsHandler.java.
150+ methods on the karate object. Key categories:
| Category | Examples |
|---|---|
| Flow | abort(), call(), callonce(), callSingle(), eval(), fail() |
| HTTP | http(url), prevRequest, request, response |
| Data | read(), readAsBytes(), readAsString(), write(), fromJson(), toJson(), toCsv() |
| Collections | append(), distinct(), filter(), map(), sort(), merge(), keysOf(), valuesOf(), range(), repeat() |
| Assertions | match(), expect() (Chai-style BDD API) |
| Process | exec(), fork(), signal(), waitForHttp(), waitForPort() |
| Mock | start(), proceed(), stop() |
| Test data | faker.* (names, emails, addresses, numbers, timestamps, etc.), uuid() |
| Logging | log(), logger.debug/info/warn/error(), embed() |
| Info | env, os, properties, config, feature, scenario, tags, tagValues |
| Templating | doc(), render() |
Full listing: see KarateJs.java, KarateJsUtils.java in karate-core.
* karate.expect(response.status).to.equal(200)
* karate.expect(response.items).to.have.length(3)
* karate.expect(response.count).to.be.within(1, 10)
* karate.expect(response).to.have.nested.property('user.address.city', 'NYC')
Supports: equal, a/an, property, keys, include/contain, above/below/within/closeTo, match (regex), oneOf, ok/empty/true/false/null/exist, negation via .not.
* def name = karate.faker.fullName()
* def email = karate.faker.email()
* def num = karate.faker.randomInt(18, 65)
* def ts = karate.faker.isoTimestamp()
Categories: names, contact, location, numbers, text, business, timestamps. See KarateJsUtils.java.
* configure auth = { type: 'basic', username: 'user', password: 'pass' }
* configure auth = { type: 'bearer', token: '#(accessToken)' }
* configure auth = { type: 'oauth2', grantType: 'client_credentials', tokenUrl: '...', clientId: '...', clientSecret: '...' }
Synchronous command execution. Accepts string, array, or map with options (line, args, workingDir, env, timeout).
Async background process. Returns ProcessHandle with:
stdOut, stdErr, exitCode, alive, pidwaitSync(), waitForOutput(predicate), waitForPort(), waitForHttp(), onStdOut(), onStdErr(), start(), close(), signal()Options: line/args, workingDir, env, useShell, redirectErrorStream, timeout, listener, errorListener, start
Communicate from forked process listener back to test flow:
* def proc = karate.fork({ args: ['node', 'server.js'], listener: function(line) { if (line.contains('listening')) karate.signal({ ready: true }) } })
* def result = listen 5000
* match result.ready == true
See MOCKS.md for mock server, CLI.md for CLI architecture, GATLING.md for performance testing.
Unified observation and control of test execution via RunListener.
Suite.fireEvent(RunEvent) → RunListener.onEvent(RunEvent) → return boolean
SUITE_ENTER
├── FEATURE_ENTER
│ ├── SCENARIO_ENTER
│ │ ├── STEP_ENTER
│ │ │ ├── HTTP_ENTER → HTTP_EXIT
│ │ └── STEP_EXIT
│ └── SCENARIO_EXIT
└── FEATURE_EXIT
SUITE_EXIT
Return false from *_ENTER events to skip execution. Events fire for all features including called ones — use event.isTopLevel() to filter.
// Single listener method — pattern matching for dispatch
public interface RunListener {
default boolean onEvent(RunEvent event) { return true; }
}
// Per-thread listeners (for debuggers)
public interface RunListenerFactory {
RunListener create();
}
HttpRunEvent gives access to request, response, scenarioRuntime, and getCurrentStep(). Return false from HTTP_ENTER to skip/mock the request.
Source files: RunEventType.java, RunEvent.java, HttpRunEvent.java, StepRunEvent.java, RunListener.java, RunListenerFactory.java
SLF4J-based with category hierarchy — karate.runtime, karate.http, karate.mock, karate.scenario, karate.console.
Thread-local collector that captures all test output (print, karate.log, HTTP logs) for reports. Also collects embeds (HTML, images) via LogContext.get().embed().
configure loggingSingle bucket for all logging behavior. Deep-merges with parent values so a partial update (e.g., flipping just the level) preserves mask + pretty.
configure logging = {
report: 'debug', // threshold for report-buffer capture (default DEBUG)
console: 'info', // threshold for SLF4J/console (default INFO; null = inherit logback.xml)
pretty: true, // pretty-print HTTP req/res JSON bodies (default true)
mask: { // HTTP-only redaction
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 }
}
}
| Threshold | Knob | What it controls | CLI |
|---|---|---|---|
| Report buffer | logging.report | What gets captured into HTML / JSONL / Cucumber JSON / JUnit XML | --log-report <level> |
| SLF4J / console | logging.console | What hits stdout via Logback (also affects file appenders) | --log-console <level> |
The HttpLogger always writes the full request/response (with bodies, headers) to the report buffer at INFO. The console emission is auto-tiered by SLF4J level: INFO = one-liner, DEBUG = +headers, TRACE = +body. The two knobs let you, e.g., capture full traces in reports for post-hoc debugging while keeping a parallel run's console quiet.
HTTP bodies show up in the HTML report by default — you do not need to crank console to TRACE. Defaults are
report: 'debug'(≥ INFO captured) andconsole: 'info'(one-liner on stdout). Bodies always land in the report buffer at INFO, so they appear in HTML / JSONL / Cucumber / JUnit regardless of the console level. Only setconsole: 'trace'if you specifically want bodies streaming to stdout — which is rarely what you want for a real test run. v1 difference: v1 emitted full bodies to console at DEBUG; v2 reserves DEBUG for headers and TRACE for body. If you used to setkarate.console.log.level=debugto see bodies in your terminal, switch to looking at the HTML report (or setconsole: 'trace'if you really want it on stdout).
configure loggingBoth forms are supported and both stick across the scenario:
// karate-config.js — applies to every scenario in the suite
karate.configure('logging', { mask: { headers: ['Authorization'] }, pretty: true });
# Background — applies to every scenario in this feature
Background:
* configure logging = { mask: { jsonPaths: ['$..token'] } }
KarateConfig is the source of truth — LogContext is a per-thread cache that ScenarioRuntime.call() repopulates from config at scenario entry. Mid-test * configure logging mutations are auto-snapshot/restored so they don't leak into the next scenario. Source: KarateConfig.applyLoggingToContext, ScenarioRuntime.call().
* configure logging = { report: 'error' } mid-flow takes effect immediately. At scenario end, the level is automatically snapshotted and restored, so the next scenario starts at whatever karate-config.js set. This automates the v1 pattern of manually reading/saving/resetting Logback's level via reflection.
Scenario: silence a noisy reusable
* configure logging = { report: 'error' }
* call read('classpath:noisy-warmup.feature')
# report level is restored to default at scenario end — no manual cleanup
logging.pretty applies to both console and report bodies. With pretty: true (default), JSON bodies are re-parsed and pretty-printed (multi-line, 2-space indent); pretty: false collapses to single-line. Non-JSON bodies pass through unchanged. The pretty pass also runs after mask so masked values stay masked.
mask applies only to HTTP request/response logging. It does NOT scan * print or karate.log output — those are user-controlled channels. If a scenario's body could leak via prints, raise logging.report: 'warn' to drop INFO captures, or tag it @report=false.
The mask object replaces v1's HttpLogModifier Java interface. Compiled once per configure logging call into a LogMask instance stored on the thread-local LogContext. Each HttpLogger.logRequest/logResponse reads the current mask and applies:
headers — case-insensitive header-name set; matching headers' values become replacement.jsonPaths — $.x.y (descend) and $..x (recursive) keys; matched values become replacement.patterns — regex/replacement pairs applied last, so they catch anything header / JSON-path didn't.enableForUri(uri) — optional JS predicate; when it returns falsy, no masking applies for that URL (useful for excluding /health so debugging stays easy).configure logging = {
mask: {
headers: ['Authorization', 'Cookie'],
jsonPaths: ['$.password', '$..token'],
patterns: [{ regex: '\\b\\d{16}\\b', replacement: '****-****-****-****' }],
replacement: '***'
}
}
The v1 keys (logPrettyRequest, logPrettyResponse, printEnabled, lowerCaseResponseHeaders, logModifier) are silent no-ops in v2 with deprecation warnings pointing at the new shape. configure report = { logLevel } is hard-removed in 2.0.6 — it now throws with a migration error. See MIGRATION_GUIDE.md § Logging.
Source files: LogContext.java, LogLevel.java, LogMask.java, HttpLogger.java, KarateConfig.configureLogging
FeatureResult.toJson() ← Single source of truth
↓
┌───┼──────┬──────────┐
↓ ↓ ↓ ↓
JSONL HTML Cucumber JUnit
JSON XML
All report formats derive from FeatureResult.toJson(). Generation is async via ResultListener implementations. HTML uses Alpine.js + Bootstrap 5 with inlined JSON.
target/karate-reports/
├── karate-summary.html # Summary dashboard
├── karate-timeline.html # Gantt-style parallel execution view
├── feature-html/ # Per-feature interactive reports
├── cucumber-json/ # Per-feature Cucumber JSON (opt-in)
├── junit-xml/ # Per-feature JUnit XML (opt-in)
└── karate-json/karate-events.jsonl # Event stream (opt-in)
Standard envelope: {"type":"EVENT_TYPE","ts":epoch_ms,"threadId":"...","data":{...}}
FEATURE_EXIT contains full toJson() — the source of truth for offline report generation. Enable with Runner.outputJsonLines(true) or --jsonl.
Runner.path("features/")
.outputJsonLines(true) // JSONL event stream
.outputCucumberJson(true) // Cucumber JSON
.outputJunitXml(true) // JUnit XML
.outputHtmlReport(false) // disable HTML (on by default)
.parallel(5);
HtmlReport.aggregate()
.json("target/run1/karate-json/karate-events.jsonl")
.json("target/run2/karate-json/karate-events.jsonl")
.outputDir("target/combined-report")
.generate();
Source files: HtmlReportListener.java, HtmlReportWriter.java, CucumberJsonWriter.java, JunitXmlWriter.java, JsonLinesEventWriter.java
| Interface | Purpose | Discovery |
|---|---|---|
CommandProvider | CLI subcommands | ServiceLoader (~/.karate/ext/ JARs) |
HttpClientFactory | Custom HTTP clients | Constructor injection |
RunListener | Event listeners | Runner.listener() or --listener CLI |
RunListenerFactory | Per-thread listeners | Runner.listenerFactory() |
ReportWriterFactory | Custom report formats | ServiceLoader (planned) |
| Doc | Covers |
|---|---|
| PRINCIPLES.md | Design philosophy and priorities |
| CLI.md | Two-tier CLI (Rust launcher + Java), subcommands, karate-pom.json |
| JS_ENGINE.md | Type system (JsValue hierarchy), Java interop, prototypes |
| DRIVER.md | Browser automation — CDP, W3C WebDriver, frame/window management |
| MOCKS.md | Mock server — feature-based definitions, proxy mode, stateful mocks |
| GATLING.md | Performance testing — Java DSL, session chaining, HTTP pooling |
| TEMPLATING.md | HTML templating — Thymeleaf + JS expressions, HTMX, server/static modes |
| MIGRATION_GUIDE.md | V1 -> V2 migration guide |
| CAPABILITIES.yaml | Complete feature inventory (366 capabilities) |
| TODOS.md | Actionable work items |
| RELEASING.md | Release checklist |