Parallel Test Execution

As your API grows, test suites can become large and slow. Running tests in parallel can significantly reduce execution time in CI/CD pipelines. This guide shows you how to organize and execute Drift tests across multiple files for parallel execution.

Consider splitting your test suite when:

  • Test execution time is slow: Your single test suite takes more than a few minutes to run

  • Logical separation exists: Your API has distinct domains (e.g., /pet, /store, /user endpoints)

  • CI pipeline optimization: You want faster feedback by running tests in parallel CI jobs

  • Team organization: Different teams own different parts of the API

How it Works

Drift doesn't currently support native parallel execution or importing other Drift files. However, you can achieve parallelism by:

  1. Creating multiple Drift test files, each covering a subset of operations

  2. Sharing common resources (datasets, Lua scripts, OpenAPI specs)

  3. Running each test file in separate CI jobs or processes

Example: Splitting the Petstore API

The Swagger Petstore API has three logical domains: pets, store, and users. Let's split these into separate test suites.

  1. Create a directory structure that separates test suites while sharing common files:

    project/
    ├── openapi.yaml              # Shared OpenAPI spec
    ├── petstore.dataset.yaml     # Shared dataset
    ├── petstore.lua              # Shared Lua functions
    └── drift/
        ├── pets.yaml             # Tests for /pet/* endpoints
        ├── store.yaml            # Tests for /store/* endpoints
        └── users.yaml            # Tests for /user/* endpoints
  2. Each Drift file references the same shared resources but tests different operations.

    drift/pets.yaml - Tests for /pet/* endpoints:

    # yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
    drift-testcase-file: v1
    title: "Petstore API - Pet Endpoints"
    
    sources:
      - name: petstore-oas
        path: ../openapi.yaml
      - name: petstore-data
        path: ../petstore.dataset.yaml
      - name: functions
        path: ../petstore.lua
    
    plugins:
      - name: oas
      - name: json
      - name: data
      - name: junit-output
    
    global:
      auth:
        apply: true
        parameters:
          authentication:
            scheme: apiKey
            name: api_key
            in: header
            value: ${functions:api_key}
    
    operations:
      addPet_Success:
        target: petstore-oas:addPet
        description: "Add a new pet to the store"
        parameters:
          request:
            body:
              id: 12345
              name: "Fluffy"
              status: "available"
        expected:
          response:
            statusCode: 200
    
      getPetById_Success:
        target: petstore-oas:getPetById
        description: "Get pet by ID"
        dataset: petstore-data
        parameters:
          path:
            petId: ${petstore-data:pets.pet1.id}
        expected:
          response:
            statusCode: 200
    
      updatePet_Success:
        target: petstore-oas:updatePet
        description: "Update an existing pet"
        parameters:
          request:
            body:
              id: 12345
              name: "Fluffy Updated"
              status: "sold"
        expected:
          response:
            statusCode: 200
    
      deletePet_Success:
        target: petstore-oas:deletePet
        description: "Delete a pet"
        parameters:
          path:
            petId: 12345
        expected:
          response:
            statusCode: 200

    drift/store.yaml - Tests for /store/* endpoints:

    # yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
    drift-testcase-file: v1
    title: "Petstore API - Store Endpoints"
    
    sources:
      - name: petstore-oas
        path: ../openapi.yaml
      - name: petstore-data
        path: ../petstore.dataset.yaml
      - name: functions
        path: ../petstore.lua
    
    plugins:
      - name: oas
      - name: json
      - name: data
      - name: junit-output
    
    operations:
      getInventory_Success:
        target: petstore-oas:getInventory
        description: "Get store inventory"
        expected:
          response:
            statusCode: 200
    
      placeOrder_Success:
        target: petstore-oas:placeOrder
        description: "Place an order for a pet"
        parameters:
          request:
            body:
              id: 1
              petId: 12345
              quantity: 1
              status: "placed"
        expected:
          response:
            statusCode: 200
    
      getOrderById_Success:
        target: petstore-oas:getOrderById
        description: "Get order by ID"
        parameters:
          path:
            orderId: 1
        expected:
          response:
            statusCode: 200
    
      deleteOrder_Success:
        target: petstore-oas:deleteOrder
        description: "Delete an order"
        parameters:
          path:
            orderId: 1
        expected:
          response:
            statusCode: 200

    drift/users.yaml - Tests for /user/* endpoints:

    # yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
    drift-testcase-file: v1
    title: "Petstore API - User Endpoints"
    
    sources:
      - name: petstore-oas
        path: ../openapi.yaml
      - name: petstore-data
        path: ../petstore.dataset.yaml
      - name: functions
        path: ../petstore.lua
    
    plugins:
      - name: oas
      - name: json
      - name: data
      - name: junit-output
    
    operations:
      createUser_Success:
        target: petstore-oas:createUser
        description: "Create a new user"
        parameters:
          request:
            body:
              id: 1
              username: "testuser"
              email: "test@example.com"
        expected:
          response:
            statusCode: 200
    
      getUserByName_Success:
        target: petstore-oas:getUserByName
        description: "Get user by username"
        parameters:
          path:
            username: "testuser"
        expected:
          response:
            statusCode: 200
    
      updateUser_Success:
        target: petstore-oas:updateUser
        description: "Update an existing user"
        parameters:
          path:
            username: "testuser"
          request:
            body:
              id: 1
              username: "testuser"
              email: "updated@example.com"
        expected:
          response:
            statusCode: 200
    
      deleteUser_Success:
        target: petstore-oas:deleteUser
        description: "Delete a user"
        parameters:
          path:
            username: "testuser"
        expected:
          response:
            statusCode: 200
  3. Local Execution. Run each suite independently in separate terminal sessions or use shell backgrounding:

    # Run all suites in parallel (backgrounded)
    drift --test-files drift/pets.yaml --server-url http://localhost:8080 &
    drift --test-files drift/store.yaml --server-url http://localhost:8080 &
    drift --test-files drift/users.yaml --server-url http://localhost:8080 &
    
    # Wait for all background jobs to complete
    wait

    CI/CD Execution (GitHub Actions). Use a matrix strategy to run each test suite in a separate job:

    name: API Contract Tests (Parallel)
    
    on:
      pull_request:
        branches: [ main ]
      push:
        branches: [ main ]
    
    jobs:
      drift-tests:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            suite: [pets, store, users]
          fail-fast: false  # Continue running other suites if one fails
    
        steps:
          - name: Checkout Code
            uses: actions/checkout@v4
    
          - name: Download and Install Drift
            run: |
              wget https://download.pactflow.io/drift/latest/linux-x86_64.zip
              unzip linux-x86_64.zip
              echo "$(pwd)" >> $GITHUB_PATH
    
          - name: Start API Provider
            run: |
              npm install
              npm start &
              sleep 5
    
          - name: Run Drift Tests - ${{ matrix.suite }}
            run: |
              drift --test-files drift/${{ matrix.suite }}.yaml \
                    --server-url http://localhost:8080 \
                    --output-dir ./drift-results-${{ matrix.suite }}
    
          - name: Archive Test Results
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: drift-junit-report-${{ matrix.suite }}
              path: ./drift-results-${{ matrix.suite }}

    This configuration runs three parallel jobs: drift-tests (pets), drift-tests (store), and drift-tests (users).

Publishing Results to PactFlow (BDCT)

When using parallel test execution with Bi-Directional Contract Testing (BDCT), you'll encounter a challenge: each test suite produces its own verification result file, but PactFlow requires a single bundled result for publishing.

When you run three parallel suites, you get three separate result files:

  • drift-results-pets/verification-result.json

  • drift-results-store/verification-result.json

  • drift-results-users/verification-result.json

PactFlow's pactflow publish-provider-contract command expects a single verification result bundle representing the complete provider verification.

Drift provides a bundle command to merge multiple verification results into a single file:

drift bundle --results drift-results-pets drift-results-store drift-results-users

This produces a single bundled result file that can be published to PactFlow.

Updated CI/CD Workflow with Result Bundling

Here's an enhanced GitHub Actions workflow that collects results from parallel jobs and publishes them to PactFlow:

name: API Contract Tests with PactFlow

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]

jobs:
  drift-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        suite: [pets, store, users]
      fail-fast: false

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Download and Install Drift
        run: |
          wget https://download.pactflow.io/drift/latest/linux-x86_64.zip
          unzip linux-x86_64.zip
          echo "$(pwd)" >> $GITHUB_PATH

      - name: Start API Provider
        run: |
          npm install
          npm start &
          sleep 5

      - name: Run Drift Tests - ${{ matrix.suite }}
        run: |
          drift --test-files drift/${{ matrix.suite }}.yaml \
                --server-url http://localhost:8080 \
                --output-dir ./drift-results-${{ matrix.suite }}

      - name: Upload Verification Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: verification-results-${{ matrix.suite }}
          path: ./drift-results-${{ matrix.suite }}

  publish-to-pactflow:
    needs: drift-tests
    runs-on: ubuntu-latest
    if: always()  # Run even if some tests failed

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Download and Install Drift
        run: |
          wget https://download.pactflow.io/drift/latest/linux-x86_64.zip
          unzip linux-x86_64.zip
          echo "$(pwd)" >> $GITHUB_PATH

      - name: Download All Verification Results
        uses: actions/download-artifact@v4
        with:
          path: ./all-results
          pattern: verification-results-*

      - name: Bundle Verification Results
        run: |
          drift bundle --results \
            ./all-results/verification-results-pets \
            ./all-results/verification-results-store \
            ./all-results/verification-results-users \
            --output ./bundled-results

      - name: Publish to PactFlow
        run: |
          pactflow publish-provider-contract \
            openapi.yaml \
            --provider "Petstore API" \
            --provider-app-version ${{ github.sha }} \
            --branch ${{ github.ref_name }} \
            --verification-results ./bundled-results/verification-result.json \
            --verification-results-content-type application/json \
            --verifier drift
        env:
          PACT_BROKER_BASE_URL: ${{ secrets.PACTFLOW_BASE_URL }}
          PACT_BROKER_TOKEN: ${{ secrets.PACTFLOW_TOKEN }}

Key differences from the basic workflow:

  • Separate jobs: Tests run in parallel in drift-tests job, publishing happens in publish-to-pactflow job

  • Artifact collection: Each parallel job uploads its verification results as artifacts

  • Result download: The publish job downloads all artifacts using a pattern match

  • Result bundling: drift bundle merges multiple result directories into a single output

  • Single publish: One pactflow publish-provider-contract call with the bundled results

State Management Considerations

Be careful with stateful tests when running in parallel. When testing the same API instance concurrently, tests can interfere with each other if they modify shared state:

# Suite A: Testing DELETE returns 404 after deletion
deletePet_ThenGet404:
  # Step 1: Delete pet ID 12345
  # Step 2: Verify GET returns 404

# Suite B: Running in parallel, testing GET returns 200
getPet_Success:
  # Expects pet ID 12345 to exist and return 200

If both suites run against the same API instance simultaneously, Suite A might delete the pet while Suite B is trying to retrieve it, causing Suite B to fail unexpectedly.

Check the available solutions:

  1. Ensure each suite operates on different data

    • Suite A tests pet IDs 10000-19999

    • Suite B tests pet IDs 20000-29999

    • Suite C tests pet IDs 30000-39999

  2. If your CI supports it, spin up a separate API instance per suite:

    jobs:
      drift-tests:
        strategy:
          matrix:
            suite: [pets, store, users]
            port: [8080, 8081, 8082]
    
        steps:
          - name: Start API Provider on Port ${{ matrix.port }}
            run: |
              PORT=${{ matrix.port }} npm start &
              sleep 5
    
          - name: Run Tests
            run: |
              drift --test-files drift/${{ matrix.suite }}.yaml \
                    --server-url http://localhost:${{ matrix.port }}
  3. For tests that inherently conflict (creating/deleting the same resources), run them serially:

    jobs:
      drift-tests-parallel:
        # Read-only tests run in parallel
        strategy:
          matrix:
            suite: [get-pets, get-store, get-users]
    
      drift-tests-serial:
        # Stateful tests run after parallel tests complete
        needs: drift-tests-parallel
        runs-on: ubuntu-latest
        steps:
          - name: Run Full CRUD Test Suite
            run: drift --test-files drift/full-crud.yaml

Tips and Best Practices

  1. Logical Grouping. Group tests by:

    • API domain/resource (e.g., /pets/orders)

    • HTTP method (e.g., read-operations.yamlwrite-operations.yaml)

    • Test type (e.g., happy-path.yaml, error-cases.yaml)

  2. Share Common Configuration. Extract authentication, common headers, and base URLs into shared global sections to avoid duplication.

  3. Use Descriptive Names.Name your test files clearly so CI logs are easy to understand

    • ✅ drift/user-authentication-tests.yaml

    • ✅ drift/product-catalog-crud.yaml

    • ❌ drift/test1.yaml

  4. Monitor Individual Suite Performance. Track how long each suite takes to identify bottlenecks:

    time drift --test-files drift/pets.yaml
  5. Balance Parallelism. Splitting into too many small suites can add overhead. Aim for 3-10 logical groups depending on your API size.

Verification

After setting up parallel execution, verify your configuration

  1. Check total execution time: Parallel execution should be faster than serial

  2. Verify no conflicts: Ensure tests don't interfere with each other

  3. Check CI reporting: Confirm all suites report results correctly

  4. Test failure handling: Verify that a failure in one suite doesn't block others (e.g. use fail-fast: false in GitHub Actions)

Next Steps

Once you have parallel tests running:

  1. Monitor your CI pipeline execution times.

  2. Identify slow-running suites for further optimization.

  3. Consider provider verification with PactFlow for distributed testing.

  4. Set up test data isolation strategies to prevent conflicts.

Publication date: