Jenkins

Run API requests, flows and load tests from a Jenkinsfile. There is no plugin to install: atra is a single binary that a sh step can call like any other command.

The shortest thing that works

A declarative pipeline that calls an endpoint and fails the build if the answer is wrong.

pipeline {
    agent any

    stages {
        stage('Health check') {
            steps {
                sh 'npx --yes @atrahasis/cli GET https://api.example.com/health -a "status eq 200"'
            }
        }
    }
}

If the status is not 200, atra exits non-zero, the sh step fails, and the build goes red. That exit code is the entire integration.

Getting atra onto the agent

Jenkins agents vary far more than hosted runners do, so pick whichever of these matches yours. All three were run against a real Jenkins instance and all three work.

Node is already on the agent

Simplest case. Call it through npx and there is nothing to install or keep updated.

sh 'npx --yes @atrahasis/cli GET $base_url/health -a "status eq 200"'

Node is not on the agent

Use the NodeJS plugin to provide it for the block, then carry on as above.

stage('Health check') {
    tools { nodejs 'node-22' }
    steps {
        sh 'npx --yes @atrahasis/cli GET $base_url/health -a "status eq 200"'
    }
}

Run the stage in a container

Good when you want the API tests isolated from whatever else the agent has installed.

stage('Health check') {
    agent { docker { image 'node:22-alpine' } }
    steps {
        sh 'npx --yes @atrahasis/cli GET $base_url/health -a "status eq 200"'
    }
}

No Node at all

Install the binary directly. Worth doing on a long-lived agent where you would rather not fetch it on every build.

sh 'curl -fsSL https://cli.atrahasis.dev | sh'
sh 'atra GET $base_url/health -a "status eq 200"'
Alpine agents are fine. The installer detects musl and fetches a statically linked build, so node:22-alpine and similar slim images work without a glibc compatibility layer. The glibc builds go back to 2.17, which covers RHEL 7 and 8, Amazon Linux 2 and Ubuntu 18.04, the kind of long-lived agent Jenkins tends to accumulate.

Passing a base URL or a credential

Flows and load specs read their values from an Atrahasis environment. A variable marked as a secret with its source set to OS is read from an operating system environment variable named exactly like the key, so a key of base_url needs an environment variable called base_url.

In Jenkins that bridge is one line. Store the value as a Secret text credential, then bind it in an environment block using the key as the variable name.

pipeline {
    agent any

    environment {
        base_url = credentials('atrahasis-base-url')
    }

    stages {
        stage('Run flow') {
            steps {
                sh 'npx --yes @atrahasis/cli run -f logout -e dev'
            }
        }
    }
}

The credential ID can be anything, here atrahasis-base-url. What matters is the name on the left of the assignment, because that is what atra looks up. Agents are almost always Linux, where environment variable names are case-sensitive, so copy the key from the app exactly.

How environments resolve, in full →

Binding a credential for one stage only

If you would rather not expose the value to the whole pipeline, use withCredentials around the step instead. Same outcome, narrower scope.

steps {
    withCredentials([string(credentialsId: 'atrahasis-base-url', variable: 'base_url')]) {
        sh 'npx --yes @atrahasis/cli run -f logout -e dev'
    }
}
Use single quotes around the sh script when it references a credential. With double quotes Groovy interpolates the value into the command string before the shell ever runs, and Jenkins will warn you that the secret could end up in the build log. Single quotes leave the $base_url for the shell to expand.

Getting your flows and specs onto the agent

Flows and load specs are built in Atrahasis, which writes them as plain files. Commit that folder and let the pipeline check it out. Most teams keep tests in their own repository, since several pipelines often want the same flow and tests outlive any one release.

Wrap the checkout in a dir block so the clone lands in its own folder and atra runs inside it.

stage('Run flow') {
    steps {
        dir('flows') {
            git url: 'https://github.com/my-org/api-flows.git', branch: 'main'
            sh 'npx --yes @atrahasis/cli run -f logout -e dev'
        }
    }
}

For a private repository add a credential: git url: '...', branch: 'main', credentialsId: 'github-token'.

Where atra runs matters

atra reads the flow group from the folder it is run in. If the group sits at the root of the checked-out repository, run inside that folder and leave the group name out. If several groups live side by side in one repository, run from the parent and name the group as the first argument: run checkout-flows -f signup -e dev.

The three jobs, one at a time

1. A request with assertions

Best after a deploy, to prove the thing answered. Every assertion has to pass or the stage fails.

stage('Health check') {
    steps {
        sh '''
            npx --yes @atrahasis/cli GET $base_url/health \
                -a "status eq 200" \
                -a "$.status eq UP" \
                -a "response_time lt 2000"
        '''
    }
}

The triple-quoted block keeps a long command readable. It has to be single quotes: with triple double quotes Groovy would try to interpolate $base_url and $.status itself.

2. A flow from the app

A flow chains requests and passes values between them, such as logging in and reusing the token. Drop -f to run every flow in the group.

stage('Flow') {
    steps {
        dir('flows') {
            git url: 'https://github.com/my-org/api-flows.git', branch: 'main'
            sh 'npx --yes @atrahasis/cli run -f logout -e dev'
        }
    }
}

3. A load test from the app

Same spec you built in the app, run headless. Pick the profile with -t, and drop -s to run every spec in the group.

stage('Load test') {
    steps {
        dir('loads') {
            git url: 'https://github.com/my-org/api-loads.git', branch: 'main'
            sh 'npx --yes @atrahasis/cli run -s logout -t load -e dev'
        }
    }
}

Load tests are long and noisy compared to the rest of a pipeline. A common arrangement is to keep requests and flows on every build and give the load test its own job on a cron trigger.

Test types and thresholds →

A complete Jenkinsfile

All three jobs together, with the test repositories checked out and the credential bridged. This is close to the pipeline the examples above were verified against.

pipeline {
    agent any

    environment {
        // The one rule: put the value in an OS environment variable and
        // match the key name. environments.json reads base_url back from
        // here, so the same flow runs unchanged on a laptop and on an agent.
        base_url = credentials('atrahasis-base-url')
    }

    stages {
        stage('Build') {
            steps {
                sh './mvnw -B -ntp -DskipTests package'
            }
        }

        stage('Health check') {
            steps {
                sh '''
                    npx --yes @atrahasis/cli GET $base_url/health \
                        -a "status eq 200" \
                        -a "$.status eq UP" \
                        -a "response_time lt 2000"
                '''
            }
        }

        stage('Flow') {
            steps {
                dir('flows') {
                    git url: 'https://github.com/my-org/api-flows.git', branch: 'main'
                    sh 'npx --yes @atrahasis/cli run -f logout -e dev'
                }
            }
        }

        stage('Load test') {
            steps {
                dir('loads') {
                    git url: 'https://github.com/my-org/api-loads.git', branch: 'main'
                    sh 'npx --yes @atrahasis/cli run -s logout -t load -e dev'
                }
            }
        }
    }
}

How the build turns red

atra exits 0 when every check passed and non-zero when any of them did not. A sh step fails on a non-zero exit status, so a failed assertion in a request, a failed step in a flow and a breached threshold in a load test all fail the stage and stop the build. No wrapper script and no parsing of the console output.

To let later stages run anyway, the usual catchError or a post block applies as it would around any other command. Reach for it sparingly: a stage that cannot fail the build is a stage nobody reads.

If something does not work

SymptomCause
npx: not foundNo Node on the agent. Add a tools { nodejs } block, use a docker agent, or install the binary with the shell installer.
Secret variables are not set in your shellThe credential is not bound, or the variable name does not match the key in the app. It is case-sensitive.
Unresolved variable(s)-e is missing from the command, or the variable is not in the environment you selected.
Jenkins warns that a secret may be exposedThe sh script uses double quotes, so Groovy interpolated the value. Switch to single quotes.
An assertion compares against the wrong valueAn unquoted assertion went through shell expansion first. atra reassembles unquoted assertions, but the shell gets there earlier and can substitute a variable or expand a glob. Quote each one: -a "status eq 200".