In Karate, how does `* karate.call('login.feature')` differ from the step `* call read('login.feature')`, and how do you get shared scope from the JavaScript form?
answer
- Two forms, two different defaults
- The step and the function disagree
- What does the boolean do?
- Needed when the call is conditional
basics
~10 sThey default to opposite scopes. The bare call step shares scope; karate.call(path) is isolated and simply returns the result map. Pass a boolean first argument, karate.call(true, path), to share scope from JavaScript.
solid answer
~40 sThe two forms are near-equivalents with **opposite defaults**, and that is the trap. `* call read('login.feature')` has no result variable, so it runs in **shared scope** and leaves the callee's variables and `configure` settings in the caller. `karate.call('login.feature')` is a JavaScript function; it is **isolated**, exactly like `* def x = call read('login.feature')` except that nothing catches the returned map. The JavaScript form exists because it can sit inside an expression - typically a conditional - where a step keyword cannot. To keep sharing there, pass a boolean first: `karate.call(true, 'login.feature')`, optionally with an argument as the third parameter. That form takes the returned map and sets each key as a variable in the calling scenario.
code
gherkin · 8 lines# shared: the callee's variables land in this scenario
* call read('classpath:common/login.feature')
# isolated: identical run, result discarded
* karate.call('classpath:common/login.feature')
# shared, from JavaScript, with an argument
* if (env != 'local') karate.call(true, 'classpath:common/login.feature', { user: 'john' })go deeper
Remember there are two spellings and they behave differently: the bare call step shares, karate.call does not. Prefer the step form until you need a conditional.
Explain why the defaults differ - the step reads the presence of a result variable, the function has no syntax to read - and give the boolean signature that restores sharing.
Grep the suite for karate.call without a boolean whose result is discarded. Those lines usually intended shared setup and have been silently no-ops since they were written.
Pick one house form for reusable setup so reviewers do not have to reason about two defaults, and make the exception - conditional calls - explicit and rare in the convention you publish.
## Same job, opposite defaults Karate offers two ways to invoke another feature, and they disagree about scope: ```gherkin * call read('login.feature') # shared scope * def out = call read('login.feature') # isolated * karate.call('login.feature') # isolated, result thrown away * def out = karate.call('login.feature')# isolated, same as the second line * karate.call(true, 'login.feature') # shared scope, explicitly ``` The step form decides on the **presence of a result variable**. The JavaScript function has no such syntax to read, so it defaults to isolated and takes an explicit boolean when you want the other behaviour. Read together, the two defaults look inconsistent; read separately, each is the safe choice for its form. Either way, the fix for surprise is to know which form you typed. ## What the boolean actually does `karate.call(true, path)` runs the feature the same way, then walks the returned map and sets **every key as a variable in the calling scenario**. That is the same effect a bare `call` step produces, so a shared-scope helper behaves identically through either route. The signatures accepted are: - `karate.call(path)` - isolated - `karate.call(path, arg)` - isolated, with a call argument - `karate.call(true, path)` - shared - `karate.call(true, path, arg)` - shared, with a call argument Passing a boolean without a path is an error, so the two-argument form is never ambiguous. ## Why the JavaScript form exists A `call` step is a step: it always runs. The JavaScript form can sit inside an expression, which is what you need for conditional reuse: ```gherkin * if (env != 'local') karate.call(true, 'classpath:common/auth.feature') ``` Without the boolean that line would authenticate and then throw the session away - the single most common bug in conditional setup. It is also available anywhere JavaScript runs, including a function defined in the config chain, which a step keyword cannot reach. ## When each form reads better | Situation | Form to reach for | |---|---| | Unconditional shared setup in a `Background` | `* call read('setup.feature')` | | You want the answer under a name you chose | `* def out = call read('helper.feature')` | | The call has to be conditional and must share | `karate.call(true, path)` | | The call has to be conditional and is a function | `karate.call(path, arg)` | | Calling from inside a JavaScript helper | the `karate.call` form, necessarily | For everyday reuse the step forms read better in a feature file, and most suites should use them by default. Reach for `karate.call` when you genuinely need an expression. ## The argument, and the loop form Both routes take the same call argument and treat it the same way. A map is bound whole to the hidden `__arg` binding inside the callee **and** spread key by key as ordinary variables, so a helper invoked with `{ user: 'john' }` can write `* path 'users', user` directly. An array argument selects the loop form in either route: the feature runs once per element, `__loop` carries the zero-based index, and the collected results come back as a list rather than a map. ```gherkin * def rows = [{ n: 1 }, { n: 2 }] * def viaStep = call read('make.feature') rows * def viaJs = karate.call('make.feature', rows) ``` Both lines above run `make.feature` twice and produce a two-element list. The difference between the routes is never *what* runs - only whether the callee's variables are published into the caller afterwards. ## Things that stay the same either way Whichever form you use, the rest of the model is unchanged: - The callee sees the caller's variables - copies when isolated, the live map when shared. - The caller's configuration is copied down in both scopes. - The `karate-config.js` chain is not re-evaluated for the called feature. - An array argument still turns the call into one run per element, returning a list. - The hidden `__arg` and `__loop` bindings are still available inside the callee. ## Review guidance Treat a bare `karate.call(...)` whose result is discarded as a smell: either the author wanted shared scope and forgot the boolean, or the call is pure side effect on the callee's own state and deserves a comment saying so. A quick grep for the JavaScript form across a suite usually turns up one or two conditional setup lines that have been quietly doing nothing for months.
- Is `* def out = karate.call('login.feature')` different from `* def out = call read('login.feature')`?No - both are isolated calls and both put the callee's own variables into `out`. The step form reads better inside a feature file; the JavaScript form is what you need when the call has to live inside an expression.
- What goes wrong with `* if (cond) karate.call('auth.feature')` in a Background?It authenticates and throws the session away. The JavaScript form is isolated by default, so the token and any `configure headers` the helper set never reach the calling scenario. Add the boolean: `karate.call(true, 'auth.feature')`.
saying these in an interview costs you the question
- Assuming karate.call shares scope like the bare call step
- Thinking the boolean argument selects an environment or a tag
- Using a conditional karate.call for setup and discarding the result
- Claiming the JavaScript form skips the call argument mechanism
- Believing the two forms run the feature differently