snap_diff-capybara 2.0.0.alpha1 → 2.0.0.beta2

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/docs/UPGRADING.md +243 -1
  4. data/docs/architecture.md +91 -53
  5. data/docs/ci-integration.md +13 -3
  6. data/docs/configuration.md +31 -2
  7. data/docs/drivers.md +6 -0
  8. data/docs/framework-setup.md +21 -3
  9. data/docs/reporters.md +20 -5
  10. data/docs/snapdiff.md +326 -0
  11. data/docs/thread_safety.md +4 -3
  12. data/lib/capybara/screenshot/diff/config_legacy.rb +26 -72
  13. data/lib/capybara/screenshot/diff/image_compare.rb +5 -2
  14. data/lib/capybara/screenshot/diff/region.rb +3 -105
  15. data/lib/capybara/screenshot/diff/reporters/default.rb +4 -106
  16. data/lib/capybara_screenshot_diff/screenshot_assertion.rb +12 -24
  17. data/lib/capybara_screenshot_diff.rb +10 -4
  18. data/lib/snap_diff/area_calculator.rb +1 -3
  19. data/lib/snap_diff/browser_helpers.rb +1 -3
  20. data/lib/snap_diff/capture/viewport.rb +6 -7
  21. data/lib/snap_diff/comparison.rb +15 -28
  22. data/lib/snap_diff/config.rb +151 -29
  23. data/lib/snap_diff/deprecation.rb +26 -8
  24. data/lib/snap_diff/drivers.rb +20 -0
  25. data/lib/snap_diff/dsl.rb +12 -12
  26. data/lib/snap_diff/errors.rb +19 -0
  27. data/lib/snap_diff/integrations/cucumber.rb +5 -4
  28. data/lib/snap_diff/integrations/minitest.rb +11 -12
  29. data/lib/snap_diff/integrations/rspec.rb +7 -6
  30. data/lib/snap_diff/legacy_shims.rb +18 -0
  31. data/lib/snap_diff/region.rb +117 -0
  32. data/lib/snap_diff/reporters/default.rb +107 -0
  33. data/lib/snap_diff/reporters/html.rb +7 -9
  34. data/lib/snap_diff/reporting.rb +13 -4
  35. data/lib/snap_diff/screenshot_assertion.rb +37 -4
  36. data/lib/snap_diff/screenshot_matcher.rb +4 -4
  37. data/lib/snap_diff/screenshoter.rb +1 -1
  38. data/lib/snap_diff/snap_manager.rb +1 -2
  39. data/lib/snap_diff/stable_screenshoter.rb +2 -2
  40. data/lib/snap_diff/utils.rb +5 -3
  41. data/lib/snap_diff/version.rb +1 -1
  42. data/lib/snap_diff.rb +44 -15
  43. metadata +5 -1
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "snap_diff/annotation_service"
4
+
5
+ module SnapDiff
6
+ module Reporters
7
+ class Default
8
+ attr_reader :difference
9
+
10
+ def initialize(difference)
11
+ @difference = difference
12
+ @annotation_service = SnapDiff::AnnotationService.new(difference)
13
+ end
14
+
15
+ def annotated_image_path
16
+ annotation_service.annotated_image_path
17
+ end
18
+
19
+ def annotated_base_image_path
20
+ annotation_service.annotated_base_image_path
21
+ end
22
+
23
+ def heatmap_diff_path
24
+ annotation_service.heatmap_diff_path
25
+ end
26
+
27
+ def generate
28
+ if difference.equal?
29
+ # NOTE: Delete previous run runtime files
30
+ clean_tmp_files
31
+ return nil
32
+ end
33
+
34
+ if difference.failed? && difference.failed_by[:different_dimensions]
35
+ return build_error_for_different_dimensions
36
+ end
37
+
38
+ annotate_and_save_images
39
+ build_error_message
40
+ end
41
+
42
+ def clean_tmp_files
43
+ annotation_service.clean_tmp_files
44
+ end
45
+
46
+ def annotate_and_save_images
47
+ annotation_service.annotate_and_save_images
48
+ end
49
+
50
+ def save_annotation_for(image, image_path)
51
+ annotation_service.save_annotation_for(image, image_path)
52
+ end
53
+
54
+ def annotate_difference(image, region)
55
+ annotation_service.annotate_difference(image, region)
56
+ end
57
+
58
+ def annotate_skip_areas(image, skip_areas)
59
+ annotation_service.annotate_skip_areas(image, skip_areas)
60
+ end
61
+
62
+ def save(image, image_path)
63
+ annotation_service.save(image, image_path)
64
+ end
65
+
66
+ def build_error_for_different_dimensions
67
+ change_msg = [comparison.base_image, comparison.new_image]
68
+ .map { |image| driver.dimension(image).join("x") }
69
+ .join(" => ")
70
+
71
+ "Dimensions have changed: #{change_msg}\n#{base_image_path.to_path}\n#{image_path.to_path}"
72
+ end
73
+
74
+ NEW_LINE = "\n"
75
+
76
+ def build_error_message
77
+ [
78
+ "(#{difference.to_h.to_json})",
79
+ image_path.to_path,
80
+ annotated_base_image_path.to_path,
81
+ annotated_image_path.to_path,
82
+ heatmap_diff_path.to_path
83
+ ].join(NEW_LINE)
84
+ end
85
+
86
+ private
87
+
88
+ attr_reader :annotation_service
89
+
90
+ def base_image_path
91
+ comparison.base_image_path
92
+ end
93
+
94
+ def image_path
95
+ comparison.new_image_path
96
+ end
97
+
98
+ def driver
99
+ @_driver ||= comparison.driver
100
+ end
101
+
102
+ def comparison
103
+ @_comparison ||= difference.comparison
104
+ end
105
+ end
106
+ end
107
+ end
@@ -5,11 +5,9 @@ require "erb"
5
5
  require "fileutils"
6
6
  require "pathname"
7
7
  require "json"
8
- # The auto-registration block at the bottom of this file calls
9
- # CapybaraScreenshotDiff.reporters, which needs the registry singleton
10
- # machinery in the *old* capybara_screenshot_diff/screenshot_assertion.rb
11
- # file (deliberately not moved -- see that file's own comment).
12
- require "capybara_screenshot_diff/screenshot_assertion"
8
+ # The auto-registration block at the bottom of this file registers with
9
+ # SnapDiff::Reporting.
10
+ require "snap_diff/reporting"
13
11
  require "capybara/screenshot/diff/config_legacy"
14
12
 
15
13
  module SnapDiff
@@ -136,8 +134,8 @@ module SnapDiff
136
134
  end
137
135
 
138
136
  # Auto-register reporter.
139
- # Framework adapters (Minitest, RSpec, Cucumber) call finalize_reporters! via native hooks.
140
- # For custom frameworks, call CapybaraScreenshotDiff.finalize_reporters! manually.
141
- unless CapybaraScreenshotDiff.reporters.any?(SnapDiff::Reporters::HTML)
142
- CapybaraScreenshotDiff.reporters << SnapDiff::Reporters::HTML.new(embed_images: !!ENV["CI"])
137
+ # Framework adapters (Minitest, RSpec, Cucumber) call SnapDiff::Reporting.finalize! via native hooks.
138
+ # For custom frameworks, call SnapDiff::Reporting.finalize! manually.
139
+ unless SnapDiff::Reporting.reporters.any?(SnapDiff::Reporters::HTML)
140
+ SnapDiff::Reporting.register(SnapDiff::Reporters::HTML.new(embed_images: !!ENV["CI"]))
143
141
  end
@@ -5,10 +5,9 @@ module SnapDiff
5
5
  # end-of-suite finalization. One list of reporters for the whole process,
6
6
  # guarded by one mutex.
7
7
  #
8
- # Deliberately separate from the per-test session lifecycle (the
9
- # thread-local AssertionRegistry reached through
10
- # CapybaraScreenshotDiff.registry): reporters outlive any single test,
11
- # the registry does not. CapybaraScreenshotDiff keeps its public
8
+ # Deliberately separate from the per-test session lifecycle
9
+ # (SnapDiff.session): reporters outlive any single test, the session does
10
+ # not. CapybaraScreenshotDiff keeps its public
12
11
  # reporters/reporters_mutex/finalize_reporters! methods as thin shims
13
12
  # over this module.
14
13
  module Reporting
@@ -18,6 +17,16 @@ module SnapDiff
18
17
  class << self
19
18
  attr_reader :reporters, :mutex
20
19
 
20
+ # Registers a reporter for the rest of the process. The canonical way
21
+ # in: the append happens under the mutex, so concurrent registrations
22
+ # cannot lose one (issue #217 item 2). `reporters` stays public and
23
+ # mutable for compatibility -- appending to it directly still works,
24
+ # it just skips the lock.
25
+ def register(reporter)
26
+ @mutex.synchronize { @reporters << reporter }
27
+ reporter
28
+ end
29
+
21
30
  # Delivers a finished test's assertions to every registered reporter.
22
31
  # Iterates over a snapshot so a reporter mutating the list mid-notify
23
32
  # cannot affect the current round. A raising reporter is warned about
@@ -2,8 +2,42 @@
2
2
 
3
3
  require "fileutils"
4
4
  require_relative "screenshot_namer"
5
+ require_relative "reporting"
5
6
 
6
7
  module SnapDiff
8
+ # --- Session lifecycle (per-test) ---
9
+ #
10
+ # The canonical home of the per-test session: the AssertionRegistry
11
+ # holding the assertions and new-screenshot names of the test running
12
+ # here. CapybaraScreenshotDiff.registry / .reset /
13
+ # .pending_screenshots_message are thin forwarders over these.
14
+ #
15
+ # Note: Thread.current[] is *fiber*-local, so a session is per fiber, not
16
+ # per thread. That is pre-existing, documented behaviour (issue #217);
17
+ # ADR-008 step 6 relocates the accessor without touching the semantics.
18
+ def self.session
19
+ Thread.current[:capybara_screenshot_diff_registry] ||= AssertionRegistry.new
20
+ end
21
+
22
+ # Ends a test: hands the finished session's assertions to the reporters,
23
+ # then clears it. The one deliberate bridge between the process-global
24
+ # reporter lifecycle (SnapDiff::Reporting) and the per-test session.
25
+ def self.reset
26
+ Reporting.notify(session.assertions)
27
+ session.reset
28
+ end
29
+
30
+ # Message to skip the test with when a new screenshot has no baseline yet
31
+ # and `pending_if_new` is enabled. Adapters call this after verifying
32
+ # screenshots, and skip the test with the returned message when present.
33
+ #
34
+ # @return [String, nil] the pending message, or nil when there is nothing to report
35
+ def self.pending_screenshots_message
36
+ return unless ::Capybara::Screenshot::Diff.pending_if_new && session.new_screenshots_present?
37
+
38
+ "No baseline for: #{session.new_screenshots.join(", ")}. Commit the captured screenshots to record them."
39
+ end
40
+
7
41
  class ScreenshotAssertion
8
42
  attr_reader :name, :args
9
43
  attr_accessor :compare, :caller
@@ -52,7 +86,7 @@ module SnapDiff
52
86
  error_msg = validate
53
87
 
54
88
  if error_msg
55
- raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg, caller)
89
+ raise SnapDiff::ExpectationNotMet.new(error_msg, caller)
56
90
  end
57
91
  end
58
92
 
@@ -118,15 +152,14 @@ module SnapDiff
118
152
  !@new_screenshots.empty?
119
153
  end
120
154
 
121
- def verify(screenshots = CapybaraScreenshotDiff.assertions)
155
+ def verify(screenshots = assertions)
122
156
  return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
123
157
 
124
- failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
125
158
  failed_screenshot = failed_assertions.first
126
159
  result = ScreenshotAssertion.verify_screenshots!(screenshots)
127
160
 
128
161
  if result
129
- raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
162
+ raise SnapDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
130
163
  end
131
164
  end
132
165
 
@@ -21,7 +21,7 @@ module SnapDiff
21
21
  end
22
22
 
23
23
  def build_screenshot_assertion(skip_stack_frames: 0)
24
- Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil)
24
+ Capture::Viewport.prepare!(Capybara::Screenshot.window_size)
25
25
  prepare_screenshot_options
26
26
  check_base_screenshot
27
27
 
@@ -32,7 +32,7 @@ module SnapDiff
32
32
  # Pre-computation: No need to compare without base screenshot
33
33
  # NOTE: Consider to return PreValid Assertion Value Object with hard coded valid result
34
34
  unless need_to_compare?
35
- CapybaraScreenshotDiff.record_new_screenshot(screenshot_full_name)
35
+ SnapDiff.session.record_new_screenshot(screenshot_full_name)
36
36
  return
37
37
  end
38
38
 
@@ -41,7 +41,7 @@ module SnapDiff
41
41
 
42
42
  # Captures a screenshot without comparing it to a baseline.
43
43
  def capture
44
- Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil)
44
+ Capture::Viewport.prepare!(Capybara::Screenshot.window_size)
45
45
  prepare_screenshot_options
46
46
 
47
47
  capture_options, comparison_options = extract_capture_and_comparison_options(driver_options)
@@ -68,7 +68,7 @@ module SnapDiff
68
68
  @snapshot.checkout_base_screenshot
69
69
 
70
70
  if Capybara::Screenshot::Diff.fail_if_new && !@snapshot.base_path.exist?
71
- raise CapybaraScreenshotDiff::ExpectationNotMet.new(<<~ERROR.chomp, caller)
71
+ raise SnapDiff::ExpectationNotMet.new(<<~ERROR.chomp, caller)
72
72
  No existing screenshot found for #{@snapshot.base_path}!
73
73
  To record baselines: RECORD_SCREENSHOTS=1 bundle exec rake test
74
74
  To allow new screenshots: Capybara::Screenshot::Diff.fail_if_new = false
@@ -88,7 +88,7 @@ module SnapDiff
88
88
  break unless pending_image
89
89
 
90
90
  if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline_at
91
- raise CapybaraScreenshotDiff::ExpectationNotMet.new("Images have not been loaded after #{timeout}s: #{pending_image.inspect}", caller)
91
+ raise SnapDiff::ExpectationNotMet.new("Images have not been loaded after #{timeout}s: #{pending_image.inspect}", caller)
92
92
  end
93
93
 
94
94
  sleep 0.025
@@ -3,7 +3,6 @@
3
3
  require "fileutils"
4
4
 
5
5
  require_relative "vcs"
6
- require "active_support/core_ext/module/attribute_accessors"
7
6
 
8
7
  require_relative "snap"
9
8
 
@@ -100,7 +99,7 @@ module SnapDiff
100
99
  # manager class or screenshot root changes (tests re-root per example).
101
100
  #
102
101
  # Thread.current[] is fiber-local — same construct as
103
- # CapybaraScreenshotDiff.registry; both split per fiber and must migrate
102
+ # SnapDiff.session; both split per fiber and must migrate
104
103
  # together if async support ever lands.
105
104
  #
106
105
  # Scope of the fix: default-root unit tests and before_setup re-rooters
@@ -34,7 +34,7 @@ module SnapDiff
34
34
  #
35
35
  # @param snapshot Snap The snapshot details to take a stable screenshot of.
36
36
  # @return [void]
37
- # @raise [CapybaraScreenshotDiff::UnstableImage] If a stable screenshot cannot be obtained within the specified `:wait` time.
37
+ # @raise [SnapDiff::UnstableImage] If a stable screenshot cannot be obtained within the specified `:wait` time.
38
38
  def take_comparison_screenshot(snapshot)
39
39
  result = take_stable_screenshot(snapshot)
40
40
 
@@ -43,7 +43,7 @@ module SnapDiff
43
43
  # The failed-stability outcome is data (the annotated attempts report); raising is this
44
44
  # boundary's decision, not the reporter's.
45
45
  # FIXME(uwe): Hand the failure to the verify path so it only reports after the test's own assertions run.
46
- raise CapybaraScreenshotDiff::UnstableImage.new(stability_failure_report(snapshot), caller)
46
+ raise SnapDiff::UnstableImage.new(stability_failure_report(snapshot), caller)
47
47
  end
48
48
 
49
49
  # store success attempt as actual screenshot
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "snap_diff/drivers"
4
+
3
5
  module SnapDiff
4
6
  module Utils
5
7
  def self.detect_available_drivers
@@ -20,9 +22,9 @@ module SnapDiff
20
22
  end
21
23
 
22
24
  def self.find_driver_class_for(driver)
23
- driver = Capybara::Screenshot::Diff::AVAILABLE_DRIVERS.first if driver == :auto
25
+ driver = Drivers.available.first if driver == :auto
24
26
 
25
- Capybara::Screenshot::Diff::LOADED_DRIVERS[driver] ||=
27
+ Drivers.loaded[driver] ||=
26
28
  case driver
27
29
  when :chunky_png
28
30
  require "snap_diff/drivers/chunky_png_driver"
@@ -31,7 +33,7 @@ module SnapDiff
31
33
  require "snap_diff/drivers/vips_driver"
32
34
  SnapDiff::Drivers::VipsDriver
33
35
  else
34
- fail "Wrong adapter #{driver.inspect}. Available adapters: #{Capybara::Screenshot::Diff::AVAILABLE_DRIVERS.inspect}"
36
+ fail "Wrong adapter #{driver.inspect}. Available adapters: #{Drivers.available.inspect}"
35
37
  end
36
38
  end
37
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SnapDiff
4
- VERSION = "2.0.0.alpha1"
4
+ VERSION = "2.0.0.beta2"
5
5
  end
data/lib/snap_diff.rb CHANGED
@@ -1,7 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # None of these requires ever leads back to this file: config_legacy is a
4
- # leaf (see its own header comment), the image_compare forwarder pulls in
3
+ # Dual-install guard: capybara-screenshot-diff and snap_diff-capybara ship
4
+ # identical files. With BOTH activated, every require silently resolves
5
+ # from whichever gem activated first, so version skew between the two is
6
+ # undetectable. Refuse that setup at the entry point. Local dev from
7
+ # source loads neither spec, so the guard fires only when both are
8
+ # genuinely installed as gems.
9
+ module SnapDiff
10
+ DualInstallError = Class.new(StandardError)
11
+
12
+ # @api private
13
+ def self.assert_single_gem!(loaded_specs = Gem.loaded_specs)
14
+ return unless loaded_specs.key?("capybara-screenshot-diff") && loaded_specs.key?("snap_diff-capybara")
15
+
16
+ raise DualInstallError,
17
+ "Both `capybara-screenshot-diff` and `snap_diff-capybara` gems are installed. " \
18
+ "They ship identical files, so files load from whichever gem activated first " \
19
+ "and versions can silently diverge. Remove one of them from your Gemfile."
20
+ end
21
+ end
22
+ SnapDiff.assert_single_gem!
23
+
24
+ # None of these requires ever leads back to this file: config_legacy sits
25
+ # just above the snap_diff/config leaf (see both headers -- since ADR-008
26
+ # step 1 the storage leaf is snap_diff/config, which config_legacy
27
+ # requires), the image_compare forwarder pulls in
5
28
  # snap_diff/comparison plus the legacy const_missing shims, and
6
29
  # "capybara/dsl" is the base gem. That keeps this file's require graph
7
30
  # acyclic -- it never requires "capybara_screenshot_diff" back, unlike the
@@ -11,12 +34,13 @@
11
34
  # stay resolvable (now with deprecation warnings) even in processes that
12
35
  # only ever require "snap_diff".
13
36
  # "capybara/dsl" is needed directly (not just transitively) so
14
- # `Capybara.default_max_wait_time` in Diff.default_options resolves even
37
+ # `Capybara.default_max_wait_time` in Config#default_options resolves even
15
38
  # when "snap_diff" is required standalone (SnapDiffTest's
16
39
  # "standalone-loadable in a fresh process" regression test).
17
40
  require "capybara/dsl"
18
41
  require "capybara/screenshot/diff/config_legacy"
19
42
  require "capybara/screenshot/diff/image_compare"
43
+ require "snap_diff/errors"
20
44
  require "snap_diff/config"
21
45
 
22
46
  # Forward-looking namespace for the gem, per ADR-004.
@@ -27,22 +51,28 @@ require "snap_diff/config"
27
51
  # shims (snap_diff/legacy_shims) that emit a deprecation warning once per
28
52
  # constant per process. See ADR-004 for the full migration plan.
29
53
  module SnapDiff
30
- def self.compare(...)
31
- Capybara::Screenshot::Diff.compare(...)
54
+ # Compare two images on disk with the configured defaults. Canonical home
55
+ # since ADR-008 step 7b; +Capybara::Screenshot::Diff.compare+ now forwards
56
+ # here rather than the other way round.
57
+ #
58
+ # Note the argument order swap: callers pass baseline first (reading
59
+ # "compare baseline against current"), Comparison takes current first.
60
+ def self.compare(baseline_path, current_path, **options)
61
+ Comparison.new(current_path, baseline_path, config.default_options.merge(options))
32
62
  end
33
63
 
34
- # v1-style configuration: yields the two existing mattr_accessor holders
64
+ # v1-style configuration: yields the two legacy accessor holders
35
65
  # (+Capybara::Screenshot+, +Capybara::Screenshot::Diff+) exactly as
36
- # +Capybara::Screenshot::Diff.configure+ always has. Kept byte-for-byte
37
- # identical to +Diff.configure+ for existing callers migrating namespaces
38
- # without changing call shape.
66
+ # +Capybara::Screenshot::Diff.configure+ always has -- and, since ADR-008
67
+ # step 7b, this is where that yield actually happens; Diff.configure
68
+ # forwards here. Both names stay identical in call shape.
39
69
  #
40
70
  # SnapDiff.start do |screenshot, diff|
41
71
  # screenshot.window_size = [1280, 1024]
42
72
  # diff.tolerance = 0.0005
43
73
  # end
44
- def self.start(&block)
45
- Capybara::Screenshot::Diff.configure(&block)
74
+ def self.start
75
+ yield Capybara::Screenshot, Capybara::Screenshot::Diff
46
76
  end
47
77
 
48
78
  # Forward-looking configuration: yields the single consolidated
@@ -58,8 +88,7 @@ module SnapDiff
58
88
  yield config
59
89
  end
60
90
 
61
- # The single consolidated settings object. See {SnapDiff::Config}.
62
- def self.config
63
- @config ||= Config.new
64
- end
91
+ # SnapDiff.config itself is defined in snap_diff/config.rb (the storage
92
+ # leaf), where the Config instance is created eagerly at require time so
93
+ # the ENV["CI"] / Rails.root defaults evaluate at load, not first call.
65
94
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snap_diff-capybara
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha1
4
+ version: 2.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uwe Kubosch
@@ -94,6 +94,7 @@ files:
94
94
  - docs/migration-guide.md
95
95
  - docs/organization.md
96
96
  - docs/reporters.md
97
+ - docs/snapdiff.md
97
98
  - docs/thread_safety.md
98
99
  - gems.rb
99
100
  - lib/capybara-screenshot-diff.rb
@@ -148,12 +149,15 @@ files:
148
149
  - lib/snap_diff/drivers/vips_driver.rb
149
150
  - lib/snap_diff/dsl.rb
150
151
  - lib/snap_diff/error_with_filtered_backtrace.rb
152
+ - lib/snap_diff/errors.rb
151
153
  - lib/snap_diff/image_preprocessor.rb
152
154
  - lib/snap_diff/integrations/cucumber.rb
153
155
  - lib/snap_diff/integrations/minitest.rb
154
156
  - lib/snap_diff/integrations/rspec.rb
155
157
  - lib/snap_diff/legacy_shims.rb
156
158
  - lib/snap_diff/os.rb
159
+ - lib/snap_diff/region.rb
160
+ - lib/snap_diff/reporters/default.rb
157
161
  - lib/snap_diff/reporters/html.rb
158
162
  - lib/snap_diff/reporters/templates/report.html.erb
159
163
  - lib/snap_diff/reporting.rb