Skip to main content

ADVANCED

Hooks and Lifecycle

What You'll Learn

  • Implement lifecycle hooks for setup and teardown operations
  • Use karate.callSingle() for one-time global initialization
  • Configure afterScenario and afterFeature hooks
  • Share state across scenarios with proper scope management
  • Cache authentication tokens and reusable data efficiently
  • Coordinate test execution phases with proper timing control

Overview

Karate provides powerful lifecycle management capabilities without requiring Java code. Through configuration hooks and the karate.callSingle() mechanism, you can control test initialization, cleanup, and resource management across your entire test suite.

Lifecycle Hooks

Hook Execution Order

Understand when different hooks execute in the test lifecycle:

Feature: Lifecycle demonstration

Background:
# Runs before EVERY Scenario
* print 'Background executing for scenario:', karate.scenario.name
* def timestamp = new Date().getTime()

Scenario: First test
* print 'Scenario executing'
# Test steps here

Scenario: Second test
* print 'Another scenario executing'
# More test steps
```gherkin

### Available Hooks

| Hook | When It Runs | Scope | Use Case |
|------|--------------|-------|----------|
| `karate-config.js` | Before every Scenario | Global | Environment setup, base configuration |
| `Background` | Before every Scenario in Feature | Feature | Common setup for all scenarios |
| `callonce` in Background | Once per Feature | Feature | Expensive setup shared by scenarios |
| `afterScenario` | After every Scenario | Scenario | Cleanup, screenshot on failure |
| `afterFeature` | After all Scenarios in Feature | Feature | Feature-level cleanup |
| `karate.callSingle()` | Once globally | Global | One-time initialization |

## After Hooks

### After Scenario Hook

Configure cleanup after each scenario:

```gherkin
Feature: After scenario hook

Background:
* configure afterScenario =
"""
function() {
var info = karate.info;
karate.log('Scenario:', info.scenarioName, 'finished with status:', info.errorMessage ? 'FAILED' : 'PASSED');

// Take screenshot on failure
if (info.errorMessage) {
karate.log('Error:', info.errorMessage);
// Custom failure handling
driver.screenshot();
}

// Cleanup resources
if (karate.get('tempFiles')) {
karate.get('tempFiles').forEach(function(file) {
karate.log('Deleting temp file:', file);
// Delete temp file logic
});
}
}
"""

Scenario: Test with cleanup
* def tempFiles = []
* tempFiles.push('temp1.txt')
* print 'Test executing'
# Cleanup runs automatically after
```gherkin

### After Feature Hook

Configure feature-level cleanup:

```gherkin
Feature: After feature hook

Background:
* configure afterFeature =
"""
function() {
karate.log('Feature completed');

// Generate feature-level report
var stats = {
feature: karate.info.featureFileName,
scenarios: karate.info.scenarioCount,
passed: karate.info.scenarioPassed
};

karate.log('Feature stats:', stats);

// Cleanup feature-level resources
if (karate.get('featureResources')) {
karate.get('featureResources').cleanup();
}
}
"""

Scenario: First scenario
* print 'Scenario 1'

Scenario: Second scenario
* print 'Scenario 2'

# afterFeature runs once after all scenarios complete
```gherkin

### After Scenario Outline

Special handling for data-driven tests:

```gherkin
Feature: Scenario Outline hooks

Background:
* configure afterScenario =
"""
function() {
// Runs after EACH example row
karate.log('Example completed:', karate.info.scenarioName);
}
"""

* configure afterScenarioOutline =
"""
function() {
// Runs after ALL example rows
karate.log('All examples completed for outline');
}
"""

Scenario Outline: Data-driven test
* print 'Testing with:', value

Examples:
| value |
| 1 |
| 2 |
| 3 |
```gherkin

## Global One-Time Setup

### Basic karate.callSingle()

Execute setup once across all features:

```javascript
// karate-config.js
function fn() {
var config = {
baseUrl: 'https://api.example.com'
};

// One-time authentication
var authToken = karate.callSingle('classpath:auth/get-token.feature', config);
config.authToken = authToken.response.token;

// One-time test data setup
var testData = karate.callSingle('classpath:setup/create-test-data.feature');
config.testUsers = testData.users;
config.testProducts = testData.products;

return config;
}
```gherkin

### Parallel Execution Safety

`karate.callSingle()` is thread-safe for parallel execution:

```javascript
// karate-config.js
function fn() {
var config = {};

// This runs only once even with parallel execution
var globalSetup = karate.callSingle('classpath:global-setup.feature', {
environment: karate.env,
timestamp: new Date().getTime()
});

// All parallel threads share this data
config.sharedData = globalSetup.response;

// Thread-local configuration
config.threadId = java.lang.Thread.currentThread().getName();

return config;
}
```gherkin

### Multiple CallSingle with Cache Keys

Use different cache keys for different setups:

```javascript
// karate-config.js
function fn() {
var config = {};

// Different cache keys for different auth types
var adminAuth = karate.callSingle('classpath:auth.feature?admin', {
username: 'admin',
password: 'admin123'
});

var userAuth = karate.callSingle('classpath:auth.feature?user', {
username: 'testuser',
password: 'user123'
});

config.adminToken = adminAuth.token;
config.userToken = userAuth.token;

// Cache key based on environment
var envData = karate.callSingle('classpath:env-setup.feature?' + karate.env, {
env: karate.env
});

config.envSettings = envData.settings;

return config;
}
```gherkin

## Caching Strategies

### Development Mode Caching

Cache authentication for faster development:

```javascript
// karate-config.js
function fn() {
var config = {};

// Enable caching in dev mode
if (karate.env === 'dev') {
karate.configure('callSingleCache', {
minutes: 15,
dir: 'target/auth-cache'
});
}

// This will be cached to disk in dev mode
var auth = karate.callSingle('classpath:expensive-auth.feature');
config.authHeader = 'Bearer ' + auth.token;

return config;
}
```gherkin

### Custom Cache Implementation

Implement custom caching logic:

```javascript
// karate-config.js
function fn() {
var config = {};

var CacheManager = Java.type('com.example.CacheManager');
var cache = new CacheManager();

// Check custom cache first
var cachedToken = cache.get('auth-token');

if (cachedToken && !cache.isExpired('auth-token')) {
config.authToken = cachedToken;
karate.log('Using cached token');
} else {
// Get fresh token
var auth = karate.callSingle('classpath:auth.feature');
config.authToken = auth.token;

// Update cache
cache.put('auth-token', auth.token, 3600); // 1 hour TTL
karate.log('Token cached for 1 hour');
}

return config;
}
```gherkin

## Background Patterns

### One-Time Feature Setup

Use `callonce` for feature-level initialization:

```gherkin
Feature: Feature with one-time setup

Background:
# This runs once per feature
* callonce read('feature-setup.feature')

# Access the setup data
* def featureData = karate.get('setupResult')

# This runs before each scenario
* def scenarioId = java.util.UUID.randomUUID()

Scenario: First scenario
* print 'Using feature data:', featureData
* print 'Scenario ID:', scenarioId

Scenario: Second scenario
* print 'Reusing feature data:', featureData
* print 'New Scenario ID:', scenarioId
```gherkin

### Conditional Background

Execute background steps conditionally:

```gherkin
Feature: Conditional background

Background:
* def skipSetup = karate.properties['skip.setup'] || false

* if (!skipSetup) karate.call('setup.feature')

# Environment-specific setup
* if (karate.env == 'staging') karate.call('staging-setup.feature')
* if (karate.env == 'prod') karate.configure('ssl', true)

Scenario: Test with conditional setup
* print 'Setup completed based on conditions'
```gherkin

## Resource Management

### Cleanup Patterns

Ensure proper resource cleanup:

```gherkin
Feature: Resource management

Background:
* def resources = []

* def createResource =
"""
function(type) {
var resource = { id: java.util.UUID.randomUUID(), type: type };
karate.get('resources').push(resource);
return resource;
}
"""

* configure afterScenario =
"""
function() {
var resources = karate.get('resources') || [];
resources.forEach(function(r) {
karate.log('Cleaning up:', r.type, r.id);
// Cleanup logic here
});
}
"""

Scenario: Test with automatic cleanup
* def user = createResource('user')
* def order = createResource('order')
* print 'Created resources:', user, order
# Cleanup happens automatically
```gherkin

### Connection Pool Management

Manage shared connections efficiently:

```javascript
// karate-config.js
function fn() {
var config = {};

// Create connection pool once
var poolSetup = karate.callSingle('classpath:setup-connection-pool.feature');

var ConnectionPool = Java.type('com.example.ConnectionPool');
config.getConnection = function() {
return ConnectionPool.getInstance().getConnection();
};

config.releaseConnection = function(conn) {
ConnectionPool.getInstance().release(conn);
};

// Register cleanup
config.afterFeature = function() {
ConnectionPool.getInstance().shutdown();
};

return config;
}
```gherkin

## State Management

### Sharing State Across Scenarios

Use proper scope for state sharing:

```gherkin
Feature: State management

Background:
# Feature-level state (shared across scenarios)
* callonce read('initialize-state.feature')
* def sharedState = karate.get('featureState')

# Scenario-level state (isolated)
* def scenarioState = { id: karate.scenario.name }

Scenario: Modify shared state
* set sharedState.lastAccess = new Date()
* print 'Shared state updated:', sharedState

Scenario: Read shared state
* print 'Shared state:', sharedState
* match sharedState.lastAccess == '#notnull'
```gherkin

### Thread-Safe State

Handle state in parallel execution:

```javascript
// karate-config.js
function fn() {
var config = {};

// Thread-safe counter
var AtomicInteger = Java.type('java.util.concurrent.atomic.AtomicInteger');
var counter = new AtomicInteger(0);

config.getNextId = function() {
return counter.incrementAndGet();
};

// Thread-safe collection
var ConcurrentHashMap = Java.type('java.util.concurrent.ConcurrentHashMap');
var sharedMap = new ConcurrentHashMap();

config.putShared = function(key, value) {
sharedMap.put(key, value);
};

config.getShared = function(key) {
return sharedMap.get(key);
};

return config;
}
```gherkin

## Best Practices

### Hook Organization

```javascript
// ✅ Good: Organized hooks in karate-config.js
function fn() {
var config = {};

// 1. Environment setup
config.env = karate.env || 'dev';

// 2. One-time setup
if (!config.env.startsWith('local')) {
var setup = karate.callSingle('classpath:global-setup.feature');
config.setupData = setup;
}

// 3. Configuration
karate.configure('connectTimeout', 5000);
karate.configure('readTimeout', 10000);

return config;
}

// ❌ Avoid: Complex objects in callSingle
var result = karate.callSingle('classpath:complex.feature');
// Don't return Java objects or JS functions
```gherkin

### Error Handling

```gherkin
# ✅ Good: Graceful failure handling
Background:
* configure afterScenario =
"""
function() {
try {
// Cleanup logic
karate.call('cleanup.feature');
} catch (e) {
karate.log('Cleanup failed:', e.message);
// Continue without failing the test
}
}
"""
```gherkin

## Troubleshooting

### Common Issues

```javascript
// Issue: callSingle not caching
// Solution: Ensure pure JSON return
var result = karate.callSingle('auth.feature');
// ✅ Returns: { token: 'abc123' }
// ❌ Avoid: { token: 'abc123', connection: JavaObject }

// Issue: afterFeature not running
// Solution: Use Runner API, not JUnit
Runner.path("classpath:features")
.parallel(5); // afterFeature works

// Issue: State pollution between scenarios
// Solution: Use proper scoping
// Feature-level: callonce
// Scenario-level: def in Background
```gherkin

## Next Steps

- Explore [Debugging and Troubleshooting](/docs/advanced/debugging) for test diagnostics
- Learn about [Parallel Execution](/docs/running-tests/parallel-execution) with hooks
- Understand [Configuration](/docs/core-syntax/variables) for environment management