Parallel Execution
Run tests in parallel to dramatically cut execution time. Karate's core parallel feature operates independently of build tools and provides comprehensive reporting for CI/CD integration.
Karate v2 uses Java 21+ virtual threads for parallel execution, enabling massive parallelism with minimal overhead. You can run hundreds of scenarios in parallel without tuning thread pools.
When running in parallel, scenarios execute in any order and must be completely isolated from each other.
Critical rules for parallel execution:
- Each scenario must be runnable independently
- Don't share state between scenarios via Background variables
- Don't depend on execution order
- You should be able to comment out any scenario without breaking others
Test your isolation: Run scenarios individually and ensure they all pass independently.
Why this matters: Parallel execution runs scenarios on different threads simultaneously. Shared state or execution order dependencies will cause random, difficult-to-debug failures.
Benefits of Parallel Execution
- 5-10x faster execution for large test suites
- Zero configuration required - works out of the box
- Multiple levels - Features and scenarios run in parallel
- Comprehensive reporting - HTML, JUnit XML, Cucumber JSON
Basic Parallel Runner
The simplest way to run tests in parallel using the Runner API:
import io.karatelabs.core.Runner;
import io.karatelabs.core.SuiteResult;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class ParallelTest {
@Test
void testParallel() {
SuiteResult results = Runner.builder()
.path("classpath:features")
.tags("~@ignore")
.parallel(5); // 5 threads
assertFalse(results.isFailed());
}
}
Key points:
parallel()must be the last method called- Returns
SuiteResultwith execution details - Thread count determines parallelism level
Runner.Builder Configuration
Path Selection
// Single package
Runner.builder().path("classpath:features")
// Multiple packages (varargs)
Runner.builder().path("classpath:api", "classpath:integration")
// Mix packages and individual features
Runner.builder().path("classpath:smoke", "classpath:critical/login.feature")
// Dynamic paths
List<String> paths = Arrays.asList("classpath:users", "classpath:products");
Runner.builder().path(paths)
Tag Filtering
// Include specific tags
.tags("@smoke") // Only smoke tests
.tags("@api", "@smoke") // AND operation - must have both
.tags("@smoke,@critical") // OR operation - either tag
// Exclude tags
.tags("~@slow") // Exclude slow tests
.tags("~@ignore", "~@manual") // Exclude multiple
// Complex combinations
.tags("(@smoke or @critical) and not @flaky")
Report Configuration
SuiteResult results = Runner.builder()
.path("classpath:features")
.outputJunitXml(true) // For CI/CD integration
.outputCucumberJson(true) // For dashboards
.outputDir("target/karate-reports") // Custom directory
.karateEnv("staging") // Set environment
.systemProperty("api.timeout", "30000") // Pass properties
.parallel(8); // Execute with 8 threads
Execution Statistics
After running tests, Karate provides detailed execution statistics:
======================================================
elapsed: 2.35 | threads: 5 | thread time: 4.98
features: 54 | ignored: 25 | efficiency: 0.42
scenarios: 145 | passed: 145 | failed: 0
======================================================
Understanding the metrics:
- elapsed: Total wall clock time
- efficiency: How well threads were utilized (higher is better)
- thread time: Total CPU time across all threads
- features/scenarios: Execution and skip counts
Parallel Behavior
Multi-Level Parallelism
Karate runs tests in parallel at multiple levels:
- Features run in parallel across threads
- Scenarios within features run in parallel
- Scenario Outline examples run in parallel
Controlling Parallelism with @lock
When some scenarios need exclusive access to shared resources (a database, a file, an external service) — or when a whole feature must run sequentially — use the @lock tag. @lock is the v2 replacement for v1's @parallel=false, which is no longer recognized.
@lock=database
Scenario: test that modifies shared database state
# only one @lock=database scenario runs at a time
# other scenarios (without this lock) continue in parallel
@lock=database
Scenario: another database test
# waits for the first @lock=database scenario to finish
@lock=*
Scenario: exclusive test
# this scenario runs completely alone — no other scenarios run concurrently
@lock=<name>— named lock; scenarios with the same name serialize against each other, but unrelated scenarios still run in parallel@lock=*— exclusive lock; the scenario runs alone with no other scenarios concurrent
Feature-level tags propagate to every scenario, so @lock=<name> on a Feature: line forces every scenario in that feature to serialize — the drop-in for v1's feature-level @parallel=false:
@lock=order-workflow
Feature: Order workflow
# every scenario in this feature serializes against every other
# scenario tagged @lock=order-workflow
Sequential execution usually indicates test dependencies. Prefer independent tests; reach for @lock only when scenarios genuinely share mutable state. Use @lock=* only when full exclusion is required — it stalls the whole worker pool.
The v1 @parallel=false tag is silently ignored in v2 — no warning, no error. Replace it with @lock=<name> (or @lock=*) on the Feature: or Scenario: line. See Migration from v1.
Fail-Fast with abortSuiteOnFailure
In CI/CD pipelines where fast feedback matters, use abortSuiteOnFailure to stop the suite on the first failure:
function fn() {
karate.configure('abortSuiteOnFailure', true);
return {};
}
Or in a feature file:
* configure abortSuiteOnFailure = true
When enabled:
- Queued features and scenarios that haven't started yet are skipped entirely
- In-flight scenarios on other threads are interrupted at the next step boundary — they don't run to completion
- The suite exits with only the failing scenario reported, giving you a fast exit code
This is especially useful for Kubernetes release gates, Argo Rollouts, and other CD workflows where a fast non-zero exit code is critical for rollback decisions.
Thread Count Optimization
Environment-Based Threading
private int getOptimalThreads() {
String env = System.getProperty("karate.env", "dev");
int cores = Runtime.getRuntime().availableProcessors();
switch (env) {
case "dev": return Math.min(cores, 4); // Don't overwhelm dev machine
case "ci": return Math.min(cores, 6); // CI resource constraints
case "perf": return cores * 2; // Maximize for load testing
default: return cores;
}
}
Resource Considerations
- CPU cores: Good starting point is 1-2x CPU cores
- Memory: ~256MB per thread for typical tests
- Network: Consider external service limits (e.g., API rate limits)
- CI environment: Usually 2-6 threads work best
Tuning Thread Count
To optimize thread count:
- Start with
availableProcessors()or 1-2x CPU cores. - Monitor
efficiencyin execution statistics (aim for >0.7). - Use tools like
htoporVisualVMto check CPU/memory usage. - Adjust based on test suite size and external service limits (e.g., reduce threads if hitting API rate limits).
Debugging Parallel Failures
If tests fail in parallel:
- Check
results.getErrorMessages()for detailed error logs per scenario. - Review
karate-timeline.htmlto identify slow or failed tests by thread. - Enable
karate.logfor verbose logging:.systemProperty("log.level", "DEBUG"). - Isolate flaky tests by running with fewer threads or tagging them
@lock=*for exclusive execution.
Designing Tests for Parallel Execution
To maximize parallelism:
- Independent Tests: Ensure scenarios don’t share state (e.g., avoid global variables or shared files).
- Test Data: Use unique data per scenario (e.g., generate random IDs with
karate.uuid()). - API Rate Limits: Implement retries with
karate.retry()for external service constraints. - Feature Structure: Group related scenarios in features to optimize thread distribution.
Advanced Patterns
Environment-Specific Execution
void environmentOptimized() {
String env = System.getProperty("karate.env", "dev");
SuiteResult results = Runner.builder()
.path("classpath:features")
.tags(getEnvironmentTags(env))
.karateEnv(env)
.parallel(getThreadCount(env));
assertFalse(results.isFailed());
}
private String[] getEnvironmentTags(String env) {
if ("prod".equals(env)) {
return new String[] { "@smoke", "~@experimental" };
}
return new String[] { "~@ignore" };
}
Feature-Specific Threading
@Test
void optimizedByFeatureType() {
// Fast API tests with high parallelism
SuiteResult apiResults = Runner.builder()
.path("classpath:api")
.tags("@api", "@fast")
.parallel(12);
// Database tests with moderate parallelism
SuiteResult dbResults = Runner.builder()
.path("classpath:database")
.tags("@database")
.parallel(4);
assertFalse(apiResults.isFailed());
assertFalse(dbResults.isFailed());
}
Report Generation
Timeline Visualization
Karate automatically generates karate-timeline.html for visual analysis:
- Thread utilization - How effectively threads are used
- Performance bottlenecks - Identify slow tests
- Load balancing - Distribution across threads
CI/CD Integration
@Test
void ciOptimized() {
SuiteResult results = Runner.builder()
.path("classpath:features")
.tags("@regression", "~@slow")
.outputJunitXml(true) // Jenkins/CI integration
.outputCucumberJson(true) // Dashboard integration
.outputDir("target/ci-reports")
.parallel(4); // Conservative for CI
assertFalse(results.isFailed());
}
CI Tool Example (GitHub Actions)
Common Gotchas
- Thread count too high: Leads to resource exhaustion and poor efficiency
- Test dependencies: Scenarios must be independent to run in parallel
- Shared state: Avoid global variables or shared files between tests
- CI constraints: Use fewer threads in CI environments than locally
Next Steps
Maximize parallel execution effectiveness:
- Tags - Organize tests for optimal parallel execution
- Test Reports - Analyze parallel execution results
- Configuration - Environment-specific settings