cucumber_characteristics 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +0 -2
- data/README.md +3 -0
- data/features/ambiguous.feature +4 -0
- data/features/characteristics/cucumber_step_characteristics.html +358 -0
- data/features/characteristics/cucumber_step_characteristics.json +1 -0
- data/features/scenario.feature +3 -3
- data/features/scenario_outline.feature +1 -1
- data/features/step_definitions/ambiguous_steps.rb +5 -0
- data/lib/cucumber_characteristics/configuration.rb +2 -1
- data/lib/cucumber_characteristics/cucumber_step_patch.rb +15 -7
- data/lib/cucumber_characteristics/exporter.rb +2 -1
- data/lib/cucumber_characteristics/formatter.rb +19 -0
- data/lib/cucumber_characteristics/profile_data.rb +88 -18
- data/lib/cucumber_characteristics/version.rb +1 -1
- data/lib/cucumber_characteristics/view/step_report.html.haml +128 -51
- metadata +10 -2
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -33,6 +33,7 @@ You can configure the export of step characteristics via the following (defaults
|
|
33
33
|
CucumberCharacteristics.configure do |config|
|
34
34
|
config.export_json = true
|
35
35
|
config.export_html = true
|
36
|
+
config.precision = 4
|
36
37
|
config.target_filename = 'cucumber_step_characteristics'
|
37
38
|
config.relative_path = 'features/characteristics'
|
38
39
|
end
|
@@ -48,6 +49,8 @@ depending on the options specified.
|
|
48
49
|
|
49
50
|
The JSON option is provided for convenience in case there is a further use case/analysis required that is not provided by the gem.
|
50
51
|
|
52
|
+
An example can be found [here](features/characteristics/cucumber_step_characteristics.html)
|
53
|
+
|
51
54
|
## Problem
|
52
55
|
|
53
56
|
The formatting hooks on the face of it provide the necessary event points to profile any given feature file.
|
@@ -0,0 +1,358 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang='en'>
|
3
|
+
<head>
|
4
|
+
<meta charset='utf-8'>
|
5
|
+
<meta content='IE=edge' http-equiv='X-UA-Compatible'>
|
6
|
+
<meta content='width=device-width, initial-scale=1' name='viewport'>
|
7
|
+
<meta content='Cucumber Step Characteristics' name='description'>
|
8
|
+
<title>Cucumber Step Characteristics</title>
|
9
|
+
<link href='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css' rel='stylesheet'>
|
10
|
+
<link href='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css' rel='stylesheet'>
|
11
|
+
<!-- Custom styles for this layout -->
|
12
|
+
<style>
|
13
|
+
body {
|
14
|
+
min-height: 2000px;
|
15
|
+
padding-top: 70px;
|
16
|
+
}
|
17
|
+
|
18
|
+
td {
|
19
|
+
white-space: nowrap;
|
20
|
+
}
|
21
|
+
|
22
|
+
table.tablesorter thead tr .headerSortUp {
|
23
|
+
background-color: #8dbdd8;
|
24
|
+
}
|
25
|
+
table.tablesorter thead tr .headerSortDown {
|
26
|
+
background-color: #8dbdd8;
|
27
|
+
}
|
28
|
+
</style>
|
29
|
+
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
30
|
+
<!--[if lt IE 9]>
|
31
|
+
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
32
|
+
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
33
|
+
<![endif]-->
|
34
|
+
</head>
|
35
|
+
<body>
|
36
|
+
<div class='container'>
|
37
|
+
<div class='page-header'>
|
38
|
+
<h1>Cucumber step characteristics</h1>
|
39
|
+
</div>
|
40
|
+
<div class='alert alert-info'>
|
41
|
+
<span>
|
42
|
+
10 Scenarios,
|
43
|
+
36 Steps completed
|
44
|
+
<span class='text-muted'>
|
45
|
+
(Failed: 2, Passed: 31, Skipped: 2, Undefined: 1)<br>
|
46
|
+
</span>
|
47
|
+
1/2 failures due to ambiguous matches<br>
|
48
|
+
Test duration 12.0048s.
|
49
|
+
<span class='text-muted'>
|
50
|
+
(11.9254s steps, 0.0794s non-steps)
|
51
|
+
</span>
|
52
|
+
</span>
|
53
|
+
<span class='text-muted pull-right small'>
|
54
|
+
Finished on 2014-06-25 20:13:34 -0400
|
55
|
+
</span>
|
56
|
+
</div>
|
57
|
+
<ul class='nav nav-tabs'>
|
58
|
+
<li class='active'>
|
59
|
+
<a data-toggle='tab' href='#steps'>Steps</a>
|
60
|
+
</li>
|
61
|
+
<li>
|
62
|
+
<a data-toggle='tab' href='#features'>Features</a>
|
63
|
+
</li>
|
64
|
+
</ul>
|
65
|
+
<div class='tab-content'>
|
66
|
+
<div class='tab-pane active' id='steps'>
|
67
|
+
<table class='table table-striped table-bordered table-condensed tablesorter' id='profile_table'>
|
68
|
+
<thead>
|
69
|
+
<tr>
|
70
|
+
<th>Step</th>
|
71
|
+
<th>Total time</th>
|
72
|
+
<th>Passed</th>
|
73
|
+
<th>Average</th>
|
74
|
+
<th>Fastest</th>
|
75
|
+
<th>Slowest</th>
|
76
|
+
<th>Variation</th>
|
77
|
+
<th>Variance</th>
|
78
|
+
<th>Std Deviation</th>
|
79
|
+
<th>Skipped</th>
|
80
|
+
<th>Error</th>
|
81
|
+
<th>Undef</th>
|
82
|
+
<th>Total count</th>
|
83
|
+
</tr>
|
84
|
+
</thead>
|
85
|
+
<tbody>
|
86
|
+
<tr>
|
87
|
+
<td>
|
88
|
+
<abbr title='/^I wait ([\d\.]+) seconds$/'>features/step_definitions/wait_steps.rb:1</abbr>
|
89
|
+
</td>
|
90
|
+
<td>11.9254</td>
|
91
|
+
<td>
|
92
|
+
<abbr title='features/failure.feature:4
features/pending.feature:4
features/scenario.feature:4
features/scenario.feature:5
features/scenario.feature:6
features/scenario_outline.feature:4 (x2)
features/scenario_outline.feature:5 (x2)
features/scenario_outline.feature:6 (x2)
features/scenario_outline.feature:7 (x2)
features/scenario_outline_with_background.feature:4 (x2)
features/scenario_outline_with_background.feature:7 (x2)
features/scenario_outline_with_background.feature:8 (x2)
features/scenario_outline_with_background.feature:9 (x2)
features/scenario_outline_with_background.feature:10 (x2)
features/scenario_with_background.feature:4 (x2)
features/scenario_with_background.feature:7
features/scenario_with_background.feature:8
features/scenario_with_background.feature:9
features/scenario_with_background.feature:12
features/scenario_with_background.feature:13
features/scenario_with_background.feature:14'>31</abbr>
|
93
|
+
</td>
|
94
|
+
<td>0.3847</td>
|
95
|
+
<td>0.1006</td>
|
96
|
+
<td>1.0004</td>
|
97
|
+
<td>0.8998</td>
|
98
|
+
<td>0.0562</td>
|
99
|
+
<td>0.2371</td>
|
100
|
+
<td>
|
101
|
+
<abbr title='features/failure.feature:6
features/pending.feature:6'>2</abbr>
|
102
|
+
</td>
|
103
|
+
<td>
|
104
|
+
0
|
105
|
+
</td>
|
106
|
+
<td>
|
107
|
+
0
|
108
|
+
</td>
|
109
|
+
<td>33</td>
|
110
|
+
</tr>
|
111
|
+
<tr>
|
112
|
+
<td>
|
113
|
+
<abbr title='/^I fail$/'>features/step_definitions/fail_steps.rb:1</abbr>
|
114
|
+
</td>
|
115
|
+
<td>-</td>
|
116
|
+
<td>
|
117
|
+
0
|
118
|
+
</td>
|
119
|
+
<td>-</td>
|
120
|
+
<td>-</td>
|
121
|
+
<td>-</td>
|
122
|
+
<td>-</td>
|
123
|
+
<td>-</td>
|
124
|
+
<td>-</td>
|
125
|
+
<td>
|
126
|
+
0
|
127
|
+
</td>
|
128
|
+
<td>
|
129
|
+
<abbr title='features/failure.feature:5'>1</abbr>
|
130
|
+
</td>
|
131
|
+
<td>
|
132
|
+
0
|
133
|
+
</td>
|
134
|
+
<td>1</td>
|
135
|
+
</tr>
|
136
|
+
<tr>
|
137
|
+
<td>
|
138
|
+
<abbr title=''>I call a pending step</abbr>
|
139
|
+
</td>
|
140
|
+
<td>-</td>
|
141
|
+
<td>
|
142
|
+
0
|
143
|
+
</td>
|
144
|
+
<td>-</td>
|
145
|
+
<td>-</td>
|
146
|
+
<td>-</td>
|
147
|
+
<td>-</td>
|
148
|
+
<td>-</td>
|
149
|
+
<td>-</td>
|
150
|
+
<td>
|
151
|
+
0
|
152
|
+
</td>
|
153
|
+
<td>
|
154
|
+
0
|
155
|
+
</td>
|
156
|
+
<td>
|
157
|
+
<abbr title='features/pending.feature:5'>1</abbr>
|
158
|
+
</td>
|
159
|
+
<td>1</td>
|
160
|
+
</tr>
|
161
|
+
</tbody>
|
162
|
+
</table>
|
163
|
+
</div>
|
164
|
+
<div class='tab-pane' id='features'>
|
165
|
+
<table class='table table-striped table-bordered table-condensed' id='feature_table'>
|
166
|
+
<thead>
|
167
|
+
<tr>
|
168
|
+
<th>Feature</th>
|
169
|
+
<th>Total time</th>
|
170
|
+
<th>Step count</th>
|
171
|
+
<th>Status</th>
|
172
|
+
<th></th>
|
173
|
+
</tr>
|
174
|
+
</thead>
|
175
|
+
<tbody>
|
176
|
+
<tr class='danger'>
|
177
|
+
<td>features/failure.feature:3</td>
|
178
|
+
<td>1.0004</td>
|
179
|
+
<td>3</td>
|
180
|
+
<td>failed</td>
|
181
|
+
<td>
|
182
|
+
</td>
|
183
|
+
</tr>
|
184
|
+
<tr class='danger'>
|
185
|
+
<td>features/ambiguous.feature:3</td>
|
186
|
+
<td>-</td>
|
187
|
+
<td>1</td>
|
188
|
+
<td>failed</td>
|
189
|
+
<td>
|
190
|
+
</td>
|
191
|
+
</tr>
|
192
|
+
<tr class='warning'>
|
193
|
+
<td>features/pending.feature:3</td>
|
194
|
+
<td>1.0004</td>
|
195
|
+
<td>3</td>
|
196
|
+
<td>undefined</td>
|
197
|
+
<td>
|
198
|
+
</td>
|
199
|
+
</tr>
|
200
|
+
<tr class=''>
|
201
|
+
<td>features/scenario_outline_with_background.feature:6</td>
|
202
|
+
<td>3.6076</td>
|
203
|
+
<td>10</td>
|
204
|
+
<td>passed</td>
|
205
|
+
<td>
|
206
|
+
<button class='btn btn-primary' data-target='#ee4ab50980effe45ea18c9afc38e97e5' data-toggle='modal'>
|
207
|
+
2 Examples
|
208
|
+
</button>
|
209
|
+
</td>
|
210
|
+
</tr>
|
211
|
+
<tr class=''>
|
212
|
+
<td>features/scenario_with_background.feature:11</td>
|
213
|
+
<td>2.2036</td>
|
214
|
+
<td>4</td>
|
215
|
+
<td>passed</td>
|
216
|
+
<td>
|
217
|
+
</td>
|
218
|
+
</tr>
|
219
|
+
<tr class=''>
|
220
|
+
<td>features/scenario_outline.feature:3</td>
|
221
|
+
<td>1.6051</td>
|
222
|
+
<td>8</td>
|
223
|
+
<td>passed</td>
|
224
|
+
<td>
|
225
|
+
<button class='btn btn-primary' data-target='#76f61671f111ebbc40b80f6aa9f5f838' data-toggle='modal'>
|
226
|
+
2 Examples
|
227
|
+
</button>
|
228
|
+
</td>
|
229
|
+
</tr>
|
230
|
+
<tr class=''>
|
231
|
+
<td>features/scenario.feature:3</td>
|
232
|
+
<td>1.5057</td>
|
233
|
+
<td>3</td>
|
234
|
+
<td>passed</td>
|
235
|
+
<td>
|
236
|
+
</td>
|
237
|
+
</tr>
|
238
|
+
<tr class=''>
|
239
|
+
<td>features/scenario_with_background.feature:6</td>
|
240
|
+
<td>1.0026</td>
|
241
|
+
<td>4</td>
|
242
|
+
<td>passed</td>
|
243
|
+
<td>
|
244
|
+
</td>
|
245
|
+
</tr>
|
246
|
+
</tbody>
|
247
|
+
</table>
|
248
|
+
<div aria-hidden='true' aria-labelledby='myLargeModalLabel' class='modal fade bs-example-modal-lg' id='ee4ab50980effe45ea18c9afc38e97e5' role='dialog' tabindex='-1'>
|
249
|
+
<div class='modal-dialog modal-lg'>
|
250
|
+
<div class='modal-content'>
|
251
|
+
<div class='modal-header'>
|
252
|
+
<h4>features/scenario_outline_with_background.feature:6</h4>
|
253
|
+
</div>
|
254
|
+
<div class='modal-body'>
|
255
|
+
<table class='table table-striped table-bordered table-condensed'>
|
256
|
+
<thead>
|
257
|
+
<tr>
|
258
|
+
<th>Example</th>
|
259
|
+
<th>Total time</th>
|
260
|
+
<th>Step count</th>
|
261
|
+
<th>Status</th>
|
262
|
+
</tr>
|
263
|
+
</thead>
|
264
|
+
<tbody>
|
265
|
+
<tr class=''>
|
266
|
+
<td>| 0.5 | 0.6 | 0.7 |</td>
|
267
|
+
<td>2.4043</td>
|
268
|
+
<td>5</td>
|
269
|
+
<td>passed</td>
|
270
|
+
</tr>
|
271
|
+
<tr class=''>
|
272
|
+
<td>| 0.1 | 0.2 | 0.3 |</td>
|
273
|
+
<td>1.2033</td>
|
274
|
+
<td>5</td>
|
275
|
+
<td>passed</td>
|
276
|
+
</tr>
|
277
|
+
</tbody>
|
278
|
+
<tfooter>
|
279
|
+
<tr>
|
280
|
+
<th></th>
|
281
|
+
<th>3.6076</th>
|
282
|
+
<th>10</th>
|
283
|
+
<th>passed</th>
|
284
|
+
</tr>
|
285
|
+
</tfooter>
|
286
|
+
</table>
|
287
|
+
</div>
|
288
|
+
<div class='modal-footer'>
|
289
|
+
<button class='btn btn-default' data-dismiss='modal' type='button'>
|
290
|
+
Close
|
291
|
+
</button>
|
292
|
+
</div>
|
293
|
+
</div>
|
294
|
+
</div>
|
295
|
+
</div>
|
296
|
+
<div aria-hidden='true' aria-labelledby='myLargeModalLabel' class='modal fade bs-example-modal-lg' id='76f61671f111ebbc40b80f6aa9f5f838' role='dialog' tabindex='-1'>
|
297
|
+
<div class='modal-dialog modal-lg'>
|
298
|
+
<div class='modal-content'>
|
299
|
+
<div class='modal-header'>
|
300
|
+
<h4>features/scenario_outline.feature:3</h4>
|
301
|
+
</div>
|
302
|
+
<div class='modal-body'>
|
303
|
+
<table class='table table-striped table-bordered table-condensed'>
|
304
|
+
<thead>
|
305
|
+
<tr>
|
306
|
+
<th>Example</th>
|
307
|
+
<th>Total time</th>
|
308
|
+
<th>Step count</th>
|
309
|
+
<th>Status</th>
|
310
|
+
</tr>
|
311
|
+
</thead>
|
312
|
+
<tbody>
|
313
|
+
<tr class=''>
|
314
|
+
<td>| 0.3 | 0.2 | 0.1 |</td>
|
315
|
+
<td>0.8025</td>
|
316
|
+
<td>4</td>
|
317
|
+
<td>passed</td>
|
318
|
+
</tr>
|
319
|
+
<tr class=''>
|
320
|
+
<td>| 0.1 | 0.2 | 0.3 |</td>
|
321
|
+
<td>0.8025</td>
|
322
|
+
<td>4</td>
|
323
|
+
<td>passed</td>
|
324
|
+
</tr>
|
325
|
+
</tbody>
|
326
|
+
<tfooter>
|
327
|
+
<tr>
|
328
|
+
<th></th>
|
329
|
+
<th>1.6051</th>
|
330
|
+
<th>8</th>
|
331
|
+
<th>passed</th>
|
332
|
+
</tr>
|
333
|
+
</tfooter>
|
334
|
+
</table>
|
335
|
+
</div>
|
336
|
+
<div class='modal-footer'>
|
337
|
+
<button class='btn btn-default' data-dismiss='modal' type='button'>
|
338
|
+
Close
|
339
|
+
</button>
|
340
|
+
</div>
|
341
|
+
</div>
|
342
|
+
</div>
|
343
|
+
</div>
|
344
|
+
</div>
|
345
|
+
</div>
|
346
|
+
</div>
|
347
|
+
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'></script>
|
348
|
+
<script src='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js'></script>
|
349
|
+
<script src='https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js'></script>
|
350
|
+
<script>
|
351
|
+
$(document).ready(function()
|
352
|
+
{
|
353
|
+
$("#profile_table").tablesorter( {sortList: [[1,1],[3,1]], widgets: ['zebra']} );
|
354
|
+
}
|
355
|
+
);
|
356
|
+
</script>
|
357
|
+
</body>
|
358
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
[["features/step_definitions/wait_steps.rb:1",{"total_count":33,"passed":{"count":31,"feature_location":{"features/failure.feature:4":[1.000400775],"features/pending.feature:4":[1.000400775],"features/scenario.feature:4":[0.501914414],"features/scenario.feature:5":[0.501914414],"features/scenario.feature:6":[0.501914414],"features/scenario_outline.feature:4":[0.100597662,0.30053452],"features/scenario_outline.feature:5":[0.200701096,0.200701096],"features/scenario_outline.feature:6":[0.30053452,0.100597662],"features/scenario_outline.feature:7":[0.200701096,0.200701096],"features/scenario_outline_with_background.feature:4":[0.400732052,0.400732052],"features/scenario_outline_with_background.feature:7":[0.100597662,0.501914414],"features/scenario_outline_with_background.feature:8":[0.200701096,0.600408111],"features/scenario_outline_with_background.feature:9":[0.30053452,0.700576606],"features/scenario_outline_with_background.feature:10":[0.200701096,0.200701096],"features/scenario_with_background.feature:4":[0.400732052,0.400732052],"features/scenario_with_background.feature:7":[0.100597662],"features/scenario_with_background.feature:8":[0.200701096],"features/scenario_with_background.feature:9":[0.30053452],"features/scenario_with_background.feature:12":[0.501914414],"features/scenario_with_background.feature:13":[0.600408111],"features/scenario_with_background.feature:14":[0.700576606]}},"failed":{"count":0,"feature_location":{}},"skipped":{"count":2,"feature_location":{"features/failure.feature:6":[],"features/pending.feature:6":[]}},"undefined":{"count":0,"feature_location":{}},"regexp":"/^I wait ([\\d\\.]+) seconds$/","fastest":0.100597662,"slowest":1.000400775,"average":0.3846906050967743,"total_duration":11.925408758000003,"standard_deviation":0.2370893233738576,"variation":0.8998031129999999,"variance":0.05621134725787363}],["features/step_definitions/fail_steps.rb:1",{"total_count":1,"passed":{"count":0,"feature_location":{}},"failed":{"count":1,"feature_location":{"features/failure.feature:5":[]}},"skipped":{"count":0,"feature_location":{}},"undefined":{"count":0,"feature_location":{}},"regexp":"/^I fail$/","fastest":null,"slowest":null,"average":null,"total_duration":null,"standard_deviation":null,"variation":null}],["I call a pending step",{"total_count":1,"passed":{"count":0,"feature_location":{}},"failed":{"count":0,"feature_location":{}},"skipped":{"count":0,"feature_location":{}},"undefined":{"count":1,"feature_location":{"features/pending.feature:5":[]}},"fastest":null,"slowest":null,"average":null,"total_duration":null,"standard_deviation":null,"variation":null}]]
|
data/features/scenario.feature
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Feature: As a user I want to understand where my tests are spending their time in a scenario
|
2
2
|
|
3
3
|
Scenario: Timings for normal scenario
|
4
|
-
Given I wait
|
5
|
-
When I wait
|
6
|
-
Then I wait
|
4
|
+
Given I wait 0.5 seconds
|
5
|
+
When I wait 0.5 seconds
|
6
|
+
Then I wait 0.5 seconds
|
@@ -2,11 +2,12 @@ module CucumberCharacteristics
|
|
2
2
|
|
3
3
|
class Configuration
|
4
4
|
|
5
|
-
attr_accessor :export_json, :export_html, :target_filename, :relative_path
|
5
|
+
attr_accessor :export_json, :export_html, :target_filename, :relative_path, :precision
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@export_json = true
|
9
9
|
@export_html = true
|
10
|
+
@precision = 4
|
10
11
|
@target_filename = 'cucumber_step_characteristics'
|
11
12
|
@relative_path = 'features/characteristics'
|
12
13
|
end
|
@@ -2,14 +2,16 @@
|
|
2
2
|
|
3
3
|
module Cucumber
|
4
4
|
class StepMatch
|
5
|
-
|
6
|
-
|
5
|
+
if self.method_defined?(:invoke)
|
6
|
+
old_invoke = instance_method(:invoke)
|
7
|
+
attr_reader :duration
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
define_method(:invoke) do | multiline_arg |
|
10
|
+
start_time = Time.now
|
11
|
+
ret = old_invoke.bind(self).(multiline_arg)
|
12
|
+
@duration = Time.now - start_time
|
13
|
+
ret
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -20,5 +22,11 @@ module Cucumber
|
|
20
22
|
class StepInvocation
|
21
23
|
attr_reader :step_match
|
22
24
|
end
|
25
|
+
|
26
|
+
class OutlineTable
|
27
|
+
class ExampleRow
|
28
|
+
attr_reader :step_invocations
|
29
|
+
end
|
30
|
+
end
|
23
31
|
end
|
24
32
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'haml'
|
2
|
+
require 'digest/md5'
|
2
3
|
|
3
4
|
module CucumberCharacteristics
|
4
5
|
|
@@ -35,7 +36,7 @@ module CucumberCharacteristics
|
|
35
36
|
# HELPERS
|
36
37
|
|
37
38
|
def format_ts(t)
|
38
|
-
t ? sprintf("%0.
|
39
|
+
t ? sprintf("%0.#{@config.precision}f", t) : '-'
|
39
40
|
end
|
40
41
|
|
41
42
|
def format_step_usage(step_feature_data)
|
@@ -6,8 +6,27 @@ module CucumberCharacteristics
|
|
6
6
|
@runtime = runtime
|
7
7
|
@io = io
|
8
8
|
@options = options
|
9
|
+
@features = {}
|
9
10
|
end
|
10
11
|
|
12
|
+
# def before_feature(scenario)
|
13
|
+
# pp scenario.class.name
|
14
|
+
# if scenario.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
|
15
|
+
# feature_location = scenario.scenario_outline.file_colon_line
|
16
|
+
# feature_name = scenario.scenario_outline.name.chomp
|
17
|
+
# feature_example = scenario.name
|
18
|
+
# @features[feature_location] ||= {}
|
19
|
+
# @features[feature_location][feature_example] ||= {start_time: Time.now}
|
20
|
+
# else
|
21
|
+
# feature_location = scenario.file_colon_line
|
22
|
+
# feature_name = scenario.name.chomp
|
23
|
+
# @features[feature_location] ||= {start_time: Time.now}
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
|
27
|
+
# def after_feature(scenario)
|
28
|
+
# end
|
29
|
+
|
11
30
|
def after_features(features)
|
12
31
|
profile = ProfileData.new(@runtime, features)
|
13
32
|
Exporter.new(profile).export
|
@@ -7,36 +7,103 @@ module CucumberCharacteristics
|
|
7
7
|
def_delegators :@runtime, :scenarios, :steps
|
8
8
|
attr_reader :duration
|
9
9
|
|
10
|
-
|
10
|
+
STATUS_ORDER = {passed: 0, failed: 2000, skipped: 1000, undefined: 500}
|
11
|
+
|
12
|
+
STATUS = STATUS_ORDER.keys
|
11
13
|
|
12
14
|
def initialize(runtime, features)
|
13
15
|
@runtime = runtime
|
14
16
|
@duration = features.duration
|
17
|
+
@features = features
|
18
|
+
end
|
19
|
+
|
20
|
+
def ambiguous_count
|
21
|
+
@runtime.steps.count{|s| ambiguous?(s)}
|
22
|
+
end
|
23
|
+
|
24
|
+
def unmatched_steps
|
25
|
+
unmatched = {}
|
26
|
+
@runtime.unmatched_step_definitions.each do |u|
|
27
|
+
unmatched[u.file_colon_line] = u.regexp_source
|
28
|
+
end
|
29
|
+
unmatched.sort
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_unmatched_steps?
|
33
|
+
unmatched_steps.count > 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def feature_profiles
|
37
|
+
feature_profiles = { }
|
38
|
+
@runtime.scenarios.each do |f|
|
39
|
+
if f.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
|
40
|
+
feature_id = f.scenario_outline.file_colon_line
|
41
|
+
feature_profiles[feature_id] ||= {name: f.scenario_outline.name, total_duration: 0, step_count: 0, example_count: 0, examples: {} }
|
42
|
+
example_id = f.name
|
43
|
+
feature_profiles[feature_id][:examples][example_id] ||= {total_duration: 0, step_count: 0}
|
44
|
+
feature_profiles[feature_id][:examples][example_id][:total_duration] = f.step_invocations.select{|s| s.status == :passed}.map{|s| s.step_match.duration}.inject(&:+)
|
45
|
+
feature_profiles[feature_id][:examples][example_id][:step_count] = f.step_invocations.count
|
46
|
+
feature_profiles[feature_id][:examples][example_id][:status] = f.status
|
47
|
+
else
|
48
|
+
feature_id = f.file_colon_line
|
49
|
+
feature_profiles[feature_id] = {name: f.name, total_duration: 0, step_count: 0}
|
50
|
+
feature_profiles[feature_id][:total_duration] = f.steps.select{|s| s.status == :passed}.map{|s| s.step_match.duration}.inject(&:+)
|
51
|
+
feature_profiles[feature_id][:step_count] = f.steps.count
|
52
|
+
feature_profiles[feature_id][:status] = f.status
|
53
|
+
end
|
54
|
+
end
|
55
|
+
with_feature_calculations(feature_profiles)
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_feature_calculations(feature_profiles)
|
59
|
+
feature_profiles.each do |feature, meta|
|
60
|
+
if meta[:examples]
|
61
|
+
feature_profiles[feature][:example_count] = meta[:examples].keys.count
|
62
|
+
feature_profiles[feature][:total_duration] = meta[:examples].map{|e,m| m[:total_duration]}.inject(&:+)
|
63
|
+
feature_profiles[feature][:step_count] = meta[:examples].map{|e,m| m[:step_count]}.inject(&:+)
|
64
|
+
feature_profiles[feature][:examples] = feature_profiles[feature][:examples].sort_by{|k, v| v[:total_duration]}.reverse
|
65
|
+
feature_profiles[feature][:status] = if meta[:examples].all?{|e,m| m[:status] == :passed}
|
66
|
+
:passed
|
67
|
+
elsif meta[:examples].any?{|e,m| m[:status] == :failed}
|
68
|
+
:failed
|
69
|
+
elsif meta[:examples].any?{|e,m| m[:status] == :skipped}
|
70
|
+
:skipped
|
71
|
+
else
|
72
|
+
:unknown
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
feature_profiles.sort_by{|k, v| (STATUS_ORDER[v[:status]]||0)+(v[:total_duration] || 0)}.reverse
|
15
77
|
end
|
16
78
|
|
17
79
|
def step_profiles
|
18
|
-
#return @step_profiles if @step_profiles
|
19
80
|
step_profiles = {}
|
20
81
|
@runtime.steps.each do |s|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
82
|
+
unless ambiguous?(s)
|
83
|
+
step_name = s.status == :undefined ? s.name : s.step_match.step_definition.file_colon_line
|
84
|
+
# Initialize data structure
|
85
|
+
step_profiles[step_name] ||= { :total_count => 0}
|
86
|
+
STATUS.each {|status| step_profiles[step_name][status] ||= {:count => 0, :feature_location => {} }}
|
87
|
+
feature_location = s.file_colon_line
|
88
|
+
step_profiles[step_name][s.status][:count] += 1
|
89
|
+
step_profiles[step_name][:total_count] += 1
|
90
|
+
step_profiles[step_name][s.status][:feature_location][feature_location] ||= []
|
91
|
+
if s.status != :undefined
|
92
|
+
step_profiles[step_name][:regexp] = s.step_match.step_definition.regexp_source
|
93
|
+
if s.status == :passed
|
94
|
+
step_profiles[step_name][s.status][:feature_location][feature_location] << s.step_match.duration
|
95
|
+
end
|
33
96
|
end
|
34
97
|
end
|
35
98
|
end
|
36
|
-
|
99
|
+
with_step_calculations(step_profiles)
|
37
100
|
end
|
38
101
|
|
39
|
-
def
|
102
|
+
def ambiguous?(step)
|
103
|
+
step.status == :failed && step.step_match.step_definition.nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
def with_step_calculations(step_profiles)
|
40
107
|
step_profiles.each do |step, meta|
|
41
108
|
meta.merge!(fastest: nil, slowest: nil, average: nil, total_duration: nil, standard_deviation: nil, variation: nil)
|
42
109
|
next unless meta[:passed][:count] > 0
|
@@ -55,7 +122,7 @@ module CucumberCharacteristics
|
|
55
122
|
step_profiles[step][:variance] = sum/(timings.length ).to_f
|
56
123
|
step_profiles[step][:standard_deviation] = Math.sqrt( step_profiles[step][:variance])
|
57
124
|
end
|
58
|
-
step_profiles
|
125
|
+
step_profiles.sort_by{|k, v| v[:total_duration]||0}.reverse
|
59
126
|
end
|
60
127
|
|
61
128
|
def step_duration
|
@@ -83,6 +150,10 @@ module CucumberCharacteristics
|
|
83
150
|
status
|
84
151
|
end
|
85
152
|
|
153
|
+
def step_count(status)
|
154
|
+
step_count_by_status[status]
|
155
|
+
end
|
156
|
+
|
86
157
|
def scenario_count_by_status
|
87
158
|
status = {}
|
88
159
|
@runtime.scenarios.each do |s|
|
@@ -94,5 +165,4 @@ module CucumberCharacteristics
|
|
94
165
|
|
95
166
|
end
|
96
167
|
|
97
|
-
|
98
168
|
end
|
@@ -38,63 +38,140 @@
|
|
38
38
|
#{profile.scenarios.count} Scenarios,
|
39
39
|
#{profile.steps.count} Steps completed
|
40
40
|
%span.text-muted
|
41
|
-
(#{step_status_summary(profile)})
|
42
|
-
|
41
|
+
(#{step_status_summary(profile)})<br>
|
42
|
+
- if profile.ambiguous_count > 0
|
43
|
+
#{profile.ambiguous_count}/#{profile.step_count(:failed)} failures due to ambiguous matches<br>
|
43
44
|
Test duration #{format_ts(profile.duration)}s.
|
44
45
|
%span.text-muted
|
45
46
|
(#{format_ts(profile.step_duration)}s steps, #{format_ts(profile.nonstep_duration)}s non-steps)
|
46
47
|
%span.text-muted.pull-right.small
|
47
48
|
Finished on #{Time.now}
|
48
49
|
|
49
|
-
%
|
50
|
-
%
|
51
|
-
%
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
%
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
%
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
%
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
%
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
50
|
+
%ul.nav.nav-tabs
|
51
|
+
%li.active
|
52
|
+
%a{:href => '#steps', :'data-toggle' => 'tab'} Steps
|
53
|
+
%li
|
54
|
+
%a{:href => '#features', :'data-toggle' => 'tab'} Features
|
55
|
+
- if profile.has_unmatched_steps?
|
56
|
+
%li
|
57
|
+
%a{:href => '#unused_steps', :'data-toggle' => 'tab'} Unused Steps (#{profile.unmatched_steps.count})
|
58
|
+
%div.tab-content
|
59
|
+
%div.tab-pane.active#steps
|
60
|
+
%table.table.table-striped.table-bordered.table-condensed.tablesorter#profile_table
|
61
|
+
%thead
|
62
|
+
%tr
|
63
|
+
%th Step
|
64
|
+
%th Total time
|
65
|
+
%th Passed
|
66
|
+
%th Average
|
67
|
+
%th Fastest
|
68
|
+
%th Slowest
|
69
|
+
%th Variation
|
70
|
+
%th Variance
|
71
|
+
%th Std Deviation
|
72
|
+
%th Skipped
|
73
|
+
%th Error
|
74
|
+
%th Undef
|
75
|
+
%th Total count
|
76
|
+
%tbody
|
77
|
+
- profile.step_profiles.each do |step, meta|
|
78
|
+
%tr
|
79
|
+
%td
|
80
|
+
%abbr{:title => "#{meta[:regexp]}"} #{step}
|
81
|
+
%td #{format_ts(meta[:total_duration])}
|
82
|
+
%td
|
83
|
+
- if meta[:passed][:count] > 0
|
84
|
+
%abbr{:title => "#{ format_step_usage(meta[:passed]) }" } #{meta[:passed][:count]}
|
85
|
+
- else
|
86
|
+
0
|
87
|
+
%td #{format_ts(meta[:average])}
|
88
|
+
%td #{format_ts(meta[:fastest])}
|
89
|
+
%td #{format_ts(meta[:slowest])}
|
90
|
+
%td #{format_ts(meta[:variation])}
|
91
|
+
%td #{format_ts(meta[:variance])}
|
92
|
+
%td #{format_ts(meta[:standard_deviation])}
|
93
|
+
%td
|
94
|
+
- if meta[:skipped][:count] > 0
|
95
|
+
%abbr{:title => "#{ format_step_usage(meta[:skipped]) }" } #{meta[:skipped][:count]}
|
96
|
+
- else
|
97
|
+
0
|
98
|
+
%td
|
99
|
+
- if meta[:failed][:count] > 0
|
100
|
+
%abbr{:title => "#{ format_step_usage(meta[:failed]) }" } #{meta[:failed][:count]}
|
101
|
+
- else
|
102
|
+
0
|
103
|
+
%td
|
104
|
+
- if meta[:undefined][:count] > 0
|
105
|
+
%abbr{:title => "#{ format_step_usage(meta[:undefined]) }" } #{meta[:undefined][:count]}
|
106
|
+
- else
|
107
|
+
0
|
108
|
+
%td #{meta[:total_count]}
|
109
|
+
|
110
|
+
%div.tab-pane#features
|
111
|
+
%table.table.table-striped.table-bordered.table-condensed#feature_table
|
112
|
+
%thead
|
113
|
+
%tr
|
114
|
+
%th Feature
|
115
|
+
%th Total time
|
116
|
+
%th Step count
|
117
|
+
%th Status
|
118
|
+
%th
|
119
|
+
%tbody
|
120
|
+
- profile.feature_profiles.each do |feature, meta|
|
121
|
+
%tr{:class => "#{meta[:status] == :failed ? :danger : meta[:status] == :undefined ? :warning : nil }"}
|
122
|
+
%td #{feature}
|
123
|
+
%td #{format_ts(meta[:total_duration])}
|
124
|
+
%td #{meta[:step_count]}
|
125
|
+
%td #{meta[:status]}
|
126
|
+
%td
|
127
|
+
- if meta[:examples]
|
128
|
+
%button.btn.btn-primary{:'data-toggle'=>'modal', :'data-target'=>"##{Digest::MD5.hexdigest(feature)}"}
|
129
|
+
#{meta[:example_count]} Examples
|
130
|
+
|
131
|
+
- profile.feature_profiles.select{|f,m| m.key?(:examples) }.each do |feature, meta|
|
132
|
+
%div{ :class=> 'modal fade bs-example-modal-lg', :tabindex=>"-1", :role=>"dialog", :'aria-labelledby'=>"myLargeModalLabel", :'aria-hidden'=>"true", :id => "#{Digest::MD5.hexdigest(feature)}"}
|
133
|
+
%div.modal-dialog.modal-lg
|
134
|
+
%div.modal-content
|
135
|
+
%div.modal-header
|
136
|
+
%h4 #{feature}
|
137
|
+
%div.modal-body
|
138
|
+
%table.table.table-striped.table-bordered.table-condensed
|
139
|
+
%thead
|
140
|
+
%tr
|
141
|
+
%th Example
|
142
|
+
%th Total time
|
143
|
+
%th Step count
|
144
|
+
%th Status
|
145
|
+
%tbody
|
146
|
+
- meta[:examples].each do |example, meta|
|
147
|
+
%tr{:class => "#{meta[:status] == :failed ? :danger : meta[:status] == :undefined ? :warning : nil }"}
|
148
|
+
%td #{example}
|
149
|
+
%td #{format_ts(meta[:total_duration])}
|
150
|
+
%td #{meta[:step_count]}
|
151
|
+
%td #{meta[:status]}
|
152
|
+
%tfooter
|
153
|
+
%tr
|
154
|
+
%th
|
155
|
+
%th #{format_ts(meta[:total_duration])}
|
156
|
+
%th #{meta[:step_count]}
|
157
|
+
%th #{meta[:status]}
|
158
|
+
%div.modal-footer
|
159
|
+
%button.btn.btn-default{:type=>"button", :'data-dismiss'=>"modal"}
|
160
|
+
Close
|
161
|
+
|
162
|
+
- if profile.has_unmatched_steps?
|
163
|
+
%div.tab-pane#unused_steps
|
164
|
+
%table.table.table-striped.table-bordered.table-condensed.tablesorter#unmatched_steps_table
|
165
|
+
%thead
|
166
|
+
%tr
|
167
|
+
%th Step Location
|
168
|
+
%th Step
|
169
|
+
%tbody
|
170
|
+
- profile.unmatched_steps.each do |step_location, step|
|
171
|
+
%tr
|
172
|
+
%td #{step_location}
|
173
|
+
%td #{step}
|
174
|
+
|
98
175
|
%script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"}
|
99
176
|
%script{:src => "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"}
|
100
177
|
%script{:src => "https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js"}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cucumber_characteristics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-06-
|
12
|
+
date: 2014-06-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: haml
|
@@ -136,12 +136,16 @@ files:
|
|
136
136
|
- README.md
|
137
137
|
- Rakefile
|
138
138
|
- cucumber_characteristics.gemspec
|
139
|
+
- features/ambiguous.feature
|
140
|
+
- features/characteristics/cucumber_step_characteristics.html
|
141
|
+
- features/characteristics/cucumber_step_characteristics.json
|
139
142
|
- features/failure.feature
|
140
143
|
- features/pending.feature
|
141
144
|
- features/scenario.feature
|
142
145
|
- features/scenario_outline.feature
|
143
146
|
- features/scenario_outline_with_background.feature
|
144
147
|
- features/scenario_with_background.feature
|
148
|
+
- features/step_definitions/ambiguous_steps.rb
|
145
149
|
- features/step_definitions/fail_steps.rb
|
146
150
|
- features/step_definitions/wait_steps.rb
|
147
151
|
- lib/cucumber_characteristics.rb
|
@@ -179,11 +183,15 @@ signing_key:
|
|
179
183
|
specification_version: 3
|
180
184
|
summary: Gem to profile cucumber steps and features
|
181
185
|
test_files:
|
186
|
+
- features/ambiguous.feature
|
187
|
+
- features/characteristics/cucumber_step_characteristics.html
|
188
|
+
- features/characteristics/cucumber_step_characteristics.json
|
182
189
|
- features/failure.feature
|
183
190
|
- features/pending.feature
|
184
191
|
- features/scenario.feature
|
185
192
|
- features/scenario_outline.feature
|
186
193
|
- features/scenario_outline_with_background.feature
|
187
194
|
- features/scenario_with_background.feature
|
195
|
+
- features/step_definitions/ambiguous_steps.rb
|
188
196
|
- features/step_definitions/fail_steps.rb
|
189
197
|
- features/step_definitions/wait_steps.rb
|