The GitLab CI coverage check is failing because the CI job isn’t producing a valid coverage report in the format GitLab expects.

Here are the common reasons and how to fix them:

1. Incorrect Coverage Report Format

GitLab’s coverage check relies on parsing a specific file format for coverage data. The most common formats it supports are Cobertura XML and JaCoCo XML. If your coverage tool outputs in a different format, or if the file is malformed, GitLab won’t be able to read it.

  • Diagnosis: Check the output of your coverage tool. Does it explicitly state it’s generating Cobertura or JaCoCo XML? Look for a file named something like coverage.xml or cobertura-coverage.xml in your job’s artifacts. You can also manually inspect the contents of this file.

  • Fix: Configure your coverage tool to output in Cobertura XML format. For example, if you’re using pytest-cov in Python, modify your pytest.ini or setup.cfg to include:

    [tool:pytest]
    cov_report = xml:coverage.xml
    

    If using jest for JavaScript, ensure your jest.config.js has:

    module.exports = {
      // ... other Jest configurations
      coverageReporters: ["text", "cobertura"],
    };
    

    This works because GitLab’s CI parser is hardcoded to look for and understand the structure of Cobertura XML reports.

2. Incorrect coverage Keyword in .gitlab-ci.yml

The coverage keyword in your .gitlab-ci.yml file tells GitLab where to find the coverage report. If this path is wrong, GitLab will never find the file, even if it’s generated correctly.

  • Diagnosis: Examine your .gitlab-ci.yml file. Look for the coverage keyword within the job that’s supposed to generate coverage. Is the path relative to the project root, and does it accurately point to the generated report file?

  • Fix: Ensure the coverage path is correct. If your coverage report is generated in the reports/ directory as coverage.xml, your .gitlab-ci.yml should have:

    test_coverage:
      stage: test
      script:
        - pytest --cov=my_module --cov-report=xml:reports/coverage.xml
      coverage: '/reports\/coverage\.xml/'
    

    Note the use of a regular expression. GitLab uses regex to match the filename that contains the coverage data. The forward slashes need to be escaped (\/) if you’re using a regex that includes them, and the dot (.) also needs escaping (\.) to match a literal dot. This fix works because the regex tells GitLab exactly which file artifact to inspect for coverage metrics.

3. Coverage Report Not Included in Artifacts

Even if the report is generated, if it’s not uploaded as a job artifact, GitLab’s CI runner won’t have access to it when the coverage check runs.

  • Diagnosis: Check the job logs for artifact uploads. Does it mention uploading the coverage report file? Look at the "Artifacts" section of the completed job in the GitLab UI. Is the coverage report file listed there?

  • Fix: Add the coverage report file to the artifacts:paths: directive in your .gitlab-ci.yml. For example:

    test_coverage:
      stage: test
      script:
        - pytest --cov=my_module --cov-report=xml:coverage.xml
      artifacts:
        paths:
          - coverage.xml
        reports:
          coverage_report:
            coverage_format: cobertura
            path: coverage.xml
    

    This works because it explicitly tells the CI runner to save the specified file and make it available for later stages or for GitLab’s UI parsing. The reports:coverage_report section is also crucial for GitLab to properly identify and process the coverage data.

4. Empty or Invalid Coverage Data within the Report

The report file might be generated in the correct format, but contain no actual coverage data, or the data might be malformed in a way that prevents parsing. This can happen if the coverage tool fails to run properly or if there are no test files found.

  • Diagnosis: Manually inspect the generated coverage.xml (or equivalent) file. Look for <class> or <line> elements. If the file is empty or contains only minimal structural tags without actual coverage details, this is the problem.

  • Fix: Ensure your tests are actually running and instrumenting code. Check your test runner’s output for errors before the coverage report generation. For example, with pytest-cov, ensure your tests are configured to run and that the cov_source setting (or equivalent) correctly points to the code you want to measure. If no tests are found, the coverage report will be empty.

    test_coverage:
      stage: test
      script:
        - echo "Running tests..."
        - pytest --cov=my_app --cov-report=xml:coverage.xml || true # Use || true if you want the job to continue even if tests fail, but coverage is still generated
        - echo "Coverage report generated."
      coverage: '/coverage\.xml/'
      artifacts:
        paths:
          - coverage.xml
    

    This fix ensures that the coverage tool is invoked correctly and that the output file is created, even if tests themselves fail (though you’d typically want tests to pass). The || true is a common trick to prevent a failing test suite from stopping the pipeline immediately, allowing the coverage report to be generated.

5. Incorrect Regular Expression for Coverage Extraction

If you’re not using the reports:coverage_report section and instead relying solely on the coverage keyword with a regex, a faulty regex can cause GitLab to incorrectly parse (or fail to parse) the coverage percentage.

  • Diagnosis: Test your regex against the content of your coverage report file using an online regex tester or a command-line tool like grep. Does it correctly capture the total coverage percentage?

  • Fix: Refine your regex. For a Cobertura XML report, the coverage percentage is often found in a tag like <coverage line-rate="0.75">. A common regex to extract this is:

    test_coverage:
      stage: test
      script:
        - pytest --cov=my_module --cov-report=xml:coverage.xml
      coverage: '/<coverage line-rate="([0-9\.]+)".*>/'
    

    This regex captures the numerical value associated with line-rate. This works by directly instructing GitLab’s parser on how to find and interpret the coverage metric within the XML structure. The .* allows for other attributes before the closing angle bracket.

6. CI Runner Environment Issues

Less common, but possible: the CI runner might not have the necessary tools installed to generate the coverage report, or there might be file permission issues preventing the report from being written.

  • Diagnosis: Check the job logs for any errors related to missing executables or file system access. Does the job explicitly install the coverage tool?

  • Fix: Ensure your Dockerfile (if using Docker executors) or your job’s before_script section installs all necessary dependencies, including the coverage tool and any required libraries.

    test_coverage:
      stage: test
      image: python:3.9-slim
      before_script:
        - pip install pytest pytest-cov
      script:
        - pytest --cov=my_module --cov-report=xml:coverage.xml
      coverage: '/coverage\.xml/'
      artifacts:
        paths:
          - coverage.xml
    

    This ensures the environment where the job runs has the tools needed to generate the report.

The next error you’ll likely encounter after fixing coverage is a pipeline failure due to failing tests themselves, or potentially issues with other artifact processing if your pipeline is complex.

Want structured learning?

Take the full Gitlab-ci course →