pretty_face 0.1 → 0.2

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