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:
Creating multiple Drift test files, each covering a subset of operations
Sharing common resources (datasets, Lua scripts, OpenAPI specs)
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.
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/* endpointsEach 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: 200drift/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: 200drift/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: 200Local 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), anddrift-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-testsjob, publishing happens inpublish-to-pactflowjobArtifact 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 bundlemerges multiple result directories into a single outputSingle publish: One
pactflow publish-provider-contractcall 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:
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
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 }}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
Logical Grouping. Group tests by:
API domain/resource (e.g.,
/pets,/orders)HTTP method (e.g.,
read-operations.yaml,write-operations.yaml)Test type (e.g.,
happy-path.yaml,error-cases.yaml)
Share Common Configuration. Extract authentication, common headers, and base URLs into shared global sections to avoid duplication.
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
Monitor Individual Suite Performance. Track how long each suite takes to identify bottlenecks:
time drift --test-files drift/pets.yaml
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
Check total execution time: Parallel execution should be faster than serial
Verify no conflicts: Ensure tests don't interfere with each other
Check CI reporting: Confirm all suites report results correctly
Test failure handling: Verify that a failure in one suite doesn't block others (e.g. use
fail-fast: falsein GitHub Actions)
Next Steps
Once you have parallel tests running:
Monitor your CI pipeline execution times.
Identify slow-running suites for further optimization.
Consider provider verification with PactFlow for distributed testing.
Set up test data isolation strategies to prevent conflicts.