diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b5a1ee4..c6e554de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -396,6 +396,16 @@ One removal 2.0 cannot warn about, written down instead: the legacy `LOADED_DRIV After each capture it used to click the input it had blurred. The click moved the pointer onto it, so the input rendered hovered in later screenshots (Firefox 157 draws that darker), and it fired the page's click handlers. It now calls `focus()` +- **A screenshot that changed size is in the HTML report.** It was counted in the + summary line but left out of `snap_diff_report.html` (and a run whose only failures + were size changes wrote no report at all): the null comparison result is frozen, and + reading its `area_size` raised `FrozenError`, which the reporter swallowed unless + `DEBUG` was set. The entry shows the baseline, the new capture and both sizes, and + says why it has no heatmap or annotations +- **An error while building a report entry no longer hides the screenshot.** The HTML + reporter warns on stderr without `DEBUG` (`[snap_diff] HTML report: could not render + 'name' (Error: message)`) and keeps the screenshot in the report as "could not render", + counted as failed, instead of dropping it and counting it as passed ### Known limitations - **One process per worker** (`parallel_tests`, RSpec, CI sharding) still writes one diff --git a/README.md b/README.md index c6889602..162d8e70 100644 --- a/README.md +++ b/README.md @@ -332,8 +332,9 @@ You do not need a flag to keep the diff images — a failing run leaves `.diff.p deletes them (`SnapManager#cleanup!` is this repository's own test-harness call, not something your suite runs). -`DEBUG=1` does one thing: it makes the HTML reporter print why it skipped an assertion -instead of failing quietly — useful when `snap_diff_report.html` is missing entries. +Nor do you need a flag to see report problems: if the HTML reporter cannot render a +screenshot, it prints `[snap_diff] HTML report: could not render '' ()` and +still lists the screenshot in `snap_diff_report.html`, marked "could not render". ## Installation diff --git a/lib/snap_diff/comparison_result.rb b/lib/snap_diff/comparison_result.rb index b01010a9..ada5e381 100644 --- a/lib/snap_diff/comparison_result.rb +++ b/lib/snap_diff/comparison_result.rb @@ -59,8 +59,9 @@ def blank? region.nil? || region_area_size.zero? end + # Not memoized: build_null freezes its result, and Region#size is cheap. def region_area_size - @region_area_size ||= region&.size || 0 + region&.size || 0 end def ratio diff --git a/lib/snap_diff/reporters/default.rb b/lib/snap_diff/reporters/default.rb index 9bcc439c..dbe1988d 100644 --- a/lib/snap_diff/reporters/default.rb +++ b/lib/snap_diff/reporters/default.rb @@ -52,11 +52,15 @@ def annotate_and_save_images end def build_error_for_different_dimensions - change_msg = [comparison.base_image, comparison.new_image] + "Dimensions have changed: #{dimension_change}\n#{base_image_path.to_path}\n#{image_path.to_path}" + end + + # "WxH => WxH", baseline first. Public so Reporters::HTML says it the + # same way. + def dimension_change + [comparison.base_image, comparison.new_image] .map { |image| driver.dimension(image).join("x") } .join(" => ") - - "Dimensions have changed: #{change_msg}\n#{base_image_path.to_path}\n#{image_path.to_path}" end NEW_LINE = "\n" diff --git a/lib/snap_diff/reporters/html.rb b/lib/snap_diff/reporters/html.rb index ca82a2ed..dda8cb45 100644 --- a/lib/snap_diff/reporters/html.rb +++ b/lib/snap_diff/reporters/html.rb @@ -31,15 +31,22 @@ def record(assertions) 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 - warn "[snap_diff] Reporter skipped '#{assertion.name}': #{e.message}" if ENV["DEBUG"] + # 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} end @mutex.synchronize do @@ -115,18 +122,31 @@ def self.default_output_path # Build a report entry from a comparison, omitting unavailable images and metrics. def failure_entry_for(name, compare) - difference = compare.difference + details = compare.dimensions_changed? ? dimension_change_details(compare) : difference_details(compare) { name: name, original: resolve_image(compare.base_image_path), - new: resolve_image(compare.image_path), + new: resolve_image(compare.image_path) + }.merge(details).compact + end + + # Images of different sizes get no annotated or heatmap images, and + # no region metrics. Any annotated images on disk for this name are + # left over from an earlier same-size failure, so they are not linked. + def dimension_change_details(compare) + {dimensions_changed: compare.reporter.dimension_change} + end + + def difference_details(compare) + difference = compare.difference + { base_diff: resolve_image(compare.reporter.annotated_base_image_path), diff: resolve_image(compare.reporter.annotated_image_path), heatmap: resolve_image(compare.reporter.heatmap_diff_path), diff_level: difference.ratio && (difference.ratio * 100).round(2), area_size: difference.region_area_size, max_color_distance: difference.meta[:max_color_distance]&.round(1) - }.compact + } end # Contributed annotations (AI triage, ...), attached at RENDER diff --git a/lib/snap_diff/reporters/templates/report.html.erb b/lib/snap_diff/reporters/templates/report.html.erb index 86c5aa34..00707524 100644 --- a/lib/snap_diff/reporters/templates/report.html.erb +++ b/lib/snap_diff/reporters/templates/report.html.erb @@ -129,6 +129,9 @@ .img-box img { display: block; object-fit: contain; transform-origin: 0 0; } /* At 100% zoom, image fills the box */ .img-box[data-zoom="100"] img { width: 100%; height: 100%; } + .img-empty { height: 100%; display: flex; align-items: center; justify-content: center; padding: 1rem; font-size: .75rem; line-height: 1.5; color: var(--text2); } + .img-empty p { max-width: 42em; margin: 0; } + .img-empty a { color: var(--accent); } /* Single image */ .view-single { width: 100%; height: 100%; display: flex; flex-direction: column; } @@ -266,12 +269,13 @@ btn.dataset.idx = idx; /* Thumbnail: annotated mode → heatmap; normal → new image (no annotation) */ var thumb = state.annotated ? (item.heatmap || item.new || item.original || '') : (item.new || item.original || ''); - var hasDiff = item.diff || item.heatmap; - var badgeText = hasDiff - ? (item.diff_level != null ? item.diff_level + '%' : 'changed') + var hasDiff = item.diff || item.heatmap || item.dimensions_changed || item.error; + var badgeText = item.error ? 'error' + : item.dimensions_changed ? 'resized' + : hasDiff ? (item.diff_level != null ? item.diff_level + '%' : 'changed') : 'pass'; btn.innerHTML = - '
' + + '
' + (thumb ? '' : '') + '
' + '