Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix attachments (fixes #474) #550

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
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
61 changes: 61 additions & 0 deletions allure-pytest-bdd/src/attachment_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os.path
import os


class AttachmentWorker:

def __init__(self, test_result, item):
self.test_result = test_result
self.attachments_dir = AttachmentWorker.get_path_to_attachments(item)

def delete_duplicates(self):
if len(self.test_result.attachments) == 0:
return

for step in self.test_result.steps:
for attach in step.attachments:
to_delete = self._find_duplicate(attach)
if to_delete is not None:
self.test_result.attachments.remove(to_delete)
os.remove(os.path.join(self.attachments_dir, to_delete.source))

@staticmethod
def get_path_to_attachments(item):
splitted_param = AttachmentWorker._get_allurdir_param(item).split('=')

project_dir = str(item.config.invocation_params.dir)
if len(splitted_param) == 1:
return project_dir

allure_dir = os.path.normpath(splitted_param[1])
if not os.path.isabs(allure_dir):
allure_dir = os.path.join(project_dir, allure_dir.lstrip("\\"))

return allure_dir

def _find_duplicate(self, attachment_from_step):
for attachment in self.test_result.attachments:
if self._are_attachments_equal(attachment, attachment_from_step):
return attachment

return None

def _are_attachments_equal(self, first, second):
first_file = open(os.path.join(self.attachments_dir, first.source), 'br')
first_content = first_file.read()
first_file.close()

second_file = open(os.path.join(self.attachments_dir, second.source), 'br')
second_content = second_file.read()
second_file.close()

return \
first.name == second.name and \
first.type == second.type and \
first_content == second_content

@staticmethod
def _get_allurdir_param(item):
for param in item.config.invocation_params.args:
if param.startswith("--alluredir"):
return param
5 changes: 5 additions & 0 deletions allure-pytest-bdd/src/pytest_bdd_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from allure_commons.lifecycle import AllureLifecycle
from .utils import get_full_name, get_name, get_params

from .attachment_worker import AttachmentWorker


class PytestBDDListener(object):
def __init__(self):
Expand Down Expand Up @@ -113,6 +115,9 @@ def pytest_runtest_makereport(self, item, call):
test_result.status = status
test_result.statusDetails = status_details

if test_result and test_result.status:
AttachmentWorker(test_result, item).delete_duplicates()

if report.when == 'teardown':
self.lifecycle.write_test_case(uuid=uuid)

Expand Down
16 changes: 16 additions & 0 deletions allure-pytest-bdd/test/acceptance.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Created by denis at 11.01.2021
Feature: test
# Enter feature description here

Scenario: scenario
Given py file with name: example
And with imports: pytest, pytest_bdd, allure
And with func:
"""
@pytest_bdd.given("given_step")
def my_func():
allure.attach("blah", ...)
raise Exception("message")
"""
And test for scenario_name from file.feature
And py file saved
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Feature: Bug #474
Scenario: allure.attach calling in function decorated with When and Pytest.fixture
Given example.feature with content:
"""
Feature: Feature Test
Scenario: My Scenario Test
Given passed step
When when-step is fixture with attachment
Then passed step using fixture
"""
And py file with name: example_test
And with imports: pytest, pytest_bdd, allure
And with passed steps
And with func:
"""
@pytest.fixture()
@pytest_bdd.when("when-step is fixture with attachment")
def step_with_attachment():
allure.attach('Attachment content', 'allure attachment', allure.attachment_type.TEXT)
"""
And with func:
"""
@pytest_bdd.then("passed step using fixture")
def then_step(step_with_attachment):
pass
"""
And test for My Scenario Test from example.feature
And py file saved

When run pytest-bdd with allure

Then attachment allure attachment must be in When when-step is fixture with attachment
And this attachment with content:
"""
Attachment content
"""
And attachments must not be in attachments
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pytest_bdd import scenario, then


@scenario("bug474.feature", "allure.attach calling in function decorated with When and Pytest.fixture")
def test_my_scenario():
pass
45 changes: 45 additions & 0 deletions allure-pytest-bdd/test/attachments_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os.path

from pytest_bdd import then, parsers
import pytest


@then(parsers.re("this attachment with content:(?:\n)(?P<expected_content>[\\S|\\s]*)"))
def check_attachment_content(expected_content, suitable_attachment, testdir):
file_path = os.path.join(testdir.tmpdir.strpath, suitable_attachment["source"])
with open(file_path, "r") as file:
actual_content = file.read()

assert actual_content == expected_content


@pytest.fixture()
@then(parsers.parse("attachment {attachment_name} must be in {location_name}"))
def suitable_attachment(attachment_name, location_name, allure_report):
test_case_report = allure_report.test_cases[0]

if location_name == "attachments":
attachments = test_case_report["attachments"]
else:
attachments = _get_step_report(test_case_report, location_name)["attachments"]

suitable_attachments = [attachment for attachment in attachments
if attachment["name"] == attachment_name]

assert len(suitable_attachments) == 1
return suitable_attachments[0]


@then(parsers.parse("attachments must not be in {location_name}"))
def attachments_must_no_be_in(location_name, allure_report):
test_case_report = allure_report.test_cases[0]

if location_name == "attachments":
assert "attachments" not in test_case_report.keys()
else:
assert "attachments" not in _get_step_report(test_case_report, location_name).keys()


def _get_step_report(test_case_report, step_name):
return next(step for step in test_case_report["steps"]
if step["name"] == step_name)
52 changes: 51 additions & 1 deletion allure-pytest-bdd/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import allure_commons
from allure_commons_test.report import AllureReport
from allure_commons.logger import AllureFileLogger
from .steps import * # noqa F401 F403
from .steps import * # noqa F401 F403
from pytest_bdd import given, when, parsers

from .py_file_builder import PyFileBuilder

pytest_plugins = "pytester"


@contextmanager
def fake_logger(path, logger):
Expand Down Expand Up @@ -61,3 +65,49 @@ def feature_definition(name, extension, content, testdir):
@when("run pytest-bdd with allure")
def run(allured_testdir):
allured_testdir.run_with_allure()


@pytest.fixture()
@given(parsers.parse("py file with name: {name}"))
def current_py_file_builder(name):
return PyFileBuilder(name)


@given(parsers.parse("with imports: {modules}"))
def add_imports_in_builder(modules, current_py_file_builder):
modules_names = [module.strip() for module in modules.split(",")]
current_py_file_builder.add_imports(*modules_names)


@given(parsers.re("with func:(?:\n)(?P<content>[\\S|\\s]*)"))
def add_func_in_builder(content, current_py_file_builder):
current_py_file_builder.add_func(content)


@given("with passed steps")
def add_passed_steps(current_py_file_builder):

passed_steps = '@pytest_bdd.given("passed step")\n' \
'@pytest_bdd.when("passed step")\n' \
'@pytest_bdd.then("passed step")\n' \
'def step_impl():\n' \
' pass'

current_py_file_builder.add_func(passed_steps)


@given(parsers.parse("test for {scenario_name} from {feature_file}"))
def add_scenario_step(scenario_name, feature_file, current_py_file_builder):

scenario_func = f'@pytest_bdd.scenario("{feature_file}", "{scenario_name}")\n' \
'def test_scenario():\n' \
' pass'

current_py_file_builder.add_func(scenario_func)


@given(parsers.parse("py file saved"))
def save_py_file(current_py_file_builder, testdir):
testdir.makefile(
".py",
**dict([(current_py_file_builder.name, current_py_file_builder.get_content())]))
29 changes: 29 additions & 0 deletions allure-pytest-bdd/test/py_file_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class PyFileBuilder:

def __init__(self, name):
self._import_line = None
self._file_funcs = []
self.name = name

def add_imports(self, *modules_names):
import_list = []

for module in modules_names:
import_list.append("import " + module)

if len(import_list) != 0:
self._import_line = "\n".join(import_list)

def add_func(self, str_func):
self._file_funcs.append(str_func)

def get_content(self):
if len(self._file_funcs) == 0:
raise Exception("There are no functions in this file")

content = "\n\n\n".join(self._file_funcs)

if self._import_line is not None:
content = self._import_line + "\n\n\n" + content

return content
Loading