Metadata-Version: 2.4
Name: pytest-jira-xfail
Version: 1.4.0
Summary: Plugin skips (xfail) tests if unresolved Jira issue(s) linked
Home-page: https://github.com/JamalZeynalov/pytest-jira-xfail
Author: Jamal Zeinalov
Author-email: jamal.zeynalov@gmail.com
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pytest>=7.2.0
Requires-Dist: requests>=2.28.1
Requires-Dist: setuptools>=65.5.1
Requires-Dist: jira>=3.4.1
Requires-Dist: singleton-decorator>=1.0.0
Requires-Dist: allure-pytest>=2.11.1
Requires-Dist: selenium>=4.6.0
Requires-Dist: pytest-playwright>=0.3.3
Requires-Dist: playwright>=1.43.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# pytest-jira-xfail

[![Tests](https://github.com/JamalZeynalov/pytest-jira-xfail/actions/workflows/tests.yml/badge.svg)](https://github.com/JamalZeynalov/pytest-jira-xfail/actions/workflows/tests.yml)

Plugin skips (xfail) tests linked to unresolved Jira issue(s)

## 1. Generate your Jira API token

You should have Jira user
with [API token generated](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)

## 2. Add PytestJiraHelper to your pytest hook:

```python
import pytest

from pytest_jira_xfail.jira_helper import PytestJiraHelper


@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
    jira = PytestJiraHelper(
        jira_url="https://company.atlassian.net",
        jira_username="my_jira_user@company.com",
        jira_api_token="my_jira_user_api_token",
    )
    jira.process_linked_jira_issues(items)
```

## 3. Link bugs to your tests

```python
from pytest_jira_xfail.annotations import bug


@bug("MP-123")
def test_my_test_fails():
    assert False


@bug("MP-124", IndexError)
def test_my_test_broken():
    db_records = []
    assert db_records[0]


@bug("MP-124")
@bug("MP-124", IndexError)
def test_multiple_exceptions():
    db_records = []
    assert db_records[0][0] == 'active'
```

### Skip the test entirely (never run it)

By default a test linked to an open bug is still executed and reported as `XFAIL`
(so an unexpected pass shows up as `XPASS`). If the bug makes the test unsafe or
pointless to run (e.g. it hangs, corrupts data, or blocks the suite), pass
`run=False` to skip execution completely while the issue is open. The test is
reported as `XFAIL [NOTRUN]` and its body is never executed:

```python
from pytest_jira_xfail.annotations import bug


@bug("MP-123", run=False)
def test_not_executed_until_fixed():
    assert False  # never runs while MP-123 is open
```

Once the linked issue is resolved, the test runs normally again.

### Match the error message, not only the error type

By default a test is treated as `XFAIL` whenever it raises the expected error
**type** (`raises`). If the same error type can be raised for unrelated reasons,
use `error_contains` to xfail the test only when the raised error message
contains an expected substring (or one of several substrings). If the type
matches but the message does not, the test is reported as a **real failure** so a
different problem is not silently hidden:

```python
from pytest_jira_xfail.annotations import bug


# XFAIL only if an IndexError with this exact message is raised
@bug("MP-123", IndexError, error_contains="list index out of range")
def test_single_substring():
    records = []
    assert records[0]


# XFAIL if a KeyError message contains any of the listed substrings
@bug("MP-124", KeyError, error_contains=["'user_id'", "'account_id'"])
def test_multiple_substrings():
    payload = {}
    assert payload["user_id"]
```

Matching is **case-sensitive by default**. Pass `case_sensitive=False` for
case-insensitive matching:

```python
# Case-sensitive (default): the case must match exactly
@bug("MP-125", ValueError, error_contains="Invalid token")
def test_case_sensitive():
    raise ValueError("invalid token")  # does NOT match -> reported as a failure


# Case-insensitive: matches regardless of case
@bug("MP-126", ValueError, error_contains="invalid token", case_sensitive=False)
def test_case_insensitive():
    raise ValueError("INVALID TOKEN")  # matches -> XFAIL
```

`error_contains` can be combined with `run=False` and with multiple `@bug`
markers (each marker keeps its own expected type, substrings and case option).

XFAIL message format:

```
XFAIL The test is skipped because of open bugs:
https://company.atlassian.net/browse/MP-123
```

## 4. [Optional] Set custom resolved statuses

By default, only issues with the status "Done" and "Closed" are considered as resolved.<br>
But you can override this and add more statuses, as following:

```python
import pytest

from pytest_jira_xfail.jira_helper import PytestJiraHelper


@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
    jira = PytestJiraHelper(
        jira_url="https://company.atlassian.net",
        jira_username="my_jira_user@company.com",
        jira_api_token="my_jira_user_api_token",
        resolved_statuses=["Done", "Closed", "Released", "Declined"]
    )
    jira.process_linked_jira_issues(items)
```
