skip to content

In Karate, what does `* configure afterScenario = function(){ ... }` do, and why does the same line have no effect inside a feature that is invoked with `call`?

level: seniorimportance: should knowfreq 42%

answer

  1. a value, not a declaration
  2. who is allowed to fire it
  3. top-level scenarios only
  4. it runs after a failure too

basics

~20 s

It assigns a JavaScript function to a config key, and Karate runs it after every scenario in scope, on the passing and the failing path. Hooks fire for top-level scenarios only, so a called feature's never runs.

solid answer

~40 s

There is no annotation and no hook class here: a Karate hook is a JavaScript function stored under a `configure` key. `configure afterScenario` runs after each scenario in the configure's scope, on both the pass and the fail path, so teardown still happens when an assertion failed. Karate deliberately restricts it to **top-level** scenarios — the runtime returns early when the scenario has a caller, so `afterScenario`, `afterScenarioOutline` and `afterFeature` are all no-ops inside a feature reached through `call` or `callonce`. Karate's own demo suite carries a called feature with that line and a comment saying it will have no effect. The usual place for a real teardown hook is therefore the top-level feature or the config file, never a shared helper feature.

code

gherkin · 17 lines
gherkin
Feature: hooks are values assigned to configure keys

Background:
* configure afterScenario =
"""
function(){
  karate.log('after scenario:', karate.scenario.name);
  karate.call('after-scenario.feature', { caller: karate.feature.fileName });
}
"""
* configure afterFeature = function(){ karate.call('after-feature.feature') }

Scenario: first
  * print 'one'

Scenario: the hooks above do not apply inside this called feature
  * def result = call read('called.feature')

go deeper

for a junior

Know the shape: a hook is a JavaScript function assigned to a configure key, and the afterScenario one runs after each scenario in scope rather than once per feature.

for a middle

Explain the top-level gate and why it exists: the runtime returns early when the scenario has a caller, which stops a hook that uses karate.call from re-entering itself.

for a senior

Diagnose the silent case. Teardown that never ran because it was installed in a called feature looks exactly like teardown that ran and did nothing, so check where the hook is installed before you debug its body.

for a principal

Weigh hooks against explicit steps as a house style. A hook is reuse that is invisible at the point of failure, and on a large suite that invisibility is what makes cleanup bugs expensive to trace.

## A hook here is a value, not a declaration In a step-definition framework a hook is something you declare — an annotated method that a registry discovers. Karate has no step-definition layer to hang that on, so a hook is simply a JavaScript function assigned to a configure key: ```gherkin * configure afterScenario = function(){ karate.log('done:', karate.scenario.name) } ``` The three after-hook keys work the same way: | key | fires | |---|---| | `afterScenario` | after each scenario, including each `Examples` row of an outline | | `afterScenarioOutline` | once after the whole outline has run its rows | | `afterFeature` | after the feature's scenarios have finished | Because the value is an ordinary function, it can be read from a shared `.js` file, and it can hand control to a whole feature with `karate.call('after-scenario.feature')` when the teardown is more than a line. ## It runs on the failing path too The hook is invoked from the scenario's cleanup path, which runs regardless of how the scenario ended. A scenario that failed on a `match` still gets its `afterScenario`, which is what makes the key usable for teardown — deleting a record the scenario created, releasing a fixture, logging a correlation id for the failure. A dry run skips it. ## Why a called feature's hook does nothing This is the part people hit in practice. Before invoking the hook Karate checks whether the current scenario has a caller and returns immediately if it does; the source comments the check as *do not call hooks on called scenarios/features*. All three after-hook keys are gated the same way. The reason is recursion: a hook is allowed to use `karate.call`, and a hook that fired inside called features would re-enter itself for every level of nesting. The consequence for design is concrete: 1. Putting `configure afterScenario` in a shared helper feature does nothing when that helper is called — and quietly starts working if someone ever runs the helper directly. 2. Teardown that must always happen belongs in the top-level feature, or in the config file so every top-level scenario picks it up. 3. If a called feature genuinely needs cleanup, do it with ordinary steps at the end of the called feature, not with a hook. Karate's own demo suite pins this: a called feature carries the line together with the comment that it is not supported when the feature is called, and the calling feature has a scenario named for the same point. ## What happens when the hook itself throws This is the one place where the two Karate lines genuinely differ, so it is worth stating with versions. On **1.5.2** an exception inside the hook is caught, logged at WARN as `afterScenario hook failed: ...`, and swallowed — the scenario still reports as passed. On **2.x** the hook is recorded as a step of its own and a throw **fails the scenario**, the same convention a failing step follows; you opt out by wrapping the hook body in a `try`/`catch`. The practical reading is the same on both: a teardown hook that can fail should say so itself. Do not rely on the runtime to surface it, and do not rely on it staying quiet either. ## Reviewing a hook-based teardown - Is the hook installed where it will actually fire — top-level feature or config file, not a called helper? - Does the body assume the scenario passed? It also runs after failures, where variables may be unset. - Does it call out to an external system? A slow hook multiplies across every scenario in the suite. - Is a failure inside it visible to whoever reads the report, rather than only to whoever reads the log? - Would ordinary steps be clearer? A hook is invisible at the point of failure, and that cost is real for anyone reading the feature file cold. ## The short version "A Karate hook is a JavaScript function assigned to a configure key. `afterScenario` runs after every scenario in scope, whether it passed or failed, but only for top-level scenarios — the runtime skips it whenever the scenario has a caller, so the line does nothing in a called feature."

  • Where should a Karate teardown hook be installed so it runs for every scenario in a suite?
    In the config file, with `karate.configure('afterScenario', fn)`, because every top-level scenario picks its configuration up from there. Installing it in a shared helper feature is the common mistake: hooks are skipped whenever a scenario has a caller, so a helper reached through `call` or `callonce` never fires its own hook. If teardown must happen inside a called feature, write it as ordinary steps at the end of that feature instead.
  • How does `configure afterScenarioOutline` differ from `configure afterScenario` on a Scenario Outline?
    `afterScenario` fires once per `Examples` row, because each row is a scenario in its own right; `afterScenarioOutline` fires once after the whole outline has run, and after the per-row `afterScenario` hooks. Under parallel execution the row that finishes last is not necessarily the last row in the table, so an outline hook should not assume it can read state left by a particular row.
  • Why is a hook that calls a feature restricted to top-level scenarios at all?
    Because the hook body may itself use `karate.call`, and a hook that fired inside called features would re-enter for every level of nesting — a call-triggered hook that calls a feature that triggers the hook again. Gating on the scenario having no caller cuts that off cleanly. The same gate covers `afterScenario`, `afterScenarioOutline` and `afterFeature`, which is why all three behave identically inside a called feature.

saying these in an interview costs you the question

  • Expects a hook in a called feature to run like one in the top-level feature
  • Thinks afterScenario is skipped when the scenario failed
  • Describes the hook as an annotation or a registered class
  • Assumes afterScenarioOutline fires once per Examples row
  • Relies on a hook failure being reported the same way everywhere
  • Puts teardown that must always run into a shared helper feature