report_builder 1.4 → 1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,30 +7,26 @@ require 'ostruct'
7
7
  require 'report_builder/core-ext/hash'
8
8
 
9
9
  module ReportBuilder
10
+
10
11
  ##
11
12
  # ReportBuilder Main class
12
13
  #
13
14
  class Builder
14
15
 
15
- attr_accessor :options
16
-
17
16
  ##
18
17
  # ReportBuilder Main method
19
18
  #
20
- def build_report(opts = nil)
21
- options = self.options || default_options.marshal_dump
22
- options.merge! opts if opts.is_a? Hash
19
+ def build_report
20
+ options = ReportBuilder.options
23
21
 
24
- fail 'Error:: Invalid report_types. Use: [:json, :html]' unless options[:report_types].is_a? Array
25
- options[:report_types].map!(&:to_s).map!(&:upcase)
26
-
27
- options[:input_path] ||= options[:json_path] || Dir.pwd
28
22
  groups = get_groups options[:input_path]
29
23
 
30
24
  json_report_path = options[:json_report_path] || options[:report_path]
31
- File.open(json_report_path + '.json', 'w') do |file|
32
- file.write JSON.pretty_generate(groups.size > 1 ? groups : groups.first['features'])
33
- end if options[:report_types].include? 'JSON'
25
+ if options[:report_types].include? 'JSON'
26
+ File.open(json_report_path + '.json', 'w') do |file|
27
+ file.write JSON.pretty_generate(groups.size > 1 ? groups : groups.first['features'])
28
+ end
29
+ end
34
30
 
35
31
  if options[:additional_css] and Pathname.new(options[:additional_css]).file?
36
32
  options[:additional_css] = File.read(options[:additional_css])
@@ -41,42 +37,29 @@ module ReportBuilder
41
37
  end
42
38
 
43
39
  html_report_path = options[:html_report_path] || options[:report_path]
44
- File.open(html_report_path + '.html', 'w') do |file|
45
- file.write get(groups.size > 1 ? 'group_report' : 'report').result(binding).gsub(' ', '').gsub("\n\n", '')
46
- end if options[:report_types].include? 'HTML'
40
+ if options[:report_types].include? 'HTML'
41
+ File.open(html_report_path + '.html', 'w') do |file|
42
+ file.write get(groups.size > 1 ? 'group_report' : 'report').result(binding)
43
+ end
44
+ end
47
45
 
48
46
  retry_report_path = options[:retry_report_path] || options[:report_path]
49
- File.open(retry_report_path + '.retry', 'w') do |file|
50
- groups.each do |group|
51
- group['features'].each do |feature|
52
- if feature['status'] == 'broken'
53
- feature['elements'].each {|scenario| file.puts "#{feature['uri']}:#{scenario['line']}" if scenario['status'] == 'failed'}
47
+ if options[:report_types].include? 'RETRY'
48
+ File.open(retry_report_path + '.retry', 'w') do |file|
49
+ groups.each do |group|
50
+ group['features'].each do |feature|
51
+ if feature['status'] == 'broken'
52
+ feature['elements'].each do |scenario|
53
+ file.puts "#{feature['uri']}:#{scenario['line']}" if scenario['status'] == 'failed'
54
+ end
55
+ end
54
56
  end
55
57
  end
56
58
  end
57
- end if options[:report_types].include? 'RETRY'
59
+ end
58
60
  [json_report_path, html_report_path, retry_report_path]
59
61
  end
60
62
 
61
- ##
62
- # ReportBuilder default configuration
63
- #
64
- def default_options
65
- OpenStruct.new(json_path: nil,
66
- input_path: nil,
67
- report_types: [:html],
68
- report_title: 'Test Results',
69
- include_images: true,
70
- additional_info: {},
71
- report_path: 'test_report',
72
- json_report_path: nil,
73
- html_report_path: nil,
74
- retry_report_path: nil,
75
- additional_css: nil,
76
- additional_js: nil
77
- )
78
- end
79
-
80
63
  private
81
64
 
82
65
  def get(template)
@@ -90,13 +73,17 @@ module ReportBuilder
90
73
  input_path.each do |group_name, group_path|
91
74
  files = get_files group_path
92
75
  puts "Error:: No file(s) found at #{group_path}" if files.empty?
93
- groups << {'name' => group_name, 'features' => get_features(files)} rescue next
76
+ groups << {'name' => group_name, 'features' => get_features(files)}
94
77
  end
95
- fail 'Error:: Invalid Input File(s). Please provide valid cucumber JSON output file(s)' if groups.empty?
96
78
  else
97
79
  files = get_files input_path
98
- fail "Error:: No file(s) found at #{input_path}" if files.empty?
99
- groups << {'features' => get_features(files)} rescue fail('Error:: Invalid Input File(s). Please provide valid cucumber JSON output file(s)')
80
+ raise "Error:: No file(s) found at #{input_path}" if files.empty?
81
+ groups << {'features' => get_features(files)}
82
+ end
83
+ groups.each do |this_group|
84
+ this_group['features'].each do |feature|
85
+ feature['elements'] = feature['elements'].sort_by { |v| v['line']}
86
+ end
100
87
  end
101
88
  groups
102
89
  end
@@ -126,63 +113,100 @@ module ReportBuilder
126
113
  end
127
114
 
128
115
  def get_features(files)
129
- files.each_with_object([]) {|file, features|
116
+ files.each_with_object([]) do |file, features|
130
117
  data = File.read(file)
131
118
  next if data.empty?
132
- features << JSON.parse(data) rescue next
133
- }.flatten.group_by {|feature|
134
- feature['uri']+feature['id']+feature['line'].to_s
135
- }.values.each_with_object([]) {|group, features|
119
+ begin
120
+ features << JSON.parse(data)
121
+ rescue StandardError
122
+ puts 'Warning:: Invalid Input File ' + file
123
+ puts 'JSON Error:: ' + $!.to_s
124
+ next
125
+ end
126
+ end.flatten.group_by do |feature|
127
+ feature['uri'] + feature['id'] + feature['line'].to_s
128
+ end.values.each_with_object([]) do |group, features|
136
129
  features << group.first.except('elements').merge('elements' => group.map {|feature| feature['elements']}.flatten)
137
- }.sort_by! {|feature| feature['name']}.each {|feature|
130
+ end.sort_by! do |feature|
131
+ feature['name']
132
+ end.each do |feature|
133
+ feature['name'] = ERB::Util.html_escape feature['name']
138
134
  if feature['elements'][0]['type'] == 'background'
139
135
  (0..feature['elements'].size-1).step(2) do |i|
140
136
  feature['elements'][i]['steps'] ||= []
141
- feature['elements'][i]['steps'].each {|step| step['name']+=(' ('+feature['elements'][i]['keyword']+')')}
137
+ feature['elements'][i]['steps'].each {|step| step['name'] += (' (' + feature['elements'][i]['keyword'] + ')')}
142
138
  if feature['elements'][i+1]
143
139
  feature['elements'][i+1]['steps'] = feature['elements'][i]['steps'] + feature['elements'][i+1]['steps']
144
140
  feature['elements'][i+1]['before'] = feature['elements'][i]['before'] if feature['elements'][i]['before']
145
141
  end
146
142
  end
147
- feature['elements'].reject! {|element| element['type'] == 'background'}
143
+ feature['elements'].reject! do |element|
144
+ element['type'] == 'background'
145
+ end
148
146
  end
149
- feature['elements'].each {|scenario|
147
+ feature['elements'].each do |scenario|
148
+ scenario['name'] = ERB::Util.html_escape scenario['name']
150
149
  scenario['before'] ||= []
151
- scenario['before'].each {|before|
150
+ scenario['before'].each do |before|
152
151
  before['result']['duration'] ||= 0
152
+ if before['embeddings']
153
+ before['embeddings'].map! do |embedding|
154
+ decode_embedding(embedding)
155
+ end
156
+ end
153
157
  before.merge! 'status' => before['result']['status'], 'duration' => before['result']['duration']
154
- }
158
+ end
155
159
  scenario['steps'] ||= []
156
- scenario['steps'].each {|step|
160
+ scenario['steps'].each do |step|
161
+ step['name'] = ERB::Util.html_escape step['name']
157
162
  step['result']['duration'] ||= 0
158
163
  duration = step['result']['duration']
159
164
  status = step['result']['status']
160
- step['after'].each {|after|
161
- after['result']['duration'] ||= 0
162
- duration += after['result']['duration']
163
- status = 'failed' if after['result']['status'] == 'failed'
164
- after['embeddings'].map! { |embedding|
165
+ if step['after']
166
+ step['after'].each do |after|
167
+ after['result']['duration'] ||= 0
168
+ duration += after['result']['duration']
169
+ status = 'failed' if after['result']['status'] == 'failed'
170
+ if after['embeddings']
171
+ after['embeddings'].map! do |embedding|
172
+ decode_embedding(embedding)
173
+ end
174
+ end
175
+ after.merge! 'status' => after['result']['status'], 'duration' => after['result']['duration']
176
+ end
177
+ end
178
+ if step['embeddings']
179
+ step['embeddings'].map! do |embedding|
165
180
  decode_embedding(embedding)
166
- } if after['embeddings']
167
- after.merge! 'status' => after['result']['status'], 'duration' => after['result']['duration']
168
- } if step['after']
169
- step['embeddings'].map! { |embedding|
170
- decode_embedding(embedding)
171
- } if step['embeddings']
181
+ end
182
+ end
172
183
  step.merge! 'status' => status, 'duration' => duration
173
- }
184
+ end
174
185
  scenario['after'] ||= []
175
- scenario['after'].each {|after|
186
+ scenario['after'].each do |after|
176
187
  after['result']['duration'] ||= 0
177
- after['embeddings'].map! { |embedding|
178
- decode_embedding(embedding)
179
- } if after['embeddings']
188
+ if after['embeddings']
189
+ after['embeddings'].map! do |embedding|
190
+ decode_embedding(embedding)
191
+ end
192
+ end
180
193
  after.merge! 'status' => after['result']['status'], 'duration' => after['result']['duration']
181
- }
194
+ end
182
195
  scenario.merge! 'status' => scenario_status(scenario), 'duration' => total_time(scenario['before']) + total_time(scenario['steps']) + total_time(scenario['after'])
183
- }
196
+ end
197
+ feature['elements'] = feature['elements'].group_by do |scenario|
198
+ scenario['id'] + ':' + scenario['line'].to_s
199
+ end.values.map do |scenario_group|
200
+ the_scenario = scenario_group.find do |scenario|
201
+ scenario['status'] == 'passed'
202
+ end || scenario_group.last
203
+ if scenario_group.size > 1
204
+ the_scenario['name'] += " (x#{scenario_group.size})"
205
+ end
206
+ the_scenario
207
+ end
184
208
  feature.merge! 'status' => feature_status(feature), 'duration' => total_time(feature['elements'])
185
- }
209
+ end
186
210
  end
187
211
 
188
212
  def feature_status(feature)
@@ -204,9 +228,9 @@ module ReportBuilder
204
228
  end
205
229
 
206
230
  def decode_image(data)
207
- base64 = /^([A-Za-z0-9+\/]{4})*([A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)$/
231
+ base64 = %r{^([A-Za-z0-9+\/]{4})*([A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)$}
208
232
  if data =~ base64
209
- data_base64 = Base64.decode64(data).gsub(/^data:image\/(png|gif|jpg|jpeg)\;base64,/, '')
233
+ data_base64 = Base64.urlsafe_decode64(data).gsub(%r{^data:image\/(png|gif|jpg|jpeg)\;base64,}, '') rescue data
210
234
  if data_base64 =~ base64
211
235
  data_base64
212
236
  else
@@ -218,13 +242,13 @@ module ReportBuilder
218
242
  end
219
243
 
220
244
  def decode_text(data)
221
- Base64.decode64 data
245
+ Base64.urlsafe_decode64 data rescue ''
222
246
  end
223
247
 
224
248
  def decode_embedding(embedding)
225
249
  if embedding['mime_type'] =~ /^image\/(png|gif|jpg|jpeg)/
226
250
  embedding['data'] = decode_image(embedding['data'])
227
- elsif embedding['mime_type'] =~ /^text\/plain/
251
+ elsif embedding['mime_type'] =~ /^text\/(plain|html)/
228
252
  embedding['data'] = decode_text(embedding['data'])
229
253
  end
230
254
  embedding
@@ -1,22 +1,22 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'report_builder'
3
- s.version = '1.4'
3
+ s.version = '1.9'
4
4
  s.bindir = 'bin'
5
5
  s.summary = 'ReportBuilder'
6
6
  s.description = 'Ruby gem to merge Cucumber JSON reports and build mobile-friendly HTML Test Report, JSON report and retry file.'
7
7
  s.post_install_message = 'Happy reporting!'
8
8
  s.authors = ['Rajat Thareja']
9
9
  s.email = 'rajat.thareja.1990@gmail.com'
10
- s.homepage = 'http://reportbuilder.rajatthareja.com'
10
+ s.homepage = 'https://reportbuilder.rajatthareja.com'
11
11
  s.license = 'MIT'
12
- s.required_ruby_version = '>= 1.9.1'
12
+ s.required_ruby_version = '>= 2.0.0'
13
13
  s.requirements << 'Cucumber >= 2.1.0 test results in JSON format'
14
14
 
15
15
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(sample/|css/|js/|pkg/|testing/|coverage/|CNAME|.gitignore|appveyor.yml|.travis.yml|_config.yml|Gemfile|Rakefile|rb.ico)}) }
16
16
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  s.test_files = s.files.grep(%r{^(testing)/})
18
18
 
19
- s.add_runtime_dependency 'json', '>= 1.8.1'
19
+ s.add_runtime_dependency 'json', '>= 2.3.0'
20
20
 
21
21
  s.add_development_dependency 'rake', '< 13.0'
22
22
  s.add_development_dependency 'rspec', '< 4.0'
@@ -3,19 +3,19 @@
3
3
  <i class="material-icons">bug_report</i><%= error %>
4
4
  </div>
5
5
 
6
- <div class="collapsible-body brown lighten-4">
7
- <ul class="collection failedScenarioList">
6
+ <div class="collapsible-body <%= options[:color] %> lighten-4">
7
+ <div class="collection failedScenarioList">
8
8
  <% features.each_with_index do |feature, f| %>
9
9
  <% feature['elements'].each_with_index do |scenario, s| %>
10
10
  <% if scenario['error'] %>
11
11
  <% if scenario['error'] == error %>
12
- <li class="collection-item failedScenario brown lighten-5 red-text">
13
- <i class="material-icons">highlight_off</i>&nbsp;<a class="modal-trigger red-text" href="#<%= "#{gid}f#{f}s#{s}" %>"><%= scenario['name'] %></a>
14
- </li>
12
+ <a class="collection-item failedScenario <%= options[:color] %> lighten-5 red-text modal-trigger waves-effect waves-light" href="#<%= "#{gid}f#{f}s#{s}" %>">
13
+ <i class="material-icons">highlight_off</i>&nbsp;<%= scenario['name'] %>
14
+ </a>
15
15
  <% end %>
16
16
  <% end %>
17
17
  <% end %>
18
18
  <% end %>
19
- </ul>
19
+ </div>
20
20
  </div>
21
21
  </li>
@@ -1,18 +1,16 @@
1
1
  <li class="feature <%= feature['status'] %>">
2
- <div class="collapsible-header brown lighten-1 waves-effect waves-light">
2
+ <div class="collapsible-header <%= options[:color] %> lighten-1 waves-effect waves-light">
3
3
  <i class="material-icons">featured_play_list</i>
4
4
  <b><%= feature['keyword'] %></b>&nbsp;<%= feature['name'] %>&nbsp;(<%= duration(feature['duration']) %>)
5
5
  </div>
6
6
 
7
- <div class="collapsible-body brown lighten-4">
8
- <ul class="collection scenarioList">
7
+ <div class="collapsible-body <%= options[:color] %> lighten-4">
8
+ <div class="collection scenarioList <%= options[:color] %>">
9
9
  <% feature['elements'].each_with_index do |scenario, s| %>
10
- <li class="collection-item scenario <%= scenario['status'] %>">
11
- <a class="waves-effect waves-light modal-trigger white-text" href="#<%= "#{fid}s#{s}" %>">
12
- <b><%= scenario['keyword'] %></b> <%= scenario['name'] %>&nbsp;(<%= duration(scenario['duration']) %>)
13
- </a>
14
- </li>
10
+ <a class="collection-item scenario modal-trigger waves-effect waves-light white-text <%= scenario['status'] %>" href="#<%= "#{fid}s#{s}" %>">
11
+ <b><%= scenario['keyword'] %></b> <%= scenario['name'] %>&nbsp;(<%= duration(scenario['duration']) %>)
12
+ </a>
15
13
  <% end %>
16
- </ul>
14
+ </div>
17
15
  </div>
18
16
  </li>
@@ -1,13 +1,39 @@
1
- <footer class="page-footer brown lighten-4">
2
- <div class="footer-copyright brown lighten-1">
1
+ <footer class="page-footer <%= options[:color] %> lighten-4">
2
+ <div class="footer-copyright <%= options[:color] %> lighten-1">
3
3
  <div class="container">
4
4
  Happy Reporting!
5
- <a class="white-text text-lighten-4 right" href="http://reportbuilder.rajatthareja.com">Generated by Report Builder</a>
5
+ <a class="white-text text-lighten-4 right" href="https://reportbuilder.rajatthareja.com">Generated by Report Builder</a>
6
6
  </div>
7
7
  </div>
8
8
  </footer>
9
9
 
10
- <script type="text/javascript" src="https://cdn.rawgit.com/rajatthareja/ReportBuilder/v1.4/js/report.builder.min.js"></script>
10
+ <script type="text/javascript" src="https://cdn.rawgit.com/rajatthareja/ReportBuilder/v1.9/js/report.builder.min.js"></script>
11
+
12
+ <% if options[:voice_commands] %>
13
+ <script src="//cdnjs.cloudflare.com/ajax/libs/annyang/2.6.0/annyang.min.js"></script>
14
+ <script>
15
+ var commands = {
16
+ 'show :tab': function(tab) {
17
+ if (tab === "overview") {
18
+ $("a[data-tooltip='Results Overview']").click();
19
+ } else if (tab === "features" || tab === "feature") {
20
+ $("a[data-tooltip='Scenarios by Features']").click();
21
+ } else if (tab === "summary") {
22
+ $("a[data-tooltip='Scenarios Summary Table']").click();
23
+ } else if (tab === "errors" || tab === "error" || tab === "failed" || tab === "bugs") {
24
+ $("a[data-tooltip='Failed Scenarios']").click();
25
+ }
26
+ },
27
+ 'search *search': function (search) {
28
+ $("a[data-tooltip='Scenarios Summary Table']").click();
29
+ $("input[type='search']").val(search);
30
+ $('table#summaryTable').DataTable().search(search).draw();
31
+ }
32
+ };
33
+ annyang.addCommands(commands);
34
+ annyang.start();
35
+ </script>
36
+ <% end %>
11
37
 
12
38
  <% if options[:additional_js] %>
13
39
  <% if options[:additional_js] =~ /^http(|s):\/\/.*\.js$/ %>
@@ -10,7 +10,7 @@
10
10
  <b><%= group['name'] %></b>
11
11
  </div>
12
12
 
13
- <div class="collapsible-body brown lighten-4">
13
+ <div class="collapsible-body <%= options[:color] %> lighten-4">
14
14
  <%= get('errors').result(binding) %>
15
15
  </div>
16
16
  </li>
@@ -5,12 +5,12 @@
5
5
  <% features = group['features'] %>
6
6
  <li class="group">
7
7
 
8
- <div class="collapsible-header brown lighten-1 waves-effect waves-light">
8
+ <div class="collapsible-header <%= options[:color] %> lighten-1 waves-effect waves-light">
9
9
  <i class="material-icons">group_work</i>
10
10
  <b><%= group['name'] %></b>&nbsp;(<%= duration(total_time(features)) %>)
11
11
  </div>
12
12
 
13
- <div class="collapsible-body brown lighten-4">
13
+ <div class="collapsible-body <%= options[:color] %> lighten-4">
14
14
  <%= get('features').result(binding) %>
15
15
  </div>
16
16
  </li>
@@ -12,21 +12,21 @@
12
12
  <%= get('scenarios').result(binding) %>
13
13
  <% end %>
14
14
 
15
- <main class="brown lighten-5">
15
+ <main class="<%= options[:color] %> lighten-5">
16
16
  <div class="row">
17
- <div id="overview" class="col s12 brown lighten-5">
17
+ <div id="overview" class="col s12 <%= options[:color] %> lighten-5">
18
18
  <%= get('group_overview').result(binding) %>
19
19
  </div>
20
20
 
21
- <div id="features" class="col s12 brown lighten-5 white-text">
21
+ <div id="features" class="col s12 <%= options[:color] %> lighten-5 white-text">
22
22
  <%= get('group_features').result(binding) %>
23
23
  </div>
24
24
 
25
- <div id="summary" class="col s12 brown lighten-5">
25
+ <div id="summary" class="col s12 <%= options[:color] %> lighten-5">
26
26
  <%= get('group_summary').result(binding) %>
27
27
  </div>
28
28
 
29
- <div id="errors" class="col s12 brown lighten-5">
29
+ <div id="errors" class="col s12 <%= options[:color] %> lighten-5">
30
30
  <%= get('group_errors').result(binding) %>
31
31
  </div>
32
32
  </div>