pretty_face 0.1 → 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.
@@ -1,6 +1,33 @@
1
1
  module PrettyFace
2
2
  module Formatter
3
3
 
4
+ module Formatting
5
+ def summary_percent(number, total)
6
+ percent = (number.to_f / total) * 100
7
+ "#{number} (#{'%.1f' % percent}%)"
8
+ end
9
+
10
+ def formatted_duration(duration)
11
+ m, s = duration.divmod(60)
12
+ "#{m}m#{'%.3f' % s}s"
13
+ end
14
+
15
+ def image_tag_for(status, source=nil)
16
+ dir = "#{directory_prefix_for(source)}images"
17
+ "<img src=\"#{dir}/#{status}.png\" alt=\"#{status}\" title=\"#{status}\" width=\"30\">"
18
+ end
19
+
20
+ def directory_prefix_for(source=nil)
21
+ dir = ''
22
+ back_dir = source.count('/') if source
23
+ back_dir.times do
24
+ dir += '../'
25
+ end
26
+ dir
27
+ end
28
+ end
29
+
30
+
4
31
  class Report
5
32
  attr_reader :features
6
33
 
@@ -24,44 +51,148 @@ module PrettyFace
24
51
  current_feature.scenarios << scenario
25
52
  end
26
53
 
54
+ def begin_background
55
+ @processing_background = true
56
+ end
57
+
58
+ def end_background
59
+ @processing_background = false
60
+ end
61
+
62
+ def processing_background_steps?
63
+ @processing_background
64
+ end
65
+
27
66
  def add_step(step)
28
67
  current_scenario.steps << step
29
68
  end
30
69
  end
31
70
 
32
71
  class ReportFeature
33
- attr_accessor :title, :scenarios
72
+ include Formatting
73
+ attr_accessor :scenarios, :background, :description
74
+ attr_reader :title, :file, :start_time, :duration, :parent_filename
75
+
76
+ def initialize(feature, parent_filename)
77
+ @scenarios = []
78
+ @background = []
79
+ @start_time = Time.now
80
+ @description = feature.description
81
+ @parent_filename = parent_filename
82
+ end
83
+
84
+ def close(feature)
85
+ @title = feature.title
86
+ @duration = Time.now - start_time
87
+ a_file = feature.file.sub(/\.feature/, '.html')
88
+ to_cut = a_file.split('/').first
89
+ @file = a_file.sub("#{to_cut}/", '')
90
+ end
91
+
92
+ def steps
93
+ steps = []
94
+ scenarios.each { |scenario| steps += scenario.steps }
95
+ steps
96
+ end
97
+
98
+ def background_title
99
+ title = @background.find { |step| step.keyword.nil? }
100
+ end
34
101
 
35
- def initialize(feature)
36
- self.scenarios = []
102
+ def background_steps
103
+ @background.find_all { |step| step.keyword }
104
+ end
105
+
106
+ def scenarios_for(status)
107
+ scenarios.find_all { |scenario| scenario.status == status }
108
+ end
109
+
110
+ def scenario_summary_for(status)
111
+ scenarios_with_status = scenarios_for(status)
112
+ summary_percent(scenarios_with_status.length, scenarios.length)
113
+ end
114
+
115
+ def step_summary_for(status)
116
+ steps_with_status = steps.find_all { |step| step.status == status }
117
+ summary_percent(steps_with_status.length, steps.length)
118
+ end
119
+
120
+ def scenario_average_duration
121
+ durations = scenarios.collect { |scenario| scenario.duration }
122
+ formatted_duration(durations.reduce(:+).to_f / durations.size)
123
+ end
124
+
125
+ def step_average_duration
126
+ steps = scenarios.collect { |scenario| scenario.steps }
127
+ durations = steps.flatten.collect { |step| step.duration }
128
+ formatted_duration(durations.reduce(:+).to_f / durations.size)
129
+ end
130
+
131
+ def get_binding
132
+ binding
133
+ end
134
+
135
+ def description?
136
+ !description.nil? && !description.empty?
137
+ end
138
+
139
+ def has_background?
140
+ background.length > 0
37
141
  end
38
142
  end
39
143
 
40
144
  class ReportScenario
41
- attr_accessor :name, :file_colon_line, :status, :steps
145
+ attr_accessor :name, :file_colon_line, :status, :steps, :duration, :image, :image_label, :image_id
42
146
 
43
147
  def initialize(scenario)
44
- self.steps = []
148
+ @steps = []
149
+ @start = Time.now
45
150
  end
46
151
 
47
152
  def populate(scenario)
153
+ @duration = Time.now - @start
48
154
  if scenario.instance_of? Cucumber::Ast::Scenario
49
- self.name = scenario.name
50
- self.file_colon_line = scenario.file_colon_line
155
+ @name = scenario.name
156
+ @file_colon_line = scenario.file_colon_line
51
157
  elsif scenario.instance_of? Cucumber::Ast::OutlineTable::ExampleRow
52
- self.name = scenario.scenario_outline.name
53
- self.file_colon_line = scenario.backtrace_line
158
+ @name = scenario.scenario_outline.name
159
+ @file_colon_line = scenario.backtrace_line
54
160
  end
55
- self.status = scenario.status
161
+ @status = scenario.status
162
+ end
163
+
164
+ def has_image?
165
+ not image.nil?
56
166
  end
57
167
  end
58
168
 
59
169
  class ReportStep
60
- attr_accessor :name, :file_colon_line, :status
170
+ attr_accessor :name, :keyword, :file_colon_line, :status, :duration, :table, :multiline_arg, :error
171
+
61
172
  def initialize(step)
62
- self.name = step.name
63
- self.file_colon_line = step.file_colon_line
64
- self.status = step.status
173
+ @name = step.name
174
+ @file_colon_line = step.file_colon_line
175
+ unless step.instance_of? Cucumber::Ast::Background
176
+ if step.respond_to? :actual_keyword
177
+ @keyword = step.actual_keyword
178
+ else
179
+ @keyword = step.keyword
180
+ end
181
+ @status = step.status
182
+ @multiline_arg = step.multiline_arg
183
+ @error = step.exception
184
+ end
185
+ end
186
+
187
+ def failed_with_error?
188
+ status == :failed && !error.nil?
189
+ end
190
+
191
+ def has_table?
192
+ not table.nil?
193
+ end
194
+ def has_multiline_arg?
195
+ !multiline_arg.nil? && !has_table?
65
196
  end
66
197
  end
67
198
  end
@@ -20,12 +20,17 @@ module PrettyFace
20
20
  @duration
21
21
  end
22
22
 
23
- def step_average_duration
24
- format_duration get_average_from_float_array @step_times
23
+ def step_average_duration(features)
24
+ scenarios = features.collect { |feature| feature.scenarios }
25
+ steps = scenarios.flatten.collect { |scenario| scenario.steps }
26
+ durations = steps.flatten.collect { |step| step.duration }
27
+ format_duration get_average_from_float_array durations
25
28
  end
26
29
 
27
- def scenario_average_duration
28
- format_duration get_average_from_float_array @scenario_times
30
+ def scenario_average_duration(features)
31
+ scenarios = features.collect { |feature| feature.scenarios }
32
+ durations = scenarios.flatten.collect { |scenario| scenario.duration }
33
+ format_duration get_average_from_float_array durations
29
34
  end
30
35
 
31
36
  def scenarios_summary_for(status)
@@ -40,14 +45,10 @@ module PrettyFace
40
45
  scenario.status == :failed
41
46
  end
42
47
 
43
- def image_tag_for(scenario)
44
- status = scenario.status.to_s
45
- "<img src=\"images/#{status}.jpg\" alt=\"#{status}\" title=\"#{status}\" width=\"30\""
46
- end
47
48
 
48
49
  private
49
50
 
50
- def get_average_from_float_array (arr)
51
+ def get_average_from_float_array(arr)
51
52
  arr.reduce(:+).to_f / arr.size
52
53
  end
53
54
 
@@ -0,0 +1,41 @@
1
+ <%= "#{step.keyword} #{step.name}" %>
2
+ <% if step.has_table? %>
3
+ <br />
4
+ <table border="1">
5
+ <tr>
6
+ <% step.table.first.each do |column| %>
7
+ <th><%= column %></th>
8
+ <% end %>
9
+ </tr>
10
+ <% step.table.delete_at 0 %>
11
+ <% step.table.each do |row| %>
12
+ <tr>
13
+ <% row.each do |column| %>
14
+ <td><%= column %></td>
15
+ <% end %>
16
+ </tr>
17
+ <% end %>
18
+ </table>
19
+ <% end %>
20
+ <% if step.has_multiline_arg? %>
21
+ <br />
22
+ <table border="1">
23
+ <tr>
24
+ <td><pre><%= step.multiline_arg %></pre></td>
25
+ </tr>
26
+ </table>
27
+ <% end %>
28
+ <% if step.failed_with_error? %>
29
+ <table>
30
+ <tr class="error">
31
+ <td>
32
+ <strong><%= "#{step.error.message} (#{step.error.class})" %></strong>
33
+ </td>
34
+ </tr>
35
+ <tr class="error">
36
+ <td>
37
+ <pre><%= step.error.backtrace.join("\n") %></pre>
38
+ </td>
39
+ </tr>
40
+ </table>
41
+ <% end %>
@@ -0,0 +1,147 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns='http://www.w3.org/1999/xhtml'>
3
+ <head>
4
+ <link href='<%= feature.directory_prefix_for(feature.file) %>stylesheets/style.css' type='text/css' rel='stylesheet' />
5
+
6
+ <title>Feature Results</title>
7
+ </head>
8
+
9
+ <body>
10
+
11
+ <div style="float: left;">
12
+ <img src="<%= feature.directory_prefix_for(feature.file) %>images/face.jpg" width="200" />
13
+ </div>
14
+ <div style="float: left;">
15
+ <a href="<%= feature.parent_filename %>">Back</a>
16
+ <h2 class="results">Feature Results: <%= feature.title %></h2>
17
+ <br />
18
+ <span>Feature started: <%= feature.start_time.strftime("%a %B %-d, %Y at %H:%M:%S") %></span>
19
+ <br />
20
+ <span>Duration: <%= feature.formatted_duration(feature.duration) %></span>
21
+ <% if feature.description? %>
22
+ <pre class="description"><%= feature.description %></pre>
23
+ <% end %>
24
+ </div>
25
+
26
+ <div class="summary_body">
27
+ <h2 class="summary">Summary</h2>
28
+
29
+ <table cellspacing="0" cellpadding="7" class="summary">
30
+ <tr>
31
+ <th></th>
32
+ <th>Executed</th>
33
+ <th>Passed</th>
34
+ <th>Failed</th>
35
+ <th>Skipped</th>
36
+ <th>Undefined</th>
37
+ <th>Pending</th>
38
+ <th>Average<br/>Duration</th>
39
+ </tr>
40
+ <tr>
41
+ <td class="summary_row_header">Scenarios</td>
42
+ <td><%= feature.scenarios.length %></td>
43
+ <td><%= feature.scenario_summary_for :passed %></td>
44
+ <td><%= feature.scenario_summary_for :failed %></td>
45
+ <td><%= feature.scenario_summary_for :skipped %></td>
46
+ <td><%= feature.scenario_summary_for :undefined %></td>
47
+ <td><%= feature.scenario_summary_for :pending %></td>
48
+ <td><%= feature.scenario_average_duration %></td>
49
+ </tr>
50
+ <tr>
51
+ <td class="summary_row_header">Steps</td>
52
+ <td><%= feature.steps.length %></td>
53
+ <td><%= feature.step_summary_for :passed %></td>
54
+ <td><%= feature.step_summary_for :failed %></td>
55
+ <td><%= feature.step_summary_for :skipped %></td>
56
+ <td><%= feature.step_summary_for :undefined %></td>
57
+ <td><%= feature.step_summary_for :pending %></td>
58
+ <td><%= feature.step_average_duration %></td>
59
+ </tr>
60
+ </table>
61
+ </div>
62
+
63
+ <div style="width: 100%; float: left;">
64
+ <% if feature.has_background? %>
65
+ <br />
66
+ <h2 class="failures">Background: <%= feature.background_title.name %></h2>
67
+ <% end %>
68
+ <table cellspacing="0" cellpadding="7" border="0" class="failed">
69
+ <% feature.background_steps.each do |step| %>
70
+ <tr>
71
+ <td><%= raw(feature.image_tag_for(step.status, feature.file)) %></td>
72
+ <td>
73
+ <%= render "step", :step => step %>
74
+ </td>
75
+ </tr>
76
+ <% end %>
77
+ </table>
78
+ <br />
79
+ <h2 class="failures">Scenarios:</h2>
80
+ <br />
81
+ <% feature.scenarios.each do |scenario| %>
82
+ <table cellspacing="0" cellpadding="7" border="0" class="scenario <%= scenario.status %>">
83
+ <tr style="background-color: #E0E0E0;">
84
+ <th></th>
85
+ <th><%= scenario.name %></th>
86
+ </tr>
87
+ <% scenario.steps.each do |step| %>
88
+ <tr>
89
+ <td> <%= raw(feature.image_tag_for(step.status, feature.file)) %></td>
90
+ <td>
91
+ <%= "#{step.keyword} #{step.name}" %>
92
+ <% if step.has_table? %>
93
+ <br />
94
+ <table class="param_table">
95
+ <tr>
96
+ <% step.table.first.each do |column| %>
97
+ <th><%= column %></th>
98
+ <% end %>
99
+ </tr>
100
+ <% step.table.delete_at 0 %>
101
+ <% step.table.each do |row| %>
102
+ <tr>
103
+ <% row.each do |column| %>
104
+ <td><%= column %></td>
105
+ <% end %>
106
+ </tr>
107
+ <% end %>
108
+ </table>
109
+ <% end %>
110
+ <% if step.has_multiline_arg? %>
111
+ <br />
112
+ <div class="multiline_arg">
113
+ <pre><code><%= step.multiline_arg %></code></pre>
114
+ </div>
115
+ <% end %>
116
+ <% if step.failed_with_error? %>
117
+ <table>
118
+ <tr class="error message">
119
+ <td>
120
+ <%= "#{step.error.message} (#{step.error.class})" %>
121
+ </td>
122
+ </tr>
123
+ <tr class="error detail">
124
+ <td>
125
+ <pre><%= step.error.backtrace.join("\n") %></pre>
126
+ </td>
127
+ </tr>
128
+ </table>
129
+ <% end %>
130
+ </td>
131
+ </tr>
132
+ <% end %>
133
+ </table>
134
+ <% if scenario.has_image? %>
135
+ <a href='' onclick="img=document.getElementById('<%= scenario.image_id %>'); img.style.display = (img.style.display == 'none' ? 'block' : 'none');return false"><%= "#{scenario.image_label}" %></a><br />
136
+ <img id='<%= scenario.image_id %>' style='display: none' src='<%= feature.directory_prefix_for(feature.file) %>images/<%= scenario.image %>'/>
137
+ <% end %>
138
+ <br /><br />
139
+
140
+ <% end %>
141
+ </div>
142
+ <div class="footer">
143
+ Generated by PrettyFace
144
+ </div>
145
+
146
+ </body>
147
+ </html>
@@ -1,88 +1,8 @@
1
1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
2
  <html xmlns='http://www.w3.org/1999/xhtml'>
3
3
  <head>
4
- <style type='text/css'>
5
- /* TODO: lets move this to an external .css file */
6
- body {
7
- font-family: GeosansLightRegular,Arial,Helvetica,sans-serif;
8
- font-size: 1em;
9
- }
10
- a, a:visited {
11
- color: #4B7CAB;
12
- }
13
- div {
14
- padding: 5px;
15
- }
4
+ <link href='stylesheets/style.css' type='text/css' rel='stylesheet' />
16
5
 
17
- h2 {
18
- margin: 0;
19
- }
20
-
21
- .summary_body {
22
- width: 99%;
23
- margin-top: 15px;
24
- float: left;
25
- -moz-border-radius: 15px;
26
- border-radius: 15px;
27
- background-color: #E0E0E0;
28
- }
29
- .summary_row_header {
30
- text-align: left;
31
- font-size: 1em;
32
- font-weight: bold;
33
- }
34
- .summary td {
35
- font-size: .8em;
36
- text-align: center;
37
- width: 150px;
38
- }
39
- .summary td:first-child {
40
- font-size: inherit;
41
- text-align: inherit;
42
- width: inherit;
43
- }
44
-
45
- .failed td {
46
- font-size: .8em;
47
- }
48
-
49
- .footer {
50
- width: 100%;
51
- background-color: #4B7CAB;
52
- float: left;
53
- margin-left: -7px;
54
- margin-top: 25px;
55
- font-family: Verdana;
56
- font-size: 0.8em;
57
- color: white
58
- }
59
- h2.failures {
60
- color: #A00000;
61
- margin-top: 15px;
62
- margin-left: 5px;
63
- }
64
- h2.scenarios {
65
- color: #4B7CAB;
66
- margin-top: 15px;
67
- margin-left: 5px;
68
- }
69
- h2.results {
70
- color: #282828;
71
- margin-top: 12px;
72
- margin-bottom: 15px;
73
- }
74
- h2.summary {
75
- color: white;
76
- margin-left: 5px;
77
- margin-top: 5px;
78
- }
79
-
80
- .feature_title {
81
- background-color: #F0F0F0;
82
- font-size: 0.9em;
83
- }
84
- </style>
85
-
86
6
  <title>Test Results</title>
87
7
  </head>
88
8
 
@@ -92,11 +12,11 @@
92
12
  </div>
93
13
  <div style="float: left;">
94
14
  <h2 class="results">Test Results</h2>
95
- <span>Tests started: <%= start_time %></span>
15
+ <span>Tests started: <%= report.start_time %></span>
96
16
  <br />
97
- <span>Duration: <%= total_duration %></span>
17
+ <span>Duration: <%= report.total_duration %></span>
98
18
  </div>
99
-
19
+
100
20
  <div class="summary_body">
101
21
  <h2 class="summary">Summary</h2>
102
22
 
@@ -113,43 +33,43 @@
113
33
  </tr>
114
34
  <tr>
115
35
  <td class="summary_row_header">Scenarios</td>
116
- <td><%= scenario_count %></td>
117
- <td><%= scenarios_summary_for :passed %></td>
118
- <td><%= scenarios_summary_for :failed %></td>
119
- <td><%= scenarios_summary_for :skipped %></td>
120
- <td><%= scenarios_summary_for :undefined %></td>
121
- <td><%= scenarios_summary_for :pending %></td>
122
- <td><%= scenario_average_duration %></td>
36
+ <td><%= report.scenario_count %></td>
37
+ <td><%= report.scenarios_summary_for :passed %></td>
38
+ <td><%= report.scenarios_summary_for :failed %></td>
39
+ <td><%= report.scenarios_summary_for :skipped %></td>
40
+ <td><%= report.scenarios_summary_for :undefined %></td>
41
+ <td><%= report.scenarios_summary_for :pending %></td>
42
+ <td><%= report.scenario_average_duration(report.features) %></td>
123
43
  </tr>
124
44
  <tr>
125
45
  <td class="summary_row_header">Steps</td>
126
- <td><%= step_count %></td>
127
- <td><%= steps_summary_for :passed %></td>
128
- <td><%= steps_summary_for :failed %></td>
129
- <td><%= steps_summary_for :skipped %></td>
130
- <td><%= steps_summary_for :undefined %></td>
131
- <td><%= steps_summary_for :pending %></td>
132
- <td><%= step_average_duration %></td>
46
+ <td><%= report.step_count %></td>
47
+ <td><%= report.steps_summary_for :passed %></td>
48
+ <td><%= report.steps_summary_for :failed %></td>
49
+ <td><%= report.steps_summary_for :skipped %></td>
50
+ <td><%= report.steps_summary_for :undefined %></td>
51
+ <td><%= report.steps_summary_for :pending %></td>
52
+ <td><%= report.step_average_duration(report.features) %></td>
133
53
  </tr>
134
54
  </table>
135
55
  </div>
136
56
 
137
- <div style="width: 100%; float: left;">
57
+ <div class="failures">
138
58
  <br />
139
- <h2 class="failures">Tests With Failures:</h2>
59
+ <h2>Tests With Failures:</h2>
140
60
  <br />
141
- <table cellspacing="0" cellpadding="7" border="0" class="failed">
61
+ <table cellspacing="0" cellpadding="7" border="0">
142
62
  <tr style="background-color: #E0E0E0;">
143
63
  <th></th>
144
64
  <th style="text-align: left;">Feature</th>
145
65
  <th style="text-align: left;">Scenario</th>
146
66
  <th style="text-align: left;">File</th>
147
67
  </tr>
148
- <% features.each do |feature| %>
68
+ <% report.features.each do |feature| %>
149
69
  <% feature.scenarios.each do |scenario| %>
150
- <% if failed_scenario? scenario %>
70
+ <% if report.failed_scenario? scenario %>
151
71
  <tr>
152
- <td><img src="images/failed.jpg" width="30"/></td>
72
+ <td><img src="images/failed.png" width="30"/></td>
153
73
  <td><%= feature.title %> </td>
154
74
  <td><%= scenario.name %></td>
155
75
  <td style="color: gray;"><%= scenario.file_colon_line %></td>
@@ -158,37 +78,58 @@
158
78
  <% end %>
159
79
  <% end %>
160
80
  </table>
161
-
162
- <br />
163
- <h2 class="scenarios">Scenario Overview:</h2>
164
- <br />
165
81
 
166
- <table cellspacing="0" cellpadding="7" border="0" class="failed">
167
- <tr style="background-color: #E0E0E0;">
168
- <th style="text-align: left;">Result</th>
169
- <th style="text-align: left;">Name</th>
170
- <th style="text-align: left;">File</th>
171
- <th style="text-align: left;"># Steps</th>
172
- </tr>
173
- <% features.each do |feature| %>
174
- <tr>
175
- <td class="feature_title" colspan="4">Feature: <%= feature.title %></td>
176
- </tr>
177
- <% feature.scenarios.each do |scenario| %>
178
- <tr>
179
- <td><%= image_tag_for scenario %></td>
180
- <td><%= scenario.name %></td>
181
- <td style="color: gray;"><%= scenario.file_colon_line %></td>
182
- <td><%= scenario.steps.length %></td>
183
- </tr>
82
+ <section class="feature-overview">
83
+ <br />
84
+ <h2 class="scenarios">Feature Overview:</h2>
85
+ <br />
86
+
87
+ <table cellspacing="0" cellpadding="7" border="0">
88
+ <tr>
89
+ <th>Feature</th>
90
+ <th>File</th>
91
+ <th>Passed</th>
92
+ <th>Failed</th>
93
+ <th>Pending</th>
94
+ <th>Undefined</th>
95
+ <th>Skipped</th>
96
+ <th>Duration</th>
97
+ </tr>
98
+ <% report.features.each do |feature| %>
99
+ <tr>
100
+ <td class="feature_title">
101
+ <a href="<%= feature.file %>"><%= feature.title %></a>
102
+ </td>
103
+ <td>
104
+ <%= feature.file %>
105
+ </td>
106
+ <td>
107
+ <%= feature.scenarios_for(:passed).length %>
108
+ </td>
109
+ <td>
110
+ <%= feature.scenarios_for(:failed).length %>
111
+ </td>
112
+ <td>
113
+ <%= feature.scenarios_for(:pending).length %>
114
+ </td>
115
+ <td>
116
+ <%= feature.scenarios_for(:undefined).length %>
117
+ </td>
118
+ <td>
119
+ <%= feature.scenarios_for(:skipped).length %>
120
+ </td>
121
+ <td>
122
+ <%= feature.formatted_duration(feature.duration) %>
123
+ </td>
124
+ </tr>
184
125
  <% end %>
185
- <% end %>
186
- </table>
126
+ </table>
127
+ </section>
187
128
  </div>
188
-
129
+
189
130
  <div class="footer">
190
131
  Generated by PrettyFace
191
132
  </div>
192
-
133
+
193
134
  </body>
194
135
  </html>