sweetchuck/junit-merger-cli
CLI tool to merge multiple JUnit XML reports into a single file. Reads file paths from stdin or arguments, outputs to stdout or a specified file. Supports DOM-based and fast substring handlers, with an option to recalculate testsuite counts.
spatie/laravel-test-failures, third-party analytics, or custom dashboards).\DOMDocument and standard PHP libraries, ensuring no conflicts with Laravel’s dependencies or runtime.storage/logs/junit/).dom_read_write, dom_read, substr), allowing trade-offs between:
tests, failures, time).SimpleXML or DOMDocument before merging.dom_read_write handler for strict schema compliance.dom_read_write recalculates attributes, which could slow down large test suites (e.g., 1000+ files).substr handler for identical-format files (common in parallel PHPUnit runs).exec() calls, which may be restricted in serverless/Laravel Octane environments.junit-merger library directly).TestsWorked events and auto-merge reports.tests:unit/tests:feature Artisan commands or CI hooks.dom_read_write) for accurate reporting, or can raw speed (substr) suffice?time/failures stats but adds overhead.TestsWorked event to auto-collect and merge reports in memory.spatie/junit-merger)?# .github/workflows/tests.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: php artisan test --parallel --distribute
- run: find storage/logs/junit -name '*.xml' | vendor/bin/junit-merger merge:files --handler=dom_read_write > merged-report.xml
- uses: actions/upload-artifact@v3
with:
name: junit-report
path: merged-report.xml
// app/Console/Commands/MergeJUnitReports.php
class MergeJUnitReports extends Command {
protected $signature = 'junit:merge
{--output= : Output file path (default: storage/logs/junit/merged.xml)}
{--handler=dom_read_write : Merge handler (dom_read_write|dom_read|substr)}';
public function handle() {
$files = glob(storage_path('logs/junit/*.xml'));
if (empty($files)) {
$this->error('No JUnit files found!');
return 1;
}
$output = $this->option('output') ?? storage_path('logs/junit/merged.xml');
$handler = $this->option('handler');
$command = "php vendor/bin/junit-merger merge:files --handler={$handler} " . implode(' ', $files) . " > {$output}";
$exitCode = shell_exec($command);
if ($exitCode !== 0) {
$this->error("Merge failed with exit code {$exitCode}");
return $exitCode;
}
$this->info("Merged report saved to: {$output}");
return 0;
}
}
app/Console/Kernel.php:
protected $commands = [
Commands\MergeJUnitReports::class,
];
# In a CI script or Artisan command
find storage/logs/junit -name '*.xml' | vendor/bin/junit-merger merge:files > merged.xml
php artisan junit:merge --output=reports/ci.xml --handler=substr
junit-merger library and publish it as a Laravel package (e.g., spatie/junit-merger).TestsWorked).spatie/junit-merger/
├── src/
│ ├── JUnitMergerServiceProvider.php
│ ├── Console/MergeCommand.php
│ ├── Events/JUnitMerged.php
│ └── Handlers/DomReadWriteHandler.php
├── config/junit-merger.php
└── README.md
How can I help you explore Laravel packages today?