Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ This project adheres to [Semantic Versioning](https://semver.org/). Version numb

## [1.13.3]

_released 02-03-2026
_released 03-03-2026

### Added
- Added version notification system and a new update command for easier updating
- Improved blob support for handling multiple test result files for efficient automation
- Attachment handling and custom fields refactor for more efficient handling
- Attachment handling and custom fields refactor for more efficient test data handling

## [1.13.2]

Expand Down
132 changes: 131 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,139 @@ Options:
| `<testsuite>` | section |
| `<testcase>` | case |

For further detail, please refer to the
For further detail, please refer to the
[JUnit to TestRail mapping](https://support.gurock.com/hc/en-us/articles/12989737200276) documentation.

### Using Glob Patterns for Multiple Files

TRCLI supports glob patterns to process multiple report files in a single command. This feature is available for **JUnit XML**, **Robot Framework**, and **Cucumber JSON** parsers.

#### Important: Shell Quoting Requirement

**Glob patterns must be quoted** to prevent shell from expanding them prematurely. Without quotes, the shell will expand the pattern before passing it to TRCLI, causing unexpected errors.

```bash
# CORRECT - Pattern quoted (TRCLI handles the expansion)
trcli parse_junit -f "reports/*.xml" --title "Test Results"
```

#### Supported Glob Patterns

**Standard wildcards:**
```bash
# Match all XML files in directory
-f "reports/*.xml"

# Match files with specific prefix
-f "target/surefire-reports/TEST-*.xml"

# Match files with specific suffix
-f "build/test-results/*-report.xml"
```

**Recursive search** (matches subdirectories):
```bash
# Search all subdirectories recursively
-f "test-results/**/*.xml"

# Match specific pattern in any subdirectory
-f "**/robot-output-*.xml"
```

#### How File Merging Works

When a glob pattern matches **multiple files**, TRCLI automatically:

1. **Expands the pattern** to find all matching files
2. **Parses each file** individually
3. **Merges test results** into a single combined report
4. **Writes merged file** to current directory:
- JUnit: `Merged-JUnit-report.xml`
- Robot Framework: `Merged-Robot-report.xml`
5. **Processes the merged file** as a single test run upload

When a pattern matches **only one file**, TRCLI processes it directly without merging.

#### Examples

**JUnit XML - Multiple test suites:**
```bash
# Merge all JUnit XML files from Maven surefire reports
trcli -y \
-h https://example.testrail.com \
--project "My Project" \
parse_junit \
-f "target/surefire-reports/junitreports/*.xml" \
--title "Merged Test Results"

# Merge test results from multiple modules
trcli parse_junit \
-f "build/test-results/**/*.xml" \
--title "All Module Tests" \
--case-matcher auto
```

**Robot Framework - Multiple output files:**
```bash
# Merge multiple Robot Framework test runs
trcli -y \
-h https://example.testrail.com \
--project "My Project" \
parse_robot \
-f "reports/robot-*.xml" \
--title "Merged Robot Tests"

# Recursive search for all Robot outputs
trcli parse_robot \
-f "test-results/**/output.xml" \
--title "All Robot Results" \
--case-matcher property
```

**Cucumber JSON - Multiple test runs:**
```bash
# Merge multiple Cucumber JSON reports
trcli -y \
-h https://example.testrail.com \
--project "My Project" \
parse_cucumber \
-f "reports/cucumber-*.json" \
--title "Merged Cucumber Tests"

# Recursive search for all Cucumber JSON results
trcli parse_cucumber \
-f "test-results/**/cucumber.json" \
--title "All Cucumber Results" \
--case-matcher auto
```

#### Troubleshooting

**Error: "Got unexpected extra argument"**
- **Cause:** Glob pattern not quoted - shell expanded it before TRCLI
- **Solution:** Add quotes around the pattern: `-f "reports/*.xml"`

**Error: "File not found"**
- **Cause:** No files match the glob pattern
- **Solution:** Verify the pattern and file paths:
```bash
# Check what files match your pattern
ls reports/*.xml

# Use absolute path if relative path doesn't work
trcli parse_junit -f "/full/path/to/reports/*.xml"
```

**Pattern matches nothing in subdirectories:**
- **Cause:** Need recursive glob (`**`)
- **Solution:** Use `**` for recursive matching: `-f "reports/**/*.xml"`

#### Limitations

1. Glob patterns are expanded by Python's `glob` module (not shell), so some advanced bash features may not work
2. Very large numbers of files (100+) may cause performance issues during merging
3. Merged files are created in the current working directory

### Uploading test results
To submit test case results, the TestRail CLI will attempt to match the test cases in your automation suite to test cases in TestRail.
There are 2 mechanisms to match test cases:
Expand Down
89 changes: 89 additions & 0 deletions tests/test_cucumber_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,92 @@ def test_cucumber_indentation_in_generated_feature(self, advanced_environment):
# Examples should be indented with 4 spaces
examples_lines = [l for l in lines if "Examples:" in l]
assert any(l.startswith(" Examples:") for l in examples_lines)

@pytest.mark.parse_cucumber
def test_cucumber_json_parser_glob_pattern_single_file(self):
"""Test glob pattern that matches single file"""
env = Environment()
env.case_matcher = MatchersParser.AUTO
env.suite_name = None
# Use single file path
env.file = Path(__file__).parent / "test_data/CUCUMBER/sample_cucumber.json"

# This should work just like a regular file path
parser = CucumberParser(env)
result = parser.parse_file()

assert len(result) == 1
from trcli.data_classes.dataclass_testrail import TestRailSuite

assert isinstance(result[0], TestRailSuite)
# Verify it has test sections and cases
assert len(result[0].testsections) > 0

@pytest.mark.parse_cucumber
def test_cucumber_json_parser_glob_pattern_multiple_files(self):
"""Test glob pattern that matches multiple files and merges them"""
env = Environment()
env.case_matcher = MatchersParser.AUTO
env.suite_name = None
# Use glob pattern that matches multiple Cucumber JSON files
env.file = Path(__file__).parent / "test_data/CUCUMBER/testglob/*.json"

parser = CucumberParser(env)
result = parser.parse_file()

# Should return a merged result
assert len(result) == 1
from trcli.data_classes.dataclass_testrail import TestRailSuite

assert isinstance(result[0], TestRailSuite)

# Verify merged file was created
merged_file = Path.cwd() / "Merged-Cucumber-report.json"
assert merged_file.exists(), "Merged Cucumber report should be created"

# Verify the merged result contains test cases from both files
total_cases = sum(len(section.testcases) for section in result[0].testsections)
assert total_cases > 0, "Merged result should contain test cases"

# Clean up merged file
if merged_file.exists():
merged_file.unlink()

@pytest.mark.parse_cucumber
def test_cucumber_json_parser_glob_pattern_no_matches(self):
"""Test glob pattern that matches no files"""
with pytest.raises(FileNotFoundError):
env = Environment()
env.case_matcher = MatchersParser.AUTO
env.suite_name = None
# Use glob pattern that matches no files
env.file = Path(__file__).parent / "test_data/CUCUMBER/nonexistent_*.json"
CucumberParser(env)

@pytest.mark.parse_cucumber
def test_cucumber_check_file_glob_returns_path(self):
"""Test that check_file method returns valid Path for glob pattern"""
# Test single file match
single_file_glob = Path(__file__).parent / "test_data/CUCUMBER/sample_cucumber.json"
result = CucumberParser.check_file(single_file_glob)
assert isinstance(result, Path)
assert result.exists()

# Test multiple file match (returns merged file path)
multi_file_glob = Path(__file__).parent / "test_data/CUCUMBER/testglob/*.json"
result = CucumberParser.check_file(multi_file_glob)
assert isinstance(result, Path)
assert result.name == "Merged-Cucumber-report.json"
assert result.exists()

# Verify merged file contains valid JSON array
import json

with open(result, "r", encoding="utf-8") as f:
merged_data = json.load(f)
assert isinstance(merged_data, list), "Merged Cucumber JSON should be an array"
assert len(merged_data) > 0, "Merged array should contain features"

# Clean up
if result.exists() and result.name == "Merged-Cucumber-report.json":
result.unlink()
Loading