2828 ("But " , None ),
2929]
3030
31+ TYPES_WITH_DESCRIPTIONS = [types .FEATURE , types .SCENARIO , types .SCENARIO_OUTLINE ]
32+
3133if typing .TYPE_CHECKING :
3234 from typing import Any , Iterable , Mapping , Match , Sequence
3335
@@ -125,7 +127,8 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu
125127 multiline_step = False
126128 stripped_line = line .strip ()
127129 clean_line = strip_comments (line )
128- if not clean_line and (not prev_mode or prev_mode not in types .FEATURE ):
130+ if not clean_line and (not prev_mode or prev_mode not in TYPES_WITH_DESCRIPTIONS ):
131+ # Blank lines are included in feature and scenario descriptions
129132 continue
130133 mode = get_step_type (clean_line ) or mode
131134
@@ -142,7 +145,9 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu
142145 feature .line_number = line_number
143146 feature .tags = get_tags (prev_line )
144147 elif prev_mode == types .FEATURE :
145- description .append (clean_line )
148+ # Do not include comments in descriptions
149+ if not stripped_line .startswith ("#" ):
150+ description .append (line )
146151 else :
147152 raise exceptions .FeatureError (
148153 "Multiple features are not allowed in a single feature file" ,
@@ -157,6 +162,15 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu
157162 keyword , parsed_line = parse_line (clean_line )
158163
159164 if mode in [types .SCENARIO , types .SCENARIO_OUTLINE ]:
165+ # Lines between the scenario declaration
166+ # and the scenario's first step line
167+ # are considered part of the scenario description.
168+ if scenario and not keyword :
169+ # Do not include comments in descriptions
170+ if stripped_line .startswith ("#" ):
171+ continue
172+ scenario .add_description_line (line )
173+ continue
160174 tags = get_tags (prev_line )
161175 scenario = ScenarioTemplate (
162176 feature = feature ,
@@ -215,6 +229,7 @@ class ScenarioTemplate:
215229 tags : set [str ] = field (default_factory = set )
216230 examples : Examples | None = field (default_factory = lambda : Examples ())
217231 _steps : list [Step ] = field (init = False , default_factory = list )
232+ _description_lines : list [str ] = field (init = False , default_factory = list )
218233
219234 def add_step (self , step : Step ) -> None :
220235 step .scenario = self
@@ -241,7 +256,20 @@ def render(self, context: Mapping[str, Any]) -> Scenario:
241256 for step in self ._steps
242257 ]
243258 steps = background_steps + scenario_steps
244- return Scenario (feature = self .feature , name = self .name , line_number = self .line_number , steps = steps , tags = self .tags )
259+ return Scenario (feature = self .feature , name = self .name , line_number = self .line_number , steps = steps , tags = self .tags , description = self ._description_lines )
260+
261+ def add_description_line (self , description_line ):
262+ """Add a description line to the scenario.
263+ :param str description_line:
264+ """
265+ self ._description_lines .append (description_line )
266+
267+ @property
268+ def description (self ):
269+ """Get the scenario's description.
270+ :return: The scenario description
271+ """
272+ return u"\n " .join (self ._description_lines )
245273
246274
247275@dataclass
@@ -251,6 +279,7 @@ class Scenario:
251279 line_number : int
252280 steps : list [Step ]
253281 tags : set [str ] = field (default_factory = set )
282+ description : list [str ] = field (default_factory = list )
254283
255284
256285@dataclass
0 commit comments