Q37 of 40 · Karate

How would you handle test execution speed in a 500+ feature Karate suite?

KarateSeniorkarateperformancescaleparallel-executionci-cd

Short answer

Short answer: Profile with the timeline report to find slow feature files. Increase parallel thread count incrementally and measure. Extract expensive shared setup (auth, seed data) into karate.callSingle so it runs once. Tag fast smoke tests @smoke and run them as a pre-gate before the full suite in CI.

Detail

Step 1 — profile first: open target/karate-reports/karate-timeline.html after a run. Identify:

  • Features with long individual durations (slow API under test, excessive test data setup)
  • Thread imbalance (one thread with many long features, others idle)

Step 2 — quick wins before changing parallelism:

  • Move auth token fetch to karate.callSingle (once per JVM, cached) — if 200 features each call get-token.feature, that's 200 HTTP calls instead of 1
  • Move expensive seed data creation to a callSingle-backed helper
  • Eliminate redundant repeated HTTP calls across features (find and share read-only fixtures)

Step 3 — increase threads:

  • Start at (CPU cores) threads; measure total time
  • Increase by 2, measure again; stop at diminishing returns or CI agent memory limit

Step 4 — split large feature files:

  • A feature with 50 scenarios is one parallel task on one thread
  • Split into multiple feature files (10 scenarios each) to distribute across threads

Step 5 — tiered execution:

  • @smoke (50 tests, < 2 min): run on every PR
  • Full suite (500 tests, ~ 10 min): run on merge to main
  • @slow (pagination, async, load tests): run nightly
mvn test -Dkarate.options="--tags @smoke"      # PR gate
mvn test                                        # merge gate
mvn test -Dkarate.options="--tags @nightly"    # nightly

// EXAMPLE

SuiteRunner.java

class SuiteRunner {

    // Fast PR gate — runs @smoke tagged features only
    @Test
    @Tag("smoke")
    void smokeTests() {
        Results results = Runner
            .path("classpath:features")
            .tags("@smoke", "~@wip")
            .parallel(4);
        assertThat(results.getFailCount()).isZero();
    }

    // Full suite — runs on merge to main
    @Test
    @Tag("full")
    void fullSuite() {
        int threads = Integer.parseInt(
            System.getProperty("karate.threads",
                String.valueOf(Runtime.getRuntime().availableProcessors())));

        Results results = Runner
            .path("classpath:features")
            .tags("~@wip", "~@nightly", "~@manual")
            .outputJunitXml(true)
            .parallel(threads);
        assertThat(results.getFailCount()).isZero();
    }
}
// CI:
// PR:   mvn test -Dkarate.threads=4 -Dgroups=smoke
// Main: mvn test -Dkarate.threads=8 -Dgroups=full

// WHAT INTERVIEWERS LOOK FOR

Profile-before-optimise discipline, callSingle for expensive shared setup, splitting large feature files for better parallelism, and tiered execution (smoke/full/nightly). These are the concrete levers for a 500+ feature suite.

// COMMON PITFALL

Blindly increasing thread count without measuring — at some point (usually 8-16 threads for typical API tests), the server or database becomes the bottleneck and more threads make execution slower, not faster.