Skip to content

fix: skip at_exit when framework adapter handles finalization#183

Merged
pftg merged 5 commits into
masterfrom
fix/skip-at-exit-when-framework-loaded
Apr 12, 2026
Merged

fix: skip at_exit when framework adapter handles finalization#183
pftg merged 5 commits into
masterfrom
fix/skip-at-exit-when-framework-loaded

Conversation

@pftg
Copy link
Copy Markdown
Collaborator

@pftg pftg commented Apr 12, 2026

Summary

  • Only register at_exit fallback when no known framework (Minitest/RSpec/Cucumber) is loaded
  • Framework adapters already call finalize_reporters! via native hooks — at_exit was a wasted no-op

Test plan

  • 224 unit tests pass
  • Summary prints once (not twice) in jetthoughts.github.io

🤖 Generated with Claude Code

Summary by Sourcery

Bug Fixes:

  • Prevent duplicate reporter finalization by skipping the at_exit hook when Minitest, RSpec, or Cucumber integrations are present.

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added "Custom Test Frameworks" section with integration instructions for Minitest, RSpec, and Cucumber
    • Extended installation requirements with macOS (brew install vips) and Ubuntu (apt-get install libvips-dev) setup commands for the vips driver
  • Improvements

    • Enhanced HTML report generation and finalization handling across supported test frameworks

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 12, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Conditionally register the HTML reporter at_exit finalization fallback only when no known test framework integration is present, to avoid duplicate finalization when Minitest, RSpec, or Cucumber adapters already handle it via native hooks.

Sequence diagram for reporter finalization with and without framework adapters

sequenceDiagram
  actor Developer
  participant TestFramework as TestFramework(Minitest_RSpec_Cucumber)
  participant RubyAtExit as Ruby_at_exit
  participant Capybara as CapybaraScreenshotDiff
  participant HTML as HTMLReporter

  Developer->>Capybara: require html_reporter
  Capybara->>HTML: register embed_images option
  alt Known framework loaded
    Developer->>TestFramework: run tests
    TestFramework->>Capybara: finalize_reporters! via native hook
    Capybara->>HTML: write_report
    note over RubyAtExit: at_exit fallback not registered
  else No known framework loaded
    Developer->>RubyAtExit: register at_exit { finalize_reporters! }
    Developer->>RubyAtExit: process exits
    RubyAtExit->>Capybara: finalize_reporters!
    Capybara->>HTML: write_report
  end
Loading

File-Level Changes

Change Details Files
Guard the HTML reporter at_exit finalization fallback so it only runs when no known test framework is loaded.
  • Updated the comment to clarify that Minitest, RSpec, and Cucumber adapters already invoke finalize_reporters! via native hooks.
  • Wrapped the at_exit finalize_reporters! registration in an unless guard that checks for the presence of Minitest, RSpec, or Cucumber (via AfterAll).
lib/capybara_screenshot_diff/reporters/html.rb

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
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Warning

Rate limit exceeded

@pftg has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 5 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 5 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d3f20a2-b1e6-46fe-bec1-bc1a37231284

📥 Commits

Reviewing files that changed from the base of the PR and between ddbf51e and 047725e.

📒 Files selected for processing (3)
  • README.md
  • docs/framework-setup.md
  • lib/capybara_screenshot_diff/reporters/html.rb
📝 Walkthrough

Walkthrough

The PR introduces a coordination mechanism for HTML reporter finalization across test frameworks. It adds an external_at_exit flag that allows Minitest, RSpec, and Cucumber adapters to manage finalization through native test hooks, preventing duplicate finalization. The HTML reporter's at_exit block now conditionally skips finalization when this flag is set. Documentation adds a Custom Test Frameworks section and libvips installation instructions.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added "Custom Test Frameworks" section documenting how to call finalize_reporters! in test suite hooks, plus macOS and Ubuntu libvips installation commands for the :vips driver.
Core State Management
lib/capybara_screenshot_diff/screenshot_assertion.rb
Added public accessor external_at_exit and predicate alias external_at_exit? to expose finalization coordination flag.
HTML Reporter
lib/capybara_screenshot_diff/reporters/html.rb
Modified at_exit handler to conditionally skip finalize_reporters! when external_at_exit? is true, allowing test framework adapters to control finalization sequence.
Test Framework Adapters
lib/capybara_screenshot_diff/cucumber.rb, lib/capybara_screenshot_diff/minitest.rb, lib/capybara_screenshot_diff/rspec.rb
Each adapter sets external_at_exit = true before registering its native test hook that calls finalize_reporters!, establishing explicit control over finalization timing.

Sequence Diagram(s)

sequenceDiagram
    participant Framework as Test Framework<br/>(Minitest/RSpec/Cucumber)
    participant Reporter as CapybaraScreenshotDiff<br/>(Flag & Core)
    participant HTMLRptr as HTML Reporter
    participant AtExit as at_exit Handler

    rect rgba(100, 150, 200, 0.5)
        note over Framework,AtExit: Setup Phase
        Framework->>Reporter: external_at_exit = true
        Framework->>Framework: Register native test hook<br/>(after_run / after(:suite) / AfterAll)
    end

    rect rgba(150, 100, 200, 0.5)
        note over Framework,AtExit: Test Execution Phase
        HTMLRptr->>AtExit: Register at_exit block
    end

    rect rgba(200, 150, 100, 0.5)
        note over Framework,AtExit: Finalization Phase
        Framework->>Reporter: finalize_reporters!<br/>(via native hook)
        Reporter->>Reporter: Generate report & summary
        AtExit->>Reporter: Check external_at_exit?
        alt external_at_exit is true
            AtExit->>AtExit: Skip finalization
        else external_at_exit is false
            AtExit->>Reporter: finalize_reporters!
        end
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Poem

🐰 Three frameworks unite with a flag so bright,
No double finales, just one done right!
Minitest, RSpec, Cucumber agree—
External control sets finalization free! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: skip at_exit when framework adapter handles finalization' directly and clearly summarizes the main change: preventing redundant at_exit calls when framework adapters already handle finalization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/skip-at-exit-when-framework-loaded

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 and usage tips.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Using defined?(Minitest) || defined?(RSpec) || defined?(AfterAll) to detect framework presence may be brittle; consider checking for the specific integration hook/adapter you rely on (e.g., a module or method your adapters define) to avoid false positives/negatives from unrelated constants.
  • If this file can be loaded multiple times in a process (e.g., with code reloading), it may be safer to guard the at_exit registration with an idempotent flag on CapybaraScreenshotDiff rather than only checking framework constants.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `defined?(Minitest) || defined?(RSpec) || defined?(AfterAll)` to detect framework presence may be brittle; consider checking for the specific integration hook/adapter you rely on (e.g., a module or method your adapters define) to avoid false positives/negatives from unrelated constants.
- If this file can be loaded multiple times in a process (e.g., with code reloading), it may be safer to guard the `at_exit` registration with an idempotent flag on `CapybaraScreenshotDiff` rather than only checking framework constants.

## Individual Comments

### Comment 1
<location path="lib/capybara_screenshot_diff/reporters/html.rb" line_range="141-137" />
<code_context>
-at_exit { CapybaraScreenshotDiff.finalize_reporters! }
+# Fallback for frameworks without explicit integration.
+# Minitest, RSpec, and Cucumber adapters call finalize_reporters! via native hooks.
+unless defined?(Minitest) || defined?(RSpec) || defined?(AfterAll)
+  at_exit { CapybaraScreenshotDiff.finalize_reporters! }
+end
</code_context>
<issue_to_address>
**issue:** Guarding `at_exit` based on `defined?` may be unreliable if frameworks are loaded after this file

This relies on Minitest/RSpec/AfterAll being loaded before this file. If the test framework is loaded later (e.g., via lazy loading or a different boot path), these `defined?` checks will be false and the fallback `at_exit` will still be registered, leading to duplicate `finalize_reporters!` calls when the framework-specific hooks run. It may be safer to use an explicit configuration flag or have the adapters actively disable this fallback instead of inferring framework presence at require time.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread lib/capybara_screenshot_diff/reporters/html.rb
Minitest, RSpec, and Cucumber adapters call finalize_reporters! via
native hooks. The at_exit fallback in html.rb was firing as a wasted
no-op for those frameworks (hitting @Finalized guard every time).

Now only registers at_exit when no known framework is loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pftg pftg force-pushed the fix/skip-at-exit-when-framework-loaded branch from 03f2745 to 504676a Compare April 12, 2026 23:06
pftg and others added 3 commits April 13, 2026 01:21
Framework adapters set external_at_exit = true and register native
hooks (Minitest.after_run, RSpec after(:suite), Cucumber AfterAll).
The at_exit block checks the flag and skips when a framework adapter
handles finalization.

This eliminates both the double-call problem and the LIFO ordering
problem cleanly — same pattern SimpleCov uses successfully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document how to call finalize_reporters! for custom test frameworks
- Add brew/apt install commands for libvips in Requirements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The external_at_exit flag was overkill — @Finalized in #finalize already
prevents double work. Simpler: at_exit always registers, framework
adapters also register native hooks, @Finalized ensures only the first
call does work.

Also move custom framework docs from README to docs/framework-setup.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 197-206: Update the README's "Custom Test Frameworks" example to
document and recommend setting the external_at_exit flag before calling
finalize_reporters! so the HTML reporter will skip its at_exit hook; mention
CapybaraScreenshotDiff.external_at_exit = true, show the call to
CapybaraScreenshotDiff.finalize_reporters! in the framework "after suite" hook,
and change the explanatory text to say the report/summary will be generated once
after the suite completes; reference the reporters/html.rb behavior (it checks
CapybaraScreenshotDiff.external_at_exit?) so readers understand why the flag is
required for custom runners.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b72ba67b-222a-4271-9552-7cb124e63b9c

📥 Commits

Reviewing files that changed from the base of the PR and between fc8dbaa and ddbf51e.

📒 Files selected for processing (6)
  • README.md
  • lib/capybara_screenshot_diff/cucumber.rb
  • lib/capybara_screenshot_diff/minitest.rb
  • lib/capybara_screenshot_diff/reporters/html.rb
  • lib/capybara_screenshot_diff/rspec.rb
  • lib/capybara_screenshot_diff/screenshot_assertion.rb

Comment thread README.md Outdated
Framework adapters (Minitest, RSpec, Cucumber) call finalize_reporters!
via native hooks. at_exit was unreliable due to LIFO ordering and added
complexity for a case nobody hits. Custom frameworks can call
finalize_reporters! manually (documented in docs/framework-setup.md).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pftg pftg merged commit 18bd171 into master Apr 12, 2026
8 checks passed
@pftg pftg deleted the fix/skip-at-exit-when-framework-loaded branch April 12, 2026 23:39
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.

1 participant