class Minitest::Reporters::HtmlReporter

A reporter for generating HTML test reports This is recommended to be used with a CI server, where the report is kept as an artifact and is accessible via a shared link

The reporter sorts the results alphabetically and then by results so that failing and skipped tests are at the top.

When using Minitest Specs, the number prefix is dropped from the name of the test so that it reads well

On each test run all files in the reports directory are deleted, this prevents a build up of old reports

The report is generated using ERB. A custom ERB template can be provided but it is not required The default ERB template uses JQuery and Bootstrap, both of these are included by referencing the CDN sites

Attributes

title[R]

The title of the report

Public Class Methods

new(args = {}) click to toggle source

The constructor takes a hash, and uses the following keys: :title - the title that will be used in the report, defaults to 'Test Results' :reports_dir - the directory the reports should be written to, defaults to 'test/html_reports' :erb_template - the path to a custom ERB template, defaults to the supplied ERB template :mode - Useful for debugging, :terse suppresses errors and is the default, :verbose lets errors bubble up :output_filename - the report's filename, defaults to 'index.html'

Calls superclass method Minitest::Reporters::BaseReporter.new
# File lib/minitest/reporters/html_reporter.rb, line 56
def initialize(args = {})
  super({})

  defaults = {
      :title           => 'Test Results',
      :erb_template    => "#{File.dirname(__FILE__)}/../templates/index.html.erb",
      :reports_dir     => 'test/html_reports',
      :mode            => :safe,
      :output_filename => 'index.html'
  }

  settings = defaults.merge(args)

  @mode = settings[:mode]
  @title = settings[:title]
  @erb_template = settings[:erb_template]
  @output_filename = settings[:output_filename]
  reports_dir = settings[:reports_dir]

  @reports_path = File.absolute_path(reports_dir)
end

Public Instance Methods

friendly_name(test) click to toggle source

Trims off the number prefix on test names when using Minitest Specs

# File lib/minitest/reporters/html_reporter.rb, line 44
def friendly_name(test)
  groups = test.name.scan(/(test_\d+_)(.*)/i)
  return test.name if groups.empty?
  "it #{groups[0][1]}"
end
passes() click to toggle source

The number of tests that passed

# File lib/minitest/reporters/html_reporter.rb, line 24
def passes
  count - failures - errors - skips
end
percent_errors_failures() click to toggle source

The percentage of tests that failed

# File lib/minitest/reporters/html_reporter.rb, line 39
def percent_errors_failures
  ((errors+failures)/count.to_f * 100).to_i
end
percent_passes() click to toggle source

The percentage of tests that passed, calculated in a way that avoids rounding errors

# File lib/minitest/reporters/html_reporter.rb, line 29
def percent_passes
  100 - percent_skipps - percent_errors_failures
end
percent_skipps() click to toggle source

The percentage of tests that were skipped

# File lib/minitest/reporters/html_reporter.rb, line 34
def percent_skipps
  (skips/count.to_f * 100).to_i
end
report() click to toggle source

Called by the framework to generate the report

# File lib/minitest/reporters/html_reporter.rb, line 87
def report
  super

  begin
    puts "Writing HTML reports to #{@reports_path}"
    erb_str = File.read(@erb_template)
    renderer = ERB.new(erb_str)

    tests_by_suites = tests.group_by(&:class) # taken from the JUnit reporter

    suites = tests_by_suites.map do |suite, tests|
      suite_summary = summarize_suite(suite, tests)
      suite_summary[:tests] = tests.sort { |a, b| compare_tests(a, b) }
      suite_summary
    end

    suites.sort! { |a, b| compare_suites(a, b) }

    result = renderer.result(binding)
    File.open(html_file, 'w') do |f|
      f.write(result)
    end

  rescue Exception => e
    puts 'There was an error writing the HTML report'
    puts 'This may have been caused by cancelling the test run'
    puts 'Use mode => :verbose in the HTML reporters constructor to see more detail' if @mode == :terse
    puts 'Use mode => :terse in the HTML reporters constructor to see less detail' if @mode != :terse
    raise e if @mode != :terse
  end

end
start() click to toggle source
Calls superclass method
# File lib/minitest/reporters/html_reporter.rb, line 78
def start
  super

  puts "Emptying #{@reports_path}"
  FileUtils.mkdir_p(@reports_path)
  File.delete(html_file) if File.exist?(html_file)
end

Private Instance Methods

compare_suites(suite_a, suite_b) click to toggle source

Test suites are first ordered by evaluating the results of the tests, then by test suite name Test suites which have failing tests are given highest order Tests suites which have skipped tests are given second highest priority

# File lib/minitest/reporters/html_reporter.rb, line 136
def compare_suites(suite_a, suite_b)
  return compare_suites_by_name(suite_a, suite_b) if suite_a[:has_errors_or_failures] && suite_b[:has_errors_or_failures]
  return -1 if suite_a[:has_errors_or_failures] && !suite_b[:has_errors_or_failures]
  return 1 if !suite_a[:has_errors_or_failures] && suite_b[:has_errors_or_failures]

  return compare_suites_by_name(suite_a, suite_b) if suite_a[:has_skipps] && suite_b[:has_skipps]
  return -1 if suite_a[:has_skipps] && !suite_b[:has_skipps]
  return 1 if !suite_a[:has_skipps] && suite_b[:has_skipps]

  compare_suites_by_name(suite_a, suite_b)
end
compare_suites_by_name(suite_a, suite_b) click to toggle source
# File lib/minitest/reporters/html_reporter.rb, line 125
def compare_suites_by_name(suite_a, suite_b)
  suite_a[:name] <=> suite_b[:name]
end
compare_tests(test_a, test_b) click to toggle source

Tests are first ordered by evaluating the results of the tests, then by tests names Tess which fail are given highest order Tests which are skipped are given second highest priority

# File lib/minitest/reporters/html_reporter.rb, line 151
def compare_tests(test_a, test_b)
  return compare_tests_by_name(test_a, test_b) if test_fail_or_error?(test_a) && test_fail_or_error?(test_b)

  return -1 if test_fail_or_error?(test_a) && !test_fail_or_error?(test_b)
  return 1 if !test_fail_or_error?(test_a) && test_fail_or_error?(test_b)

  return compare_tests_by_name(test_a, test_b) if test_a.skipped? && test_b.skipped?
  return -1 if test_a.skipped? && !test_b.skipped?
  return 1 if !test_a.skipped? && test_b.skipped?

  compare_tests_by_name(test_a, test_b)
end
compare_tests_by_name(test_a, test_b) click to toggle source
# File lib/minitest/reporters/html_reporter.rb, line 129
def compare_tests_by_name(test_a, test_b)
  friendly_name(test_a) <=> friendly_name(test_b)
end
html_file() click to toggle source
# File lib/minitest/reporters/html_reporter.rb, line 121
def html_file
  "#{@reports_path}/#{@output_filename}"
end
location(exception) click to toggle source

taken from the JUnit reporter

# File lib/minitest/reporters/html_reporter.rb, line 201
def location(exception)
  last_before_assertion = ''
  exception.backtrace.reverse_each do |s|
    break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
    last_before_assertion = s
  end
  last_before_assertion.sub(/:in .*$/, '')
end
message_for(test) click to toggle source

based on #message_for(test) from the JUnit reporter

# File lib/minitest/reporters/html_reporter.rb, line 184
def message_for(test)
  suite = test.class
  name = test.name
  e = test.failure

  if test.passed?
    nil
  elsif test.skipped?
    "Skipped:\n#{name}(#{suite}) [#{location(e)}]:\n#{e.message}\n"
  elsif test.failure
    "Failure:\n#{name}(#{suite}) [#{location(e)}]:\n#{e.message}\n"
  elsif test.error?
    "Error:\n#{name}(#{suite}):\n#{e.message}"
  end
end
summarize_suite(suite, tests) click to toggle source

based on analyze_suite from the JUnit reporter

# File lib/minitest/reporters/html_reporter.rb, line 169
def summarize_suite(suite, tests)
  summary = Hash.new(0)
  summary[:name] = suite.to_s
  tests.each do |test|
    summary[:"#{result(test)}_count"] += 1
    summary[:assertion_count] += test.assertions
    summary[:test_count] += 1
    summary[:time] += test.time
  end
  summary[:has_errors_or_failures] = (summary[:fail_count] + summary[:error_count]) > 0
  summary[:has_skipps] = summary[:skip_count] > 0
  summary
end
test_fail_or_error?(test) click to toggle source
# File lib/minitest/reporters/html_reporter.rb, line 164
def test_fail_or_error?(test)
  test.error? || test.failure
end
total_time_to_hms() click to toggle source
# File lib/minitest/reporters/html_reporter.rb, line 210
def total_time_to_hms
  return ('%.2fs' % total_time) if total_time < 1

  hours = (total_time / (60 * 60)).round
  minutes = ((total_time / 60) % 60).round.to_s.rjust(2,'0')
  seconds = (total_time % 60).round.to_s.rjust(2,'0')

  "#{ hours }h#{ minutes }m#{ seconds }s"
end