Skip to content

CI/CD Integration

This guide covers how to integrate qf into your CI/CD pipeline: storing your access token securely, letting the CLI auto-detect git context, and running the two-step upload pattern.

The Two-Step Pattern

Every CI integration follows the same two commands:

bash
qf login <identifier> "$YOUR_TOKEN" --force
qf <identifier> collect test-results/*.xml
  1. qf login --force saves the token without prompting (safe in non-interactive CI jobs).
  2. qf <identifier> collect uploads the results.

Use ci as the identifier in CI pipelines — it's short, descriptive, and easy to reference.

Environment Variables

The CLI reads configuration from environment variables, making it ideal for CI/CD environments.

Configuration Priority

Values are applied in the following order (highest to lowest priority):

  1. CLI flags (--environment, --branch, etc.)
  2. Environment variables (QF_*)
  3. Auto-detection (Git metadata, CI variables)

QF Environment Variables

VariablePurposeDefaultExample
QF_ENVIRONMENTEnvironment name for this test runstagingproduction, ci
QF_BRANCHGit branch nameAuto-detectedmain, feature/login
QF_COMMITGit commit SHAAuto-detecteda1b2c3d4e5f6...
QF_RETRY_MAXMaximum number of retry attempts35
QF_RETRY_DELAYBase delay between retries (Go duration format)1s2s, 500ms
QF_RETRY_MAX_DELAYMaximum delay between retries (Go duration format)30s60s
QF_TIMEOUTRequest timeout (Go duration format)30s60s, 2m
QF_VERBOSEEnable verbose outputfalsetrue, 1
QF_QUIETSuppress non-error outputfalsetrue, 1

See CLI Configuration for the full reference.

Auto-Detection

The CLI automatically detects Git branch and commit information from common CI environment variables:

Branch Detection (checked in order)

  1. QF_BRANCH (explicit override)
  2. GIT_BRANCH (Jenkins, GitLab, etc.)
  3. GITHUB_REF_NAME (GitHub Actions)
  4. CI_COMMIT_REF_NAME (GitLab CI)
  5. BITBUCKET_BRANCH (Bitbucket Pipelines)

Commit Detection (checked in order)

  1. QF_COMMIT (explicit override)
  2. GIT_COMMIT (Jenkins, GitLab, etc.)
  3. GITHUB_SHA (GitHub Actions)
  4. CI_COMMIT_SHA (GitLab CI)
  5. BITBUCKET_COMMIT (Bitbucket Pipelines)

Duration Format

Duration-related variables (QF_RETRY_DELAY, QF_RETRY_MAX_DELAY, QF_TIMEOUT) use Go duration format:

UnitSuffixExamples
Secondss30s, 90s
Millisecondsms500ms, 1500ms
Minutesm1m, 5m

Platform-Specific Examples

GitHub Actions

Install qf via Homebrew or a binary download, then run the two-step pattern. Store your token as a repository secret named QF_TOKEN.

yaml
name: Test Results

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

jobs:
  collect-test-results:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run tests
        run: |
          pytest --junitxml=results.xml

      - name: Upload results to Qualflare
        if: always()  # Run even if tests fail
        env:
          QF_TOKEN: ${{ secrets.QF_TOKEN }}
          QF_BRANCH: ${{ github.ref_name }}
          QF_COMMIT: ${{ github.sha }}
        run: |
          # Install qf CLI
          brew install qualflare/tap/qf

          # Upload results
          qf login ci "$QF_TOKEN" --force
          qf ci collect results.xml

Setting the access-token secret:

  1. Go to your repository Settings > Secrets and variables > Actions
  2. Click New repository secret
  3. Name: QF_TOKEN
  4. Value: Your Qualflare access token
  5. Click Add secret

GitLab CI

Add to your .gitlab-ci.yml:

yaml
stages:
  - test
  - report

# Run your tests
test:
  stage: test
  script:
    - pytest --junitxml=results.xml
  artifacts:
    when: always
    paths:
      - results.xml
    expire_in: 1 week

# Upload to Qualflare
upload-results:
  stage: report
  needs:
    - test
  script:
    # Install qf CLI
    - VERSION=0.1.8
    - curl -L "https://github.com/qualflare/qualflare-cli/releases/download/v${VERSION}/qf_${VERSION}_linux_amd64.tar.gz" -o qf.tar.gz
    - tar -xzf qf.tar.gz && mv qf /usr/local/bin/

    # Upload to Qualflare
    - qf login ci "$QF_TOKEN" --force
    - qf ci collect results.xml
  variables:
    QF_ENVIRONMENT: gitlab-ci
    # Branch and commit auto-detected from CI_COMMIT_REF_NAME and CI_COMMIT_SHA

Setting the access-token variable:

  1. Go to your project Settings > CI/CD > Variables
  2. Click Add variable
  3. Key: QF_TOKEN
  4. Value: Your Qualflare access token
  5. Mask variable: Checked
  6. Protect variable: Optional
  7. Click Add variable

Jenkins

Add a stage to your Jenkinsfile:

groovy
pipeline {
    agent any

    stages {
        stage('Test') {
            steps {
                sh 'pytest --junitxml=results.xml'
            }
            post {
                always {
                    junit 'results.xml'
                }
            }
        }

        stage('Upload to Qualflare') {
            steps {
                // Install qf CLI (run once or bake into your agent image)
                sh '''
                    if ! command -v qf &> /dev/null; then
                        VERSION=0.1.8
                        curl -L "https://github.com/qualflare/qualflare-cli/releases/download/v${VERSION}/qf_${VERSION}_linux_amd64.tar.gz" -o qf.tar.gz
                        tar -xzf qf.tar.gz && mv qf /usr/local/bin/
                    fi
                '''

                // Upload to Qualflare
                withCredentials([string(credentialsId: 'QF_TOKEN', variable: 'QF_TOKEN')]) {
                    sh '''
                        qf login ci "$QF_TOKEN" --force
                        qf ci collect results.xml
                    '''
                }
            }
        }
    }
}

Setting the access-token credential:

  1. Go to Manage Jenkins > Manage Credentials
  2. Select your domain (typically "Global")
  3. Click Add Credentials > Secret text
  4. Secret: Your Qualflare access token
  5. ID: QF_TOKEN
  6. Click Create

Azure DevOps

Add to your azure-pipelines.yml:

yaml
trigger:
  branches:
    include:
      - main
      - develop

variables:
  QF_ENVIRONMENT: 'azure-devops'

stages:
- stage: Test
  jobs:
  - job: TestAndReport
    pool:
      vmImage: 'ubuntu-latest'

    steps:
    - checkout: self
      displayName: 'Checkout code'

    - script: |
        VERSION=0.1.8
        curl -L "https://github.com/qualflare/qualflare-cli/releases/download/v${VERSION}/qf_${VERSION}_linux_amd64.tar.gz" -o qf.tar.gz
        tar -xzf qf.tar.gz && sudo mv qf /usr/local/bin/
      displayName: 'Install qf CLI'

    - script: pytest --junitxml=results.xml
      displayName: 'Run tests'

    - script: |
        qf login ci "$QF_TOKEN" --force
        qf ci collect results.xml
      env:
        QF_TOKEN: $(QF_TOKEN)
        # Branch and commit auto-detected from BUILD_SOURCEBRANCHNAME and BUILD_SOURCEVERSION
      displayName: 'Upload results to Qualflare'
      condition: always()

Setting the access-token secret:

  1. Go to your project Pipelines > Library
  2. Go to Variable groups, create or select a group
  3. Add variable: QF_TOKEN, value: Your Qualflare access token
  4. Mark as Secret: Checked
  5. Click Save

Security Best Practices

Token Management

  • Never hardcode tokens in your repository
  • Always use your CI/CD platform's secret/variable management
  • Restrict tokens to specific projects when possible
  • Rotate tokens regularly and revoke unused ones

Example: Checking for Leaked Tokens

Add a pre-commit hook to catch accidentally committed tokens:

bash
#!/bin/bash
# .git/hooks/pre-commit

# Check for Qualflare token patterns in staged files
if git diff --cached --name-only | xargs grep -l "qf_" 2>/dev/null; then
    echo "ERROR: Possible Qualflare token detected in staged files!"
    echo "Please remove tokens before committing."
    exit 1
fi

Troubleshooting

Login Fails in CI

Ensure the token value is correct and not empty:

yaml
- run: |
    echo "Token length: ${#QF_TOKEN}"  # Should be non-zero
    qf login ci "$QF_TOKEN" --force

Upload Fails with Timeout

Increase the timeout using QF_TIMEOUT:

yaml
env:
  QF_TIMEOUT: 120s  # 2 minutes

Retry Failures

Adjust retry settings for unreliable networks:

yaml
env:
  QF_RETRY_MAX: 5
  QF_RETRY_DELAY: 2s
  QF_RETRY_MAX_DELAY: 60s

Branch/Commit Not Detected

Manually override if auto-detection fails:

yaml
env:
  QF_BRANCH: ${GIT_BRANCH}
  QF_COMMIT: ${GIT_COMMIT}

Advanced Usage

Conditional Upload

Only upload on specific branches:

yaml
- name: Upload to Qualflare
  if: github.ref == 'refs/heads/main'
  env:
    QF_TOKEN: ${{ secrets.QF_TOKEN }}
  run: |
    qf login ci "$QF_TOKEN" --force
    qf ci collect results.xml

Multiple Test Files

Upload multiple test result files:

bash
# All XML files
qf ci collect test-results/*.xml

# Specific files
qf ci collect unit-tests.xml integration-tests.xml

Dry Run in CI

Test your CI configuration without actually uploading:

yaml
- name: Validate test results (dry run)
  run: qf ci collect results.xml --dry-run

Note: --dry-run does not upload to Qualflare, so the token does not need to be valid — but the identifier must still be registered locally with qf login.

See Also