Skip to content

Setup Snippets

Copy-paste CI setup for each test runner and each CI system. Every snippet runs your tests the way they already run, has the runner write a result file QASight reads, and records it with no qasight.yaml. Replace my-app with your project's name: runs recorded under the same cycle build its history.

Rather not copy anything? In the project folder, pip install qasight && qasight init detects the runners and writes these files for you, plus a qasight.yaml so qasight run works locally too (Command Reference § qasight init). qasight init --ci gitlab --dry-run prints one without writing it.

How each CI keeps the history between runs:

  • GitHub Actions: the action caches it (history: auto) and comments on pull requests. Inputs: CI-CD Pipeline Setup § GitHub Action.
  • GitLab CI and CircleCI: the snippet caches .testo-history/ per branch, falling back to the default branch's.
  • Jenkins: the history stays in the agent's workspace. For one history across agents and CI systems, run a team server and replace qasight ingest ... with qasight ingest --server https://your-server --token "$TESTO_API_TOKEN" ....

Outside GitHub, the snippets install qasight with uv (one binary that brings its own Python, so the image needs only curl). An image that has Python can run pip install qasight instead. Each run's summary is saved as testo-summary.md next to the build.

Python (pytest)

Test command: mkdir -p test-results && pytest -q tests --junitxml=test-results/pytest.xml

Python (pytest): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: mkdir -p test-results && pytest -q tests --junitxml=test-results/pytest.xml
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

Python (pytest): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

pytest:
  stage: test
  image: python:3.12
  cache:
    key: testo-history-pytest-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-pytest-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - pip install -r requirements.txt
    - mkdir -p test-results && pytest -q tests --junitxml=test-results/pytest.xml
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

Python (pytest): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('pytest') {
      steps {
        sh 'pip install -r requirements.txt'
        sh 'mkdir -p test-results && pytest -q tests --junitxml=test-results/pytest.xml'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

Python (pytest): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  pytest:
    docker:
      - image: cimg/python:3.12
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-pytest-{{ .Branch }}-
            - testo-history-pytest-
      - run: pip install -r requirements.txt
      - run: mkdir -p test-results && pytest -q tests --junitxml=test-results/pytest.xml
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-pytest-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - pytest

JavaScript (Jest)

Test command: mkdir -p test-results && npx jest --ci --json --outputFile=test-results/jest.json

JavaScript (Jest): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
      - run: npm ci
      - run: mkdir -p test-results && npx jest --ci --json --outputFile=test-results/jest.json
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

JavaScript (Jest): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

jest:
  stage: test
  image: node:22
  cache:
    key: testo-history-jest-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-jest-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - npm ci
    - mkdir -p test-results && npx jest --ci --json --outputFile=test-results/jest.json
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

JavaScript (Jest): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('Jest') {
      steps {
        sh 'npm ci'
        sh 'mkdir -p test-results && npx jest --ci --json --outputFile=test-results/jest.json'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

JavaScript (Jest): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  jest:
    docker:
      - image: cimg/node:lts
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-jest-{{ .Branch }}-
            - testo-history-jest-
      - run: npm ci
      - run: mkdir -p test-results && npx jest --ci --json --outputFile=test-results/jest.json
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-jest-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - jest

JavaScript (Vitest)

Test command: mkdir -p test-results && npx vitest run --reporter=default --reporter=json --outputFile.json=test-results/vitest.json

JavaScript (Vitest): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
      - run: npm ci
      - run: mkdir -p test-results && npx vitest run --reporter=default --reporter=json --outputFile.json=test-results/vitest.json
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

JavaScript (Vitest): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

vitest:
  stage: test
  image: node:22
  cache:
    key: testo-history-vitest-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-vitest-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - npm ci
    - mkdir -p test-results && npx vitest run --reporter=default --reporter=json --outputFile.json=test-results/vitest.json
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

JavaScript (Vitest): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('Vitest') {
      steps {
        sh 'npm ci'
        sh 'mkdir -p test-results && npx vitest run --reporter=default --reporter=json --outputFile.json=test-results/vitest.json'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

JavaScript (Vitest): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  vitest:
    docker:
      - image: cimg/node:lts
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-vitest-{{ .Branch }}-
            - testo-history-vitest-
      - run: npm ci
      - run: mkdir -p test-results && npx vitest run --reporter=default --reporter=json --outputFile.json=test-results/vitest.json
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-vitest-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - vitest

Browser tests (Playwright)

Test command: mkdir -p test-results && PLAYWRIGHT_JSON_OUTPUT_NAME=test-results/playwright.json npx playwright test --reporter=list,json

Browser tests (Playwright): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: mkdir -p test-results && PLAYWRIGHT_JSON_OUTPUT_NAME=test-results/playwright.json npx playwright test --reporter=list,json
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

Browser tests (Playwright): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

playwright:
  stage: test
  image: node:22
  cache:
    key: testo-history-playwright-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-playwright-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - npm ci
    - npx playwright install --with-deps
    - mkdir -p test-results && PLAYWRIGHT_JSON_OUTPUT_NAME=test-results/playwright.json npx playwright test --reporter=list,json
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

Browser tests (Playwright): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('Playwright') {
      steps {
        sh 'npm ci'
        sh 'npx playwright install --with-deps'
        sh 'mkdir -p test-results && PLAYWRIGHT_JSON_OUTPUT_NAME=test-results/playwright.json npx playwright test --reporter=list,json'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

Browser tests (Playwright): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  playwright:
    docker:
      - image: cimg/node:lts
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-playwright-{{ .Branch }}-
            - testo-history-playwright-
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: mkdir -p test-results && PLAYWRIGHT_JSON_OUTPUT_NAME=test-results/playwright.json npx playwright test --reporter=list,json
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-playwright-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - playwright

Go (go test)

Test command: mkdir -p test-results && go test -json ./... > test-results/go-test.json

Go (go test): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
      - run: mkdir -p test-results && go test -json ./... > test-results/go-test.json
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

Go (go test): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

go:
  stage: test
  image: golang:1.23
  cache:
    key: testo-history-go-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-go-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - mkdir -p test-results && go test -json ./... > test-results/go-test.json
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

Go (go test): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('go test') {
      steps {
        sh 'mkdir -p test-results && go test -json ./... > test-results/go-test.json'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

Go (go test): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  go:
    docker:
      - image: cimg/go:1.23
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-go-{{ .Branch }}-
            - testo-history-go-
      - run: mkdir -p test-results && go test -json ./... > test-results/go-test.json
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-go-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - go

.NET (dotnet test)

Test command: dotnet test --logger trx --results-directory test-results/dotnet

.NET (dotnet test): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet test --logger trx --results-directory test-results/dotnet
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

.NET (dotnet test): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

dotnet:
  stage: test
  image: mcr.microsoft.com/dotnet/sdk:8.0
  cache:
    key: testo-history-dotnet-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-dotnet-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - dotnet test --logger trx --results-directory test-results/dotnet
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

.NET (dotnet test): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('dotnet test') {
      steps {
        sh 'dotnet test --logger trx --results-directory test-results/dotnet'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

.NET (dotnet test): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  dotnet:
    docker:
      - image: mcr.microsoft.com/dotnet/sdk:8.0
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-dotnet-{{ .Branch }}-
            - testo-history-dotnet-
      - run: dotnet test --logger trx --results-directory test-results/dotnet
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-dotnet-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - dotnet

Java (Maven)

Test command: mvn -B test

Java (Maven): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"
          cache: maven
      - run: mvn -B test
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

Java (Maven): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

maven:
  stage: test
  image: maven:3-eclipse-temurin-21
  cache:
    key: testo-history-maven-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-maven-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - mvn -B test
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

Java (Maven): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('Maven (Surefire)') {
      steps {
        sh 'mvn -B test'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

Java (Maven): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  maven:
    docker:
      - image: cimg/openjdk:21.0
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-maven-{{ .Branch }}-
            - testo-history-maven-
      - run: mvn -B test
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-maven-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - maven

Java or Kotlin (Gradle)

Test command: ./gradlew test

Java or Kotlin (Gradle): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - run: ./gradlew test
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

Java or Kotlin (Gradle): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

gradle:
  stage: test
  image: gradle:jdk21
  cache:
    key: testo-history-gradle-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-gradle-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - ./gradlew test
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

Java or Kotlin (Gradle): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('Gradle') {
      steps {
        sh './gradlew test'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

Java or Kotlin (Gradle): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  gradle:
    docker:
      - image: cimg/openjdk:21.0
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-gradle-{{ .Branch }}-
            - testo-history-gradle-
      - run: ./gradlew test
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-gradle-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - gradle

Ruby (RSpec)

Test command: mkdir -p test-results && bundle exec rspec --format progress --format json --out test-results/rspec.json

Ruby (RSpec): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - run: mkdir -p test-results && bundle exec rspec --format progress --format json --out test-results/rspec.json
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

Ruby (RSpec): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

rspec:
  stage: test
  image: ruby:3.3
  cache:
    key: testo-history-rspec-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-rspec-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - bundle install
    - mkdir -p test-results && bundle exec rspec --format progress --format json --out test-results/rspec.json
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

Ruby (RSpec): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('RSpec') {
      steps {
        sh 'bundle install'
        sh 'mkdir -p test-results && bundle exec rspec --format progress --format json --out test-results/rspec.json'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

Ruby (RSpec): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  rspec:
    docker:
      - image: cimg/ruby:3.3
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-rspec-{{ .Branch }}-
            - testo-history-rspec-
      - run: bundle install
      - run: mkdir -p test-results && bundle exec rspec --format progress --format json --out test-results/rspec.json
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-rspec-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - rspec

PHP (PHPUnit)

Test command: mkdir -p test-results && vendor/bin/phpunit --log-junit test-results/phpunit.xml

PHP (PHPUnit): GitHub Actions

.github/workflows/tests.yml

name: tests
on:
  push:
    branches: ["main"]
  pull_request:
permissions:
  contents: read
  pull-requests: write  # the pull request comment
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: "8.3"
      - run: composer install --no-interaction --prefer-dist
      - run: mkdir -p test-results && vendor/bin/phpunit --log-junit test-results/phpunit.xml
      - uses: arieleven/qasight@v1
        if: ${{ !cancelled() }}  # record red runs too
        with:
          cycle: "my-app"

PHP (PHPUnit): GitLab CI

.gitlab-ci.yml

# QASight: records each run's results and keeps their history in the GitLab cache.
# Docs: https://arieleven.github.io/qasight/docs/
stages: [test]

phpunit:
  stage: test
  image: composer:2
  cache:
    key: testo-history-phpunit-$CI_COMMIT_REF_SLUG
    fallback_keys: [testo-history-phpunit-$CI_DEFAULT_BRANCH]
    paths: [.testo-history/]
    when: always
  script:
    - composer install --no-interaction --prefer-dist
    - mkdir -p test-results && vendor/bin/phpunit --log-junit test-results/phpunit.xml
  after_script:
    - command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - uv tool install --quiet qasight
    - mkdir -p "$CI_PROJECT_DIR/.testo-history"
    - export DATABASE_URL="sqlite:///$CI_PROJECT_DIR/.testo-history/testo_history.db"
    - qasight ingest --cycle my-app
    - qasight report markdown --cycle my-app --out testo-summary.md
  artifacts:
    when: always
    paths: [testo-summary.md, test-results/]

PHP (PHPUnit): Jenkins

Jenkinsfile

// QASight: records each build's results. History lives in the workspace
// (.testo-history); for one history across agents, use a team server
// (qasight ingest --server). Docs: https://arieleven.github.io/qasight/docs/
pipeline {
  agent any  // needs the test tools and curl
  stages {
    stage('PHPUnit') {
      steps {
        sh 'composer install --no-interaction --prefer-dist'
        sh 'mkdir -p test-results && vendor/bin/phpunit --log-junit test-results/phpunit.xml'
      }
    }
  }
  post {
    always {
      sh '''
          command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
          export PATH="$HOME/.local/bin:$PATH"
          uv tool install --quiet qasight
          mkdir -p "$WORKSPACE/.testo-history"
          export DATABASE_URL="sqlite:///$WORKSPACE/.testo-history/testo_history.db"
          qasight ingest --cycle my-app
          qasight report markdown --cycle my-app --out testo-summary.md
      '''
      archiveArtifacts artifacts: 'testo-summary.md', allowEmptyArchive: true
    }
  }
}

PHP (PHPUnit): CircleCI

.circleci/config.yml

# QASight: records each run's results and keeps their history in the CircleCI cache.
# Docs: https://arieleven.github.io/qasight/docs/
version: 2.1
jobs:
  phpunit:
    docker:
      - image: cimg/php:8.3
    steps:
      - checkout
      - restore_cache:
          keys:
            - testo-history-phpunit-{{ .Branch }}-
            - testo-history-phpunit-
      - run: composer install --no-interaction --prefer-dist
      - run: mkdir -p test-results && vendor/bin/phpunit --log-junit test-results/phpunit.xml
      - run:
          name: Record the results with QASight
          when: always
          command: |
            command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh
            export PATH="$HOME/.local/bin:$PATH"
            uv tool install --quiet qasight
            mkdir -p "$HOME/.testo-history"
            export DATABASE_URL="sqlite:///$HOME/.testo-history/testo_history.db"
            qasight ingest --cycle my-app
            qasight report markdown --cycle my-app --out testo-summary.md
      - save_cache:
          when: always
          key: testo-history-phpunit-{{ .Branch }}-{{ .BuildNum }}
          paths: [~/.testo-history]
      - store_artifacts:
          path: testo-summary.md
workflows:
  tests:
    jobs:
      - phpunit