report_builder 0.1.6 → 1.0
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 +4 -4
- data/.gitignore +1 -1
- data/LICENSE +2 -2
- data/README.md +7 -10
- data/bin/report_builder +3 -8
- data/lib/report_builder.rb +14 -8
- data/lib/report_builder/builder.rb +48 -524
- data/report_builder.gemspec +3 -4
- data/template/html_report.erb +338 -0
- metadata +5 -28
- data/vendor/assets/javascripts/highcharts-3d.js +0 -48
- data/vendor/assets/javascripts/highcharts.js +0 -337
- data/vendor/assets/javascripts/jquery-min.js +0 -4
- data/vendor/assets/javascripts/jquery-ui.min.js +0 -7
- data/vendor/assets/stylesheets/jquery-ui.min.css +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 330d350552316c577bff8a52a168924822c724e6
|
4
|
+
data.tar.gz: 5fb29cd31933f0233d164c468929da923a7abc98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad9ca0719bc60179d1bd9368c3a9f27c57bcc53efdced8e5ed06b21ed8607f9b83d0782b651b207e716e43861a3a057211b55b0f313c5b81b77cfa7e2426b70b
|
7
|
+
data.tar.gz: 9e2aa291fa26ab62aacd0940ca006c66bcf0dcd48a891a958f7b001cafdf9c3e15e490bd0d82285aeb449c5f013ba7be90e4815e0d0ab3a76d5a216a90a07026
|
data/.gitignore
CHANGED
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
1
|
+
MIT License
|
2
2
|
|
3
|
-
Copyright (c)
|
3
|
+
Copyright (c) 2017 Rajat Thareja
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
[](https://badge.fury.io/rb/report_builder)
|
4
4
|
[](https://gitter.im/rajatthareja/ReportBuilder?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
5
5
|
|
6
|
-
Ruby gem to merge Cucumber JSON reports and build
|
6
|
+
Ruby gem to merge Cucumber JSON reports and build mobile friendly HTML Test Report, JSON report and retry file.
|
7
|
+
|
8
|
+
[View sample report](http://reportbuilder.rajatthareja.com/)
|
7
9
|
|
8
10
|
## Installation
|
9
11
|
|
@@ -29,9 +31,8 @@ gem install report_builder
|
|
29
31
|
| json_path | [String] / [Array] | (current directory) | json files path / array of json files or path |
|
30
32
|
| report_path | [String] | 'test_report' | output file path with file name without extension |
|
31
33
|
| report_types | [Array] | [:html] | :json, :html, :retry (output file types) |
|
32
|
-
| report_tabs | [Array] | [:overview, :features] | :overview, :features, :scenarios, :errors (tabs to build) |
|
33
34
|
| report_title | [String] | 'Test Results' | report and html title |
|
34
|
-
|
|
35
|
+
| include_images | [Boolean] | true | true / false (If false, the size of HTML report is reduced by excluding embedded images) |
|
35
36
|
| additional_info | [Hash] | {} | additional info for report summary |
|
36
37
|
|
37
38
|
### Code Examples:
|
@@ -45,9 +46,8 @@ gem install report_builder
|
|
45
46
|
config.json_path = 'cucumber_sample/logs'
|
46
47
|
config.report_path = 'my_test_report'
|
47
48
|
config.report_types = [:json, :html]
|
48
|
-
config.report_tabs = [:overview, :features, :scenarios, :errors]
|
49
49
|
config.report_title = 'My Test Results'
|
50
|
-
config.
|
50
|
+
config.include_images = false
|
51
51
|
config.additional_info = {browser: 'Chrome', environment: 'Stage 5'}
|
52
52
|
end
|
53
53
|
|
@@ -58,9 +58,8 @@ gem install report_builder
|
|
58
58
|
json_path: 'cucumber_sample/logs',
|
59
59
|
report_path: 'my_test_report',
|
60
60
|
report_types: ['json', 'html'],
|
61
|
-
report_tabs: [ 'overview', 'features', 'scenarios', 'errors'],
|
62
61
|
report_title: 'My Test Results',
|
63
|
-
|
62
|
+
include_images: false,
|
64
63
|
additional_info: {'browser' => 'Chrome', 'environment' => 'Stage 5'}
|
65
64
|
}
|
66
65
|
|
@@ -78,8 +77,7 @@ gem install report_builder
|
|
78
77
|
| --html_out | [PATH]NAME | Same as the -o option but will only apply the html report format |
|
79
78
|
| --retry_out | [PATH]NAME | Same as the -o option but will only apply the retry report format |
|
80
79
|
| -f, --format | x,y,z | List of report format - html,json,retry |
|
81
|
-
| -
|
82
|
-
| -c, --compress | | Reduce report size if embedding images |
|
80
|
+
| --[no-]images | | Reduce report size by excluding embedded images |
|
83
81
|
| -T, --title | TITLE | Report title |
|
84
82
|
| -I, --info | a:x,b:y,c:z | List of additional info about test - key:value |
|
85
83
|
| -h, --help | | Show available command line switches |
|
@@ -92,7 +90,6 @@ gem install report_builder
|
|
92
90
|
report_builder
|
93
91
|
report_builder -s 'path/of/json/files/dir'
|
94
92
|
report_builder -s 'path/of/json/files/dir' -o my_report_file
|
95
|
-
report_builder -s 'path/of/json/files/dir' -o my_report_file -t overview,features,scenarios,errors
|
96
93
|
|
97
94
|
```
|
98
95
|
|
data/bin/report_builder
CHANGED
@@ -7,9 +7,8 @@ options = {
|
|
7
7
|
:json_path => nil,
|
8
8
|
:report_path => 'test_report',
|
9
9
|
:report_types => [:html],
|
10
|
-
:report_tabs => [:overview, :features],
|
11
10
|
:report_title => 'Test Results',
|
12
|
-
:
|
11
|
+
:include_images => true,
|
13
12
|
:additional_info => {}
|
14
13
|
}
|
15
14
|
|
@@ -41,12 +40,8 @@ opt_parser = OptionParser.new do |opts|
|
|
41
40
|
options[:report_types] = list
|
42
41
|
end
|
43
42
|
|
44
|
-
opts.on('-
|
45
|
-
options[:
|
46
|
-
end
|
47
|
-
|
48
|
-
opts.on('-c', '--compress', 'Reduce report size if embedding images') do |compress|
|
49
|
-
options[:compress_images] = compress
|
43
|
+
opts.on('--[no-]images', 'Reduce report size by excluding embedded images') do |include_images|
|
44
|
+
options[:include_images] = include_images
|
50
45
|
end
|
51
46
|
|
52
47
|
opts.on('-T', '--title TITLE', String, 'Report title') do |report_title|
|
data/lib/report_builder.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
|
-
|
1
|
+
require_relative 'report_builder/builder'
|
2
2
|
|
3
3
|
|
4
4
|
module ReportBuilder
|
5
5
|
|
6
|
+
##
|
7
|
+
# Configure ReportBuilder
|
6
8
|
#
|
7
|
-
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# ReportBuilder.configure do |config|
|
8
12
|
# config.json_path = 'cucumber_sample/logs'
|
9
13
|
# config.report_path = 'my_test_report'
|
10
14
|
# config.report_types = [:JSON, :HTML]
|
11
|
-
# config.report_tabs = [:Overview, :Features, :Scenarios, :Errors]
|
12
15
|
# config.report_title = 'My Test Results'
|
13
|
-
# config.
|
16
|
+
# config.include_images = true
|
14
17
|
# config.additional_info = {Browser: 'Chrome', Environment: 'Stage 5'}
|
15
|
-
#
|
18
|
+
# end
|
16
19
|
#
|
17
20
|
def self.configure
|
18
21
|
defaults = builder.default_options
|
@@ -20,16 +23,19 @@ module ReportBuilder
|
|
20
23
|
builder.options = defaults.marshal_dump
|
21
24
|
end
|
22
25
|
|
26
|
+
##
|
27
|
+
# Build Report
|
23
28
|
#
|
24
29
|
# @param [Hash] options override the default and configured options.
|
25
30
|
#
|
26
|
-
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# options = {
|
27
34
|
# json_path: 'cucumber_sample/logs',
|
28
35
|
# report_path: 'my_test_report',
|
29
36
|
# report_types: ['json', 'html'],
|
30
|
-
# report_tabs: [ 'overview', 'features', 'scenarios', 'errors'],
|
31
37
|
# report_title: 'My Test Results',
|
32
|
-
#
|
38
|
+
# include_images: true,
|
33
39
|
# additional_info: {'browser' => 'Chrome', 'environment' => 'Stage 5'}
|
34
40
|
# }
|
35
41
|
#
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'json'
|
2
|
-
require '
|
2
|
+
require 'erb'
|
3
|
+
require 'pathname'
|
3
4
|
require 'base64'
|
5
|
+
|
4
6
|
require 'report_builder/core-ext/hash'
|
5
7
|
|
6
8
|
module ReportBuilder
|
@@ -8,487 +10,86 @@ module ReportBuilder
|
|
8
10
|
|
9
11
|
attr_accessor :options
|
10
12
|
|
11
|
-
|
12
|
-
# colors corresponding to status
|
13
|
-
COLOR = {
|
14
|
-
passed: '#90ed7d',
|
15
|
-
working: '#90ed7d',
|
16
|
-
failed: '#f45b5b',
|
17
|
-
broken: '#f45b5b',
|
18
|
-
undefined: '#e4d354',
|
19
|
-
incomplete: '#e7a35c',
|
20
|
-
pending: '#f7a35c',
|
21
|
-
skipped: '#7cb5ec',
|
22
|
-
output: '#007fff'
|
23
|
-
}
|
24
|
-
|
25
13
|
def build_report(opts = nil)
|
26
14
|
options = self.options || default_options.marshal_dump
|
27
15
|
options.merge! opts if opts.is_a? Hash
|
28
16
|
|
29
|
-
raise 'Error
|
30
|
-
raise 'Error: Invalid report_tabs Use: [:overview, :features, :scenarios, :errors]' unless options[:report_tabs].is_a? Array
|
17
|
+
raise 'Error:: Invalid report_types. Use: [:json, :html]' unless options[:report_types].is_a? Array
|
31
18
|
|
32
19
|
options[:report_types].map!(&:to_s).map!(&:upcase)
|
33
|
-
options[:report_tabs].map!(&:to_s).map!(&:downcase)
|
34
|
-
|
35
|
-
input = files options[:json_path]
|
36
|
-
all_features = features input rescue (raise 'ReportBuilderParsingError')
|
37
|
-
|
38
|
-
report_name = options[:json_report_path] || options[:report_path]
|
39
|
-
File.open(report_name + '.json', 'w') do |file|
|
40
|
-
file.write JSON.pretty_generate all_features
|
41
|
-
end if options[:report_types].include? 'JSON'
|
42
|
-
|
43
|
-
all_scenarios = scenarios all_features
|
44
|
-
all_steps = steps all_scenarios
|
45
|
-
all_tags = tags all_scenarios
|
46
|
-
total_time = total_time all_features
|
47
|
-
feature_data = data all_features
|
48
|
-
scenario_data = data all_scenarios
|
49
|
-
step_data = data all_steps
|
50
|
-
|
51
|
-
report_name = options[:html_report_path] || options[:report_path]
|
52
|
-
File.open(report_name + '.html', 'w:UTF-8') do |file|
|
53
|
-
@builder = ::Builder::XmlMarkup.new(target: file, indent: 0)
|
54
|
-
@builder.declare!(:DOCTYPE, :html)
|
55
|
-
@builder << '<html>'
|
56
|
-
|
57
|
-
@builder.head do
|
58
|
-
@builder.meta(charset: 'UTF-8')
|
59
|
-
@builder.title options[:report_title]
|
60
20
|
|
61
|
-
|
62
|
-
|
63
|
-
COLOR.each do |color|
|
64
|
-
@builder << ".#{color[0].to_s}{background:#{color[1]};color:#434348;padding:2px}"
|
65
|
-
end
|
66
|
-
@builder << '.summary{margin-bottom:4px;border: 1px solid #c5c5c5;border-radius:4px;background:#f1f1f1;color:#434348;padding:4px;overflow:hidden;vertical-align:bottom;}'
|
67
|
-
@builder << '.summary .results{text-align:right;float:right;}'
|
68
|
-
@builder << '.summary .info{text-align:left;float:left;}'
|
69
|
-
@builder << '.data_table{border-collapse: collapse;} .data_table td{padding: 5px; border: 1px solid #ddd;}'
|
70
|
-
@builder << '.ui-tooltip{background: black; color: white; font-size: 12px; padding: 2px 4px; border-radius: 20px; box-shadow: 0 0 7px black;}'
|
71
|
-
end
|
72
|
-
|
73
|
-
@builder.script(type: 'text/javascript') do
|
74
|
-
%w(jquery-min jquery-ui.min highcharts highcharts-3d).each do |js|
|
75
|
-
@builder << File.read(File.dirname(__FILE__) + '/../../vendor/assets/javascripts/' + js + '.js')
|
76
|
-
end
|
77
|
-
@builder << '$(function(){$("#results").tabs();});'
|
78
|
-
@builder << "$(function(){$('#features').accordion({collapsible: true, heightStyle: 'content', active: false, icons: false});});"
|
79
|
-
(0..all_features.size).each do |n|
|
80
|
-
@builder << "$(function(){$('#feature#{n}').accordion({collapsible: true, heightStyle: 'content', active: false, icons: false});});"
|
81
|
-
end
|
82
|
-
@builder << "$(function(){$('#status').accordion({collapsible: true, heightStyle: 'content', active: false, icons: false});});"
|
83
|
-
scenario_data.each do |data|
|
84
|
-
@builder << "$(function(){$('##{data[:name]}').accordion({collapsible: true, heightStyle: 'content', active: false, icons: false});});"
|
85
|
-
end
|
86
|
-
@builder << '$(function() {$(document).tooltip({track: true});});'
|
87
|
-
end
|
88
|
-
end
|
21
|
+
files = get_files options[:json_path]
|
22
|
+
raise "Error:: No file(s) found at #{options[:json_path]}" if files.empty?
|
89
23
|
|
90
|
-
|
24
|
+
features = get_features(files) rescue raise('Error:: Invalid Input File(s). Please provide valid cucumber JSON output file(s)')
|
91
25
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
@builder.span(style: "font-size:#{info ? 36 : 18 }px;font-weight: bold;") do
|
97
|
-
@builder << options[:report_title]
|
98
|
-
end
|
99
|
-
options[:additional_info].each do |l|
|
100
|
-
@builder << '<br/>' + l[0].to_s.capitalize + ' : ' + l[1].to_s
|
101
|
-
end
|
102
|
-
end if options[:additional_info].is_a? Hash
|
103
|
-
@builder.span(class: 'results') do
|
104
|
-
s = all_features.size
|
105
|
-
@builder << s.to_s + " feature#{'s' if s > 1} ("
|
106
|
-
feature_data.each do |data|
|
107
|
-
@builder << ' ' + data[:count].to_s + ' ' + data[:name]
|
108
|
-
end
|
109
|
-
s = all_scenarios.size
|
110
|
-
@builder << ')<br/>' + s.to_s + " scenario#{'s' if s > 1} ("
|
111
|
-
scenario_data.each do |data|
|
112
|
-
@builder << ' ' + data[:count].to_s + ' ' + data[:name]
|
113
|
-
end
|
114
|
-
s = all_steps.size
|
115
|
-
@builder << ')<br/>' + s.to_s + " step#{'s' if s > 1} ("
|
116
|
-
step_data.each do |data|
|
117
|
-
@builder << ' ' + data[:count].to_s + ' ' + data[:name]
|
118
|
-
end
|
119
|
-
@builder << ')<br/>🕐 ' + duration(total_time).to_s
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
@builder.div(id: 'results') do
|
124
|
-
build_menu options[:report_tabs]
|
125
|
-
|
126
|
-
@builder.div(id: 'overviewTab') do
|
127
|
-
@builder << "<div id='featurePieChart' style=\"float:left;width:33%\"></div>"
|
128
|
-
@builder << "<div id='scenarioPieChart' style=\"display:inline-block;width:33%\"></div>"
|
129
|
-
@builder << "<div id='stepPieChart' style=\"float:right;width:33%\"></div>"
|
130
|
-
end if options[:report_tabs].include? 'overview'
|
131
|
-
|
132
|
-
@builder.div(id: 'featuresTab') do
|
133
|
-
build_tags_drop_down(all_tags)
|
134
|
-
@builder.div(id: 'features') do
|
135
|
-
all_features.each_with_index do |feature, n|
|
136
|
-
@builder.h3(style: "background:#{COLOR[feature['status'].to_sym]}") do
|
137
|
-
@builder.span(class: feature['status']) do
|
138
|
-
@builder << "<strong>#{feature['keyword']}</strong> #{feature['name']} (#{duration(feature['duration'])})"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
@builder.div do
|
142
|
-
@builder.div(id: "feature#{n}") do
|
143
|
-
feature['elements'].each {|scenario| build_scenario scenario}
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
@builder << "<div id='featureTabPieChart'></div>"
|
149
|
-
end if options[:report_tabs].include? 'features'
|
150
|
-
|
151
|
-
@builder.div(id: 'scenariosTab') do
|
152
|
-
build_tags_drop_down(all_tags)
|
153
|
-
@builder.div(id: 'status') do
|
154
|
-
all_scenarios.group_by {|scenario| scenario['status']}.each do |data|
|
155
|
-
@builder.h3(style: "background:#{COLOR[data[0].to_sym]}") do
|
156
|
-
@builder.span(class: data[0]) do
|
157
|
-
@builder << "<strong>#{data[0].capitalize} scenarios (Count: <span id='count'>#{data[1].size}</span>)</strong>"
|
158
|
-
end
|
159
|
-
end
|
160
|
-
@builder.div do
|
161
|
-
@builder.div(id: data[0]) do
|
162
|
-
data[1].sort_by {|scenario| scenario['name']}.each {|scenario| build_scenario scenario}
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
@builder << "<div id='scenarioTabPieChart'></div>"
|
168
|
-
end if options[:report_tabs].include? 'scenarios'
|
169
|
-
|
170
|
-
@builder.div(id: 'errorsTab') do
|
171
|
-
@builder.ol do
|
172
|
-
all_scenarios.each {|scenario| build_error_list scenario}
|
173
|
-
end
|
174
|
-
end if options[:report_tabs].include? 'errors'
|
175
|
-
end
|
176
|
-
|
177
|
-
@builder.script(type: 'text/javascript') do
|
178
|
-
@builder << pie_chart_js('featurePieChart', 'Features', feature_data) if options[:report_tabs].include? 'overview'
|
179
|
-
@builder << donut_js('featureTabPieChart', 'Features', feature_data) if options[:report_tabs].include? 'features'
|
180
|
-
@builder << pie_chart_js('scenarioPieChart', 'Scenarios', scenario_data) if options[:report_tabs].include? 'overview'
|
181
|
-
@builder << donut_js('scenarioTabPieChart', 'Scenarios', scenario_data) if options[:report_tabs].include? 'scenarios'
|
182
|
-
@builder << pie_chart_js('stepPieChart', 'Steps', step_data) if options[:report_tabs].include? 'overview'
|
183
|
-
unless all_tags.empty?
|
184
|
-
@builder << '$("#featuresTab .select-tags").change(function(){
|
185
|
-
$("#featuresTab .scenario-all").hide().next().hide().parent().hide().parent().hide().prev().hide();
|
186
|
-
$("#featuresTab ." + $(this).val()).show().parent().show().parent().prev().show();});' if options[:report_tabs].include? 'features'
|
187
|
-
@builder << '$("#scenariosTab .select-tags").change(function(){var val = $(this).val();$("#scenariosTab .scenario-all").hide().next().hide();
|
188
|
-
$("#scenariosTab ." + val).show();$("#scenariosTab #count").each(function(){status = $(this).parent().parent().prop("className");
|
189
|
-
count = $("#scenariosTab #" + status + " ." + val).length;countElement = $("#scenariosTab ." + status + " #count");
|
190
|
-
countElement.parent().parent().parent().show();if(count==0){countElement.parent().parent().parent().hide().next().hide();}
|
191
|
-
countElement.html(count);});});' if options[:report_tabs].include? 'scenarios'
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
@builder << '</body>'
|
196
|
-
@builder << '</html>'
|
26
|
+
json_report_path = options[:json_report_path] || options[:report_path]
|
27
|
+
File.open(json_report_path + '.json', 'w') do |file|
|
28
|
+
file.write JSON.pretty_generate features
|
29
|
+
end if options[:report_types].include? 'JSON'
|
197
30
|
|
31
|
+
html_report_path = options[:html_report_path] || options[:report_path]
|
32
|
+
File.open(html_report_path + '.html', 'w') do |file|
|
33
|
+
file.write ERB.new(File.read(File.dirname(__FILE__) + '/../../template/html_report.erb')).result(binding).gsub(' ', '').gsub("\n\n", '')
|
198
34
|
end if options[:report_types].include? 'HTML'
|
199
35
|
|
200
|
-
|
201
|
-
File.open(
|
202
|
-
|
36
|
+
retry_report_path = options[:retry_report_path] || options[:report_path]
|
37
|
+
File.open(retry_report_path + '.retry', 'w') do |file|
|
38
|
+
features.each do |feature|
|
203
39
|
if feature['status'] == 'broken'
|
204
40
|
feature['elements'].each {|scenario| file.puts "#{feature['uri']}:#{scenario['line']}" if scenario['status'] == 'failed'}
|
205
41
|
end
|
206
42
|
end
|
207
43
|
end if options[:report_types].include? 'RETRY'
|
208
|
-
|
209
|
-
[total_time, feature_data, scenario_data, step_data]
|
44
|
+
[json_report_path, html_report_path, retry_report_path]
|
210
45
|
end
|
211
46
|
|
212
47
|
def default_options
|
213
48
|
OpenStruct.new(
|
214
|
-
json_path:
|
49
|
+
json_path: Dir.pwd, # [String] / [Array] Input json file, array of json files/path or json files path, (Default current directory)
|
215
50
|
report_path: 'test_report', # [String] Output file path with name
|
216
51
|
report_types: [:html], # [Array] Output file types to build, [:json, :html] or ['html', 'json']
|
217
|
-
report_tabs: [:overview, :features], # [Array] Tabs to build, [:overview, :features, :scenarios, :errors] or ['overview', 'features', 'scenarios', 'errors']
|
218
52
|
report_title: 'Test Results', # [String] Report and html title
|
219
|
-
|
220
|
-
additional_info: {} # [Hash] Additional info for report
|
53
|
+
include_images: true, # [Boolean] Set false to reducing the size of HTML report, by excluding embedded images
|
54
|
+
additional_info: {} # [Hash] Additional info for report
|
221
55
|
)
|
222
56
|
end
|
223
57
|
|
224
|
-
|
225
|
-
|
226
|
-
def build_menu(tabs)
|
227
|
-
@builder.ul do
|
228
|
-
tabs.each do |tab|
|
229
|
-
@builder.li do
|
230
|
-
@builder.a(href: "##{tab}Tab") do
|
231
|
-
@builder << tab.capitalize
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def build_scenario(scenario)
|
239
|
-
tags = (scenario['tags'] ? scenario['tags'].map {|tag| tag['name']}.join(' ') : '')
|
240
|
-
@builder.h3(style: "background:#{COLOR[scenario['status'].to_sym]}", title: tags, class: 'scenario-all ' + tags.gsub('@', 'tag-')) do
|
241
|
-
@builder.span(class: scenario['status']) do
|
242
|
-
@builder << "<strong>#{scenario['keyword']}</strong> #{scenario['name']} (#{duration(scenario['duration'])})"
|
243
|
-
end
|
244
|
-
end
|
245
|
-
@builder.div do
|
246
|
-
scenario['before'].each do |before|
|
247
|
-
build_hook_error before
|
248
|
-
end
|
249
|
-
scenario['steps'].each do |step|
|
250
|
-
build_step step, scenario['keyword']
|
251
|
-
end
|
252
|
-
scenario['after'].each do |after|
|
253
|
-
build_output after['output']
|
254
|
-
build_hook_error after
|
255
|
-
build_embedding after['embeddings']
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
def build_step(step, scenario_keyword)
|
261
|
-
@builder.div(class: step['status']) do
|
262
|
-
@builder << "<strong>#{step['keyword']}</strong> #{step['name']} (#{duration(step['duration'])})"
|
263
|
-
end
|
264
|
-
build_data_table step['rows']
|
265
|
-
build_output step['output']
|
266
|
-
build_step_error step
|
267
|
-
build_embedding step['embeddings']
|
268
|
-
step['after'].each do |after|
|
269
|
-
build_output after['output']
|
270
|
-
build_step_hook_error after, scenario_keyword
|
271
|
-
build_embedding after['embeddings']
|
272
|
-
end if step['after']
|
273
|
-
end
|
274
|
-
|
275
|
-
def build_data_table(rows)
|
276
|
-
@builder.table(class: 'data_table', style: 'margin: 10px') do
|
277
|
-
rows.each do |row|
|
278
|
-
@builder.tr do
|
279
|
-
row['cells'].each do |cell|
|
280
|
-
@builder << "<td> #{cell} </td>"
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
end if rows.is_a? Array
|
285
|
-
end
|
286
|
-
|
287
|
-
def build_output(outputs)
|
288
|
-
outputs.each do |output|
|
289
|
-
@builder << "<span style='color:#{COLOR[:output]}'>#{output.to_s.gsub("\n", '</br>').gsub("\t", ' ').gsub(' ', ' ')}</span><br/>"
|
290
|
-
end if outputs.is_a?(Array)
|
58
|
+
def decode(data)
|
59
|
+
Base64.decode64(data) rescue data
|
291
60
|
end
|
292
61
|
|
293
|
-
|
294
|
-
@builder.div(style: 'text-align:center;padding:5px;') do
|
295
|
-
@builder << '<strong>Tag: </strong>'
|
296
|
-
@builder.select(class: 'select-tags') do
|
297
|
-
@builder.option(value: 'scenario-all') do
|
298
|
-
@builder << 'All'
|
299
|
-
end
|
300
|
-
tags.sort.each do |tag|
|
301
|
-
@builder.option(value: tag.gsub('@', 'tag-')) do
|
302
|
-
@builder << tag
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
end if tags.is_a?(Array)
|
307
|
-
end
|
308
|
-
|
309
|
-
def build_step_error(step)
|
310
|
-
if step['status'] == 'failed' && step['result']['error_message']
|
311
|
-
@builder << "<strong style=color:#{COLOR[:failed]}>Error: </strong>"
|
312
|
-
error = step['result']['error_message'].split("\n")
|
313
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
314
|
-
error[0..-3].each do |line|
|
315
|
-
@builder << line + '<br/>'
|
316
|
-
end
|
317
|
-
end
|
318
|
-
@builder << "<strong>SD: </strong>#{error[-2]} <br/>"
|
319
|
-
@builder << "<strong>FF: </strong>#{error[-1]}<br/>"
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
def build_hook_error(hook)
|
324
|
-
if hook['status'] == 'failed'
|
325
|
-
@builder << "<strong style=color:#{COLOR[:failed]}>Error: </strong>"
|
326
|
-
error = hook['result']['error_message'].split("\n")
|
327
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
328
|
-
error[0..-2].each do |line|
|
329
|
-
@builder << line + '<br/>'
|
330
|
-
end
|
331
|
-
end
|
332
|
-
@builder << "<strong>Hook: </strong>#{error[-1]}<br/>"
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
def build_step_hook_error(hook, scenario_keyword)
|
337
|
-
if hook['result']['error_message']
|
338
|
-
@builder << "<strong style=color:#{COLOR[:failed]}>Error: </strong>"
|
339
|
-
error = hook['result']['error_message'].split("\n")
|
340
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
341
|
-
(scenario_keyword == 'Scenario Outline' ? error[0..-8] : error[0..-5]).each do |line|
|
342
|
-
@builder << line + '<br/>'
|
343
|
-
end
|
344
|
-
end
|
345
|
-
@builder << "<strong>Hook: </strong>#{scenario_keyword == 'Scenario Outline' ? error[-7] : error[-4]} <br/>"
|
346
|
-
@builder << "<strong>FF: </strong>#{error[-2]}<br/>"
|
347
|
-
end
|
348
|
-
end
|
62
|
+
private
|
349
63
|
|
350
|
-
def
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
end
|
363
|
-
@builder << '<br/>'
|
364
|
-
options[:compress_images] ? build_unique_image(embedding, id) : build_image(embedding, id)
|
365
|
-
end
|
366
|
-
rescue => e
|
367
|
-
puts 'Image embedding failed!'
|
368
|
-
puts [e.class, e.message, e.backtrace[0..10].join("\n")].join("\n")
|
369
|
-
end
|
370
|
-
elsif embedding['mime_type'] =~ /^text\/plain/
|
371
|
-
begin
|
372
|
-
if src.include?('|||')
|
373
|
-
title, link = src.split('|||')
|
374
|
-
@builder.span(class: 'link') do
|
375
|
-
@builder.a(id: id, style: 'text-decoration: none;', href: link, title: title) do
|
376
|
-
@builder.span(style: "color: #{COLOR[:output]}; font-weight: bold; border-bottom: 1px solid #{COLOR[:output]};") do
|
377
|
-
@builder << title
|
378
|
-
end
|
379
|
-
end
|
380
|
-
@builder << '<br/>'
|
381
|
-
end
|
64
|
+
def get_files(path)
|
65
|
+
if path.is_a?(String) and Pathname.new(path).exist?
|
66
|
+
if Pathname.new(path).directory?
|
67
|
+
Dir.glob("#{path}/*.json")
|
68
|
+
else
|
69
|
+
[path]
|
70
|
+
end
|
71
|
+
elsif path.is_a? Array
|
72
|
+
path.map do |file|
|
73
|
+
if Pathname.new(file).exist?
|
74
|
+
if Pathname.new(file).directory?
|
75
|
+
Dir.glob("#{file}/*.json")
|
382
76
|
else
|
383
|
-
|
384
|
-
@builder << src
|
385
|
-
@builder << '<br/>'
|
386
|
-
end
|
77
|
+
file
|
387
78
|
end
|
388
|
-
|
389
|
-
|
390
|
-
puts [e.class, e.message, e.backtrace[0..10].join("\n")].join("\n")
|
79
|
+
else
|
80
|
+
[]
|
391
81
|
end
|
392
|
-
end
|
393
|
-
@embedding_count += 1
|
394
|
-
end if embeddings.is_a?(Array)
|
395
|
-
end
|
396
|
-
|
397
|
-
def build_unique_image(image, id)
|
398
|
-
@images ||= []
|
399
|
-
index = @images.find_index image
|
400
|
-
if index
|
401
|
-
klass = "image_#{index}"
|
82
|
+
end.flatten
|
402
83
|
else
|
403
|
-
|
404
|
-
|
405
|
-
@builder.style(type: 'text/css') do
|
406
|
-
begin
|
407
|
-
src = Base64.decode64(image['data'])
|
408
|
-
src = 'data:' + image['mime_type'] + ';base64,' + src unless src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
|
409
|
-
@builder << "img.#{klass} {content: url(#{src});}"
|
410
|
-
rescue
|
411
|
-
src = image['data']
|
412
|
-
src = 'data:' + image['mime_type'] + ';base64,' + src unless src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
|
413
|
-
@builder << "img.#{klass} {content: url(#{src});}"
|
414
|
-
end
|
415
|
-
end
|
416
|
-
end
|
417
|
-
@builder << %{<img id='#{id}' class='#{klass}' style='display: none; border: 1px solid #{COLOR[:output]};' />}
|
418
|
-
end
|
419
|
-
|
420
|
-
def build_image(image, id)
|
421
|
-
begin
|
422
|
-
src = Base64.decode64(image['data'])
|
423
|
-
src = 'data:' + image['mime_type'] + ';base64,' + src unless src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
|
424
|
-
@builder << %{<img id='#{id}' style='display: none; border: 1px solid #{COLOR[:output]};' src='#{src}'/>}
|
425
|
-
rescue
|
426
|
-
src = image['data']
|
427
|
-
src = 'data:' + image['mime_type'] + ';base64,' + src unless src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
|
428
|
-
@builder << %{<img id='#{id}' style='display: none; border: 1px solid #{COLOR[:output]};' src='#{src}'/>}
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
def build_error_list(scenario)
|
433
|
-
scenario['before'].each do |before|
|
434
|
-
next unless before['status'] == 'failed'
|
435
|
-
@builder.li do
|
436
|
-
error = before['result']['error_message'].split("\n")
|
437
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
438
|
-
error[0..-2].each do |line|
|
439
|
-
@builder << line + '<br/>'
|
440
|
-
end
|
441
|
-
end
|
442
|
-
@builder << "<strong>Hook: </strong>#{error[-1]} <br/>"
|
443
|
-
@builder << "<strong>Scenario: </strong>#{scenario['name']} <br/><hr/>"
|
444
|
-
end
|
445
|
-
end
|
446
|
-
scenario['steps'].each do |step|
|
447
|
-
step['after'].each do |after|
|
448
|
-
next unless after['status'] == 'failed'
|
449
|
-
@builder.li do
|
450
|
-
error = after['result']['error_message'].split("\n")
|
451
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
452
|
-
(scenario['keyword'] == 'Scenario Outline' ? error[0..-8] : error[0..-5]).each do |line|
|
453
|
-
@builder << line + '<br/>'
|
454
|
-
end
|
455
|
-
end
|
456
|
-
@builder << "<strong>Hook: </strong>#{scenario['keyword'] == 'Scenario Outline' ? error[-7] : error[-4]} <br/>"
|
457
|
-
@builder << "<strong>FF: </strong>#{error[-2]} <br/><hr/>"
|
458
|
-
end
|
459
|
-
end if step['after']
|
460
|
-
next unless step['status'] == 'failed' && step['result']['error_message']
|
461
|
-
@builder.li do
|
462
|
-
error = step['result']['error_message'].split("\n")
|
463
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
464
|
-
error[0..-3].each do |line|
|
465
|
-
@builder << line + '<br/>'
|
466
|
-
end
|
467
|
-
end
|
468
|
-
@builder << "<strong>SD: </strong>#{error[-2]} <br/>"
|
469
|
-
@builder << "<strong>FF: </strong>#{error[-1]} <br/><hr/>"
|
470
|
-
end
|
471
|
-
end
|
472
|
-
scenario['after'].each do |after|
|
473
|
-
next unless after['status'] == 'failed'
|
474
|
-
@builder.li do
|
475
|
-
error = after['result']['error_message'].split("\n")
|
476
|
-
@builder.span(style: "color:#{COLOR[:failed]}") do
|
477
|
-
error[0..-2].each do |line|
|
478
|
-
@builder << line + '<br/>'
|
479
|
-
end
|
480
|
-
end
|
481
|
-
@builder << "<strong>Hook: </strong>#{error[-1]} <br/>"
|
482
|
-
@builder << "<strong>Scenario: </strong>#{scenario['name']} <br/><hr/>"
|
483
|
-
end
|
484
|
-
end
|
84
|
+
[]
|
85
|
+
end.uniq
|
485
86
|
end
|
486
87
|
|
487
|
-
def
|
88
|
+
def get_features(files)
|
488
89
|
files.each_with_object([]) {|file, features|
|
489
90
|
data = File.read(file)
|
490
91
|
next if data.empty?
|
491
|
-
features << JSON.parse(data)
|
92
|
+
features << JSON.parse(data) rescue next
|
492
93
|
}.flatten.group_by {|feature|
|
493
94
|
feature['uri']+feature['id']+feature['line'].to_s
|
494
95
|
}.values.each_with_object([]) {|group, features|
|
@@ -498,8 +99,10 @@ module ReportBuilder
|
|
498
99
|
(0..feature['elements'].size-1).step(2) do |i|
|
499
100
|
feature['elements'][i]['steps'] ||= []
|
500
101
|
feature['elements'][i]['steps'].each {|step| step['name']+=(' ('+feature['elements'][i]['keyword']+')')}
|
501
|
-
|
502
|
-
|
102
|
+
if feature['elements'][i+1]
|
103
|
+
feature['elements'][i+1]['steps'] = feature['elements'][i]['steps'] + feature['elements'][i+1]['steps']
|
104
|
+
feature['elements'][i+1]['before'] = feature['elements'][i]['before'] if feature['elements'][i]['before']
|
105
|
+
end
|
503
106
|
end
|
504
107
|
feature['elements'].reject! {|element| element['type'] == 'background'}
|
505
108
|
end
|
@@ -543,12 +146,6 @@ module ReportBuilder
|
|
543
146
|
feature_status
|
544
147
|
end
|
545
148
|
|
546
|
-
def scenarios(features)
|
547
|
-
features.map do |feature|
|
548
|
-
feature['elements']
|
549
|
-
end.flatten
|
550
|
-
end
|
551
|
-
|
552
149
|
def scenario_status(scenario)
|
553
150
|
(scenario['before'] + scenario['steps'] + scenario['after']).each do |step|
|
554
151
|
status = step['status']
|
@@ -557,42 +154,6 @@ module ReportBuilder
|
|
557
154
|
'passed'
|
558
155
|
end
|
559
156
|
|
560
|
-
def steps(scenarios)
|
561
|
-
scenarios.map do |scenario|
|
562
|
-
scenario['steps']
|
563
|
-
end.flatten
|
564
|
-
end
|
565
|
-
|
566
|
-
def tags(scenarios)
|
567
|
-
scenarios.map do |scenario|
|
568
|
-
scenario['tags'] ? scenario['tags'].map {|t| t['name']} : []
|
569
|
-
end.flatten.uniq
|
570
|
-
end
|
571
|
-
|
572
|
-
def files(path)
|
573
|
-
files = if path.is_a? String
|
574
|
-
(path =~ /\.json$/) ? [path] : Dir.glob("#{path}/*.json")
|
575
|
-
elsif path.nil?
|
576
|
-
Dir.glob('*.json')
|
577
|
-
elsif path.is_a? Array
|
578
|
-
path.map do |file|
|
579
|
-
(file =~ /\.json$/) ? file : Dir.glob("#{file}/*.json")
|
580
|
-
end.flatten
|
581
|
-
else
|
582
|
-
raise 'InvalidInput'
|
583
|
-
end
|
584
|
-
raise 'InvalidOrNoInputFile' if files.empty?
|
585
|
-
files.uniq
|
586
|
-
end
|
587
|
-
|
588
|
-
def data(all_data)
|
589
|
-
all_data.group_by {|db| db['status']}.map do |data|
|
590
|
-
{name: data[0],
|
591
|
-
count: data[1].size,
|
592
|
-
color: COLOR[data[0].to_sym]}
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
157
|
def total_time(data)
|
597
158
|
total_time = 0
|
598
159
|
data.each {|item| total_time += item['duration']}
|
@@ -604,42 +165,5 @@ module ReportBuilder
|
|
604
165
|
m, s = seconds.divmod(60)
|
605
166
|
"#{m}m #{'%.3f' % s}s"
|
606
167
|
end
|
607
|
-
|
608
|
-
def pie_chart_js(id, title, data)
|
609
|
-
data = data.each_with_object('') do |h, s|
|
610
|
-
s << "{name: '#{h[:name].capitalize}'"
|
611
|
-
s << ",y: #{h[:count]}"
|
612
|
-
s << ',sliced: true' if h[:sliced]
|
613
|
-
s << ',selected: true' if h[:selected]
|
614
|
-
s << ",color: '#{h[:color]}'" if h[:color]
|
615
|
-
s << '},'
|
616
|
-
end.chop
|
617
|
-
"$(function (){$('##{id}').highcharts({credits: {enabled: false}, chart: {type: 'pie',
|
618
|
-
options3d: {enabled: true, alpha: 45, beta: 0}}, title: {text: '#{title}'},
|
619
|
-
tooltip: {pointFormat: 'Count: <b>{point.y}</b><br/>Percentage: <b>{point.percentage:.1f}%</b>'},
|
620
|
-
plotOptions: {pie: {allowPointSelect: true, cursor: 'pointer', depth: 35, dataLabels: {enabled: true,
|
621
|
-
format: '{point.name}'}}}, series: [{type: 'pie', name: 'Results', data: [#{data}]}]});});"
|
622
|
-
end
|
623
|
-
|
624
|
-
def donut_js(id, title, data)
|
625
|
-
data = data.each_with_object('') do |h, s|
|
626
|
-
s << "{name: '#{h[:name].capitalize}'"
|
627
|
-
s << ",y: #{h[:count]}"
|
628
|
-
s << ',sliced: true' if h[:sliced]
|
629
|
-
s << ',selected: true' if h[:selected]
|
630
|
-
s << ",color: '#{h[:color]}'" if h[:color]
|
631
|
-
s << '},'
|
632
|
-
end.chop
|
633
|
-
"$(function (){$('##{id}').highcharts({credits: {enabled: false},
|
634
|
-
chart: {plotBackgroundColor: null, plotBorderWidth: 0, plotShadow: false, width: $(document).width()-80},
|
635
|
-
title: {text: '#{title}', align: 'center', verticalAlign: 'middle', y: 40},
|
636
|
-
tooltip: {pointFormat: 'Count: <b>{point.y}</b><br/>Percentage: <b>{point.percentage:.1f}%</b>'},
|
637
|
-
plotOptions: {pie: {dataLabels: {enabled: true, distance: -50,
|
638
|
-
style: {fontWeight: 'bold', color: 'white', textShadow: '0px 1px 2px black'}},
|
639
|
-
startAngle: -90, endAngle: 90, center: ['50%', '75%']}},
|
640
|
-
series: [{type: 'pie', innerSize: '50%', name: 'Results', data: [#{data}]}]});});"
|
641
|
-
end
|
642
|
-
|
643
168
|
end
|
644
|
-
|
645
169
|
end
|