karatelabs/karate · error · JsErrorException

is not defined

Error message

${varName} is not defined

What it means

Thrown as a ReferenceError when an identifier cannot be resolved through any binding frame — no local slot, closure, or global carries the name. `evalRefExprByName` calls context.resolve and, if it returns null, reports the variable as undefined.

Solutions

  1. Fix the variable-name spelling or declare the variable before use (var/let/const).
  2. Pass required values into embedded JS via the available context (e.g. karate.get / function arguments).
  3. Verify the host object or global you expect is actually registered with the Karate JS engine in this scope.

Example fix

// before
var total = price * qty; // price never defined
// after
var price = 10;
var total = price * qty;
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof price === 'undefined') throw new Error('price must be defined before use');

Type guard

function isDefined(v) { return typeof v !== 'undefined'; }

Try / catch

try { total = price * qty; } catch (e) { if (String(e).includes('is not defined')) { price = karate.get('price'); total = price * qty; } }

Prevention

When it happens

Trigger: Reading an undeclared identifier in Karate JS: typo in a variable name, using a variable defined in another feature/JS scope without passing it, or referencing a host object not registered in the engine context.

Common situations: Calling shared JS utilities that expect injected variables (karate, config values) which were not provided; misspelling camelCase names; assuming variables from one scenario leak into another.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/4dd3c09e4cde3f9d. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/Interpreter.java:2609

    // The name-keyed tail of evalRefExpr, outlined so the slot fast path above
    // stays small enough to inline reliably — an at-threshold hot method here
    // flips whole benchmark rows run to run depending on JIT timing.
    private static Object evalRefExprByName(Node node, CoreContext context) {
        String varName = node.getText();
        if ("this".equals(varName)) {
            return context.getThisObject();
        }
        if (context.callArgs != null && "arguments".equals(varName)) {
            JsArray a = context.argumentsForRead();
            if (a != null) {
                return a;
            }
            // arrow frame with no enclosing function — ordinary resolution
        }
        BindingSlot s = context.resolve(varName);
        if (s == null) {
            throw JsErrorException.referenceError(varName + " is not defined");
        }
        return context.readSlot(s, varName);
    }

    private static Object evalReturnStmt(Node node, CoreContext context) {
        if (node.size() > 1) {
            Object value = eval(node.get(1), context);
            // `return somethingThatThrew()` — a callee that threw has already propagated its throw
            // onto THIS context (JsFunctionNode.bindArgsAndExecute), and stopAndReturn would clear
            // it and complete the function normally. The throw then no longer exists anywhere: a
            // surrounding try/catch never fires, and the host-call boundary sees a clean return.
            // Leave the context in its error state and let the exit unwind carry it.
            if (context.isError()) {
                return value;
            }
            return context.stopAndReturn(value);
        } else {
            // a valueless `return` completes with undefined, not null — the two are

View on GitHub (pinned to a22eb90246)