rspec_pretty_html_reporter 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb778b6e59fc1d897dd91626bdc82f763f32010ec6e618d10536a24fa7137260
4
+ data.tar.gz: 28be25ab79d74a6f363700dde7a7af73953d79ce3289a4dc9a3795267b28c86b
5
+ SHA512:
6
+ metadata.gz: c65d6d8a1f5fbe9086aa2b028b108e69503c813d8f827fc5646ef668d66e700c28a38b03e1720b2d5c18ca37eed9afec7243ac485f38e4b68be900f091a5e8b6
7
+ data.tar.gz: bc1ffcc7489649cab6466b0403059e258c0bb43114faa9b5e3cb9a4dff7bcb41b363f92547db592579ed8b8ce19712867e4bc6ca148f8dc68f705cde7bb5c019
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2022 Carlos Gutierrez
2
+ Copyright (c) 2017 Vishal Banthia
3
+ Copyright (c) 2014 Kingsley Hendrickse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+
2
+ [![Gem Version](https://badge.fury.io/rb/rspec_pretty_html_reporter.svg)](https://badge.fury.io/rb/rspec_pretty_html_reporter)
3
+
4
+ # RSpec Pretty HTML Reporter
5
+
6
+ Produce pretty [RSpec](http://rspec.info/) reports.
7
+
8
+ This is a custom reporter for RSpec which generates pretty HTML reports showing the results of rspec tests. It has
9
+ features to embed images and videos into the report providing better debugging information when a test fails.
10
+
11
+ <img src="https://github.com/TheSpartan1980/rspec_pretty_html_reporter/blob/master/images/group_overview_report.png" width="80%"/>
12
+
13
+ ## Setup
14
+
15
+ Add this to your Gemfile:
16
+
17
+ ```rb
18
+ gem 'rspec-pretty-html-reporter'
19
+ ```
20
+
21
+ ## Generating the report
22
+
23
+ Either add the below into your `.rspec` file
24
+
25
+ ```rb
26
+ --format RspecPrettyHtmlReporter
27
+ ```
28
+
29
+ or run RSpec with `--format RspecPrettyHtmlReporter` like below:
30
+
31
+ ```bash
32
+ REPORT_PATH=reports/$(date +%s) bundle exec rspec --format RspecPrettyHtmlReporter spec
33
+ ```
34
+
35
+ This will create the reports in the `reports` directory.
36
+
37
+ ## Usage
38
+
39
+ Images and videos can be embed by adding their path into example's metadata. For an example of how to do this, please
40
+ check out this [Sample Test](./spec/embed_graphics_spec.rb).
41
+
42
+ ## Credits
43
+
44
+ This library is forked from [vbanthia/rspec_html_reporter](https://github.com/vbanthia/rspec_html_reporter). The
45
+ original credit goes to *[kingsleyh](https://github.com/kingsleyh)*
46
+ for [kingsleyh/rspec_reports_formatter](https://github.com/kingsleyh/rspec_reports_formatter)
@@ -0,0 +1,78 @@
1
+ require 'rspec_pretty_html_reporter/oopsy'
2
+
3
+ class Example
4
+ def self.load_spec_comments!(examples)
5
+ examples.group_by(&:file_path).each do |file_path, file_examples|
6
+ lines = File.readlines(file_path)
7
+
8
+ file_examples.zip(file_examples.rotate).each do |ex, next_ex|
9
+ lexically_next = next_ex &&
10
+ next_ex.file_path == ex.file_path &&
11
+ next_ex.metadata[:line_number] > ex.metadata[:line_number]
12
+ start_line_idx = ex.metadata[:line_number] - 1
13
+ next_start_idx = (lexically_next ? next_ex.metadata[:line_number] : lines.size) - 1
14
+ spec_lines = lines[start_line_idx...next_start_idx].select { |l| l.match(/#->/) }
15
+ ex.set_spec(spec_lines.join) unless spec_lines.empty?
16
+ end
17
+ end
18
+ end
19
+
20
+ attr_reader :example_group, :description, :full_description, :run_time, :duration, :status, :exception, :file_path,
21
+ :metadata, :spec, :screenshots, :screenrecord, :failed_screenshot
22
+
23
+ def initialize(example)
24
+
25
+ @example_group = example.example_group.to_s
26
+ @description = example.description
27
+ @full_description = example.full_description
28
+ @execution_result = example.execution_result
29
+ @run_time = (@execution_result.run_time).round(5)
30
+ @duration = @execution_result.run_time.to_fs(:rounded, precision: 5)
31
+ @status = @execution_result.status.to_s
32
+ @metadata = example.metadata
33
+ @file_path = @metadata[:file_path]
34
+ @exception = Oopsy.new(example, @file_path)
35
+ @spec = nil
36
+ @screenshots = @metadata[:screenshots]
37
+ @screenrecord = @metadata[:screenrecord]
38
+ @failed_screenshot = @metadata[:failed_screenshot]
39
+ end
40
+
41
+ def example_title
42
+ title_arr = @example_group.to_s.split('::') - %w[RSpec ExampleGroups]
43
+ title_arr.push @description
44
+
45
+ title_arr.join(' → ')
46
+ end
47
+
48
+ def has_exception?
49
+ !@exception.klass.nil?
50
+ end
51
+
52
+ def has_spec?
53
+ !@spec.nil?
54
+ end
55
+
56
+ def has_screenshots?
57
+ !@screenshots.nil? && !@screenshots.empty?
58
+ end
59
+
60
+ def has_screenrecord?
61
+ !@screenrecord.nil?
62
+ end
63
+
64
+ def has_failed_screenshot?
65
+ !@failed_screenshot.nil?
66
+ end
67
+
68
+ def set_spec(spec_text)
69
+ formatter = Rouge::Formatters::HTMLLegacy.new(css_class: 'highlight')
70
+ lexer = Rouge::Lexers::Gherkin.new
71
+ @spec = formatter.format(lexer.lex(spec_text.gsub('#->', '')))
72
+ end
73
+
74
+ def klass(prefix = 'badge-')
75
+ class_map = { passed: "#{prefix}success", failed: "#{prefix}danger", pending: "#{prefix}warning" }
76
+ class_map[@status.to_sym]
77
+ end
78
+ end
@@ -0,0 +1,79 @@
1
+ require 'rbconfig'
2
+ require 'rspec/core/formatters/base_formatter'
3
+ require 'rouge'
4
+
5
+ class Oopsy
6
+ attr_reader :klass, :message, :backtrace, :highlighted_source, :explanation, :backtrace_message
7
+
8
+ def initialize(example, file_path)
9
+ @example = example
10
+ @exception = @example.exception
11
+ @file_path = file_path
12
+ unless @exception.nil?
13
+ @klass = @exception.class
14
+ @message = @exception.message.encode('utf-8')
15
+ @backtrace = @exception.backtrace
16
+ @backtrace_message = formatted_backtrace(@example, @exception)
17
+ @highlighted_source = process_source
18
+ @explanation = process_message
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def os
25
+ @os ||= begin
26
+ host_os = RbConfig::CONFIG['host_os']
27
+ case host_os
28
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
29
+ :windows
30
+ when /darwin|mac os/
31
+ :macosx
32
+ when /linux/
33
+ :linux
34
+ when /solaris|bsd/
35
+ :unix
36
+ else
37
+ raise StandardError, "unknown os: #{host_os.inspect}"
38
+ end
39
+ end
40
+ end
41
+
42
+ def formatted_backtrace(example, exception)
43
+ # To avoid an error in format_backtrace. RSpec's version below v3.5 will throw exception.
44
+ return [] unless example
45
+
46
+ formatter = RSpec.configuration.backtrace_formatter
47
+ formatter.format_backtrace(exception.backtrace, example.metadata)
48
+ end
49
+
50
+ def process_source
51
+
52
+ return '' if @backtrace_message.empty?
53
+
54
+ data = @backtrace_message.first.split(':')
55
+ unless data.empty?
56
+ if os == :windows
57
+ file_path = "#{data[0]}:#{data[1]}"
58
+ line_number = data[2].to_i
59
+ else
60
+ file_path = data.first
61
+ line_number = data[1].to_i
62
+ end
63
+ lines = File.readlines(file_path)
64
+ start_line = line_number - 2
65
+ end_line = line_number + 3
66
+ source = lines[start_line..end_line].join('').sub(lines[line_number - 1].chomp, "--->#{lines[line_number - 1].chomp}")
67
+ formatter = Rouge::Formatters::HTMLLegacy.new(css_class: 'highlight', line_numbers: true, start_line: start_line + 1)
68
+ lexer = Rouge::Lexers::Ruby.new
69
+ original_format = formatter.format(lexer.lex(source.encode('utf-8')))
70
+ original_format.gsub!(/<table class="rouge-table">/, '<table class="rouge-table" style="width:100%">')
71
+ end
72
+ end
73
+
74
+ def process_message
75
+ formatter = Rouge::Formatters::HTMLLegacy.new(css_class: 'highlight pl-3')
76
+ lexer = Rouge::Lexers::Ruby.new
77
+ formatter.format(lexer.lex(@message))
78
+ end
79
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/numeric'
5
+ require 'active_support/inflector'
6
+ require 'fileutils'
7
+ require 'erb'
8
+ require 'rspec_pretty_html_reporter/example'
9
+
10
+ I18n.enforce_available_locales = false
11
+
12
+ class RspecPrettyHtmlReporter < RSpec::Core::Formatters::BaseFormatter
13
+ DEFAULT_REPORT_PATH = File.join(Bundler.root, 'reports', Time.now.strftime('%Y%m%d-%H%M%S'))
14
+ REPORT_PATH = ENV['REPORT_PATH'] || DEFAULT_REPORT_PATH
15
+
16
+ SCREENRECORD_DIR = File.join(REPORT_PATH, 'screenrecords')
17
+ SCREENSHOT_DIR = File.join(REPORT_PATH, 'screenshots')
18
+ RESOURCE_DIR = File.join(REPORT_PATH, 'resources')
19
+
20
+ RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_pending,
21
+ :example_group_finished
22
+
23
+ def initialize(_io_standard_out)
24
+ create_reports_dir
25
+ create_screenshots_dir
26
+ create_screenrecords_dir
27
+ copy_resources
28
+ @all_groups = {}
29
+
30
+ @group_level = 0
31
+ end
32
+
33
+ def example_group_started(_notification)
34
+ if @group_level.zero?
35
+ @example_group = {}
36
+ @examples = []
37
+ @group_example_count = 0
38
+ @group_example_success_count = 0
39
+ @group_example_failure_count = 0
40
+ @group_example_pending_count = 0
41
+ end
42
+
43
+ @group_level += 1
44
+ end
45
+
46
+ def example_started(_notification)
47
+ @group_example_count += 1
48
+ end
49
+
50
+ def example_passed(notification)
51
+ # require 'byebug'; byebug
52
+
53
+ @group_example_success_count += 1
54
+ @examples << Example.new(notification.example)
55
+ end
56
+
57
+ def example_failed(notification)
58
+ @group_example_failure_count += 1
59
+ @examples << Example.new(notification.example)
60
+ end
61
+
62
+ def example_pending(notification)
63
+ @group_example_pending_count += 1
64
+ @examples << Example.new(notification.example)
65
+ end
66
+
67
+ def example_group_finished(notification)
68
+ @group_level -= 1
69
+
70
+ if @group_level.zero?
71
+
72
+ File.open("#{REPORT_PATH}/#{notification.group.description.parameterize}.html", 'w') do |f|
73
+ @passed = @group_example_success_count
74
+ @failed = @group_example_failure_count
75
+ @pending = @group_example_pending_count
76
+
77
+ duration_values = @examples.map(&:run_time)
78
+
79
+ duration_keys = duration_values.size.times.to_a
80
+ if (duration_values.size < 2) && duration_values.size.positive?
81
+ duration_values.unshift(duration_values.first)
82
+ duration_keys = duration_keys << 1
83
+ end
84
+
85
+ @title = notification.group.description
86
+ @durations = duration_keys.zip(duration_values)
87
+
88
+ @summary_duration = duration_values.inject(0) { |sum, i| sum + i }.to_fs(:rounded, precision: 5)
89
+ Example.load_spec_comments!(@examples)
90
+
91
+ @total_group_examples = @passed + @failed + @pending
92
+
93
+
94
+ class_map = { passed: 'success', failed: 'danger', pending: 'warning' }
95
+ statuses = @examples.map(&:status)
96
+ @status = if statuses.include?('failed')
97
+ 'failed'
98
+ else
99
+ (statuses.include?('passed') ? 'passed' : 'pending')
100
+ end
101
+ @all_groups[notification.group.description.parameterize] = {
102
+ group: notification.group.description,
103
+ examples: @examples.size,
104
+ status: @status,
105
+ klass: class_map[@status.to_sym],
106
+ passed: statuses.select { |s| s == 'passed' },
107
+ failed: statuses.select { |s| s == 'failed' },
108
+ pending: statuses.select { |s| s == 'pending' },
109
+ duration: @summary_duration
110
+ }
111
+
112
+ @example_status = @all_groups[notification.group.description.parameterize][:klass]
113
+
114
+ template_file = File.read("#{File.dirname(__FILE__)}/../templates/report.erb")
115
+
116
+ f.puts ERB.new(template_file).result(binding)
117
+ end
118
+ end
119
+ end
120
+
121
+ def close(notification)
122
+ File.open("#{REPORT_PATH}/overview.html", 'w') do |f|
123
+ @overview = @all_groups
124
+
125
+ @passed = @overview.values.map { |g| g[:passed].size }.inject(0) { |sum, i| sum + i }
126
+ @failed = @overview.values.map { |g| g[:failed].size }.inject(0) { |sum, i| sum + i }
127
+ @pending = @overview.values.map { |g| g[:pending].size }.inject(0) { |sum, i| sum + i }
128
+
129
+ duration_values = @overview.values.map { |e| e[:duration] }
130
+
131
+ duration_keys = duration_values.size.times.to_a
132
+ if duration_values.size < 2
133
+ duration_values.unshift(duration_values.first)
134
+ duration_keys = duration_keys << 1
135
+ end
136
+
137
+ @durations = duration_keys.zip(duration_values.map { |d| d.to_f.round(5) })
138
+ @summary_duration = duration_values.map do |d|
139
+ d.to_f.round(5)
140
+ end.inject(0) { |sum, i| sum + i }.to_fs(:rounded, precision: 5)
141
+ @total_examples = @passed + @failed + @pending
142
+ template_file = File.read("#{File.dirname(__FILE__)}/../templates/overview.erb")
143
+ f.puts ERB.new(template_file).result(binding)
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def create_reports_dir
150
+ FileUtils.rm_rf(REPORT_PATH) if File.exist?(REPORT_PATH)
151
+ FileUtils.mkpath(REPORT_PATH)
152
+ end
153
+
154
+ def create_screenshots_dir
155
+ FileUtils.mkdir_p SCREENSHOT_DIR unless File.exist?(SCREENSHOT_DIR)
156
+ end
157
+
158
+ def create_screenrecords_dir
159
+ FileUtils.mkdir_p SCREENRECORD_DIR unless File.exist?(SCREENRECORD_DIR)
160
+ end
161
+
162
+ def copy_resources
163
+ FileUtils.cp_r("#{File.dirname(__FILE__)}/../resources", REPORT_PATH)
164
+ end
165
+ end