Flood.io and Tricentis Integration: Enterprise Load Testing

Flood.io and Tricentis Integration: Enterprise Load Testing

Tricentis acquired Flood.io in 2021 and has been integrating it into its testing platform since. If you're in an enterprise environment already using Tricentis products — NeoLoad, qTest, Tosca — there are specific integration points worth knowing about. And even if you're not in the Tricentis ecosystem, Flood's REST API makes it straightforward to trigger load tests from any CI/CD pipeline.

Flood in the Tricentis Ecosystem

Tricentis positions Flood as the cloud-scale load testing layer in its platform. The complementary products:

NeoLoad — Tricentis's primary load testing tool. GUI-based, enterprise-focused, with its own protocol support and enterprise license model. NeoLoad runs on-premise or in Tricentis's managed cloud. Flood and NeoLoad serve overlapping use cases but different audiences — NeoLoad for teams deeply invested in Tricentis tooling, Flood for teams with JMeter/Gatling scripts who want cloud infrastructure.

qTest — Tricentis's test management platform. Flood results can be linked to qTest test runs for traceability from load test execution back to test plans.

Tosca — Tricentis's no-code test automation platform. Tosca doesn't directly integrate with Flood at the script level (Tosca tests aren't JMeter/Gatling scripts), but enterprise implementations often run Tosca functional tests alongside Flood load tests in CI/CD pipelines as separate stages.

The Tricentis integration matters most for enterprises where procurement and tooling decisions are already made. If your organization bought a Tricentis enterprise license, Flood is likely included, and the integration with qTest for reporting is worth setting up. For smaller teams or teams choosing tools independently, the Tricentis-specific integrations are less relevant than Flood's standalone API.

The Flood API

Everything you can do in the Flood UI, you can do via the Flood API. This is the foundation of any CI/CD integration.

Authentication

Generate an API token in your Flood account settings (Account → API Access). All API requests use HTTP Basic Auth with your token as the username and empty password:

curl -u your_api_token: https://api.flood.io/api/v1/account

Starting a Flood via API

The core API call to launch a flood:

curl -u $FLOOD_API_TOKEN: \
  -X POST https://api.flood.io/api/v1/floods \
  -H "Content-Type: application/json" \
  -d '{
    "flood": {
      "tool": "jmeter",
      "threads": 100,
      "duration": 600,
      "rampup": 60,
      "grids": [
        {
          "infrastructure": "demand",
          "instance_quantity": 2,
          "region": "us-east-1",
          "instance_type": "m5.xlarge",
          "stop_after_iteration": false
        }
      ],
      "tag_list": "ci-build,staging",
      "name": "Sprint 23 Load Test"
    },
    "flood_files": [
      {
        "url": "https://your-storage.example.com/tests/my-test.jmx"
      }
    ]
  }'

This returns a flood object with a UUID. Use that UUID to poll status.

Polling Flood Status

FLOOD_UUID="abc123-..."

curl -u $FLOOD_API_TOKEN: \
  https://api.flood.io/api/v1/floods/$FLOOD_UUID

Response includes:

{
  "uuid": "abc123-...",
  "status": "executing",
  "started_at": "2024-01-15T10:00:00Z",
  "stopped_at": null,
  "current_sample": {
    "requests": 45230,
    "response_time": 187,
    "concurrency": 98,
    "transaction_rate": 75.4,
    "error_rate": 0.02
  }
}

Status values: queued, provisioning, executing, finished, failed, stopped.

Retrieving Results

After a flood finishes:

curl -u $FLOOD_API_TOKEN: \
  https://api.flood.io/api/v1/floods/$FLOOD_UUID/report

This returns aggregate results including percentile response times, total requests, error rate, and transaction breakdown.

For raw time-series data:

curl -u $FLOOD_API_TOKEN: \
  https://api.flood.io/api/v1/floods/$FLOOD_UUID/result/timeseries

Jenkins Integration

Here's a complete Jenkins pipeline stage that runs a Flood load test and fails the build if performance thresholds are exceeded:

pipeline {
  agent any

  environment {
    FLOOD_API_TOKEN = credentials('flood-api-token')
    FLOOD_TEST_URL  = "https://your-file-storage.example.com/tests/my-load-test.jmx"
  }

  stages {
    stage('Deploy to Staging') {
      steps {
        // your deployment steps
        sh './deploy.sh staging'
      }
    }

    stage('Load Test') {
      steps {
        script {
          // Launch flood
          def startResponse = sh(
            script: """
              curl -sf -u ${FLOOD_API_TOKEN}: \
                -X POST https://api.flood.io/api/v1/floods \
                -H 'Content-Type: application/json' \
                -d '{
                  "flood": {
                    "tool": "jmeter",
                    "threads": 200,
                    "duration": 600,
                    "rampup": 60,
                    "grids": [{
                      "infrastructure": "demand",
                      "instance_quantity": 2,
                      "region": "us-east-1",
                      "instance_type": "m5.xlarge"
                    }],
                    "name": "Build #${BUILD_NUMBER} Load Test"
                  },
                  "flood_files": [{"url": "${FLOOD_TEST_URL}"}]
                }'
            """,
            returnStdout: true
          ).trim()

          def floodData = readJSON text: startResponse
          def floodUuid = floodData.uuid
          echo "Flood started: ${floodUuid}"
          env.FLOOD_UUID = floodUuid

          // Poll until complete
          def maxWait = 900  // 15 minutes
          def waited = 0
          def interval = 30

          while (waited < maxWait) {
            sleep(interval)
            waited += interval

            def statusResponse = sh(
              script: "curl -sf -u ${FLOOD_API_TOKEN}: https://api.flood.io/api/v1/floods/${floodUuid}",
              returnStdout: true
            ).trim()

            def floodStatus = readJSON text: statusResponse
            echo "Status: ${floodStatus.status} | RPS: ${floodStatus.current_sample?.transaction_rate} | P95: ${floodStatus.current_sample?.response_time}ms | Errors: ${floodStatus.current_sample?.error_rate}%"

            if (floodStatus.status == 'finished' || floodStatus.status == 'failed' || floodStatus.status == 'stopped') {
              break
            }
          }
        }
      }
    }

    stage('Evaluate Results') {
      steps {
        script {
          def reportResponse = sh(
            script: "curl -sf -u ${FLOOD_API_TOKEN}: https://api.flood.io/api/v1/floods/${env.FLOOD_UUID}/report",
            returnStdout: true
          ).trim()

          def report = readJSON text: reportResponse

          def errorRate = report.summary.errors_rate as Double
          def p95ResponseTime = report.summary.response_time_percentile_95 as Integer

          echo "Final Results:"
          echo "  Total Requests: ${report.summary.requests}"
          echo "  Error Rate: ${errorRate}%"
          echo "  P95 Response Time: ${p95ResponseTime}ms"

          // Fail build on threshold breach
          if (errorRate > 1.0) {
            error("Load test FAILED: Error rate ${errorRate}% exceeds 1% threshold")
          }

          if (p95ResponseTime > 2000) {
            error("Load test FAILED: P95 response time ${p95ResponseTime}ms exceeds 2000ms threshold")
          }

          echo "Load test PASSED: All thresholds met"
        }
      }
    }
  }

  post {
    always {
      script {
        // Archive results link
        echo "Results: https://app.flood.io/floods/${env.FLOOD_UUID}"
      }
    }
  }
}

Azure DevOps Integration

For Azure DevOps pipelines, the same pattern applies using Azure DevOps pipeline tasks:

# azure-pipelines.yml

trigger:
  - main

variables:
  FLOOD_API_TOKEN: $(floodApiToken)  # Set in pipeline secret variables

stages:
  - stage: Deploy
    jobs:
      - job: DeployToStaging
        steps:
          - script: ./deploy.sh staging
            displayName: 'Deploy to staging'

  - stage: LoadTest
    dependsOn: Deploy
    jobs:
      - job: RunFloodTest
        timeoutInMinutes: 30
        steps:
          - script: |
              # Start flood
              RESPONSE=$(curl -sf -u $(FLOOD_API_TOKEN): \
                -X POST https://api.flood.io/api/v1/floods \
                -H 'Content-Type: application/json' \
                -d '{
                  "flood": {
                    "tool": "gatling",
                    "threads": 500,
                    "duration": 600,
                    "rampup": 120,
                    "grids": [{
                      "infrastructure": "demand",
                      "instance_quantity": 3,
                      "region": "eu-west-1",
                      "instance_type": "m5.2xlarge"
                    }],
                    "name": "Release $(Build.BuildNumber)"
                  },
                  "flood_files": [{
                    "url": "$(GATLING_SCRIPT_URL)"
                  }]
                }')

              FLOOD_UUID=$(echo $RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['uuid'])")
              echo "##vso[task.setvariable variable=FLOOD_UUID]$FLOOD_UUID"
              echo "Flood UUID: $FLOOD_UUID"
            displayName: 'Launch load test'

          - script: |
              # Poll for completion
              while true; do
                STATUS=$(curl -sf -u $(FLOOD_API_TOKEN): \
                  https://api.flood.io/api/v1/floods/$(FLOOD_UUID) | \
                  python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")

                echo "Current status: $STATUS"

                if [ "$STATUS" = "finished" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "stopped" ]; then
                  break
                fi

                sleep 30
              done

              if [ "$STATUS" != "finished" ]; then
                echo "##vso[task.logissue type=error]Flood ended with status: $STATUS"
                exit 1
              fi
            displayName: 'Wait for flood to complete'

          - script: |
              # Evaluate thresholds
              REPORT=$(curl -sf -u $(FLOOD_API_TOKEN): \
                https://api.flood.io/api/v1/floods/$(FLOOD_UUID)/report)

              ERROR_RATE=$(echo $REPORT | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['errors_rate'])")
              P95=$(echo $REPORT | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['response_time_percentile_95'])")

              echo "Error rate: ${ERROR_RATE}%"
              echo "P95 response time: ${P95}ms"

              python3 -c "
              error_rate = float('${ERROR_RATE}')
              p95 = int('${P95}')
              if error_rate > 1.0:
                  raise SystemExit(f'FAIL: Error rate {error_rate}% > 1% threshold')
              if p95 > 2000:
                  raise SystemExit(f'FAIL: P95 {p95}ms > 2000ms threshold')
              print('PASS: All thresholds met')
              "
            displayName: 'Evaluate performance thresholds'

GitHub Actions Integration

name: Load Test on Release

on:
  release:
    types: [published]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - name: Launch Flood
        id: launch
        run: |
          RESPONSE=$(curl -sf -u ${{ secrets.FLOOD_API_TOKEN }}: \
            -X POST https://api.flood.io/api/v1/floods \
            -H 'Content-Type: application/json' \
            -d '{
              "flood": {
                "tool": "jmeter",
                "threads": 300,
                "duration": 900,
                "rampup": 120,
                "grids": [{
                  "infrastructure": "demand",
                  "instance_quantity": 2,
                  "region": "us-east-1",
                  "instance_type": "m5.xlarge"
                }],
                "name": "Release ${{ github.ref_name }}"
              },
              "flood_files": [{
                "url": "${{ vars.FLOOD_SCRIPT_URL }}"
              }]
            }')
          UUID=$(echo $RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['uuid'])")
          echo "flood_uuid=$UUID" >> $GITHUB_OUTPUT

      - name: Wait and evaluate
        run: |
          UUID=${{ steps.launch.outputs.flood_uuid }}

          for i in $(seq 1 30); do
            sleep 60
            STATUS=$(curl -sf -u ${{ secrets.FLOOD_API_TOKEN }}: \
              https://api.flood.io/api/v1/floods/$UUID | \
              python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
            echo "Status: $STATUS"
            [ "$STATUS" = "finished" ] && break
            [ "$STATUS" = "failed" ] && exit 1
          done

          REPORT=$(curl -sf -u ${{ secrets.FLOOD_API_TOKEN }}: \
            https://api.flood.io/api/v1/floods/$UUID/report)

          echo "Results: https://app.flood.io/floods/$UUID"
          python3 << 'EOF'
          import json, sys
          report = json.loads("""$REPORT""")
          summary = report['summary']
          error_rate = float(summary['errors_rate'])
          p95 = int(summary['response_time_percentile_95'])
          print(f"P95: {p95}ms, Error rate: {error_rate:.2f}%")
          if error_rate > 1.0 or p95 > 2000:
              sys.exit(1)
          EOF

Exporting Results to External Systems

Flood results can be pushed to external systems for long-term storage and trending.

Pushing to InfluxDB/Grafana

import requests
import json
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS

FLOOD_TOKEN = "your-flood-api-token"
FLOOD_UUID = "your-flood-uuid"

# Fetch time series from Flood
response = requests.get(
    f"https://api.flood.io/api/v1/floods/{FLOOD_UUID}/result/timeseries",
    auth=(FLOOD_TOKEN, "")
)
data = response.json()

# Write to InfluxDB
client = InfluxDBClient(url="http://your-influxdb:8086", token="influx-token", org="your-org")
write_api = client.write_api(write_options=SYNCHRONOUS)

for sample in data['timeseries']:
    point = (
        Point("load_test_results")
        .tag("flood_uuid", FLOOD_UUID)
        .tag("flood_name", data['name'])
        .field("requests_per_second", sample['transaction_rate'])
        .field("response_time_mean", sample['mean_response_time'])
        .field("response_time_p95", sample['response_time_percentile_95'])
        .field("error_rate", sample['error_rate'])
        .field("concurrency", sample['concurrency'])
        .time(sample['timestamp'])
    )
    write_api.write(bucket="load-tests", record=point)

print(f"Wrote {len(data['timeseries'])} data points to InfluxDB")

Linking to Tricentis qTest

If your organization uses qTest for test management, Flood results can be linked to qTest test runs via qTest's API. The typical integration:

  1. Before launching a flood, create a qTest test run for the load test scenario
  2. After the flood completes, update the test run with PASS/FAIL status based on thresholds
  3. Attach the Flood results URL as a test run attachment

This gives you traceability: a qTest test plan references a Flood test run, which links back to the actual flood results. This satisfies audit requirements in regulated industries where test evidence needs to be attached to a test management system.

The qTest API integration uses their REST API — consult qTest's API documentation for the specific endpoints, as they vary by qTest version.


Flood Agent for On-Premise Testing

If your application isn't reachable from Flood's cloud infrastructure, Flood provides an agent that runs on your own hardware. The agent connects outbound to Flood's control plane, registers itself as an available grid node, and executes floods on command.

Agent setup:

# Download the Flood agent for Linux
curl -O https://flood.io/downloads/flood-agent-linux-amd64.tar.gz
tar -xzf flood-agent-linux-amd64.tar.gz

# Configure with your API token and grid name
./flood-agent \
  --token your-api-token \
  --grid your-private-grid-name \
  --tags env:internal,network:private

Once registered, your private grid appears in the Flood UI alongside hosted grids. You can launch floods targeting your private grid using the same API calls — just specify the grid name instead of an infrastructure type.

This model is the most common integration for enterprises with internal applications: Flood handles orchestration and results aggregation, your own hardware handles the actual traffic generation.

Read more

Start now free