28
28
("But " , None ),
29
29
]
30
30
31
+ TYPES_WITH_DESCRIPTIONS = [types .FEATURE , types .SCENARIO , types .SCENARIO_OUTLINE ]
32
+
31
33
if typing .TYPE_CHECKING :
32
34
from typing import Any , Iterable , Mapping , Match , Sequence
33
35
@@ -125,7 +127,8 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu
125
127
multiline_step = False
126
128
stripped_line = line .strip ()
127
129
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
129
132
continue
130
133
mode = get_step_type (clean_line ) or mode
131
134
@@ -142,7 +145,9 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu
142
145
feature .line_number = line_number
143
146
feature .tags = get_tags (prev_line )
144
147
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 )
146
151
else :
147
152
raise exceptions .FeatureError (
148
153
"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
157
162
keyword , parsed_line = parse_line (clean_line )
158
163
159
164
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
160
174
tags = get_tags (prev_line )
161
175
scenario = ScenarioTemplate (
162
176
feature = feature ,
@@ -215,6 +229,7 @@ class ScenarioTemplate:
215
229
tags : set [str ] = field (default_factory = set )
216
230
examples : Examples | None = field (default_factory = lambda : Examples ())
217
231
_steps : list [Step ] = field (init = False , default_factory = list )
232
+ _description_lines : list [str ] = field (init = False , default_factory = list )
218
233
219
234
def add_step (self , step : Step ) -> None :
220
235
step .scenario = self
@@ -241,7 +256,20 @@ def render(self, context: Mapping[str, Any]) -> Scenario:
241
256
for step in self ._steps
242
257
]
243
258
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 )
245
273
246
274
247
275
@dataclass
@@ -251,6 +279,7 @@ class Scenario:
251
279
line_number : int
252
280
steps : list [Step ]
253
281
tags : set [str ] = field (default_factory = set )
282
+ description : list [str ] = field (default_factory = list )
254
283
255
284
256
285
@dataclass
0 commit comments