ugly_face 0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/ChangeLog +2 -0
- data/Gemfile +13 -0
- data/Guardfile +19 -0
- data/LICENSE +21 -0
- data/README.md +25 -0
- data/Rakefile +19 -0
- data/autotrader.png +0 -0
- data/cucumber.yml +4 -0
- data/features/feature_pages.feature +110 -0
- data/features/pretty_face_report.feature +81 -0
- data/features/step_definitions/report_steps.rb +36 -0
- data/features/support/_feature_header.erb +2 -0
- data/features/support/_suite_header.erb +2 -0
- data/features/support/env.rb +20 -0
- data/features/support/error_display.rb +16 -0
- data/features/support/hooks.rb +11 -0
- data/features/support/logo.png +0 -0
- data/fixtures/advanced.feature +57 -0
- data/fixtures/background.feature +10 -0
- data/fixtures/basic.feature +20 -0
- data/fixtures/failing_background.feature +7 -0
- data/fixtures/more/more.feature +8 -0
- data/fixtures/onemore/deep/more.feature +8 -0
- data/fixtures/onemore/more.feature +8 -0
- data/fixtures/step_definitions/advanced_steps.rb +34 -0
- data/fixtures/step_definitions/basic_steps.rb +25 -0
- data/fixtures/support/env.rb +3 -0
- data/lib/.DS_Store +0 -0
- data/lib/ugly_face/.DS_Store +0 -0
- data/lib/ugly_face/formatter/html.rb +289 -0
- data/lib/ugly_face/formatter/report.rb +285 -0
- data/lib/ugly_face/formatter/view_helper.rb +61 -0
- data/lib/ugly_face/templates/_main_header.erb +1 -0
- data/lib/ugly_face/templates/_page_header.erb +2 -0
- data/lib/ugly_face/templates/_step.erb +39 -0
- data/lib/ugly_face/templates/debug.png +0 -0
- data/lib/ugly_face/templates/failed.png +0 -0
- data/lib/ugly_face/templates/feature.erb +143 -0
- data/lib/ugly_face/templates/logo.png +0 -0
- data/lib/ugly_face/templates/main.erb +162 -0
- data/lib/ugly_face/templates/passed.png +0 -0
- data/lib/ugly_face/templates/pending.png +0 -0
- data/lib/ugly_face/templates/screenshot.png +0 -0
- data/lib/ugly_face/templates/skipped.png +0 -0
- data/lib/ugly_face/templates/style.css +346 -0
- data/lib/ugly_face/templates/table_failed.png +0 -0
- data/lib/ugly_face/templates/table_passed.png +0 -0
- data/lib/ugly_face/templates/table_pending.png +0 -0
- data/lib/ugly_face/templates/table_skipped.png +0 -0
- data/lib/ugly_face/templates/table_undefined.png +0 -0
- data/lib/ugly_face/templates/undefined.png +0 -0
- data/lib/ugly_face/version.rb +3 -0
- data/lib/ugly_face.rb +45 -0
- data/spec/lib/customization_spec.rb +23 -0
- data/spec/lib/html_formatter_spec.rb +140 -0
- data/spec/spec_helper.rb +5 -0
- data/ugly_face.gemspec +29 -0
- metadata +199 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'cucumber/formatter/io'
|
4
|
+
require 'cucumber/formatter/duration'
|
5
|
+
require 'cucumber/formatter/console'
|
6
|
+
require 'cucumber/ast/scenario'
|
7
|
+
require 'cucumber/ast/table'
|
8
|
+
require 'cucumber/ast/outline_table'
|
9
|
+
require File.join(File.dirname(__FILE__), 'view_helper')
|
10
|
+
require File.join(File.dirname(__FILE__), 'report')
|
11
|
+
|
12
|
+
# Starting with ActionPack 4.1.1, the module Mime doesn't get initialized before it's needed by UglyFace and so
|
13
|
+
# it would blow up with errors about uninitialized constants. We need to explicitly load it to prevent this problem.
|
14
|
+
require 'action_dispatch/http/mime_type'
|
15
|
+
|
16
|
+
module UglyFace
|
17
|
+
module Formatter
|
18
|
+
|
19
|
+
class Html
|
20
|
+
include Cucumber::Formatter::Io
|
21
|
+
include Cucumber::Formatter::Duration
|
22
|
+
include Cucumber::Formatter::Console
|
23
|
+
include ViewHelper
|
24
|
+
|
25
|
+
attr_reader :report, :logo
|
26
|
+
|
27
|
+
def initialize(step_mother, path_or_io, options)
|
28
|
+
@path = path_or_io
|
29
|
+
set_path_and_file(path_or_io)
|
30
|
+
@path_to_erb = File.join(File.dirname(__FILE__), '..', 'templates')
|
31
|
+
@step_mother = step_mother
|
32
|
+
@options = options
|
33
|
+
# The expand option is set to true by RubyMine and cannot be turned off using the IDE. This option causes
|
34
|
+
# a test run while using this gem to terminate.
|
35
|
+
@options[:expand] = false unless @options.nil?
|
36
|
+
@report = Report.new
|
37
|
+
@img_id = 0
|
38
|
+
@logo = 'logo.png'
|
39
|
+
@delayed_messages = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_path_and_file(path_or_io)
|
43
|
+
return if path_or_io.nil?
|
44
|
+
dir = File.dirname(path_or_io)
|
45
|
+
FileUtils.mkdir_p dir unless File.directory? dir
|
46
|
+
@io = ensure_io(path_or_io, 'html')
|
47
|
+
end
|
48
|
+
|
49
|
+
def embed(src, mime_type, label)
|
50
|
+
case(mime_type)
|
51
|
+
when /^image\/(png|gif|jpg|jpeg)/
|
52
|
+
embed_image(src, label)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def embed_image(src, label)
|
57
|
+
@report.current_scenario.image << src.split(separator).last
|
58
|
+
@report.current_scenario.image_label << label
|
59
|
+
@report.current_scenario.image_id << "img_#{@img_id}"
|
60
|
+
@img_id += 1
|
61
|
+
filename = "#{File.dirname(@path)}#{separator}images"
|
62
|
+
FileUtils.cp src, filename
|
63
|
+
end
|
64
|
+
|
65
|
+
def before_features(features)
|
66
|
+
make_output_directories
|
67
|
+
@tests_started = Time.now
|
68
|
+
end
|
69
|
+
|
70
|
+
def features_summary_file
|
71
|
+
parts = @io.path.split(separator)
|
72
|
+
parts[parts.length - 1]
|
73
|
+
end
|
74
|
+
|
75
|
+
def before_feature(feature)
|
76
|
+
@report.add_feature ReportFeature.new(feature, features_summary_file)
|
77
|
+
end
|
78
|
+
|
79
|
+
def after_feature(feature)
|
80
|
+
@report.current_feature.close(feature)
|
81
|
+
end
|
82
|
+
|
83
|
+
def before_background(background)
|
84
|
+
@report.begin_background
|
85
|
+
end
|
86
|
+
|
87
|
+
def after_background(background)
|
88
|
+
@report.end_background
|
89
|
+
@report.current_feature.background << ReportStep.new(background)
|
90
|
+
end
|
91
|
+
|
92
|
+
def before_feature_element(feature_element)
|
93
|
+
unless scenario_outline? feature_element
|
94
|
+
@report.add_scenario ReportScenario.new(feature_element)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def after_feature_element(feature_element)
|
99
|
+
unless scenario_outline?(feature_element)
|
100
|
+
process_scenario(feature_element)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def before_table_row(example_row)
|
105
|
+
@report.add_scenario ReportScenario.new(example_row) unless info_row?(example_row)
|
106
|
+
end
|
107
|
+
|
108
|
+
def after_table_row(example_row)
|
109
|
+
unless info_row?(example_row)
|
110
|
+
@report.current_scenario.populate(example_row)
|
111
|
+
build_scenario_outline_steps(example_row)
|
112
|
+
end
|
113
|
+
populate_cells(example_row) if example_row.instance_of? Cucumber::Ast::Table::Cells
|
114
|
+
end
|
115
|
+
|
116
|
+
def before_step(step)
|
117
|
+
@step_timer = Time.now
|
118
|
+
end
|
119
|
+
|
120
|
+
def after_step(step)
|
121
|
+
step = process_step(step) unless step_belongs_to_outline? step
|
122
|
+
if @cells
|
123
|
+
step.table = @cells
|
124
|
+
@cells = nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def after_features(features)
|
129
|
+
@features = features
|
130
|
+
@duration = format_duration(Time.now - @tests_started)
|
131
|
+
copy_images
|
132
|
+
copy_stylesheets
|
133
|
+
generate_report
|
134
|
+
end
|
135
|
+
|
136
|
+
def features
|
137
|
+
@report.features
|
138
|
+
end
|
139
|
+
|
140
|
+
def custom_suite_header?
|
141
|
+
return false unless customization_directory
|
142
|
+
|
143
|
+
Dir.foreach(customization_directory) do |file|
|
144
|
+
return true if file == '_suite_header.erb'
|
145
|
+
end
|
146
|
+
false
|
147
|
+
end
|
148
|
+
|
149
|
+
def custom_feature_header?
|
150
|
+
return false unless customization_directory
|
151
|
+
|
152
|
+
Dir.foreach(customization_directory) do |file|
|
153
|
+
return true if file == '_feature_header.erb'
|
154
|
+
end
|
155
|
+
false
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def generate_report
|
161
|
+
paths = [@path_to_erb, customization_directory.to_s]
|
162
|
+
renderer = ActionView::Base.new(paths)
|
163
|
+
filename = File.join(@path_to_erb, 'main')
|
164
|
+
@io.puts renderer.render(:file => filename, :locals => {:report => self, :logo => @logo})
|
165
|
+
features.each do |feature|
|
166
|
+
write_feature_file(feature)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def write_feature_file(feature)
|
171
|
+
paths = [@path_to_erb, customization_directory.to_s]
|
172
|
+
renderer = ActionView::Base.new(paths)
|
173
|
+
filename = File.join(@path_to_erb, 'feature')
|
174
|
+
output_file = "#{File.dirname(@path)}#{separator}#{feature.file}"
|
175
|
+
to_cut = output_file.split(separator).last
|
176
|
+
directory = output_file.sub("#{separator}#{to_cut}", '')
|
177
|
+
FileUtils.mkdir_p directory unless File.directory? directory
|
178
|
+
file = File.new(output_file, Cucumber.file_mode('w'))
|
179
|
+
file.puts renderer.render(:file => filename, :locals => {:feature => feature, :logo => @logo, :customize => custom_feature_header?})
|
180
|
+
file.flush
|
181
|
+
file.close
|
182
|
+
end
|
183
|
+
|
184
|
+
def make_output_directories
|
185
|
+
make_directory 'images'
|
186
|
+
make_directory 'stylesheets'
|
187
|
+
end
|
188
|
+
|
189
|
+
def make_directory(dir)
|
190
|
+
path = "#{File.dirname(@path)}#{separator}#{dir}"
|
191
|
+
FileUtils.mkdir_p path unless File.directory? path
|
192
|
+
end
|
193
|
+
|
194
|
+
def copy_directory(dir, file_names, file_extension)
|
195
|
+
path = "#{File.dirname(@path)}#{separator}#{dir}"
|
196
|
+
file_names.each do |file|
|
197
|
+
copy_file File.join(File.dirname(__FILE__), '..', 'templates', "#{file}.#{file_extension}"), path
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def copy_file(source, destination)
|
202
|
+
FileUtils.cp source, destination
|
203
|
+
end
|
204
|
+
|
205
|
+
def copy_images
|
206
|
+
copy_directory 'images', %w(debug screenshot failed passed pending undefined skipped table_failed table_passed table_pending table_undefined table_skipped), "png"
|
207
|
+
logo = logo_file
|
208
|
+
copy_file logo, "#{File.join(File.dirname(@path), 'images')}" if logo
|
209
|
+
copy_directory 'images', ['custom_logo'], 'png' unless logo
|
210
|
+
end
|
211
|
+
|
212
|
+
def copy_stylesheets
|
213
|
+
copy_directory 'stylesheets', ['style'], 'css'
|
214
|
+
end
|
215
|
+
|
216
|
+
def logo_file
|
217
|
+
dir = customization_directory
|
218
|
+
Dir.foreach(dir) do |file|
|
219
|
+
if file =~ /^logo\.(png|gif|jpg|jpeg)$/
|
220
|
+
@logo = file
|
221
|
+
return File.join(dir, file)
|
222
|
+
end
|
223
|
+
end if dir
|
224
|
+
end
|
225
|
+
|
226
|
+
def customization_directory
|
227
|
+
dir = File.join(File.expand_path('features'), 'support', 'ugly_face')
|
228
|
+
return dir if File.exists? dir
|
229
|
+
end
|
230
|
+
|
231
|
+
def process_scenario(scenario)
|
232
|
+
@report.current_scenario.populate(scenario)
|
233
|
+
end
|
234
|
+
|
235
|
+
def process_step(step, status=nil)
|
236
|
+
duration = Time.now - @step_timer
|
237
|
+
report_step = ReportStep.new(step)
|
238
|
+
report_step.duration = duration
|
239
|
+
report_step.status = status unless status.nil?
|
240
|
+
if step.background?
|
241
|
+
@report.current_feature.background << report_step if @report.processing_background_steps?
|
242
|
+
else
|
243
|
+
@report.add_step report_step
|
244
|
+
end
|
245
|
+
report_step
|
246
|
+
end
|
247
|
+
|
248
|
+
def scenario_outline?(feature_element)
|
249
|
+
feature_element.is_a? Cucumber::Ast::ScenarioOutline
|
250
|
+
end
|
251
|
+
|
252
|
+
def info_row?(example_row)
|
253
|
+
return example_row.scenario_outline.nil? if example_row.respond_to? :scenario_outline
|
254
|
+
return true if example_row.instance_of? Cucumber::Ast::Table::Cells
|
255
|
+
false
|
256
|
+
end
|
257
|
+
|
258
|
+
def step_belongs_to_outline?(step)
|
259
|
+
scenario = step.instance_variable_get "@feature_element"
|
260
|
+
not scenario.nil?
|
261
|
+
end
|
262
|
+
|
263
|
+
def build_scenario_outline_steps(example_row)
|
264
|
+
si = example_row.instance_variable_get :@step_invocations
|
265
|
+
si.each do |row|
|
266
|
+
process_step(row, row.status)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def step_error(exception)
|
271
|
+
return nil if exception.nil?
|
272
|
+
exception.backtrace[-1] =~ /^#{step.file_colon_line}/ ? exception : nil
|
273
|
+
end
|
274
|
+
|
275
|
+
def populate_cells(example_row)
|
276
|
+
@cells ||= []
|
277
|
+
values = []
|
278
|
+
example_row.to_a.each do |cell|
|
279
|
+
values << cell.value
|
280
|
+
end
|
281
|
+
@cells << values
|
282
|
+
end
|
283
|
+
|
284
|
+
def separator
|
285
|
+
File::ALT_SEPARATOR || File::SEPARATOR
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
module UglyFace
|
2
|
+
module Formatter
|
3
|
+
|
4
|
+
module Formatting
|
5
|
+
def summary_percent(number, total)
|
6
|
+
percent = (number.to_f / total) * 100
|
7
|
+
"#{number} <span class=\"percentage\">(#{'%.1f' % percent}%)</span>"
|
8
|
+
end
|
9
|
+
|
10
|
+
def formatted_duration(duration)
|
11
|
+
m, s = duration.divmod(60)
|
12
|
+
"#{m}m#{'%.3f' % s}s"
|
13
|
+
rescue
|
14
|
+
"N m Ns"
|
15
|
+
end
|
16
|
+
|
17
|
+
def image_tag_for(status, source=nil)
|
18
|
+
dir = "#{directory_prefix_for(source)}images"
|
19
|
+
"<img src=\"#{dir}/#{status}.png\" alt=\"#{status}\" title=\"#{status}\">"
|
20
|
+
end
|
21
|
+
|
22
|
+
def table_image_for(status, source=nil)
|
23
|
+
dir = "#{directory_prefix_for(source)}images"
|
24
|
+
"<img src=\"#{dir}/table_#{status}.png\" alt=\"#{status}\" title=\"#{status}\">"
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def directory_prefix_for(source=nil)
|
29
|
+
dir = ''
|
30
|
+
back_dir = source.count(separator) if source
|
31
|
+
back_dir.times do
|
32
|
+
dir += "..#{separator}"
|
33
|
+
end
|
34
|
+
dir
|
35
|
+
end
|
36
|
+
|
37
|
+
def separator
|
38
|
+
File::ALT_SEPARATOR || File::SEPARATOR
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
class Report
|
44
|
+
attr_reader :features
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@features = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def current_feature
|
51
|
+
@features.last
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_scenario
|
55
|
+
current_feature.scenarios.last
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_feature(feature)
|
59
|
+
@features << feature
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_scenario(scenario)
|
63
|
+
current_feature.scenarios << scenario
|
64
|
+
end
|
65
|
+
|
66
|
+
def begin_background
|
67
|
+
@processing_background = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def end_background
|
71
|
+
@processing_background = false
|
72
|
+
end
|
73
|
+
|
74
|
+
def processing_background_steps?
|
75
|
+
@processing_background
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_step(step)
|
79
|
+
current_scenario.steps << step
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class ReportFeature
|
84
|
+
include Formatting
|
85
|
+
attr_accessor :scenarios, :background, :description
|
86
|
+
attr_reader :title, :file, :start_time, :duration, :parent_filename
|
87
|
+
|
88
|
+
def initialize(feature, parent_filename)
|
89
|
+
@scenarios = []
|
90
|
+
@background = []
|
91
|
+
@start_time = Time.now
|
92
|
+
@description = feature.description
|
93
|
+
@parent_filename = parent_filename
|
94
|
+
end
|
95
|
+
|
96
|
+
def close(feature)
|
97
|
+
@title = feature.title
|
98
|
+
@duration = Time.now - start_time
|
99
|
+
a_file = feature.file.sub(/\.feature/, '.html')
|
100
|
+
to_cut = a_file.split(separator).first
|
101
|
+
@file = a_file.sub("#{to_cut}#{separator}", '')
|
102
|
+
end
|
103
|
+
|
104
|
+
def steps
|
105
|
+
steps = []
|
106
|
+
scenarios.each { |scenario| steps += scenario.steps }
|
107
|
+
steps
|
108
|
+
end
|
109
|
+
|
110
|
+
def background_title
|
111
|
+
title = @background.find { |step| step.keyword.nil? }
|
112
|
+
end
|
113
|
+
|
114
|
+
def background_steps
|
115
|
+
@background.find_all { |step| step.keyword }
|
116
|
+
end
|
117
|
+
|
118
|
+
def scenarios_for(status)
|
119
|
+
scenarios.find_all { |scenario| scenario.status == status }
|
120
|
+
end
|
121
|
+
|
122
|
+
def scenario_summary_for(status)
|
123
|
+
scenarios_with_status = scenarios_for(status)
|
124
|
+
summary_percent(scenarios_with_status.length, scenarios.length)
|
125
|
+
end
|
126
|
+
|
127
|
+
def step_summary_for(status)
|
128
|
+
steps_with_status = steps.find_all { |step| step.status == status }
|
129
|
+
summary_percent(steps_with_status.length, steps.length)
|
130
|
+
end
|
131
|
+
|
132
|
+
def scenario_average_duration
|
133
|
+
durations = scenarios.collect { |scenario| scenario.duration }
|
134
|
+
formatted_duration(durations.reduce(:+).to_f / durations.size)
|
135
|
+
end
|
136
|
+
|
137
|
+
def step_average_duration
|
138
|
+
steps = scenarios.collect { |scenario| scenario.steps }
|
139
|
+
durations = steps.flatten.collect { |step| step.duration }
|
140
|
+
formatted_duration(durations.reduce(:+).to_f / durations.size)
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_binding
|
144
|
+
binding
|
145
|
+
end
|
146
|
+
|
147
|
+
def description?
|
148
|
+
!description.nil? && !description.empty?
|
149
|
+
end
|
150
|
+
|
151
|
+
def has_background?
|
152
|
+
background.length > 0
|
153
|
+
end
|
154
|
+
|
155
|
+
def file
|
156
|
+
@file.split("features#{separator}").last
|
157
|
+
end
|
158
|
+
|
159
|
+
def parent_filename
|
160
|
+
@parent_filename.split(separator).last
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class ReportScenario
|
165
|
+
attr_accessor :name, :file_colon_line, :status, :steps, :duration, :image, :image_label, :image_id
|
166
|
+
|
167
|
+
def initialize(scenario)
|
168
|
+
@steps = []
|
169
|
+
@image = []
|
170
|
+
@image_label = []
|
171
|
+
@image_id = []
|
172
|
+
@start = Time.now
|
173
|
+
end
|
174
|
+
|
175
|
+
def populate(scenario)
|
176
|
+
@duration = Time.now - @start
|
177
|
+
if scenario.instance_of? Cucumber::Ast::Scenario
|
178
|
+
@name = scenario.name
|
179
|
+
@file_colon_line = scenario.file_colon_line
|
180
|
+
elsif scenario.instance_of? Cucumber::Ast::OutlineTable::ExampleRow
|
181
|
+
@name = scenario.scenario_outline.name
|
182
|
+
@file_colon_line = scenario.backtrace_line
|
183
|
+
end
|
184
|
+
@status = scenario.status
|
185
|
+
end
|
186
|
+
|
187
|
+
def has_image?
|
188
|
+
not image.nil?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class ReportStep
|
193
|
+
attr_accessor :name, :keyword, :file_colon_line, :status, :duration, :table, :multiline_arg, :error
|
194
|
+
|
195
|
+
def initialize(step)
|
196
|
+
@name = step.name
|
197
|
+
@file_colon_line = step.file_colon_line
|
198
|
+
unless step.instance_of? Cucumber::Ast::Background
|
199
|
+
if step.respond_to? :actual_keyword
|
200
|
+
@keyword = step.actual_keyword
|
201
|
+
else
|
202
|
+
@keyword = step.keyword
|
203
|
+
end
|
204
|
+
@status = step.status
|
205
|
+
@multiline_arg = step.multiline_arg
|
206
|
+
@error = step.exception
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def failed_with_error?
|
211
|
+
status == :failed && !error.nil?
|
212
|
+
end
|
213
|
+
|
214
|
+
def has_table?
|
215
|
+
not table.nil?
|
216
|
+
end
|
217
|
+
|
218
|
+
def has_multiline_arg?
|
219
|
+
!multiline_arg.nil? && !has_table?
|
220
|
+
end
|
221
|
+
|
222
|
+
def file_with_error(file_colon_line)
|
223
|
+
@snippet_extractor ||= SnippetExtractor.new
|
224
|
+
file, line = @snippet_extractor.file_name_and_line(file_colon_line)
|
225
|
+
file
|
226
|
+
end
|
227
|
+
|
228
|
+
#from cucumber ===================
|
229
|
+
def extra_failure_content(file_colon_line)
|
230
|
+
@snippet_extractor ||= SnippetExtractor.new
|
231
|
+
@snippet_extractor.snippet(file_colon_line)
|
232
|
+
end
|
233
|
+
|
234
|
+
class SnippetExtractor
|
235
|
+
require 'syntax/convertors/html';
|
236
|
+
@@converter = Syntax::Convertors::HTML.for_syntax "ruby"
|
237
|
+
|
238
|
+
def file_name_and_line(error_line)
|
239
|
+
if error_line =~ /(.*):(\d+)/
|
240
|
+
[$1, $2.to_i]
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def snippet(error)
|
245
|
+
raw_code, line, file = snippet_for(error[0])
|
246
|
+
highlighted = @@converter.convert(raw_code, false)
|
247
|
+
|
248
|
+
"<pre class=\"ruby\"><strong>#{file + "\n"}</strong><code>#{post_process(highlighted, line)}</code></pre>"
|
249
|
+
end
|
250
|
+
|
251
|
+
def snippet_for(error_line)
|
252
|
+
file, line = file_name_and_line(error_line)
|
253
|
+
if file
|
254
|
+
[lines_around(file, line), line, file]
|
255
|
+
else
|
256
|
+
["# Couldn't get snippet for #{error_line}", 1, 'File Unknown']
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def lines_around(file, line)
|
261
|
+
if File.file?(file)
|
262
|
+
# lines = File.open(file).read.split("\n")
|
263
|
+
lines = File.readlines(file)
|
264
|
+
min = [0, line-3].max
|
265
|
+
max = [line+1, lines.length-1].min
|
266
|
+
# lines[min..max].join("\n")
|
267
|
+
lines[min..max].join
|
268
|
+
else
|
269
|
+
"# Couldn't get snippet for #{file}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def post_process(highlighted, offending_line)
|
274
|
+
new_lines = []
|
275
|
+
highlighted.split("\n").each_with_index do |line, i|
|
276
|
+
new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
|
277
|
+
new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
|
278
|
+
new_lines << new_line
|
279
|
+
end
|
280
|
+
new_lines.join("\n")
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'cucumber/ast/scenario_outline'
|
2
|
+
|
3
|
+
module UglyFace
|
4
|
+
module Formatter
|
5
|
+
module ViewHelper
|
6
|
+
|
7
|
+
def start_time
|
8
|
+
@tests_started.strftime("%a %B %-d, %Y at %H:%M:%S")
|
9
|
+
end
|
10
|
+
|
11
|
+
def step_count
|
12
|
+
@step_mother.steps.length
|
13
|
+
end
|
14
|
+
|
15
|
+
def scenario_count
|
16
|
+
@step_mother.scenarios.length
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_duration
|
20
|
+
@duration
|
21
|
+
end
|
22
|
+
|
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
|
28
|
+
end
|
29
|
+
|
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
|
34
|
+
end
|
35
|
+
|
36
|
+
def scenarios_summary_for(status)
|
37
|
+
summary_percent(@step_mother.scenarios(status).length, scenario_count)
|
38
|
+
end
|
39
|
+
|
40
|
+
def steps_summary_for(status)
|
41
|
+
summary_percent(@step_mother.steps(status).length, step_count)
|
42
|
+
end
|
43
|
+
|
44
|
+
def failed_scenario?(scenario)
|
45
|
+
scenario.status == :failed
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_average_from_float_array(arr)
|
52
|
+
arr.reduce(:+).to_f / arr.size
|
53
|
+
end
|
54
|
+
|
55
|
+
def summary_percent(number, total)
|
56
|
+
percent = (number.to_f / total) * 100
|
57
|
+
"#{number} <span class=\"percentage\">(#{'%.1f' % percent}%)</span>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<h2 class="results">Run Results</h2>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<%= "#{step.keyword} #{step.name}" %>
|
2
|
+
<% if step.has_table? %>
|
3
|
+
<br />
|
4
|
+
<table border="1" class="param_table">
|
5
|
+
<% step.table.each do |row| %>
|
6
|
+
<tr>
|
7
|
+
<% row.each_with_index do |column, index| %>
|
8
|
+
<% if index == 0 %>
|
9
|
+
<th><%= column %></th>
|
10
|
+
<% else %>
|
11
|
+
<td><%= column %></td>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
14
|
+
</tr>
|
15
|
+
<% end %>
|
16
|
+
</table>
|
17
|
+
<% end %>
|
18
|
+
<% if step.has_multiline_arg? %>
|
19
|
+
<br />
|
20
|
+
<table border="1" class="multiline_arg">
|
21
|
+
<tr>
|
22
|
+
<td><pre><%= step.multiline_arg %></pre></td>
|
23
|
+
</tr>
|
24
|
+
</table>
|
25
|
+
<% end %>
|
26
|
+
<% if step.failed_with_error? %>
|
27
|
+
<table>
|
28
|
+
<tr class="error">
|
29
|
+
<td class="message">
|
30
|
+
<strong><pre><%= "#{step.error.message} (#{step.error.class})" %></pre></strong>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
<tr>
|
34
|
+
<td>
|
35
|
+
<%= raw(step.extra_failure_content(step.error.backtrace)) %>
|
36
|
+
</td>
|
37
|
+
</tr>
|
38
|
+
</table>
|
39
|
+
<% end %>
|
Binary file
|
Binary file
|