karatelabs/karate · error · RuntimeException

karate.driver can only be read within a scenario

Error message

karate.driver can only be read within a scenario

What it means

The karate.driver root binding lazily initializes the active browser driver by asking the current ScenarioRuntime. Outside a scenario (e.g. in a boot script, JS function eval before/after a feature, or a standalone JS context) there is no runtime, so reading karate.driver throws instead of returning a useless null.

Solutions

  1. Only access karate.driver inside a running scenario (Scenario/Background/JS within a scenario)
  2. Move driver-dependent logic from boot/config scripts into scenario steps
  3. In shared JS, guard with karate.runtime info or pass the driver in as a parameter instead of reading the global binding.

Example fix

// before (karate-config.js)
config.driver = karate.driver; // throws at boot
// after
config.getDriver = function() { return karate.driver; }; // read lazily inside a scenario
Defensive patterns

Strategy: type-guard

Validate before calling

// JS: only inside a scenario; in shared scripts defer the read
var getDriver = function() { return karate.driver; };

Type guard

// JS: cannot query runtime presence directly; wrap reads in functions instead of eager access
function safeDriver() { try { return karate.driver; } catch (e) { return null; } }

Try / catch

try { var d = karate.driver; } catch (e) { karate.warn('driver unavailable outside scenario'); }

Prevention

When it happens

Trigger: Reading `karate.driver` in karate-config.js / boot-time JS, in a called JS-only context with no scenario, or after the scenario runtime has been torn down (e.g. in an afterSuite hook).

Common situations: Shared utility JS scripts that reference karate.driver unconditionally but are also loaded at boot; calling driver operations in background/setup hooks without an active scenario; assuming the driver survives after quit/teardown.

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


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

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/core/KarateJs.java:1188

            } catch (Exception e) {
                throw new RuntimeException("Failed to write file: " + file.getAbsolutePath(), e);
            }
        };
    }

    /**
     * JS-side access to the active browser driver, initialising it lazily from
     * {@code configure driver = { ... }} on first read — the JS equivalent of the
     * {@code * driver ...} step. Useful when driver lifecycle is orchestrated inside
     * a JS function (e.g. iterating over a list of browser configs in a grid run),
     * where Gherkin steps aren't reachable per iteration. Returns the same instance
     * exposed via the {@code driver} root binding; after {@code driver.quit()} a
     * subsequent read re-inits cleanly via {@link ScenarioRuntime#getDriver()}.
     */
    private io.karatelabs.driver.Driver getDriverLazy() {
        ScenarioRuntime rt = getRuntime();
        if (rt == null) {
            throw new RuntimeException("karate.driver can only be read within a scenario");
        }
        return rt.getDriver();
    }

    // ========== Channel Support ==========

    private JavaInvokable channel() {
        return args -> {
            if (args.length == 0) {
                throw new RuntimeException("channel() needs a type argument, e.g. karate.channel('kafka')");
            }
            String type = args[0].toString();
            ScenarioRuntime rt = getRuntime();
            if (rt == null) {
                throw new RuntimeException("channel() can only be called within a scenario");
            }
            // An ext (e.g. boot.ext('grpc')) may have registered a factory on the Suite at boot;
            // it wins over the name-convention fallback. See Suite#registerChannelFactory.

View on GitHub (pinned to a22eb90246)