diff --git a/CHANGELOG.md b/CHANGELOG.md index ac139364..09bc11c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -179,8 +179,8 @@ and assigning `nil` hands the setting back to the environment. - **Accepting an intentional change is one setting, not folklore.** `record` replaces `fail_if_new` (removed in 2.1): `:once` records a missing baseline and compares the rest — what a local run already did; `:none` **fails** on a missing baseline with the `git add` - command attached, and is what CI gets by default; `:all` re-records every screenshot and - compares nothing, the bulk-accept verb for the run after an intentional redesign. 1.15.1 + command attached, and is what CI gets by default; `:all` compares like `:once` but accepts + what differs instead of failing, the bulk-accept verb for the run after an intentional redesign. 1.15.1 documented `RECORD_SCREENSHOTS=1` in four files and printed it in its own error message, and nothing in the gem ever read that variable — accepting a change was answered in issue comments for six years and never in the docs. See `docs/configuration.md` (#259) @@ -331,17 +331,35 @@ One removal 2.0 cannot warn about, written down instead: the legacy `LOADED_DRIV the name to install; the mirror reserves the forward-looking one. **Install one, never both.** - **Reporters can list the screenshots that were not compared.** - `SnapDiff::Reporting.uncompared` returns the ones behind the summary line's "new" and - "re-recorded" counts, complete by the time reporters `finalize` (fork-parallel workers - included): each with its `name`, the captured file's `path`, and the `reason` — `:new` - (no committed baseline) or `:rerecorded` (`record: :all`). The bundled HTML report lists - new screenshots from it + `SnapDiff::Reporting.uncompared` returns the ones behind the summary line's "new" + count, complete by the time reporters `finalize` (fork-parallel workers included): each + with its `name`, the captured file's `path`, and the `status` (`:new`, no committed + baseline). The bundled HTML report lists new screenshots from it - New documentation shipped inside the gem: [docs/snapdiff.md](docs/snapdiff.md) (canonical API) and [docs/UPGRADING.md](docs/UPGRADING.md) (every renamed constant, which names warn, rollback) ### Changed +- **`record: :all` now compares before re-recording and the report shows what changed.** + A screenshot that differs from its committed baseline is accepted as the new baseline + (it never fails the run) and leaves the usual `.base.png` and diff/heatmap files, + which the recommended `.gitignore` already ignores; a screenshot that matches is left + as committed. The summary line counts the accepted ones in a parenthesis — + `12 verified, 3 changed (3 re-recorded), 0 new (not verified).` — and the HTML report + lists each of them with its baseline, current and heatmap views and a "re-recorded" + badge, and its title and pill use the same `N changed (R re-recorded)` wording as the + terminal (the title used to say "failures"). Report entries gain a `status` (`changed`, + `new` or `rerecorded`) in place of `new_screenshot`; an entry the report could not + render also carries an `error`. +- **Reporters now receive every screenshot, each with a `status`.** New and re-recorded + (`record: :all`) captures used to bypass them; now every capture is a + `SnapDiff::ScreenshotAssertion` whose `status` is `:passed`, `:changed`, `:new` or + `:rerecorded` (`compare` is nil when there was no baseline). Reading `status` never + forces a comparison. A custom reporter that treated "has an assertion" as "was + compared" should test `assertion.status == :changed`, as the documented idiom and + `AISimple` now do. `pending_if_new` now also sees a new screenshot under `record: :none`, + where it is registered before the "no existing screenshot" failure, not only under `:once` - **No `activesupport` at runtime.** 1.15.1's `snap_manager.rb` required `active_support/core_ext/module/attribute_accessors` while `activesupport` was only a *development* dependency, so a bundle without Rails died at @@ -450,8 +468,7 @@ One removal 2.0 cannot warn about, written down instead: the legacy `LOADED_DRIV baseline appears after the failures, marked `new`, with its capture and the `git add` that makes it the baseline; a run whose only finding is a new screenshot now writes a report. The report's `new` count is the summary line's, including - across `parallelize(workers: N)`. Re-recorded screenshots (`record: :all`) are - counted in the report but not yet listed + across `parallelize(workers: N)` - **Under `record: :none` a new screenshot is counted.** The run still fails as before; the summary line now says `1 new` instead of `0 new` - **A committed baseline taken by a same-name test is reported as disappeared, in every diff --git a/README.md b/README.md index c7ff066c..c8e6bc8c 100644 --- a/README.md +++ b/README.md @@ -211,15 +211,16 @@ SnapDiff.config.record = :all unless ENV["ACCEPT_SCREENSHOTS"].to_s.empty? ```bash ACCEPT_SCREENSHOTS=1 bin/rails test:system -# [snap_diff] record: :all re-recorded 40 screenshots WITHOUT comparing: ... +# [snap_diff] 212 verified, 40 changed (40 re-recorded), 0 new (not verified). git status # forty modified baselines, and nothing else git add doc/screenshots/ git commit -m "chore: re-record baselines after the checkout redesign" ``` -`:all` writes every capture straight to its baseline path and compares nothing, so `git status` -is the review surface — look at the images before you commit. It **refuses to run under CI**, +`:all` compares every capture to its committed baseline like `:once` does, but a screenshot that +differs is accepted as the new baseline instead of failing the test — the report lists what changed, +and `git status` is the review surface — look at the images before you commit. It **refuses to run under CI**, where there would be nobody to look. The other modes are `:once` (the default) and `:none` (strict — a missing baseline always fails); see [Record modes](docs/configuration.md#record-modes--accepting-changes). diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index c18a420b..44a57e92 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -319,7 +319,7 @@ actually want, which is *accept this change*. 2.0 adds the verb: ```ruby SnapDiff.config.record = :once # default. Record a screenshot that has no baseline. SnapDiff.config.record = :none # strict. A missing baseline always fails. -SnapDiff.config.record = :all # re-record everything. THE BULK-ACCEPT MODE. +SnapDiff.config.record = :all # compare, and accept what differs. THE BULK-ACCEPT MODE. ``` | Old | New | Note | @@ -342,8 +342,8 @@ missing-baseline default is deliberately unchanged; `:none` is how you opt into explicitly. **Precedence:** an explicitly set mode outranks `fail_if_new`, and `fail_if_new` decides only when no mode was set — the same rule `fail_if_new` itself has over the `CI` sniff. -`record = :all` **refuses to run under CI**: it accepts every rendering by design, so left in a -committed config file it would be a build that compares nothing and passes forever. See +`record = :all` **refuses to run under CI**: it accepts every difference by design, so left in a +committed config file it would be a build that never fails. See [Record modes](configuration.md#record-modes--accepting-changes). #### Silent by design diff --git a/docs/architecture.md b/docs/architecture.md index dd16ca4d..194cf689 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -186,14 +186,15 @@ Test begins │ ├─ test body: │ └─ screenshot("name") - │ ├─ ScreenshotMatcher builds assertion - │ ├─ add to Thread-local registry (always) - │ └─ delayed=false → also validate immediately + │ ├─ ScreenshotMatcher builds an assertion for every capture + │ │ (compared, new, or re-recorded) and registers it in the + │ │ Thread-local registry — before any raise + │ └─ delayed=false → the DSL also validates it immediately │ ├─ teardown: │ ├─ SnapDiff.session.verify │ │ ├─ iterates the fiber-local assertions - │ │ ├─ calls validate on each + │ │ ├─ calls validate on each (new and re-recorded ones do not fail) │ │ └─ raises SnapDiff::ExpectationNotMet if any differed │ │ │ └─ SnapDiff.reset (always, in an ensure) diff --git a/docs/ci-integration.md b/docs/ci-integration.md index 905e9f16..1d37c886 100644 --- a/docs/ci-integration.md +++ b/docs/ci-integration.md @@ -273,7 +273,7 @@ jobs: > than relying on the files being there. > > **`record = :all` is not the mode for this job** — it refuses to run under CI, because it -> would accept every *changed* rendering unreviewed as well. `:once` records what is new and +> would accept every *changed* rendering unreviewed and never fail. `:once` records what is new and > keeps comparing everything that already has a baseline. **How it works:** diff --git a/docs/configuration.md b/docs/configuration.md index bbb65a65..a7d6591f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -63,7 +63,7 @@ SnapDiff.config.record = :once # what a local run already does; see Precedence |------|------------------|------------------|-------------------| | `:once` | recorded, not compared | compared | the default — you want a normal run | | `:none` | **fails** with the `git add` command attached | compared | every screenshot must already be recorded | -| `:all` | recorded | **re-recorded, not compared** | you changed the UI on purpose and want the new rendering to become the baseline | +| `:all` | recorded | compared; **re-recorded on difference, never fails** | you changed the UI on purpose and want the new rendering to become the baseline | ### `:all` — the bulk-accept verb @@ -82,19 +82,22 @@ SnapDiff.config.record = :all unless ENV["ACCEPT_SCREENSHOTS"].to_s.empty? ACCEPT_SCREENSHOTS=1 bin/rails test:system # with the line above in test_helper.rb ``` -Every screenshot is written to its baseline path and nothing is compared, so `git status` lists -exactly what changed. Review the images, then commit them — see +Every screenshot is compared to its committed baseline, as under `:once`. One that differs is kept +as the new baseline instead of failing the test, and leaves the usual `.base.png` and diff files +(ignored by the recommended `.gitignore`); one that matches changes nothing. So `git status` lists +exactly what changed, and the HTML report shows each change as "re-recorded". Review the images, then commit them — see [Accepting an intentional change](../README.md#accepting-an-intentional-change). At the end of the run the gem names what it accepted: ``` -[snap_diff] record: :all re-recorded 3 screenshots WITHOUT comparing: checkout/cart, checkout/payment, checkout/review. Review the result before committing -- an unintended change is accepted just as silently. +[snap_diff] 212 verified, 3 changed (3 re-recorded), 0 new (not verified). +[snap_diff] record: :all compared 3 screenshots, found them different and accepted them as the new baseline: checkout/cart, checkout/payment, checkout/review. `git add` the file(s) to keep the change, `git restore -- ` to discard it. The `.base.` and diff files left beside each one are covered by the recommended .gitignore. ``` > **`:all` refuses to run under CI** (when `ENV['CI']` is set to a non-empty value). It accepts -> every rendering by design, so a mode left in a committed config file would buy you a build that -> compares nothing and passes forever, with the "recorded" screenshots discarded when the runner +> every difference by design, so a mode left in a committed config file would buy you a build that +> never fails, with the "recorded" screenshots discarded when the runner > is torn down. Re-record locally, where you can look at the result. > > A CI job that needs to record screenshots with **no baseline yet** does not need `:all`: diff --git a/docs/images/snap_diff_assertion_flow.html b/docs/images/snap_diff_assertion_flow.html index 7ea1318a..34049ce2 100644 --- a/docs/images/snap_diff_assertion_flow.html +++ b/docs/images/snap_diff_assertion_flow.html @@ -139,8 +139,8 @@

What one assert_matches_screenshot actually does

HTML + summary line - record: :all sends every screenshot - down the right-hand path — baseline or not. + record: :all still compares; it accepts + a difference instead of failing on it. diff --git a/docs/reporters.md b/docs/reporters.md index 1452aada..b70d50b1 100644 --- a/docs/reporters.md +++ b/docs/reporters.md @@ -102,7 +102,7 @@ Build your own reporter by implementing `record`, `finalize` and `summary`: class MyReporter def record(assertions) assertions.each do |assertion| - next unless assertion.compare&.difference&.different? + next unless assertion.status == :changed # process the failure — send to Slack, write JSON, etc. end end @@ -130,11 +130,15 @@ the framework's end-of-suite hook (`Minitest.after_run`, RSpec `after(:suite)`, finalized and then warned about. A reporter that raises is warned about and skipped — the others still run. -`record` receives every assertion, but only a compared one carries a `compare`; the rest have -`compare: nil` and no path or reason of their own. Those — each new (`reason: :new`) or -re-recorded (`reason: :rerecorded`) screenshot, with its `name` and captured `path` — are in -`SnapDiff::Reporting.uncompared`, complete by the time `finalize` runs (fork-parallel workers -included). The HTML report lists new screenshots from it. +`record` receives every assertion — one per capture. `assertion.status` says what you hold: +`:passed` (matches its committed baseline), `:changed` (differs), `:new` (no committed +baseline) or `:rerecorded` (accepted by `record: :all`). `compare` is nil for a screenshot +without a baseline; `assertion.path` is the captured file either way. Test `status`, not +`compare`, to find failures: the tally has already run every comparison by the time `record` +is called, and reading `status` never forces one. `:rerecorded` was compared and differed, +like `:changed`, but `record: :all` accepted it. The new screenshots are also in +`SnapDiff::Reporting.uncompared` (each with `name`, `path` and `status`), complete by the time +`finalize` runs (fork-parallel workers included); `status` is the contract for everything else. Full details in [Custom reporters](snapdiff.md#custom-reporters). @@ -146,4 +150,7 @@ default, pluggable backends) and its verdicts show up in this HTML report as badges. Advisory by default — it never changes pass/fail unless you opt in with `fail_on: %w[real_bug]`. Setup and recipes: [AI triage](ai.md). +It triages `:changed` comparisons only. A `:rerecorded` one was accepted by `record: :all`, so it is +not analysed, on purpose: a bulk accept should not cost a backend call per screenshot. + [← Back to README](../README.md) diff --git a/docs/snapdiff.md b/docs/snapdiff.md index e27bd61f..1fd8c576 100644 --- a/docs/snapdiff.md +++ b/docs/snapdiff.md @@ -231,7 +231,7 @@ class SlackReporter # Called once per finished test, with that test's assertions. def record(assertions) assertions.each do |assertion| - next unless assertion.compare&.difference&.different? + next unless assertion.status == :changed @failures << assertion.name end end @@ -258,16 +258,19 @@ and then warned about (`[snap_diff] Reporter … failed (NoMethodError: undefine A reporter that raises is warned about and skipped — the other reporters still run. -`record` receives every assertion, but a screenshot that was not compared arrives with -`compare: nil` and nothing else. Those — the summary line's "new" and "re-recorded" — are in -`SnapDiff::Reporting.uncompared`, complete by the time `finalize` runs (fork-parallel workers -included): +`record` receives every assertion — one per capture. `assertion.status` says what you hold: +`:passed`, `:changed`, `:new` (no committed baseline) or `:rerecorded` (differs, accepted by `record: :all`). +`compare` is nil for a screenshot without a baseline; `assertion.path` is the captured file +either way. The tally has already run every comparison by the time `record` is called, so +test `status == :changed` rather than digging into `compare`; reading `status` never forces one. +The new screenshots — the summary line's "new" — are also in `SnapDiff::Reporting.uncompared`, +complete by the time `finalize` runs (fork-parallel workers included): ```ruby def finalize - new_ones = SnapDiff::Reporting.uncompared.select { |s| s.reason == :new } + new_ones = SnapDiff::Reporting.uncompared # each has #name, #path (the captured file as a Pathname, or nil when it isn't known) - # and #reason (:new or :rerecorded); check s.path&.exist? before reading it + # and #status (:new); check s.path&.exist? before reading it end ``` diff --git a/lib/snap_diff/config.rb b/lib/snap_diff/config.rb index 8c4442e8..3ff9a5ab 100644 --- a/lib/snap_diff/config.rb +++ b/lib/snap_diff/config.rb @@ -190,15 +190,16 @@ def fail_on_difference=(value) end # THE ACCEPT WORKFLOW (#259). What to do about a screenshot whose - # baseline is missing -- or, for +:all+, about every screenshot there - # is. VCR-shaped: modes in config, because there is no runner to hang a + # baseline is missing -- or, for +:all+, about every screenshot that + # differs. VCR-shaped: modes in config, because there is no runner to hang a # CLI flag on. # # +:once+:: (default) record a screenshot that has no committed # baseline; compare against the baseline when there is one. # +:none+:: strict. A missing baseline always fails. - # +:all+:: re-record. Every screenshot is written as the new baseline - # and nothing is compared -- the bulk-accept verb, for the + # +:all+:: +:once+ that accepts instead of fails. Every baseline is + # compared; a screenshot that differs is kept as the new + # baseline and never raises -- the bulk-accept verb, for the # redesign that changed forty screenshots at once. Refused # under CI (see {SnapDiff::ScreenshotMatcher}). # diff --git a/lib/snap_diff/dsl.rb b/lib/snap_diff/dsl.rb index f92f8a32..40a982de 100644 --- a/lib/snap_diff/dsl.rb +++ b/lib/snap_diff/dsl.rb @@ -76,7 +76,8 @@ def screenshot_group(name) # so a preceding `preload_all_images` still costs its browser # round-trips while the block costs nothing. Readiness work belongs # inside the same switch as the capture it serves. - # @return [Boolean] True if the screenshot was successfully captured and processed. + # @return [Boolean] True if the screenshot was compared against a baseline; false when it had none (it is + # captured and registered as a `:new` assertion) or screenshots are disabled. # @raise [SnapDiff::ExpectationNotMet] If comparison fails and immediate validation is enabled. # @raise [SnapDiff::UnstableImage] If the image comparison is unstable. # @raise [SnapDiff::WindowSizeMismatchError] If the window size doesn't match expectations. @@ -94,15 +95,11 @@ def assert_matches_screenshot(name, skip_stack_frames: 0, **options) .new(full_name, options) .build_screenshot_assertion(skip_stack_frames: skip_stack_frames + 1) - return false unless assertion - - # Registered either way: the end-of-run tally and the reporters see - # every finished assertion, immediate ones included. An immediate one - # validates here and is a no-op when the registry verifies at teardown. - SnapDiff.session.add_assertion(assertion) + # Already registered by the matcher. An immediate one validates here + # and is a no-op when the registry verifies at teardown. assertion.validate! unless options.fetch(:delayed, SnapDiff.config.delayed) - true + !assertion.compare.nil? end # Convenience wrapper around {#assert_matches_screenshot} and {#capture_screenshot}. diff --git a/lib/snap_diff/reporters/ai_simple.rb b/lib/snap_diff/reporters/ai_simple.rb index 25b810f6..372c16d9 100644 --- a/lib/snap_diff/reporters/ai_simple.rb +++ b/lib/snap_diff/reporters/ai_simple.rb @@ -44,10 +44,9 @@ def record(assertions) return unless @backend assertions.each do |a| - difference = a.compare&.difference - next unless difference&.different? + next unless a.status == :changed - analyze_once(a.name, difference) + analyze_once(a.name, a.compare.difference) end end diff --git a/lib/snap_diff/reporters/html.rb b/lib/snap_diff/reporters/html.rb index c1e5822f..3f4e4105 100644 --- a/lib/snap_diff/reporters/html.rb +++ b/lib/snap_diff/reporters/html.rb @@ -14,12 +14,16 @@ module SnapDiff module Reporters class HTML - attr_reader :failures, :total + # One entry per screenshot that was not a plain pass, each with a + # `status` ("changed", "rerecorded" or "new") and, when the report + # could not render it, an `error` beside it. + attr_reader :entries, :total def initialize(output_path: nil, embed_images: false) @explicit_output_path = output_path @embed_images = embed_images - @failures = [] + @entries = [] + @new_names = Set.new @total = 0 @finalized = false @mutex = Mutex.new @@ -28,39 +32,38 @@ def initialize(output_path: nil, embed_images: false) def record(assertions) return if @finalized - failures = [] + entries = [] total = 0 assertions.each do |assertion| - counted = false - compare = assertion.compare - next unless compare - - total += 1 - counted = true - next unless compare.difference&.different? - - failures << failure_entry_for(assertion.name, compare) - rescue => e - # A report bug must not hide a screenshot: warn, and keep it listed - # (counted as failed: the report can't vouch for it). - error = "#{e.class}: #{e.message}" - warn "[snap_diff] HTML report: could not render '#{assertion.name}' (#{error})" - total += 1 unless counted - failures << {name: assertion.name, error: error} + status = assertion.status + entry = begin + entry_for(assertion, status) unless status == :passed + rescue => e + # A report bug must not hide a screenshot: warn, and keep it + # listed with the status it has and the error beside it. The + # error says the report could not render it, not what became of + # the screenshot. + error = "#{e.class}: #{e.message}" + warn "[snap_diff] HTML report: could not render '#{assertion.name}' (#{error})" + {name: assertion.name, status: status.to_s, error: error} + end + + total += 1 unless status == :new + entries << entry if entry end @mutex.synchronize do return if @finalized @total += total - @failures.concat(failures) + add_entries(entries) end end def finalize @mutex.synchronize do return if @finalized - return if failures.empty? && new_count.zero? + return if entries.empty? write_report @finalized = true @@ -78,23 +81,34 @@ def output_path # file is discarded rather than half-read. Image paths were resolved # against `output_path`, which is the same in both processes. def dump_state - @mutex.synchronize { {"total" => @total, "failures" => @failures} } + @mutex.synchronize { {"total" => @total, "entries" => @entries} } end def merge_state!(state) @mutex.synchronize do - @total += state["total"] - @failures.concat(state["failures"].map { |entry| entry.transform_keys(&:to_sym) }) + @total += state.fetch("total") + add_entries(state.fetch("entries").map { |entry| entry.transform_keys(&:to_sym) }) end end - def passed = total - failures.size - def failed = failures.size - # Screenshots captured but not compared: new ones are listed, - # re-recorded ones only counted. - def new_screenshots = SnapDiff::Reporting.uncompared.select { |screenshot| screenshot.reason == :new } - def new_count = new_screenshots.size - def rerecorded_count = SnapDiff::Reporting.uncompared.size - new_count + # The entries that were compared and differed, and not accepted: + # `:changed`. (Ones the report could not render keep their status.) + def changed_entries = entries.select { |entry| entry[:status] == "changed" } + + def new_count = count_entries("new") + def rerecorded_count = count_entries("rerecorded") + + # What the report shows as "changed", like the terminal summary: the + # ones that differed, re-recorded included. + def changed_count = changed_entries.size + rerecorded_count + + # Shipped names (2.0.0.beta4), kept as shims: what is "changed" here + # was "failures" there, and the re-recorded ones are not part of it. + def failures = changed_entries + + def failed = changed_entries.size + + def passed = total - changed_count # Both customer personas named this path as the best output in the # product, so it gets its own line -- but only when a report was @@ -112,7 +126,6 @@ def summary # Attach available contributions and return the rendered HTML report. def render attach_annotations - @entries = failures + new_screenshot_entries ERB.new(File.read(self.class.template_path)).result(binding) end @@ -127,8 +140,28 @@ def self.default_output_path private + # Under the mutex. A name with no baseline is one new screenshot + # however many times it was captured (as Reporting counts it). + def add_entries(entries) + entries.each do |entry| + next if entry[:status] == "new" && !@new_names.add?(entry[:name]) + + @entries << entry + end + end + + def count_entries(status) = entries.count { |entry| entry[:status] == status } + + def entry_for(assertion, status) + if status == :new + new_screenshot_entry(assertion.name, assertion.path) + else + compared_entry_for(assertion.name, assertion.compare).merge(status: status.to_s) + end + end + # Build a report entry from a comparison, omitting unavailable images and metrics. - def failure_entry_for(name, compare) + def compared_entry_for(name, compare) { name: name, original: resolve_image(compare.base_image_path), @@ -147,11 +180,11 @@ def failure_details(compare) end # An input that vanished before the comparison leaves nothing to diff - # or annotate. The entry carries the error so the page says so, instead - # of inferring "pass" from the absent diff keys. resolve_image already - # drops the missing file from original/new. + # or annotate. The entry keeps its status (it was compared and differs) + # and carries the error so the page says why there is nothing to show. + # resolve_image already drops the missing file from original/new. def missing_input_details(compare) - {error: compare.error_message.lines.first.strip} + {error: compare.processed.error_message.lines.first.strip} end # Images of different sizes get no annotated or heatmap images, and @@ -180,7 +213,7 @@ def difference_details(compare) # HTML never names a contributor -- it renders whatever # SnapDiff::Contributions returns, empty by default. def attach_annotations - failures.each do |entry| + entries.each do |entry| # ||= would create a nil :annotations key on misses; keep the # entry clean. annotations = SnapDiff::Contributions.annotations_for(entry[:name]) @@ -188,14 +221,10 @@ def attach_annotations end end - # Built at render time: the image and the `git add` are offered only - # for a file that is on disk now (#260). - def new_screenshot_entries - new_screenshots.map { |screenshot| new_screenshot_entry(screenshot.name, screenshot.path) } - end - + # The image and the `git add` are offered only for a file that is on + # disk now (#260). def new_screenshot_entry(name, path) - entry = {name: name, new_screenshot: true} + entry = {name: name, status: "new"} image = resolve_image(path) return entry unless image diff --git a/lib/snap_diff/reporters/templates/report.html.erb b/lib/snap_diff/reporters/templates/report.html.erb index b91a059d..9237553c 100644 --- a/lib/snap_diff/reporters/templates/report.html.erb +++ b/lib/snap_diff/reporters/templates/report.html.erb @@ -3,7 +3,7 @@ - SnapDiff Report — <%= failed %> failures<%= ", #{new_count} new" if new_count.positive? %> + SnapDiff Report — <%= changed_count %> changed<%= " (#{rerecorded_count} re-recorded)" if rerecorded_count.positive? %><%= ", #{new_count} new" if new_count.positive? %>