Skip to content

fix: the HTML report lists new screenshots - #308

Merged
pftg merged 1 commit into
snap-diff:masterfrom
oboxodo:fix/report-new-screenshots
Oct 16, 2026
Merged

pftg merged 1 commit into
snap-diff:masterfrom
oboxodo:fix/report-new-screenshots

Conversation

@oboxodo

@oboxodo oboxodo commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Reopens #307, which I closed by mistake while deleting merged branches on my fork. Same commit, same body.

Problem

Measured on 7bb159c with a real Minitest run, real git baselines, capture stubbed
(test/fixtures/summary_line_case.rb):

Run Terminal Report (before) Report (after)
new only, record: :once 0 verified, 0 changed, 1 new none written new listed; pills 0 failed · 0 passed · 0 total · 1 new
changed + new, :once 1 verified, 1 changed, 1 new changed only; 1 failed · 0 passed · 1 total changed, then new; … · 1 new
new only, record: :none (CI) before 0 new, after 1 new; test fails with the same message none written new listed with its capture (embedded on CI)

Screenshots

Real Minitest runs, the report opened in headless Chrome.

Before (master): changed + new After: same run
00-before-mixed 02-mixed-new
Only changed is listed; the new capture is missing. new is listed after the failures: the capture, and the command to accept it.

CI (record: :none), only a new screenshot: master writes no report; now the report is written, with the capture embedded:

08-ci-none-embedded

The command is selected with one click, even when zoomed in (here 200%):

05-zoom200-select

Mechanism

  • Reporting's new/re-recorded tallies keep each captured path; SnapDiff::Reporting.uncompared
    returns them as frozen values (name, path, reason :new/:rerecorded), complete by
    finalize. The reporter contract is unchanged: any reporter reads it in finalize; the HTML
    reporter does, so its new is the terminal's. Documented in docs/snapdiff.md and
    docs/reporters.md.
  • The matcher tallies (and warns) once, after a successful capture and after the
    disappeared-baseline check (Fiber and thread safety: remaining decisions and hardening after the v2 core work #217); image + git add -- <root-relative, shell-escaped path>
    only for a file on disk at render time (The new-screenshot error prints a path that will never exist in CI #260).
  • Fork-parallel: the entries travel with the counts in Reporting's fragment (new uncompared
    key, read with a default; the name lists stay), so the parent's list is the whole run's.
  • Page: existing views; empty Baseline side uses the existing .img-empty message with the
    command (one click selects it; clicking it no longer toggles Annotated); new badges and
    pill in --accent.

Not changed

The reporter contract (record/finalize/summary) and reporters' state handoff; failure messages;
failure entries' rendering; view modes and shortcuts. Terminal output under :once/:all,
except: a capture that raised is no longer announced as new, and the same-name race below.
Under :none a new screenshot now prints the failure, 1 new and the existing "had no
committed baseline … : new" roll-up line — intended: it is the same line :once prints.

Adjacent fixes

Known limitations

Keyed by name (as the count already is). Under record: :all a brand-new screenshot counts
as re-recorded. One-process-per-worker runners: last writer wins, now also for a new-only process.

Evidence

  • Mutation checks (each reverted alone → named test red): list / entries not frozen, reset
    keeping entries, fragment without paths, merge ignoring them / guessing an unknown reason /
    names overriding them /
    re-recorded merged as new, every sighting treated as first, finalize condition, re-recorded
    listed, tally before the session check, existence check, template DATA,
    shell escape, -- separator, :none fall-through, no -w warning.
  • Full suite in the repo's container image (cuprite): 855 runs, 0 failures (master: 826).
  • JRuby 10.1 (local container): bin/rake test:unit 812 runs, 0 failures (PR CI runs MRI 4.0 only).
  • Size: lib/ (incl. template) +178/−54, tests +417/−17, docs/README/CHANGELOG +54/−8.
  • bin/standardrb clean.

Follow-up

PR B: list re-recorded (record: :all) screenshots with a before/after against the previous
baseline from git.

Summary by Sourcery

Include newly captured screenshots in reporting while keeping terminal counts, HTML output, and parallel-run results consistent.

New Features:

  • List newly captured screenshots in the HTML report with their captures and shell-safe commands for adding them as committed baselines.
  • Expose uncompared screenshot metadata to reporters, including names, paths, and whether screenshots are new or re-recorded.

Bug Fixes:

  • Ensure new screenshots are counted and reported in all recording modes, including runs whose only finding is a new screenshot and fork-parallel runs.
  • Report baselines that disappear during a same-name race as disappeared rather than incorrectly classifying them as new.
  • Surface invalid screenshot path configurations instead of silently falling back to the original path.

Enhancements:

  • Update the HTML report presentation to distinguish new screenshots, show matching summary counts, and keep baseline instructions selectable and usable across view modes and zoom levels.

Documentation:

  • Document new screenshot reporting, reporter metadata, CI behavior, and parallel-run support.

Tests:

  • Add integration and unit coverage for new and re-recorded screenshots, recording modes, parallel merging, disappearing baselines, shell-safe commands, and HTML rendering.

Chores:

  • Update the changelog and canonical reporter require example.

Summary by CodeRabbit

  • New Features

    • HTML reports now include newly captured screenshots, with status and summary counts for passed, failed, new, and re-recorded screenshots.
    • Reports show captured images when available and provide a safely formatted git add command to add new screenshots as baselines.
    • Runs containing only new screenshots now generate a report. Under record: :none, new screenshots are counted and fail the test by default.
  • Bug Fixes

    • Improved handling when a committed screenshot baseline disappears during a test.
  • Documentation

    • Updated reporting and CI guidance to explain new screenshot reports and when to upload them.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no actionable new defect was found.

Summary

The HTML report now lists newly captured screenshots, including runs with no comparisons.

  • The HTML report lists new screenshots and shows how to add them.
  • A committed baseline that disappears stays marked as disappeared.
  • Screenshot paths that cannot be matched to the root now surface an error.

The previous review commit is not an ancestor of this head, so the full PR diff was reviewed. No previous review threads were supplied.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Capture succeeds] --> B{Committed baseline available?}
  B -->|Yes| C[Compare and record result]
  B -->|No| D{Baseline checked out earlier?}
  D -->|Yes| E[Raise disappeared-baseline error]
  D -->|No| F[Keep name, capture path, and reason]
  F --> G[Merge fork-worker entries]
  C --> H[Finalize HTML report]
  G --> H
  H --> I[List failures, then new captures]
Loading

Reviews (2) · Last reviewed commit: "fix: the HTML report lists new screensho..." · Reviewed by Greptile

@sourcery-ai

sourcery-ai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR makes new screenshots first-class reporting results: matcher and reporting state now retain immutable capture metadata across modes and fork workers, the HTML report lists available new captures with shell-safe baseline commands and matching counts, disappeared-baseline races are classified correctly, and extensive tests and documentation cover the behavior.

Sequence diagram for reporting a new screenshot

sequenceDiagram
    participant Matcher as ScreenshotMatcher
    participant Reporting as SnapDiff::Reporting
    participant HTML as HTMLReporter
    participant Browser as HTMLReport

    Matcher->>Matcher: capture screenshot
    Matcher->>Reporting: record_missing_baseline(name, path: snapshot.path)
    Reporting-->>Matcher: first sighting
    Matcher->>Reporting: finalize
    HTML->>Reporting: uncompared
    Reporting-->>HTML: frozen name, path, reason
    HTML->>HTML: new_screenshot_entries
    HTML->>Browser: write_report with capture and git add command
Loading

Flow diagram for new versus disappeared baseline handling

flowchart TD
    Capture["Capture screenshot"] --> Baseline{Committed baseline available?}
    Baseline -->|Yes| Compare["Compare screenshot"]
    Baseline -->|No| CheckedOut{Baseline was checked out earlier?}
    CheckedOut -->|Yes| Disappeared["Report disappeared-baseline error"]
    CheckedOut -->|No| Record["Record uncompared screenshot\nreason: new"]
    Record --> Mode{record mode}
    Mode -->|:none| Fail["Fail test with add command"]
    Mode -->|:once or other| ReportNew["Count and report new screenshot"]
    Compare --> Summary["Finalize counts and report"]
    Fail --> Summary
    ReportNew --> Summary
Loading

File-Level Changes

Change Details Files
Expose uncompared screenshot metadata through the reporting API and preserve it across recording modes and fork-parallel workers.
  • Track immutable name, captured path, and :new/:rerecorded reason entries.
  • Finalize and merge entries consistently with terminal tallies, including backward-compatible fragment reads.
  • Move new-screenshot accounting after successful capture and baseline-disappearance checks; handle record: :none failures and same-name races correctly.
lib/snap_diff/reporting.rb
lib/snap_diff/screenshot_matcher.rb
lib/snap_diff/screenshot_assertion.rb
Extend the HTML reporter to generate reports containing newly captured screenshots and actionable baseline commands.
  • Write reports when new screenshots are the only finding and append them after failure entries.
  • Render available captures, empty-baseline messaging, new badges/counts, and re-recorded counts without listing re-recorded entries.
  • Generate root-relative, shell-escaped git add commands only when the capture exists, while preserving interactive selection and zoom behavior.
lib/snap_diff/reporters/html.rb
lib/snap_diff/reporters/templates/report.html.erb
Add comprehensive coverage for new-screenshot reporting, accounting, races, parallel merging, rendering, and shell-safe commands.
  • Test serial and CI recording modes, failed captures, re-recording, disappeared baselines, and duplicate names.
  • Test immutable reporting data and parallel fragment propagation, including malformed reasons and legacy fragments.
  • Test HTML ordering, counts, missing captures, path handling, selectable commands, and warning-free loading.
test/integration/summary_line_test.rb
test/unit/baseline_disappeared_test.rb
test/unit/parallel_report_merge_test.rb
test/unit/record_modes_test.rb
test/unit/reporters/html_reporter_test.rb
test/unit/reporting_counts_test.rb
test/unit/screenshot_assertion_call_site_test.rb
test/fixtures/summary_line_case.rb
Document the new report behavior and uncompared reporter API, including CI and parallel-run semantics.
  • Explain report generation for new screenshots and record: :none behavior.
  • Document uncompared entries, capture availability, and baseline acceptance commands.
  • Update release notes and the canonical HTML reporter import.
docs/reporters.md
docs/snapdiff.md
docs/ci-integration.md
README.md
CHANGELOG.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 41 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 94e871ed-5aa3-4622-ab01-960ceee378a4
📥 Commits

Reviewing files that changed from the base of the PR and between 0e1bbdd and f7ab89e.

📒 Files selected for processing: 18
  • CHANGELOG.md
  • README.md
  • docs/ci-integration.md
  • docs/reporters.md
  • docs/snapdiff.md
  • lib/snap_diff/reporters/html.rb
  • lib/snap_diff/reporters/templates/report.html.erb
  • lib/snap_diff/reporting.rb
  • lib/snap_diff/screenshot_assertion.rb
  • lib/snap_diff/screenshot_matcher.rb
  • test/fixtures/summary_line_case.rb
  • test/integration/summary_line_test.rb
  • test/unit/baseline_disappeared_test.rb
  • test/unit/parallel_report_merge_test.rb
  • test/unit/record_modes_test.rb
  • test/unit/reporters/html_reporter_test.rb
  • test/unit/reporting_counts_test.rb
  • test/unit/screenshot_assertion_call_site_test.rb

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c617937d-b230-495e-a38e-85ae758ff69c
📥 Commits

Reviewing files that changed from the base of the PR and between 7bb159c and 0e1bbdd.

📒 Files selected for processing: 18
  • CHANGELOG.md
  • README.md
  • docs/ci-integration.md
  • docs/reporters.md
  • docs/snapdiff.md
  • lib/snap_diff/reporters/html.rb
  • lib/snap_diff/reporters/templates/report.html.erb
  • lib/snap_diff/reporting.rb
  • lib/snap_diff/screenshot_assertion.rb
  • lib/snap_diff/screenshot_matcher.rb
  • test/fixtures/summary_line_case.rb
  • test/integration/summary_line_test.rb
  • test/unit/baseline_disappeared_test.rb
  • test/unit/parallel_report_merge_test.rb
  • test/unit/record_modes_test.rb
  • test/unit/reporters/html_reporter_test.rb
  • test/unit/reporting_counts_test.rb
  • test/unit/screenshot_assertion_call_site_test.rb
💤 Files with no reviewable changes: 1
  • test/unit/screenshot_assertion_call_site_test.rb

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The change adds path-aware reporting for new and re-recorded screenshots. The HTML report now includes new screenshots, and baseline disappearance handling distinguishes checked-out baselines from missing baselines. Tests and documentation cover these reporting and capture behaviors.

Changes: Screenshot reporting

Layer / File(s) Summary
Capture tracking and uncompared entries
lib/snap_diff/reporting.rb, lib/snap_diff/screenshot_*.rb, test/unit/record_modes_test.rb, test/unit/reporting_counts_test.rb, test/unit/parallel_report_merge_test.rb, test/unit/baseline_disappeared_test.rb, docs/snapdiff.md, docs/reporters.md, CHANGELOG.md
SnapDiff::Reporting.uncompared stores new and re-recorded screenshot names, optional capture paths, and reasons. Parallel merges preserve this data. Tests cover record modes and baseline disappearance after checkout.
HTML report entries and presentation
lib/snap_diff/reporters/..., test/unit/reporters/html_reporter_test.rb, test/integration/summary_line_test.rb, test/fixtures/summary_line_case.rb, docs/reporters.md, docs/ci-integration.md, README.md, CHANGELOG.md
The HTML report includes new screenshots when their captures are available, with escaped git add commands. It shows new and re-recorded counts. New screenshots alone produce a report; re-recorded screenshots alone do not.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant ScreenshotMatcher
  participant Reporting
  participant HTMLReporter
  participant ReportTemplate
  ScreenshotMatcher->>Reporting: Record uncompared screenshot details
  HTMLReporter->>Reporting: Read uncompared entries during finalization
  HTMLReporter->>ReportTemplate: Render report entries
Loading

Suggested reviewers: pftg

Merge Risk: ⚪ Minimal · up to 0e1bb

No actionable merge-blocking issue is established; the change is mergeable after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 40 functions across 11 files. (6 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: adding new screenshots to the HTML report.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 40 functions across 11 files. (6 skipped: 6 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="docs/snapdiff.md" line_range="261" />
<code_context>

 A reporter that raises is warned about and skipped — the other reporters still run.

+`record` only receives screenshots that were compared. The ones that were not — the summary
+line's "new" and "re-recorded" — are in `SnapDiff::Reporting.uncompared`, complete by the time
+`finalize` runs (fork-parallel workers included):
</code_context>
<issue_to_address>
**Uncompared assertions still reach reporters**

When a custom reporter is written based on the documented guarantee and receives a test assertion with no comparison, a custom reporter following this statement can assume every assertion has a comparison and dereference `assertion.compare`; `Reporting.notify` passes the full assertion list to `record`, including assertions whose comparison is nil, so the reporter raises and its handling of that test is skipped.

Clarify that `record` receives all assertions, including those with a nil comparison, and that `uncompared` supplies the separate captured-path and reason data.

Also at `docs/reporters.md:133`.
</issue_to_address>

### Comment 2
<location path="lib/snap_diff/reporters/html.rb" line_range="185" />
<code_context>
+        image = resolve_image(path)
+        return entry unless image
+
+        entry.merge(new: image, git_add: "git add -- #{Shellwords.escape(display_path(path))}")
+      end
+
</code_context>
<issue_to_address>
**Add command fails in Windows shells**

When a Windows user copies the suggested command for a screenshot path containing spaces into PowerShell or `cmd.exe`, `Shellwords.escape` emits POSIX shell escaping, so a path with spaces is rendered with backslash escapes; PowerShell and `cmd.exe` do not treat those escapes as quoting, and the suggested `git add` command fails for that capture.

Generate a command quoted for the supported shell, or clearly generate and label a POSIX-shell command rather than presenting it as shell-independent.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: docs/snapdiff.md:261, lib/snap_diff/reporters/html.rb:185


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread docs/snapdiff.md Outdated
Comment thread lib/snap_diff/reporters/html.rb
A screenshot captured with no committed baseline was only counted by name
for the summary line; reporters receive compared assertions only. So the
report left it out, a run whose only finding was a new screenshot wrote no
report at all, and under record: :none (CI's default) the raise came before
the tally, so the summary said `0 new` while the capture sat on disk.

Reporting's tallies of new and re-recorded screenshots now keep each
one's captured path, and SnapDiff::Reporting.uncompared returns them as
frozen Reporting::Uncompared values (name, path, reason :new or
:rerecorded): the entries behind the summary line's counts, complete by
the time reporters finalize. They travel with the counts in the
fork-parallel fragment; the name lists every version writes stay, and a
name only an older worker sent is still listed, with no capture. An
entry with a reason no version writes is a bug and raises instead of
being guessed at. The reporter contract (record/finalize/summary) is unchanged: any reporter,
the bundled HTML one included, reads the list in finalize, so the
report's `new` is the terminal's by construction, reset_run_totals!
clears it with the counts, and no reporter can alter what another reads.

The matcher now tallies a new screenshot once, after a successful capture
and after the session's disappeared-baseline check (snap-diff#217), and warns from
there; only "was a file already there" is still read before the capture.
So a listed path is a file that was captured (snap-diff#260), a failed capture is
not reported as new, and a baseline git handed over but a same-name test
moved away is never new, in any mode: every mode raises the session's
"disappeared" error. Before, record: :none told the user to `git add` a
screenshot whose baseline was committed.

The report builds the image and `git add -- <path>` only for a file on
disk at render time; `--` keeps a name starting with a dash from reading
as an option. In the page, a new screenshot goes through the existing views: the
empty Baseline side explains itself with the existing .img-empty message
and the command, which stays selectable (neither a click nor a zoomed-in
drag on it toggles or pans). Badges and the `N new` pill use the
palette's accent colour, neither pass nor fail. Re-recorded screenshots
are counted in a pill when present but not listed; listing them with a
before/after against the previous baseline is a separate follow-up.

Not changed: the reporter contract and every reporter's state handoff;
terminal output under :once and :all except that a failed capture is no
longer announced as new; other failure messages; how failure entries
render. Also fixes README's legacy
`capybara_screenshot_diff/reporters/html` require.

ScreenshotAssertion#display_path drops its `rescue ArgumentError`, like
Reporters::Default's did: a path Pathname can't relate to the root is a
misconfiguration to surface, not hide; the report's copy has none either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oboxodo
oboxodo force-pushed the fix/report-new-screenshots branch from 0e1bbdd to f7ab89e Compare October 15, 2026 15:13
@pftg
pftg merged commit eb89fc5 into snap-diff:master Oct 16, 2026
11 checks passed
@pftg

pftg commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

@oboxodo thanks

@oboxodo
oboxodo deleted the fix/report-new-screenshots branch October 16, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants