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
6 changes: 6 additions & 0 deletions qase-python-commons/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# qase-python-commons@3.5.4

## What's new

- Added support for filtering test results by status.

# qase-python-commons@3.5.3

## What's new
Expand Down
154 changes: 154 additions & 0 deletions qase-python-commons/docs/STATUS_FILTER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Status Filtering for Test Results

This document describes the status filtering functionality that allows you to exclude test results with specific statuses from being sent to Qase TestOps.

## Overview

The status filter feature enables you to configure which test result statuses should be excluded from reporting. This is useful when you want to:

- Skip reporting of passed tests to reduce noise
- Exclude certain statuses from specific test runs
- Focus on specific types of test results (e.g., only failures and errors)

## Configuration

### Configuration File

You can configure status filtering in your `qase.config.json` file:

```json
{
"testops": {
"statusFilter": ["passed", "skipped"]
}
}
```

### Environment Variables

You can also use environment variables:

```bash
export QASE_TESTOPS_STATUS_FILTER="passed,skipped"
```

### Command Line Options

For frameworks that support CLI options:

```bash
# Pytest
pytest --qase-testops-status-filter="passed,skipped"

# Tavern
pytest --qase-testops-status-filter="passed,skipped"
```

## Supported Statuses

The following statuses can be used in the filter:

- `passed` - Test passed successfully
- `failed` - Test failed
- `skipped` - Test was skipped
- `blocked` - Test was blocked
- `untested` - Test was not executed

## Examples

### Filter Out Passed Tests

```json
{
"testops": {
"statusFilter": ["passed"]
}
}
```

This configuration will exclude all passed tests from being sent to Qase TestOps.

### Filter Out Multiple Statuses

```json
{
"testops": {
"statusFilter": ["passed", "skipped"]
}
}
```

This configuration will exclude both passed and skipped tests.

### No Filtering

```json
{
"testops": {
"statusFilter": []
}
}
```

Or simply omit the `statusFilter` field to disable filtering.

## Behavior

- **Filtering Logic**: Results with statuses listed in `statusFilter` are excluded from sending
- **Batch Processing**: Filtering is applied when results are sent in batches
- **Logging**: Filtered results are logged at debug level, and the count of filtered results is logged at info level
- **Performance**: Filtering occurs before sending, so no network requests are made for filtered results

## Use Cases

### CI/CD Pipelines

In continuous integration, you might want to only report failures and errors:

```json
{
"testops": {
"statusFilter": ["passed", "skipped"]
}
}
```

### Development Testing

During development, you might want to see all results:

```json
{
"testops": {
"statusFilter": []
}
}
```

### Production Monitoring

In production, you might want to focus on critical issues:

```json
{
"testops": {
"statusFilter": ["passed", "skipped", "blocked"]
}
}
```

## Framework Support

Status filtering is supported across all Qase Python frameworks:

- **qase-pytest**: Via config file, environment variables, and CLI options
- **qase-behave**: Via config file, environment variables, and behave userdata
- **qase-robotframework**: Via config file and environment variables
- **qase-tavern**: Via config file, environment variables, and CLI options

## Notes

- Filtering is applied at the result level, not at the step level
- Results with `None` status are not filtered (they are always sent)
- The filter is case-sensitive and must match the exact status strings
- Empty or invalid filter configurations are treated as no filtering
2 changes: 1 addition & 1 deletion qase-python-commons/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "qase-python-commons"
version = "3.5.3"
version = "3.5.4"
description = "A library for Qase TestOps and Qase Report"
readme = "README.md"
authors = [{name = "Qase Team", email = "support@qase.io"}]
Expand Down
12 changes: 12 additions & 0 deletions qase-python-commons/src/qase/commons/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ def __load_file_config(self):
self.config.testops.configurations.set_create_if_not_exists(
configurations.get("createIfNotExists"))

if testops.get("statusFilter"):
status_filter = testops.get("statusFilter")
if isinstance(status_filter, list):
self.config.testops.set_status_filter(status_filter)
elif isinstance(status_filter, str):
# Parse comma-separated string
self.config.testops.set_status_filter([s.strip() for s in status_filter.split(',')])

if config.get("report"):
report = config.get("report")

Expand Down Expand Up @@ -262,6 +270,10 @@ def __load_env_config(self):
if key == 'QASE_TESTOPS_CONFIGURATIONS_CREATE_IF_NOT_EXISTS':
self.config.testops.configurations.set_create_if_not_exists(value)

if key == 'QASE_TESTOPS_STATUS_FILTER':
# Parse comma-separated string
self.config.testops.set_status_filter([s.strip() for s in value.split(',')])

if key == 'QASE_REPORT_DRIVER':
self.config.report.set_driver(value)

Expand Down
5 changes: 5 additions & 0 deletions qase-python-commons/src/qase/commons/models/config/testops.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class TestopsConfig(BaseModel):
plan: PlanConfig = None
batch: BatchConfig = None
configurations: ConfigurationsConfig = None
status_filter: List[str] = None

def __init__(self):
self.api = ApiConfig()
Expand All @@ -56,9 +57,13 @@ def __init__(self):
self.plan = PlanConfig()
self.configurations = ConfigurationsConfig()
self.defect = False
self.status_filter = []

def set_project(self, project: str):
self.project = project

def set_defect(self, defect):
self.defect = QaseUtils.parse_bool(defect)

def set_status_filter(self, status_filter: List[str]):
self.status_filter = status_filter
33 changes: 26 additions & 7 deletions qase-python-commons/src/qase/commons/reporters/testops.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,34 @@ def _send_results_threaded(self, results):

def _send_results(self) -> None:
if self.results:
# Acquire semaphore before starting the send operation
self.send_semaphore.acquire()
self.count_running_threads += 1
# Filter results by status if status_filter is configured
results_to_send = self.results.copy()

if self.config.testops.status_filter and len(self.config.testops.status_filter) > 0:
filtered_results = []
for result in results_to_send:
result_status = result.get_status()
if result_status and result_status not in self.config.testops.status_filter:
filtered_results.append(result)
else:
self.logger.log_debug(f"Filtering out result '{result.title}' with status '{result_status}'")

results_to_send = filtered_results
self.logger.log_debug(f"Filtered {len(self.results) - len(results_to_send)} results by status filter")

if results_to_send:
# Acquire semaphore before starting the send operation
self.send_semaphore.acquire()
self.count_running_threads += 1

# Start a new thread for sending results
send_thread = threading.Thread(target=self._send_results_threaded, args=(results_to_send,))
send_thread.start()
else:
self.logger.log("No results to send after filtering", "info")

# Clear results regardless of filtering
self.results = []

# Start a new thread for sending results
send_thread = threading.Thread(target=self._send_results_threaded, args=(results_to_send,))
send_thread.start()
else:
self.logger.log("No results to send", "info")

Expand Down