karatelabs/karate · error · RuntimeException
karate.setupOnce() requires a feature context
Error message
karate.setupOnce() requires a feature context
What it means
executeSetupOnce is the cached variant of setup (karate.setupOnce()), running an @setup scenario once per FeatureRuntime. Like karate.setup(), it requires an active feature context; if featureRuntime is null, this RuntimeException is thrown. The cache lookup happens only after this guard passes.
Solutions
- Invoke karate.setupOnce() only from scenario-scoped JS inside a running feature.
- For suite-level caching use karate.callSingle() with plain JS instead of setupOnce.
- Ensure the feature is executed via the Runner (not ad-hoc JS evaluation) so the FeatureRuntime is created.
- If the intent is one-time setup per feature, place karate.setupOnce() in a Background section instead.
Example fix
// before: inside callSingle JS
var fn = karate.callSingle('data-setup.js');
var data = karate.setupOnce('seed');
// after: inside the feature
Background:
* def data = karate.setupOnce('seed') Defensive patterns
Strategy: type-guard
Type guard
// call setupOnce only from scenario scope
function setupOnceSafe(name) {
try { return karate.setupOnce(name); }
catch (e) {
if (String(e).indexOf('requires a feature context') >= 0) return null;
throw e;
}
} Try / catch
try {
var data = karate.setupOnce('seed');
} catch (e) {
if (String(e).indexOf('requires a feature context') >= 0) {
karate.log('setupOnce used outside feature; skipping cache');
data = karate.call('classpath:seed.feature');
} else { throw e; }
} Prevention
- Use setupOnce only inside features (Background is a good place)
- Use karate.callSingle() for suite-level one-time data instead
- Don't nest setupOnce inside callSingle-cached JS
- Verify features run through the Runner so FeatureRuntime is attached
When it happens
Trigger: Calling karate.setupOnce() from JS running outside a feature execution — typically from karate.callSingle() cached code or a suite-level script where featureRuntime is null.
Common situations: Migrating karate.callSingle() JS to use setupOnce assuming a feature exists; invoking setupOnce from karate-config.js before scenarios start; utility JS shared between feature and suite scopes.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- karate.setup() is not available in this context
- karate.setup() requires a feature context
- karate.callonce() requires a feature context
- karate.driver can only be read within a scenario
- karate.call() requires a feature context
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/bcdb25f7ef614024.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java:544
sr.call();
// The setup runtime re-evaluates config, and a failure there is now captured rather than
// thrown from the constructor. call()'s failed result is discarded here, so without this the
// caller gets an empty variable map and the config error resurfaces as a baffling
// "x is not defined" wherever a setup variable was expected. Fail where the cause is.
Throwable setupConfigError = sr.getConfigError();
if (setupConfigError != null) {
throw setupConfigError instanceof RuntimeException re ? re : new RuntimeException(setupConfigError);
}
return sr.getAllVariables();
}
/**
* Execute the @setup scenario with caching (only runs once per feature).
*/
@SuppressWarnings("unchecked")
public Map<String, Object> executeSetupOnce(String name) {
if (featureRuntime == null) {
throw new RuntimeException("karate.setupOnce() requires a feature context");
}
String cacheKey = name == null ? "__default__" : name;
Map<String, Object> setupCache = featureRuntime.getSetupOnceCache();
Map<String, Object> cached = (Map<String, Object>) setupCache.get(cacheKey);
if (cached != null) {
// Return a shallow copy to prevent modifications affecting other scenarios
return new HashMap<>(cached);
}
synchronized (setupCache) {
// Double-check after acquiring lock
cached = (Map<String, Object>) setupCache.get(cacheKey);
if (cached != null) {
return new HashMap<>(cached);
}
Map<String, Object> result = executeSetup(name);
setupCache.put(cacheKey, result);
return new HashMap<>(result);
}View on GitHub (pinned to a22eb90246)