report_builder 1.4 → 1.9

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.
@@ -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>