Skip to content

Commit 5a5165a

Browse files
Merge pull request #227 from mjholtkamp/feature/json_cucumber_expand_option
cucumber.json expanded format
2 parents 11c6097 + 2e6b9e6 commit 5a5165a

File tree

5 files changed

+184
-3
lines changed

5 files changed

+184
-3
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ These people have contributed to `pytest-bdd`, in alphabetical order:
1818
* `Harro van der Klauw <[email protected]>`_
1919
* `Laurence Rowe <[email protected]>`_
2020
* `Leonardo Santagada <[email protected]>`_
21+
* `Michiel Holtkamp <[email protected]>`_
2122
* `Robin Pedersen <[email protected]>`_
2223
* `Sergey Kraynev <[email protected]>`_

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
2.19.0
5+
------
6+
7+
- Added --cucumber-json-expanded option for explicit selection of expanded format (mjholtkamp)
8+
- Step names are filled in when --cucumber-json-expanded is used (mjholtkamp)
9+
410
2.18.2
511
------
612

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,12 @@ To have an output in json format:
11331133

11341134
py.test --cucumberjson=<path to json report>
11351135

1136+
This will output an expanded (meaning scenario outlines will be expanded to several scenarios) cucumber format.
1137+
To also fill in parameters in the step name, you have to explicitly tell pytest-bdd to use the expanded format:
1138+
1139+
::
1140+
1141+
py.test --cucumberjson=<path to json report> --cucumberjson-expanded
11361142

11371143
To enable gherkin-formatted output on terminal, use
11381144

pytest_bdd/cucumber_json.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77

88
import py
9+
import re
910
import six
1011

1112
from .feature import force_unicode
@@ -27,12 +28,21 @@ def add_options(parser):
2728
help="create cucumber json style report file at given path.",
2829
)
2930

31+
group._addoption(
32+
"--cucumberjson-expanded",
33+
"--cucumber-json-expanded",
34+
action="store_true",
35+
dest="expand",
36+
default=False,
37+
help="expand scenario outlines into scenarios and fill in the step names",
38+
)
39+
3040

3141
def configure(config):
3242
cucumber_json_path = config.option.cucumber_json_path
3343
# prevent opening json log on slave nodes (xdist)
3444
if cucumber_json_path and not hasattr(config, "slaveinput"):
35-
config._bddcucumberjson = LogBDDCucumberJSON(cucumber_json_path)
45+
config._bddcucumberjson = LogBDDCucumberJSON(cucumber_json_path, expand=config.option.expand)
3646
config.pluginmanager.register(config._bddcucumberjson)
3747

3848

@@ -47,10 +57,11 @@ class LogBDDCucumberJSON(object):
4757

4858
"""Logging plugin for cucumber like json output."""
4959

50-
def __init__(self, logfile):
60+
def __init__(self, logfile, expand=False):
5161
logfile = os.path.expanduser(os.path.expandvars(logfile))
5262
self.logfile = os.path.normpath(os.path.abspath(logfile))
5363
self.features = {}
64+
self.expand = expand
5465

5566
def append(self, obj):
5667
self.features[-1].append(obj)
@@ -95,6 +106,23 @@ def _serialize_tags(self, item):
95106
for tag in item["tags"]
96107
]
97108

109+
def _format_name(self, name, keys, values):
110+
for param, value in zip(keys, values):
111+
name = name.replace('<{}>'.format(param), value)
112+
return name
113+
114+
def _format_step_name(self, report, step):
115+
examples = report.scenario["examples"]
116+
if len(examples) == 0:
117+
return step["name"]
118+
119+
# we take the keys from the first "examples", but in each table, the keys should
120+
# be the same anyway since all the variables need to be filled in.
121+
keys, values = examples[0]["rows"]
122+
row_index = examples[0]["row_index"]
123+
124+
return self._format_name(step["name"], keys, values[row_index])
125+
98126
def pytest_runtest_logreport(self, report):
99127
try:
100128
scenario = report.scenario
@@ -112,9 +140,17 @@ def stepmap(step):
112140
scenario['failed'] = True
113141
error_message = True
114142

143+
if self.expand:
144+
# XXX The format is already 'expanded' (scenario oultines -> scenarios),
145+
# but the step names were not filled in with parameters. To be backwards
146+
# compatible, do not fill in the step names unless explicitly asked for.
147+
step_name = self._format_step_name(report, step)
148+
else:
149+
step_name = step["name"]
150+
115151
return {
116152
"keyword": step['keyword'],
117-
"name": step['name'],
153+
"name": step_name,
118154
"line": step['line_number'],
119155
"match": {
120156
"location": "",

tests/feature/test_cucumber_json.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ def test_step_trace(testdir):
4444
Scenario: Failing
4545
Given a passing step
4646
And a failing step
47+
48+
@scenario-outline-passing-tag
49+
Scenario: Passing outline
50+
Given type <type> and value <value>
51+
52+
Examples: example1
53+
| type | value |
54+
| str | hello |
55+
| int | 42 |
56+
| float | 1.0 |
4757
"""))
4858
testdir.makepyfile(textwrap.dedent("""
4959
import pytest
@@ -61,13 +71,21 @@ def some_other_passing_step():
6171
def a_failing_step():
6272
raise Exception('Error')
6373
74+
@given('type <type> and value <value>')
75+
def type_type_and_value_value():
76+
return 'pass'
77+
6478
@scenario('test.feature', 'Passing')
6579
def test_passing():
6680
pass
6781
6882
@scenario('test.feature', 'Failing')
6983
def test_failing():
7084
pass
85+
86+
@scenario('test.feature', 'Passing outline')
87+
def test_passing_outline():
88+
pass
7189
"""))
7290
result, jsonobject = runandparse(testdir)
7391
assert result.ret
@@ -156,6 +174,84 @@ def test_failing():
156174
}
157175
],
158176
"type": "scenario"
177+
},
178+
{
179+
"description": "",
180+
"keyword": "Scenario",
181+
"tags": [
182+
{
183+
"line": 14,
184+
"name": "scenario-outline-passing-tag"
185+
}
186+
],
187+
"steps": [
188+
{
189+
"line": 16,
190+
"match": {"location": ""},
191+
"result": {
192+
"status": "passed",
193+
"duration": equals_any(int)
194+
},
195+
"keyword": "Given",
196+
"name": "type <type> and value <value>"
197+
}
198+
],
199+
"line": 15,
200+
"type": "scenario",
201+
"id": "test_passing_outline[str-hello]",
202+
"name": "Passing outline"
203+
},
204+
{
205+
"description": "",
206+
"keyword": "Scenario",
207+
"tags": [
208+
{
209+
"line": 14,
210+
"name": "scenario-outline-passing-tag"
211+
}
212+
],
213+
"steps": [
214+
{
215+
"line": 16,
216+
"match": {"location": ""},
217+
"result": {
218+
"status": "passed",
219+
"duration": equals_any(int)
220+
},
221+
"keyword": "Given",
222+
"name": "type <type> and value <value>"
223+
}
224+
],
225+
"line": 15,
226+
"type": "scenario",
227+
"id": "test_passing_outline[int-42]",
228+
"name": "Passing outline"
229+
},
230+
{
231+
"description": "",
232+
"keyword": "Scenario",
233+
"tags": [
234+
{
235+
"line": 14,
236+
"name": "scenario-outline-passing-tag"
237+
}
238+
],
239+
"steps": [
240+
{
241+
"line": 16,
242+
"match": {"location": ""},
243+
"result": {
244+
"status": "passed",
245+
"duration": equals_any(int)
246+
},
247+
"keyword": "Given",
248+
"name": "type <type> and value <value>"
249+
}
250+
],
251+
"line": 15,
252+
"type": "scenario",
253+
"id": "test_passing_outline[float-1.0]",
254+
"name": "Passing outline"
159255
}
160256
],
161257
"id": os.path.join("test_step_trace0", "test.feature"),
@@ -173,3 +269,39 @@ def test_failing():
173269
]
174270

175271
assert jsonobject == expected
272+
273+
274+
def test_step_trace_with_expand_option(testdir):
275+
"""Test step trace."""
276+
testdir.makefile('.feature', test=textwrap.dedent("""
277+
@feature-tag
278+
Feature: One scenario outline, expanded to multiple scenarios
279+
280+
@scenario-outline-passing-tag
281+
Scenario: Passing outline
282+
Given type <type> and value <value>
283+
284+
Examples: example1
285+
| type | value |
286+
| str | hello |
287+
| int | 42 |
288+
| float | 1.0 |
289+
"""))
290+
testdir.makepyfile(textwrap.dedent("""
291+
import pytest
292+
from pytest_bdd import given, scenario
293+
294+
@given('type <type> and value <value>')
295+
def type_type_and_value_value():
296+
return 'pass'
297+
298+
@scenario('test.feature', 'Passing outline')
299+
def test_passing_outline():
300+
pass
301+
"""))
302+
result, jsonobject = runandparse(testdir, '--cucumber-json-expand')
303+
assert result.ret == 0
304+
305+
assert jsonobject[0]["elements"][0]["steps"][0]["name"] == "type str and value hello"
306+
assert jsonobject[0]["elements"][1]["steps"][0]["name"] == "type int and value 42"
307+
assert jsonobject[0]["elements"][2]["steps"][0]["name"] == "type float and value 1.0"

0 commit comments

Comments
 (0)