In Karate, what does the Java line `Runner.path("classpath:animals").tags("@smoke").parallel(5)` actually do, and why does this class need no step-definition or glue package?
answer
- A builder, then one terminal call
- Nothing runs until the last call
- The argument is thread width
- No glue package to point at
- It returns counts, it does not throw
basics
~20 sRunner.path() opens Karate's builder, each chained call sets one option, and parallel(n) is the terminal call that discovers the features, runs them on n threads and returns a result object. No glue package is involved.
solid answer
~40 s`Runner.path(...)` returns a `Runner.Builder`. Every chained call — `tags`, `karateEnv`, `configDir`, `outputJunitXml` — just stores a field and returns the builder, so nothing has run yet. `parallel(int)` is the **terminal** call: it resolves the paths into feature files, applies the tag selector, executes scenarios across that many threads, writes the reports and returns a result object carrying the pass/fail counts. Because Karate parses and executes `.feature` files with its own lexer and closed keyword set, there is no step-definition registry and no glue path to point at — the launcher only says *which files* and *with what options*, never *which Java methods implement the steps*. The surrounding class is an ordinary JUnit test whose body asserts that the returned failure count is zero.
code
java · 17 linesimport com.intuit.karate.Results;
import com.intuit.karate.Runner;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ApiSuiteTest {
@Test
void runAll() {
Results results = Runner.path("classpath:animals")
.tags("@smoke")
.karateEnv("qa")
.outputJunitXml(true)
.parallel(5);
assertEquals(0, results.getFailCount(), results.getErrorMessages());
}
}go deeper
Recall the shape: Runner.path(...) starts a builder, chained calls configure it, and parallel(n) runs it and hands back counts you assert on.
Explain that every call before parallel() only sets a field, and that path resolution, tag filtering, execution and report writing all happen inside that one terminal call.
Judge what belongs in the launcher versus outside it: hard-coding tags and environment in Java means one class per pipeline job, whereas a single runner steered externally serves them all.
Weigh the launcher as an interface: it is the only contract your pipeline has with the suite, so decide deliberately how much selection logic lives in compiled Java versus in the job definition.
## What the chain is `Runner.path("classpath:animals")` is a static factory that creates a `Runner.Builder` and seeds it with one path. Every method after it is a plain setter that returns the builder, so the whole expression is one object being configured: | Call | What it sets | |---|---| | `path("classpath:animals")` | a directory, a single `.feature` file, or `file.feature:12` for one scenario — repeated calls **accumulate** | | `tags("@smoke")` | the tag selector applied to every discovered scenario | | `karateEnv("qa")` | the value features read back as `karate.env` | | `configDir("classpath:cfg")` | the directory the runner looks in for `karate-config.js` | | `outputJunitXml(true)` | opt in to JUnit XML output (off by default) | | `parallel(5)` | **terminal** — resolve, run, report, return | Nothing executes until the terminal call. That is worth saying out loud in an interview, because it is what makes the launcher trivially testable in an IDE: you can build the chain, print it, and nothing has touched the network. ## What `parallel(n)` does Reading it as "turn on parallelism" is the common misreading. It is the run trigger, and the argument is only the width: 1. **Resolve** every path into feature files — a directory is scanned recursively; `classpath:` resolves against the test classpath. 2. **Compile** the `tags(...)` array into a selector expression. 3. **Build a suite** and execute it, features and scenarios spread across `n` threads. 4. **Write the reports** into the output directory. 5. **Return** a result object holding feature/scenario counts, failure counts and the collected error messages. `parallel(1)` therefore still runs the suite — sequentially. There is no separate `run()` or `build()` to call afterwards, and no exception is thrown for a failing scenario: the failure count comes back on the returned object and it is the surrounding JUnit method that turns it into a red test. ## Why there is no glue This is the answer that separates Karate from a Cucumber-style runner. Karate reads `.feature` files with its **own** lexer and parser, not Cucumber's Gherkin library, and dispatches each step against a **closed, built-in keyword set** — `def`, `url`, `path`, `method`, `status`, `match`, `call`, `configure` and the rest. There is: - **no step-definition registry** to populate, - **no glue package** or class list to point the runner at, - **no** Cucumber-Expression or regex parameter machinery, - **no** "undefined step / here is a snippet to paste" flow — an unrecognised leading keyword is a hard error. So the builder has no `glue(...)` method to offer. `path(...)` is the *only* thing that says what to run; everything else is configuration. `Given`, `When` and `Then` are interchangeable labels in a Karate feature — `*` works everywhere — because dispatch never looks at the prefix. ## The result object, and the version to name The accessor names are the one part of this chain that is **not** stable across Karate's two lines, and naming the wrong one is a common interview slip: | | Karate 1.x | Karate 2.x | |---|---|---| | package | `com.intuit.karate` | `io.karatelabs.core` | | returned by `parallel(n)` | `Results` | `SuiteResult` | | failure count | `getFailCount()` | `getScenarioFailedCount()` | | error text | `getErrorMessages()` | `getErrors()` (a `List<String>`) | Karate 2 ships a `com.intuit.karate` compatibility shim so the 1.x snippet above still compiles and runs — but code resolving through that shim is using the old API, not the new one. ## What the class is *not* - It is **not** a JUnit `TestEngine`. Karate ships none, and it has no `@Suite`-style annotation of its own. The launcher is just Java that happens to be invoked from a `@Test` method so that Maven or Gradle will run it. - It is **not** where assertions about the API live. Those are `match` steps inside the feature files; the launcher only asserts the aggregate failure count. - It is **not** the place to encode which scenarios a particular pipeline runs, if you can avoid it — the same class can be steered from outside by system properties, which keeps one runner serving several jobs. ## The shape you will be asked to write ```java @Test void testParallel() { Results results = Runner.path("classpath:animals") .tags("@smoke") .outputJunitXml(true) .parallel(5); assertEquals(0, results.getFailCount(), results.getErrorMessages()); } ``` Four lines of Java for an entire API suite, and not one of them mentions a step implementation. That is the whole pitch of the glue-free model, and it is exactly what the interviewer is checking you can explain.
- Does `parallel(1)` skip the suite, or run it sequentially?It runs it, on a single thread. `parallel(int)` is the terminal call that executes the suite; the argument only sets the width. There is no separate `run()` — dropping the call means the builder is configured and nothing ever executes, which is the usual cause of a runner class that passes instantly with zero scenarios.
- A scenario fails. Does `parallel(n)` throw?No. It completes normally and reports the failure on the returned object — `getFailCount()` on Karate 1.x, `getScenarioFailedCount()` on 2.x. The launcher method must assert on that count itself; a runner that calls `parallel(n)` and ignores the return value is a green test over a red suite.
- What does calling `path(...)` twice do?The paths accumulate — both are scanned. That matters because the same is *not* true of an external override: a path supplied through the `karate.options` system property replaces the builder's paths outright rather than adding to them.
saying these in an interview costs you the question
- Claiming parallel(n) only enables threading, not the run itself
- Looking for a glue or step-definition package on the builder
- Saying a failing scenario makes parallel() throw an exception
- Naming getFailCount() and SuiteResult together as one API
- Thinking Karate registers a JUnit Platform TestEngine
- Believing Given/When/Then choose which Java method runs