A reusable Karate feature picks up the right `baseUrl` when run on its own, but the wrong one when the same file is reached through `call read('setup.feature')` from another suite. What is the mechanism?
answer
- Something about the chain is caller-scoped
- Top-level scenarios only
- A called feature does not re-run it
- It inherits the caller's variables instead
- Call arguments win over inherited names
basics
~10 sKarate evaluates the karate-config.js chain only for top-level scenarios. A feature reached through call never re-runs it; it inherits the caller's variables and configure settings instead, so it sees whatever the caller's config produced.
solid answer
~40 sThe config chain — `karate-base.js`, `karate-config.js`, `karate-config-<env>.js` — is evaluated **only for a top-level scenario**. When a feature is reached through `call` or `callonce`, Karate creates a fresh context for it but **passes the caller's variables and `configure` settings along** instead of re-running the chain. So run standalone, `setup.feature` evaluates config itself and gets that run's `baseUrl`; reached through `call`, it inherits whatever the *caller's* config produced. The two runs legitimately differ whenever the caller was started with a different `karate.env`, or lives in a project with a different `karate-config.js`. This is deliberate: it is what makes a called feature a genuine subroutine of the caller rather than an independent run, and it is why you cannot switch environment mid-suite by calling a feature.
code
gherkin · 11 linesFeature: caller
Background:
# baseUrl came from this run's karate-config.js
* print 'caller baseUrl:', baseUrl
Scenario: sign in through a reusable feature
# setup.feature does NOT re-evaluate karate-config.js;
# it inherits baseUrl from here. Pass what it needs explicitly:
* def session = call read('classpath:setup.feature') { user: 'admin' }
* match session.token == '#string'go deeper
Recall the rule itself: the config files run for top-level scenarios only, and a feature reached through call inherits the caller's variables rather than re-reading them.
Explain what the callee actually receives — the caller's variables and configure state, plus the call argument on top — and why that makes a called feature a subroutine of the run.
Use it when diagnosing. A reusable feature that is green standalone and wrong when called is almost always inheriting a caller's config, not failing on its own logic.
Set the convention for reusable features across teams: an explicit call-argument contract rather than an unwritten assumption that every caller's config defines the same names.
## The rule Karate evaluates its config chain for **top-level scenarios only**. A scenario reached through `call`, `callonce` or the JavaScript call API has a caller, and for it the chain — `karate-base.js`, then `karate-config.js`, then `karate-config-<env>.js` — is skipped entirely. Not deferred, not cached: skipped. This is a single condition inside the engine, and both the file-reading and the evaluation sit behind it. It is not affected by how deep the call is, whether the callee is in another directory, or whether it happens to have its own `karate-config.js` nearby. ## What the called feature gets instead Skipping the chain does not leave the callee empty. Karate builds a fresh context for the called feature and seeds it from the caller: - **Every variable the caller holds** at the moment of the call — which includes everything the caller's own config chain produced. - **The caller's `configure` settings**, so timeouts, headers and SSL behaviour carry across. - **The call argument**, if one was passed, whose keys are applied on top and therefore win over the inherited names. So a called feature can freely write `Given url baseUrl`. That name is there — it just arrived through the caller rather than through a config evaluation of its own. ## Why standalone and called runs differ Once you know the chain is caller-scoped, the reported symptom explains itself: | Run shape | Config chain | `baseUrl` comes from | |---|---|---| | `setup.feature` run directly | evaluated for it | that run's own config, with that run's `karate.env` | | `setup.feature` reached by `call` | skipped | the caller's variables, from the caller's config evaluation | The two agree whenever the caller and the standalone run share a config and an environment. They diverge as soon as either differs — the caller was launched without `karate.env`, or the caller belongs to a different module with its own `karate-config.js`, or the caller redefined `baseUrl` with a `def` before making the call. ## Consequences worth knowing 1. **You cannot switch environment mid-suite by calling a feature.** Calling a feature that lives beside a `karate-config-prod.js` does not load it. The only lever is the run's own `karate.env`. 2. **A green standalone run of a reusable feature proves less than it looks.** It exercised the feature against *its own* config. The same file inside a caller runs against the caller's. 3. **Work you put in `karate-config.js` happens once per top-level scenario, not once per call.** A suite whose scenarios each call three features still evaluates the chain once for each of those scenarios, and zero extra times for the calls. 4. **The callee cannot rely on a variable the caller never had.** If a reusable feature needs `tokenUrl`, then every caller's config must define it, or the caller must pass it as the call argument. That is the contract, and it is worth writing down at the top of the reusable file. 5. **Environment tags behave the same way.** Scenario selection tags that depend on the environment are applied at top-level selection; a called feature runs its scenarios regardless. The same top-level-only principle governs both. ## How to diagnose it When a called feature behaves unexpectedly, the fastest confirmation is to print the value at both ends: ```gherkin # in the caller, immediately before the call * print 'caller baseUrl:', baseUrl * call read('classpath:setup.feature') ``` ```gherkin # first step of setup.feature * print 'callee baseUrl:', baseUrl ``` If the two agree, the config chain is behaving exactly as designed and your problem is in the caller's config. If the callee's is undefined, the caller never had it — which means either the caller's config does not define it or you expected the callee's own config to supply it. ## Designing around it The robust pattern is to treat a reusable feature as a function with an explicit signature. Pass what it needs as the call argument rather than relying on inherited names: ```gherkin * def result = call read('classpath:signin.feature') { baseUrl: '#(baseUrl)', user: 'admin' } ``` Call-argument keys are applied on top of the inherited variables, so they are unambiguous, they document the dependency at the call site, and they survive a caller whose config happens to name things differently. Relying on inherited config names is convenient and legitimate for a small suite; it becomes a hidden coupling once several projects call the same feature.
- Does a Karate feature reached through `call` see the caller's `configure` settings as well as its variables?Yes. Karate creates a new context for the called feature but passes along both the variables and the `configure` state, so timeouts, headers and SSL settings carry across. What does not carry is per-request state such as the current `path` — the callee builds its own request.
- If a reusable Karate feature needs a value no caller happens to define, what is the cleanest fix?Pass it as the call argument. The argument's keys are applied on top of the inherited variables, so they win, they are visible at the call site, and they document the reusable feature's dependency instead of leaving it as an assumption about every caller's config.
A called feature is a subroutine, not a new process. It gets the caller's stack frame copied in; it does not re-read the program's start-up settings for itself.
saying these in an interview costs you the question
- Says the config chain re-runs for every called feature
- Thinks a called feature loads config from its own directory
- Believes calling a feature can switch the run's environment
- Assumes the callee starts with an empty variable space
- Claims configure settings do not cross a call boundary