A reusable Karate feature ends with `* configure headers = read('headers.js')`. Called as `* def auth = call read('auth.feature')`, the caller's next request still goes out without those headers. Why, and what are the two ways to fix it?
answer
- Configuration is not symmetric here
- Which direction does configure travel?
- Look at the call line, not the helper
- The def chose isolation
basics
~10 sThe def made the call isolated, and configuration travels downward into a callee but never back up. Either drop the def so the call shares scope, or repeat the configure step in the caller.
solid answer
~40 sConfiguration in Karate is **asymmetric across a feature call**: the caller's settings - headers, proxy, SSL, timeouts - are always copied down into the callee, in either scope, but a `configure` performed *inside* the callee reaches the caller **only in shared scope**. `def auth = call read('auth.feature')` is the isolated form, so the callee's `configure headers` dies with it and only the returned map crosses back. Two fixes. **Share the scope:** drop the `def` (`* call read('auth.feature')`) or use `karate.call(true, 'auth.feature')`, and the configure setting plus the callee's variables land in the caller. **Or stay isolated** and repeat `* configure headers = read('headers.js')` in the caller, unpacking from `auth` whatever variables the header function reads.
code
gherkin · 8 linesBackground:
# isolated: configure inside auth.feature does NOT reach here
* def auth = call read('classpath:common/auth.feature')
* def token = auth.token
* configure headers = read('classpath:headers.js')
# shared: the helper's configure and variables land here directly
# * call read('classpath:common/auth.feature')go deeper
Learn the shape first: a call with def keeps everything inside the helper, and a call without def brings its settings and variables out. Try both against one auth feature.
Explain the asymmetry in words: configuration always goes down into the callee, and only comes back up in shared scope. Then name both fixes and what each one costs.
Diagnose from the call line outward, and decide deliberately which reusable features are allowed to mutate caller scope. Duplicated configure lines are a signal, not automatically a defect.
Standardise it. One convention for auth-style helpers across the suite, named so the scope is obvious at the call site, so nobody has to open the helper to know whether it mutates their scenario.
## Configuration crosses a call in one direction only Karate keeps variables and configuration on separate rails when a feature calls another feature, and the rails are not symmetric. | Direction | Variables | Configuration (headers, proxy, ssl, timeouts) | |---|---|---| | Caller into callee | copies (isolated) or the live map (shared) | **always copied, in either scope** | | Callee back to caller | inside the result map (isolated) or directly (shared) | **shared scope only** | The downward row is deliberate: an isolated call opts out of variable mutation, but it still needs the caller's proxy, SSL and auth settings to reach the callee's HTTP client, or every reusable feature would have to re-establish the environment for itself. The upward row is what bites here. `def auth = ...` chose isolation, so `configure headers` inside `auth.feature` applies to that feature's own requests and stops at its boundary. ## Why the callee worked and the caller did not The called feature almost certainly authenticated correctly, which is what makes this confusing. It did so on the caller's inherited configuration plus whatever it configured for itself. Nothing it configured came back. There is a second reason the callee has no independent life of its own: **the `karate-config.js` chain is not evaluated for a called feature.** Those files run once per top-level scenario. A callee therefore starts with nothing but what it inherited from the caller and what it was passed as an argument - which is precisely why the scope decision is the interesting one on a feature call, and why "just configure it in the helper" is not a fix on its own. ## Fix one: share the scope Drop the `def`: ```gherkin Background: # shared scope - the helper's variables AND its configure settings land here * call read('classpath:common/auth.feature') * url baseUrl Scenario: the request is already authenticated Given path 'orders' When method get Then status 200 ``` If the call has to be conditional, the JavaScript form takes an explicit boolean: `* if (env != 'local') karate.call(true, 'classpath:common/auth.feature')`. Note the `true` - `karate.call('auth.feature')` on its own is isolated, the opposite default to the bare `call` step. This is the pattern reusable sign-in features exist for. The cost is that the helper can now overwrite any caller variable with the same name, so it wants a short, deliberate list of names and a comment saying so. ## Fix two: stay isolated and repeat yourself If you want the helper to stay a function - because a dozen features call it and you would rather each one say what it got - then the caller carries the configuration: ```gherkin Background: * def auth = call read('classpath:common/auth.feature') # the header function reads these, so unpack them into caller scope * def token = auth.token * def issuedAt = auth.issuedAt * configure headers = read('classpath:headers.js') ``` The duplication is real and it is the documented trade-off: the header function is a JavaScript file evaluated in the calling scenario's context, so it can only read variables that exist there. Upstream's own demo carries the same duplicated line with a comment explaining why. What you are paying for is worth naming: - Each caller states, in its own file, exactly which variables the helper produced. - The helper cannot silently overwrite a variable the caller already had. - The helper's result can be asserted on before it is used, which turns a broken helper into a clear failure. - Against that: one duplicated `configure` line per caller, and the unpacking. ## How to diagnose this class of failure quickly 1. Look at the call line first. A `def` on it means nothing configured inside the callee reached you. 2. Confirm the request that failed actually lacked the header, rather than carrying a stale one - a `configure` in the caller's `Background` may be overriding what you expected. 3. Check whether the header function depends on variables. In isolated scope those must be unpacked from the result map before the function can read them. 4. Only then consider the helper itself. It is rarely the culprit; the scope of its call usually is. ## Choosing between the fixes Prefer shared scope for the small set of features whose whole purpose is to leave state behind - sign-in, tenant setup, a seeded fixture. Prefer isolation for anything that reads like a function, and accept the unpacking. What you should not do is mix them for the same helper across a suite: a feature that is shared-scope in one file and isolated in another has two different contracts, and the next reader will assume the wrong one.
- If configuration does not travel back, how did the called feature get the caller's proxy and SSL settings?Configuration is always copied downward into a called feature, in both scopes. Only the return direction depends on scope. That asymmetry is deliberate - an isolated call opts out of mutating the caller, but still needs the environment's proxy, SSL and timeouts to reach its own HTTP client.
- Why can't the helper feature just read karate-config.js itself and configure everything there?The karate-config chain is evaluated once per top-level scenario and skipped for a called feature. A callee has no configuration of its own beyond what it inherits from the caller and what it is passed, so it cannot bootstrap itself independently.
- What is the risk of converting the helper to a shared-scope call across a large suite?Shared scope hands the helper the caller's variable map, so any name it defines overwrites the caller's value for the rest of the scenario. Across many callers that is an invisible coupling; keep the names few, prefixed and documented, and review changes to the helper as contract changes.
saying these in an interview costs you the question
- Blaming the helper feature instead of the scope of the call
- Assuming configure settings propagate in both directions
- Claiming a called feature re-runs the config chain for itself
- Expecting karate.call without a boolean to share scope
- Adding configure to the helper and declaring the bug fixed