Skip to content
Closed
37 changes: 34 additions & 3 deletions pytest_bdd/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
COMMENT_RE = re.compile(r"(^|(?<=\s))#")
SPLIT_LINE_RE = re.compile(r"(?<!\\)\|")

TYPES_WITH_DESCRIPTIONS = [types.FEATURE, types.SCENARIO, types.SCENARIO_OUTLINE]


def get_step_type(line):
"""Detect step type by the beginning of the line.
Expand Down Expand Up @@ -293,7 +295,8 @@ def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True):
multiline_step = False
stripped_line = line.strip()
clean_line = strip_comments(line)
if not clean_line and (not prev_mode or prev_mode not in types.FEATURE):
if not clean_line and (not prev_mode or prev_mode not in TYPES_WITH_DESCRIPTIONS):
# Blank lines are included in feature and scenario descriptions
continue
mode = get_step_type(clean_line) or mode

Expand Down Expand Up @@ -347,7 +350,9 @@ def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True):
self.line_number = line_number
self.tags = get_tags(prev_line)
elif prev_mode == types.FEATURE:
description.append(clean_line)
# Do not include comments in descriptions
if not stripped_line.startswith("#"):
description.append(line)
else:
raise exceptions.FeatureError(
"Multiple features are not allowed in a single feature file",
Expand All @@ -361,6 +366,16 @@ def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True):
# Remove Feature, Given, When, Then, And
keyword, parsed_line = parse_line(clean_line)
if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
# Lines between the scenario declaration
# and the scenario's first step line
# are considered part of the scenario description.
if scenario and not keyword:
# Do not include comments in descriptions
if stripped_line.startswith("#"):
continue
scenario.add_description_line(line)
continue

tags = get_tags(prev_line)
self.scenarios[parsed_line] = scenario = Scenario(self, parsed_line, line_number, tags=tags)
elif mode == types.BACKGROUND:
Expand Down Expand Up @@ -406,7 +421,7 @@ def __init__(self, basedir, filename, encoding="utf-8", strict_gherkin=True):
target.add_step(step)
prev_line = clean_line

self.description = u"\n".join(description).strip()
self.description = u"\n".join(description)

@classmethod
def get_feature(cls, base_path, filename, encoding="utf-8", strict_gherkin=True):
Expand Down Expand Up @@ -454,6 +469,7 @@ def __init__(self, feature, name, line_number, example_converters=None, tags=Non
self.tags = tags or set()
self.failed = False
self.test_function = None
self._description_lines = []

def add_step(self, step):
"""Add step to the scenario.
Expand All @@ -475,6 +491,21 @@ def steps(self):
result.extend(self._steps)
return result

def add_description_line(self, description_line):
"""Add a description line to the scenario.

:param str description_line:
"""
self._description_lines.append(description_line)

@property
def description(self):
"""Get the scenario's description.

:return: The scenario description
"""
return u"\n".join(self._description_lines)

@property
def params(self):
"""Get parameter names.
Expand Down
4 changes: 4 additions & 0 deletions tests/feature/description.feature
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ Feature: Description

Scenario: Description

Also, the scenario can have a description.

It goes here between the scenario name
and the first step.
Given I have a bar
60 changes: 52 additions & 8 deletions tests/feature/test_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,65 @@
from pytest_bdd import scenario


TEST_FILE_CONTENTS = """
import pytest
from pytest_bdd import given, scenario

def test_descriptions(request):
@scenario(
"descriptions.feature",
"Description",
)
def test():
pass

test(request)

@given("I have a bar")
def nothing():
pass

"""

with open("./tests/feature/description.feature") as f:
FEATURE_FILE_CONTENTS = f.read()

EXPECTED_FEATURE_DESCRIPTION = (
"\n"
" In order to achieve something\n"
" I want something\n"
" Because it will be cool\n"
"\n"
"\n"
" Some description goes here.\n"
)


EXPECTED_SCENARIO_DESCRIPTION = (
"\n"
" Also, the scenario can have a description.\n"
"\n"
" It goes here between the scenario name\n"
" and the first step."
)


def test_description(request):
"""Test description for the feature."""

@scenario("description.feature", "Description")
def test():
pass

assert (
test.__scenario__.feature.description
== """In order to achieve something
I want something
Because it will be cool
assert test.__scenario__.feature.description == EXPECTED_FEATURE_DESCRIPTION
assert test.__scenario__.description == EXPECTED_SCENARIO_DESCRIPTION

test(request)

Some description goes here."""
)

test(request)
def test_scenarios_are_created_when_they_have_scenario_descriptions(testdir):
testdir.makepyfile(test_descriptions=TEST_FILE_CONTENTS)
testdir.makefile(".feature", descriptions=FEATURE_FILE_CONTENTS)

result = testdir.runpytest()
result.assert_outcomes(passed=1)