{"id":41586848,"url":"https://github.com/willgeorgetaylor/junit-reducer-action","last_synced_at":"2026-01-24T09:07:36.713Z","repository":{"id":214553051,"uuid":"736796368","full_name":"willgeorgetaylor/junit-reducer-action","owner":"willgeorgetaylor","description":"GitHub Action for reducing JUnit XML reports","archived":false,"fork":false,"pushed_at":"2024-06-24T21:18:39.000Z","size":766,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-12T20:13:20.523Z","etag":null,"topics":["ci","github-actions","junit","test-automation","test-case-reduction","test-split-accuracy","test-suite","test-suite-reduction","testing"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/willgeorgetaylor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-28T22:46:28.000Z","updated_at":"2024-04-06T16:37:15.000Z","dependencies_parsed_at":"2023-12-29T00:21:25.291Z","dependency_job_id":"cd417b4c-3484-4dbf-a897-68813fa0a905","html_url":"https://github.com/willgeorgetaylor/junit-reducer-action","commit_stats":{"total_commits":43,"total_committers":2,"mean_commits":21.5,"dds":"0.046511627906976716","last_synced_commit":"b8a477bb24d2c06729e1511c61fbd9f161186038"},"previous_names":["willgeorgetaylor/junit-reducer-action"],"tags_count":9,"template":false,"template_full_name":"actions/typescript-action","purl":"pkg:github/willgeorgetaylor/junit-reducer-action","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willgeorgetaylor%2Fjunit-reducer-action","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willgeorgetaylor%2Fjunit-reducer-action/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willgeorgetaylor%2Fjunit-reducer-action/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willgeorgetaylor%2Fjunit-reducer-action/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/willgeorgetaylor","download_url":"https://codeload.github.com/willgeorgetaylor/junit-reducer-action/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willgeorgetaylor%2Fjunit-reducer-action/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28722105,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T08:27:05.734Z","status":"ssl_error","status_checked_at":"2026-01-24T08:27:01.197Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","github-actions","junit","test-automation","test-case-reduction","test-split-accuracy","test-suite","test-suite-reduction","testing"],"created_at":"2026-01-24T09:07:35.990Z","updated_at":"2026-01-24T09:07:36.707Z","avatar_url":"https://github.com/willgeorgetaylor.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# junit-reducer-action\n\n\u003c!-- markdownlint-disable MD013 MD033 --\u003e\n\n[![GitHub Super-Linter](https://github.com/willgeorgetaylor/junit-reducer-action/actions/workflows/linter.yml/badge.svg)](https://github.com/super-linter/super-linter)\n![CI](https://github.com/willgeorgetaylor/junit-reducer-action/actions/workflows/ci.yml/badge.svg)\n[![Check dist/](https://github.com/willgeorgetaylor/junit-reducer-action/actions/workflows/check-dist.yml/badge.svg)](https://github.com/willgeorgetaylor/junit-reducer-action/actions/workflows/check-dist.yml)\n[![CodeQL](https://github.com/willgeorgetaylor/junit-reducer-action/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/willgeorgetaylor/junit-reducer-action/actions/workflows/codeql-analysis.yml)\n[![Coverage](./badges/coverage.svg)](./badges/coverage.svg)\n\nJUnit Reducer Action is a GitHub Action wrapping a command-line tool that aggregates the [JUnit test XML reports](https://www.ibm.com/docs/en/developer-for-zos/14.1?topic=formats-junit-xml-format) from your CI runs and reduces them to a single, lighter set of reports to be downloaded later during CI, to steer your test splitting algorithm (e.g., [split_tests](https://github.com/marketplace/actions/split-tests)). The most typical use case is to regularly update a 'running average' of your recent test reports, which can be downloaded to your test runners in [less time](#faster-ci-) and without running an [ongoing race condition risk](#coverage-integrity-).\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./diagram-dark.png\"\u003e\n  \u003cimg alt=\"Diagram explaining how junit-reducer turns multiple sets of JUnit reports into a single set of JUnit reports.\" src=\"./diagram-light.png\"\u003e\n\u003c/picture\u003e\n\n## Quickstart\n\nTypically, you'll be using this Action within a scheduled [cron](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule) workflow to take the last `X` days of JUnit XML reports, reduce them and upload the results. It's recommended to accumulate the JUnit XML reports from individual CI runs in a cloud storage service like AWS S3 or Google Cloud Storage, as opposed to the caching API available within GitHub Actions, which is designed as an _overwrite_ key-value store.\n\n## Inputs\n\nAll inputs are optional. However, these are the most important:\n\n| Name | Description | Type | Default |\n| :---  | :--- | :--- | :--- |\n| `include` | Glob pattern to find JUnit XML reports to reduce | `string` | `./**/*.xml` |\n| `output-path` | Output path for the reduced JUnit XML reports | `string` | `./output/` |\n| `exclude` | Glob pattern to omit from included JUnit XML reports | `string` |  |\n| `version` | Version of [junit-reducer](https://github.com/willgeorgetaylor/junit-reducer/releases) command-line tool to use (e.g., `v1.2.0` or `latest`) | `string` | `latest` |\n\n### Additional inputs\n\n| Name | Description | Type | Default |\n| :---  | :--- | :--- | :--- |\n| `op-suites-time` | Reducer operation for test suite `time` values. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `op-cases-time` | Reducer operation for test case `time` values. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `op-suites-assertions` | Reducer operation for test suite assertion counts. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `op-suites-errors` | Reducer operation for test suite error counts. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `op-suites-failed` | Reducer operation for test suite failure counts. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `op-suites-skipped` | Reducer operation for test suite skipped counts. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `op-suites-tests` | Reducer operation for test suite test counts. Options: `max`, `mean`, `median`, `min`, `mode` or `sum` | `string` | `mean` |\n| `reduce-suites-by` | Specify a key (corresponding to the attribute on the test suite nodes) to group and reduce test suites by. Options: `filepath`, `name` or `name+filepath` | `string` | `name+filepath` |\n| `reduce-cases-by` | Specify a key (corresponding to the attribute on the test case nodes) to group and reduce test cases by. Options: `classname`, `file` or `name` | `string` | `name` |\n| `rounding-mode` | Rounding mode for counts (`assertions`, `errors`, `failed`, `skipped` and `tests`) that should be integers in the final result. Options: `ceil`, `floor` or `round` | `string` | `round` |\n\n## Example Workflow\n\n```yaml\nname: junit-test-report-averaging\nrun-name: Create Average JUnit Test Reports\non:\n  schedule:\n      # Run every morning at 8AM\n      - cron:  '0 8 * * *'\njobs:\n  reduce-reports:\n    runs-on: ubuntu-latest\n    steps:\n      # Configure with the Cloud storage provider of your choice.\n      - name: Setup AWS CLI\n        uses: aws-actions/configure-aws-credentials@v4\n        with:\n          aws-access-key-id: ${{ secrets.YOUR_AWS_ACCESS_KEY_ID }}\n          aws-secret-access-key: ${{ secrets.YOUR_AWS_SECRET_ACCESS_KEY }}\n          aws-region: eu-west-2\n\n      # Download all test reports from all CI runs.\n      # It is recommended to set up a lifecycle rule, to remove objects older\n      # than a certain age from this bucket/path. This will help to keep the test reports\n      # current and keep this job from taking too long.\n      - name: Download test timings\n        run: |\n          aws s3 cp s3://your-junit-report-bucket/ci-runs-reports/ reports/ \\\n            --recursive\n\n      - name: Reduce reports\n        uses: willgeorgetaylor/junit-reducer-action@v1\n        with:\n          include: ./reports/**/*.xml\n          output-path: ./average-reports/\n\n      # Upload the reduced set of test reports to a dedicated bucket/path.\n      # In your actual CI process, the CI runners will copy the contents of\n      # this path locally, to be ingested by the test splitter.\n      - name: Upload single set of averaged reports\n        run: |\n          aws s3 sync ./average-reports s3://your-junit-report-bucket/average-reports/ \\\n            --size-only \\\n            --cache-control max-age=86400\n```\n\n## Why?\n\nAs your test suite grows, you may want to start splitting tests between multiple test runners, to be **executed concurrently.** While it's relatively simple to divide up your test suites by files, using lines of code (LOC) as a proxy for test duration, the LOC metric is still just an approximation and will result in uneven individual (and therefore overall slower) test run times as your codebase and test suites change.\n\nThe preferable approach for splitting test suites accurately is to use **recently reported test times,** and the most popular format for exchanging test data (including timings) between tools is the [JUnit XML reports format](https://www.ibm.com/docs/en/developer-for-zos/14.1?topic=formats-junit-xml-format). While JUnit itself is a Java project, the schema that defines JUnit reports is equally applicable to any language and reports can be generated by most testing frameworks for JavaScript, Ruby, Python etc.\n\nIn busier projects, CI will be uploading reports frequently, so even if you take a small time window (for example, the last 24 hours), you could end up with 20MB+ of test reports. These reports need to be **downloaded to every runner in your concurrency set,** only to then perform the same splitting operation to **yield the exact same time estimates.** This means unnecessary and expensive work is being performed by each concurrent runner, potentially delaying the total test time by minutes and increasing CI costs.\n\n### Faster CI ✅\n\nYou can solve this speed issue by creating a set of reports that looks like the set produced by a single CI run. Importantly, the values for `time` taken by test suite (as well as other counts, like errors and tests) are reduced from the wider set of reports, typically by finding the `mean` of all of the aggregate `time` values. Other reducer operations, like `min` / `max` / `mode` / `median` / `sum`, are available to handle non-standard distributions.\n\n### Coverage integrity ✅\n\nIn very busy projects, there is also a more **problematic race condition possible**, with larger downloads and test runners starting at different times. As CI runs from other commits upload their reports to the same remote source that you're downloading from, if any of your concurrent runners download reports with different values, the input data is misaligned and the splitting operation is corrupted. However, because the download and splitting operation is being performed in a distributed manner (across all of the runners concurrently) this misalignment will result in some tests in your run being **skipped.**\n\nThis risk is mitigated by computing the averaged reports in one place, and updating that set as part of a scheduled job. This is exactly the approach outlined in the [example workflow](#example-workflow) section.\n\n## Dependencies\n\nThis action invokes [willgeorgetaylor/junit-reducer](https://github.com/willgeorgetaylor/junit-reducer) to reduce the test reports.\n\nIf you prefer to use the command-line tool directly, there are instructions for that on the [repository readme](https://github.com/willgeorgetaylor/junit-reducer?tab=readme-ov-file#github-actions).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillgeorgetaylor%2Fjunit-reducer-action","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillgeorgetaylor%2Fjunit-reducer-action","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillgeorgetaylor%2Fjunit-reducer-action/lists"}