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