Skip to content

Instantly share code, notes, and snippets.

@dapicester
Created November 2, 2024 10:02
Show Gist options
  • Save dapicester/24edf9bd198d52102e1bbaaff1dc045d to your computer and use it in GitHub Desktop.
Save dapicester/24edf9bd198d52102e1bbaaff1dc045d to your computer and use it in GitHub Desktop.
Instrument JavaScript code with Istanbul when running system tests on Rails 7 with importmaps
# test/application_system_test_case.rb
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
include IstanbulInstrumentation
instrument_dir "app/javascript/controllers"
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
# config/importmap.rb
pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
if Rails.env.test?
# Use instrumented code to collect coverage.
pin_all_from "app/javascript/controllers-instrumented", under: "controllers", to: "controllers-instrumented"
else
pin_all_from "app/javascript/controllers", under: "controllers"
end
# lib/istanbul_instrumentation.rb
# Mixin enabling Istanbul instrumentation and generating coverage report.
# It uses `npx` to download and run `nyc`.
module IstanbulInstrumentation
extend ActiveSupport::Concern
COVERAGE_DIR = Rails.root.join(".nyc_output")
class_methods do
# Add path to the list of directories to be instrumented.
def instrument_dir(dirname)
directories << instrument_code(dirname)
end
# Runs a command in subshell. Throws exception on non-zero exit codes or errors.
def run_command(cmd)
system cmd, exception: true
end
# Runs istanbul client to instrument the JavaScript code.
# Returns the instrumented code path.
def instrument_code(source)
destination = "#{source}-instrumented"
puts "Instrumenting the code at #{source} into #{destination}"
run_command "npx nyc instrument #{source} #{destination}"
destination
end
# Generates istanbul reports in the given format (default: text).
def generate_coverage_report(*formats)
puts "Generating Istanbul coverage report"
run_command "npx nyc report #{formats.map { |fmt| "--reporter=#{fmt}" }.join " "}"
end
# Removes instrumented code and intermediate coverage reports.
def cleanup
FileUtils.rm_r [ COVERAGE_DIR, *directories ]
end
end
included do
class_attribute :directories, default: []
Minitest.after_run do
generate_coverage_report "lcov", "text-summary"
cleanup
end
end
def teardown
super
collect_coverage
end
# Collect the coverage from the instrumented code and save into JSON file.
def collect_coverage
coverage = page.evaluate_script("window.__coverage__")
return unless coverage.present?
FileUtils.mkdir COVERAGE_DIR unless Dir.exist? COVERAGE_DIR
filename = File.join(COVERAGE_DIR, "coverage_#{self.class_name.tableize}_#{self.name}.json")
File.open(filename, "w") do |file|
file.puts JSON.pretty_generate coverage
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment