cucumber_characteristics 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|