diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a60f364279970622d5f2e28efea2489276a9bafa..35fd1ef639d60b53e08fc9337cad03e45c29e744 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,25 +14,25 @@ include:
   - local: '/templates/shared/all.yml'
   - local: '/templates/changedfiles/all.yml'
 
-#build_containers:
-#  stage: build
-#  # Note: the docker-builder image has to be build and pushed manually once to bootstrap this job.
-#  image: ${CI_REGISTRY_IMAGE}/docker-builder:master
-#  rules:
-#    - !reference [.primary_ref_jobs, rules]
-#    - !reference [.merge_request_jobs, rules]
-#  script:
-#    # Find the directories that contain a file named 'Dockerfile', and (re)build the ones
-#    # that contain changed files.
-#    - |
-#      find . -name Dockerfile -type f | while read FILE;
-#      do
-#          # Strip leading './' and trailing '/<filename>'
-#          DOCKER_DIR=$(echo "${FILE}" | sed -r 's|^\./||' | xargs dirname)
-#          if grep -q "^${DOCKER_DIR}" changed_files.log;
-#          then
-#              echo "Detected a changed file inside ./${DOCKER_DIR}/. (Re)build the container."
-#              IMAGE_NAME=$(echo "${DOCKER_DIR}" | sed -r 's|^dockerfiles/||')
-#              buildimage "${DOCKER_DIR}" "${IMAGE_NAME}:${CI_COMMIT_REF_SLUG}"
-#          fi
-#      done
+build_containers:
+  stage: build
+  # Note: the docker-builder image has to be build and pushed manually once to bootstrap this job.
+  image: ${CI_REGISTRY_IMAGE}/docker-builder:master
+  rules:
+    - !reference [.primary_ref_jobs, rules]
+    - !reference [.merge_request_jobs, rules]
+  script:
+    # Find the directories that contain a file named 'Dockerfile', and (re)build the ones
+    # that contain changed files.
+    - |
+      find . -name Dockerfile -type f | while read FILE;
+      do
+          # Strip leading './' and trailing '/<filename>'
+          DOCKER_DIR=$(echo "${FILE}" | sed -r 's|^\./||' | xargs dirname)
+          if grep -q "^${DOCKER_DIR}" changed_files.log;
+          then
+              echo "Detected a changed file inside ./${DOCKER_DIR}/. (Re)build the container."
+              IMAGE_NAME=$(echo "${DOCKER_DIR}" | sed -r 's|^dockerfiles/||')
+              buildimage "${DOCKER_DIR}" "${IMAGE_NAME}:${CI_COMMIT_REF_SLUG}"
+          fi
+      done
diff --git a/README.rst b/README.rst
index b523dd80d3851713e35a7770ecb2d247c35208e6..0f0feec514df5c07891c34b952d7df9916319561 100644
--- a/README.rst
+++ b/README.rst
@@ -281,6 +281,35 @@ FILENAME_TEX No       No        `automatically determined` Manual specification
 ============ ======== ========= ========================== ======================================
 
 
+Test report badge
+-----------------
+
+This template generates a badge that shows the percentage of successful tests in the
+test suite. It requires another job earlier in the pipeline that generates a JUnit test
+report. The ``conda_test`` job does generates such a report, if the conda recipe
+includes tests.
+
+To use this template, include the following snippet in your ``.gitlab-ci.yml`` file:
+
+.. code-block:: yaml
+
+   include:
+     - project: 'omegacen/ci-templates'
+       ref: v5
+       file: '/templates/report/badge.yml'
+
+Next, add badge to the `Project badges`_. In your GitLab project, go to
+*Settings* -> *General* -> *Badges*. Then add the following:
+
+   * Name: ``Test Success Rate``
+   * Link: ``https://gitlab.astro-wise.org/%{project_path}/-/pipelines/%{default_branch}/latest``
+   * Badge image URL: ``https://gitlab.astro-wise.org/%{project_path}/-/jobs/artifacts/%{default_branch}/raw/report.svg?job=test_report_badge``
+
+Test report change detection
+----------------------------
+
+TBW.
+
 Controlling when jobs runs
 ==========================
 
@@ -293,22 +322,22 @@ However, if you want more control over when a particular job runs, you can use t
 ``CI_AWE_RUN_<JOBNAME>`` and ``CI_AWE_SKIP_<JOBNAME>`` variables to run or to skip
 a job, respectively. The full list of variables is as follows:
 
-==================== ============================ =========================== =========================================
-     Job name               Skip variable                 Run variable                        Template
-==================== ============================ =========================== =========================================
-`autopep8`           CI_AWE_SKIP_AUTOFORMAT       CI_AWE_RUN_AUTOFORMAT       `templates/autoformat/autopep8.yml`
-`black`              CI_AWE_SKIP_AUTOFORMAT       CI_AWE_RUN_AUTOFORMAT       `templates/autoformat/black.yml`
-`changed_files_mr`   CI_AWE_SKIP_CHANGED_FILES    CI_AWE_RUN_CHANGED_FILES    `templates/changedfiles/mergerequest.yml`
-`changed_files_push` CI_AWE_SKIP_CHANGED_FILES    CI_AWE_RUN_CHANGED_FILES    `templates/changedfiles/push.yml`
-`conda_build`        CI_AWE_SKIP_CONDA_BUILD_TEST CI_AWE_RUN_CONDA_BUILD_TEST `templates/conda/build.yml`
-`conda_test`         CI_AWE_SKIP_CONDA_BUILD_TEST CI_AWE_RUN_CONDA_BUILD_TEST `templates/conda/build.yml`
-`conda_upload`       CI_AWE_SKIP_CONDA_UPLOAD     CI_AWE_RUN_CONDA_UPLOAD     `templates/conda/release.yml`
-`latex_pdf`          CI_AWE_SKIP_LATEX_PDF        CI_AWE_RUN_LATEX_PDF        `templates/latex.yml`
-`latex_pdf_diff`     CI_AWE_SKIP_LATEX_PDF_DIFF   CI_AWE_RUN_LATEX_PDF_DIFF   `templates/latex.yml`
-`sonar_branch`       CI_AWE_SKIP_SONAR_BRANCH     CI_AWE_RUN_SONAR_BRANCH     `templates/sonarqube.yml`
-`sonar_mr`           CI_AWE_SKIP_SONAR_MR         CI_AWE_RUN_SONAR_MR         `templates/sonarqube.yml`
-`report_badge`       CI_AWE_SKIP_REPORT_BADGE     CI_AWE_RUN_REPORT_BADGE     `templates/report/badge.yml`
-`report_changes`     CI_AWE_SKIP_REPORT_CHANGES   CI_AWE_RUN_REPORT_CHANGES   `templates/report/changes.yml`
+==================== ============================= ============================ =========================================
+     Job name                Skip variable                 Run variable                       Template
+==================== ============================= ============================ =========================================
+`autopep8`           CI_AWE_SKIP_AUTOFORMAT        CI_AWE_RUN_AUTOFORMAT        `templates/autoformat/autopep8.yml`
+`black`              CI_AWE_SKIP_AUTOFORMAT        CI_AWE_RUN_AUTOFORMAT        `templates/autoformat/black.yml`
+`changed_files_mr`   CI_AWE_SKIP_CHANGED_FILES     CI_AWE_RUN_CHANGED_FILES     `templates/changedfiles/mergerequest.yml`
+`changed_files_push` CI_AWE_SKIP_CHANGED_FILES     CI_AWE_RUN_CHANGED_FILES     `templates/changedfiles/push.yml`
+`conda_build`        CI_AWE_SKIP_CONDA_BUILD_TEST  CI_AWE_RUN_CONDA_BUILD_TEST  `templates/conda/build.yml`
+`conda_test`         CI_AWE_SKIP_CONDA_BUILD_TEST  CI_AWE_RUN_CONDA_BUILD_TEST  `templates/conda/build.yml`
+`conda_upload`       CI_AWE_SKIP_CONDA_UPLOAD      CI_AWE_RUN_CONDA_UPLOAD      `templates/conda/release.yml`
+`latex_pdf`          CI_AWE_SKIP_LATEX_PDF         CI_AWE_RUN_LATEX_PDF         `templates/latex.yml`
+`latex_pdf_diff`     CI_AWE_SKIP_LATEX_PDF_DIFF    CI_AWE_RUN_LATEX_PDF_DIFF    `templates/latex.yml`
+`sonar_branch`       CI_AWE_SKIP_SONAR_BRANCH      CI_AWE_RUN_SONAR_BRANCH      `templates/sonarqube.yml`
+`sonar_mr`           CI_AWE_SKIP_SONAR_MR          CI_AWE_RUN_SONAR_MR          `templates/sonarqube.yml`
+`test_report_badge`  CI_AWE_SKIP_TEST_REPORT_BADGE CI_AWE_RUN_TEST_REPORT_BADGE `templates/report/badge.yml`
+`test_report_diff`   CI_AWE_SKIP_TESt_REPORT_DIFF  CI_AWE_RUN_TEST_REPORT_DIFF  `templates/report/diff.yml`
 
 In addition, you can use the ``CI_AWE_SKIP_ALL` and ``CI_AWE_RUN_ALL` variables to
 control whether any or all of these jobs run.
diff --git a/dockerfiles/ci-tools/Dockerfile b/dockerfiles/ci-tools/Dockerfile
index b9430265d4851c3e1616fce94f146f332c67cb9f..d1b2b49ff55a39716a70befde1a23a1bd421eb0c 100644
--- a/dockerfiles/ci-tools/Dockerfile
+++ b/dockerfiles/ci-tools/Dockerfile
@@ -36,7 +36,7 @@ RUN echo "Host *\n\tStrictHostKeyChecking no" > ~/.ssh/config
 COPY ssh-addkey.sh /usr/local/bin/ssh-addkey
 COPY python-gitlab-set-private-token.sh /usr/local/bin/python-gitlab-set-private-token
 COPY report_badge.py /usr/local/bin/report_badge
-COPY report_compare.py /usr/local/bin/report_compare
+COPY report_diff.py /usr/local/bin/report_diff
 
 COPY entrypoint.sh /usr/local/bin/entrypoint
 ENTRYPOINT [ "/bin/bash", "/usr/local/bin/entrypoint" ]
diff --git a/dockerfiles/ci-tools/report_compare.py b/dockerfiles/ci-tools/report_compare.py
deleted file mode 100755
index fea475b2b3bf8c9f8fdc8f6a2c3b89a08c6e15e8..0000000000000000000000000000000000000000
--- a/dockerfiles/ci-tools/report_compare.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-
-from junitparser import JUnitXml, TestSuite
-
-
-def compare_reports(path_before, path_after, path_out):
-    before = JUnitXml.fromfile(path_before)
-    after = JUnitXml.fromfile(path_after)
-
-    passed = []
-    for suite in before:
-        for case in suite:
-            if case.is_passed:
-                passed.append(case)
-
-    failed = []
-    for suite in after:
-        for case in suite:
-            if not case.is_passed and not case.is_skipped:
-                failed.append(case)
-
-    newly_failed = []
-    for f in failed:
-        for p in passed:
-            if (f.classname, f.name) == (p.classname, p.name):
-                newly_failed.append(f)
-
-    xml = JUnitXml()
-    suite = TestSuite('newly_failed_tests')
-    suite.add_testcases(newly_failed)
-    xml.add_testsuite(suite)
-    xml.update_statistics()
-    xml.write(path_out, to_console=False)
-
-
-def main():
-    parser = argparse.ArgumentParser(
-        description='Create a new report with testcases that passed before but fail or error now.'
-    )
-    parser.add_argument("before", help="Path of the initial XML report to compare.")
-    parser.add_argument("after", help="Path of the re-run XML report to compare.")
-    parser.add_argument("output", help='Path to write compare report to.')
-    args = parser.parse_args()
-    compare_reports(args.before, args.after, args.output)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/dockerfiles/ci-tools/report_diff.py b/dockerfiles/ci-tools/report_diff.py
new file mode 100755
index 0000000000000000000000000000000000000000..675bb6723fc826e951f2eddf249a1b683ed1f8d6
--- /dev/null
+++ b/dockerfiles/ci-tools/report_diff.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+
+import argparse
+
+from junitparser import JUnitXml, TestSuite
+
+
+def case_dictionary(report):
+    result = {}
+    for suite in report:
+        for case in suite:
+            key = (case.classname, case.name)
+            result[key] = case
+    return result
+
+
+def compare_reports(path_before, path_after, path_out):
+    before = JUnitXml.fromfile(path_before)
+    after = JUnitXml.fromfile(path_after)
+    before_cases = case_dictionary(before)
+    after_cases = case_dictionary(after)
+
+    changed_cases = TestSuite('tests_with_changed_result')
+
+    for key, after_case in after_cases.items():
+        if key in before_cases:
+            before_case = before_cases[key]
+            before_results = [type(r) for r in before_case.result]
+            after_results = [type(r) for r in after_case.result]
+            if set(before_results) != set(after_results):
+                changed_cases.add_testcase(after_case)
+
+    xml = JUnitXml()
+    xml.add_testsuite(changed_cases)
+    xml.update_statistics()
+    xml.write(path_out, to_console=False)
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='Create a new report with testcases present before and after but with different status.'
+    )
+    parser.add_argument("before", help="Path of the initial XML report to compare.")
+    parser.add_argument("after", help="Path of the re-run XML report to compare.")
+    parser.add_argument("output", help='Path to write compare report to.')
+    args = parser.parse_args()
+    compare_reports(args.before, args.after, args.output)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/templates/report/changes.yml b/templates/report/changes.yml
deleted file mode 100644
index 6f79059a08959583e0851b441956673a45944f97..0000000000000000000000000000000000000000
--- a/templates/report/changes.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-report_changes:
-  image: ${CI_AWE_IMAGE_BASE}/ci-tools:${CI_AWE_IMAGE_TAG}
-  variables:
-    REPORT_CHANGES_JOB: conda_test
-    REPORT_CHANGES_ARTIFACT_FILE: report.xml
-    REPORT_CHANGES_ARTIFACT_URL: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_REF_NAME/raw/$REPORT_CHANGES_ARTIFACT_FILE?job=$REPORT_CHANGES_JOB"
-  stage: test_post
-  rules:
-    - if: $CI_AWE_SKIP_REPORT_CHANGES
-      when: never
-    - if: $CI_AWE_RUN_REPORT_CHANGES
-    - !reference [.primary_ref_jobs, rules]
-  script:
-    - |
-      if ! junitparser verify "$REPORT_CHANGES_ARTIFACT_FILE"; then
-          echo "Test report of this pipeline contains failures. Comparing it to the previous run."
-          # Download the report of the previous pipeline.
-          curl --output "$REPORT_CHANGES_FILE.old" --location --header "JOB-TOKEN: $CI_JOB_TOKEN" "$REPORT_CHANGES_ARTIFACT_URL"
-          # Compare it to the report of this pipeline.
-          report_compare "$REPORT_CHANGES_FILE.old" "$REPORT_CHANGES_ARTIFACT_FILE" report_changes.xml
-          junitparser verify report_changes.xml
-      fi
-  artifacts:
-    expire_in: 1 day
-    when: always
-    paths:
-      - report_changes.xml
-    reports:
-      junit: report_changes.xml
diff --git a/templates/report/badge.yml b/templates/testreport/badge.yml
similarity index 58%
rename from templates/report/badge.yml
rename to templates/testreport/badge.yml
index b7c3c8428750bf25c256859c8efe4900c3be9b71..27c56ee7d804e6bcc0d0312649125910f3b0be2b 100644
--- a/templates/report/badge.yml
+++ b/templates/testreport/badge.yml
@@ -1,18 +1,18 @@
 include:
   - local: '/templates/shared/all.yml'
 
-report_badge:
+test_report_badge:
   image: ${CI_AWE_IMAGE_BASE}/ci-tools:${CI_AWE_IMAGE_TAG}
   stage: test_post
   variables:
-    REPORT_BADGE_ARTIFACT_FILE: report.xml
+    TEST_REPORT_ARTIFACT_FILE: report.xml
   rules:
-    - if: $CI_AWE_SKIP_REPORT_BADGE
+    - if: $CI_AWE_SKIP_TEST_REPORT_BADGE
       when: never
-    - if: $CI_AWE_RUN_REPORT_BADGE
+    - if: $CI_AWE_RUN_TEST_REPORT_BADGE
     - !reference [.primary_ref_jobs, rules]
   script:
-    - report_badge "$REPORT_BADGE_ARTIFACT_FILE" report.svg
+    - report_badge "$TEST_REPORT_BADGE_ARTIFACT_FILE" report.svg
   artifacts:
     paths:
       - report.svg
diff --git a/templates/testreport/diff.yml b/templates/testreport/diff.yml
new file mode 100644
index 0000000000000000000000000000000000000000..25961f148661f9dd42eb3a3fa75393a95ec00695
--- /dev/null
+++ b/templates/testreport/diff.yml
@@ -0,0 +1,25 @@
+test_report_diff:
+  image: ${CI_AWE_IMAGE_BASE}/ci-tools:${CI_AWE_IMAGE_TAG}
+  variables:
+    TEST_REPORT_JOB: conda_test
+    TEST_REPORT_ARTIFACT_FILE: report.xml
+    TEST_REPORT_ARTIFACT_URL: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_REF_NAME/raw/$TEST_REPORT_ARTIFACT_FILE?job=$TEST_REPORT_JOB"
+  stage: test_post
+  rules:
+    - if: $CI_AWE_SKIP_TEST_REPORT_DIFF
+      when: never
+    - if: $CI_AWE_RUN_TEST_REPORT_DIFF
+    - !reference [.primary_ref_jobs, rules]
+  script:
+    # Download the report of the previous pipeline.
+    - 'curl --output "$TEST_REPORT_FILE.old" --location --header "JOB-TOKEN: $CI_JOB_TOKEN" "$TEST_REPORT_ARTIFACT_URL"'
+    # Compare it to the report of this pipeline.
+    - report_diff "$TEST_REPORT_FILE.old" "$TEST_REPORT_ARTIFACT_FILE" report_diff.xml
+    - junitparser verify report_diff.xml
+  artifacts:
+    expire_in: 1 day
+    when: always
+    paths:
+      - report_diff.xml
+    reports:
+      junit: report_diff.xml