karatelabs/karate · error · RuntimeException

channel() can only be called within a scenario

Error message

channel() can only be called within a scenario

What it means

Channels are scenario-scoped: karate.channel(type) resolves the ChannelFactory via the current ScenarioRuntime's Suite. When no runtime is active (boot scripts, afterSuite, pure JS eval outside a scenario) the call cannot proceed and throws this error.

Solutions

  1. Call karate.channel() only inside a running scenario step or scenario-scoped JS
  2. Move channel setup into the scenario (or Background) rather than suite/boot hooks
  3. If using the embedded engine, ensure a scenario runtime exists before resolving channels.

Example fix

// before (afterSuite JS)
karate.channel('kafka').close();
// after — close inside the scenario
Scenario: teardown
* def ch = karate.channel('kafka')
* ch.close()
Defensive patterns

Strategy: try-catch

Validate before calling

// JS: only call channel() inside scenario scope; guard shared helpers
function channelInScenario(type) { try { return karate.channel(type); } catch (e) { return null; } }

Try / catch

try { var ch = karate.channel('kafka'); } catch (e) { if (String(e).indexOf('within a scenario') >= 0) karate.warn('move channel() into a scenario'); throw e; }

Prevention

When it happens

Trigger: Calling karate.channel('kafka') in karate-config.js, a call-single JS file executed outside a scenario, @AfterSuite hook, or any JS context where getRuntime() returns null.

Common situations: Utility scripts shared between scenario and boot contexts; setting up a channel listener in a suite hook instead of a scenario; calling channel() from a JVM-embedded Karate engine without running a feature scenario.

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/fff5931c997e2ca1. Report an issue: GitHub.

Appendix: source

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

    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.
            FeatureRuntime fr = rt.getFeatureRuntime();
            Suite suite = fr != null ? fr.getSuite() : null;
            ChannelFactory factory = suite != null ? suite.getChannelFactory(type) : null;
            try {
                if (factory == null) {
                    // No ext registered a factory for this type — resolve by name convention,
                    // mirroring boot.ext: channel('grpc') -> io.karatelabs.ext.grpc.GrpcChannelFactory.
                    String cap = Character.toUpperCase(type.charAt(0)) + type.substring(1);
                    String factoryClass = "io.karatelabs.ext." + type + "." + cap + "ChannelFactory";
                    Class<?> clazz = Class.forName(factoryClass);
                    factory = (ChannelFactory) clazz.getDeclaredConstructor().newInstance();
                }
                // Channels self-configure via their rich JS object, not via global config.
                Channel channel = factory.create(rt, new HashMap<>());
                rt.registerChannel(channel);

View on GitHub (pinned to a22eb90246)