Skip to content

feat: VCR-shaped record modes — the accept workflow (#259) - #274

Merged
pftg merged 2 commits into
masterfrom
feat/record-modes
Aug 24, 2026
Merged

feat: VCR-shaped record modes — the accept workflow (#259)#274
pftg merged 2 commits into
masterfrom
feat/record-modes

Conversation

@pftg

@pftg pftg commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Closes #259.

Accepting an intentional UI change is the most frequent action in the product, forever — and it had no verb. grep -rni "def accept|approve|update_baseline" lib/ returns nothing; there are no rake tasks and no exe/. Both documented recipes were wrong, and two customer personas ended up reading vcs.rb to work out that the real answer is git add + commit.

The design

VCR is the closest Ruby precedent, and its record modes are config, not CLI flags — which is our shape too, since we have no runner to hang a flag on.

SnapDiff.config.record = :once   # default -- record when there is no committed baseline
SnapDiff.config.record = :none   # strict  -- a missing baseline always fails
SnapDiff.config.record = :all    # re-record everything, compare nothing

Also a per-screenshot option: assert_matches_screenshot "x", record: :none.

:all is the genuinely new capability — the bulk-accept verb for the redesign that changed forty screenshots at once.

Precedence

An explicitly set mode outranks fail_if_new; fail_if_new decides only when no mode was set. Per screenshot outranks the config; the config outranks fail_if_new. That is the same property #267 gave fail_if_new itself over the CI sniff — explicit outranks implicit, all the way down.

You wrote record reads Missing baseline
nothing, off CI :once recorded
nothing, under CI :none fails
record = :once :once recorded, on CI too
record = :none :none fails, off CI too
record = :once, fail_if_new = true :once recorded — the mode wins
record = nil, fail_if_new = true :none fails — nil hands it back

:once is today, structurally — not just by test

Config#record reads @record || (fail_if_new ? :none : :once). With nothing set, record_mode == :none is true exactly when config.fail_if_new was, so the two decision points in ScreenshotMatcher are a 1:1 substitution:

-      return if SnapDiff.config.fail_if_new        # check_base_screenshot
+      return if record_mode == :none
-      return unless SnapDiff.config.fail_if_new    # fail_if_new_screenshot
+      return unless record_mode == :none

No new branch is added on the default path. A subprocess probe pins it: a setup that sets none of the settings is byte-for-byte silent and still reads :none under CI=1. Mutating the fallback to a plain :once reds 8 tests.

The missing-baseline default is unchanged — CI-only failure is what Jest, AVA, Vitest, testthat and jest-image-snapshot all chose, and a locally recorded screenshot baseline is often worthless across OS. :none makes strictness an explicit choice instead; that is the point of the mode.

Guarding :all

:all accepts every rendering by design, so left in a committed config file it is a build that compares nothing and passes forever, with the "recorded" screenshots discarded when the runner is torn down. That is precisely how Percy goes green on a job that lost its token.

:all refuses to run under CI. A CI job that needs to record new baselines does not need it: :once records those and still compares everything that has a baseline. #capture is exempt — it never compares against a baseline, so the mode is inert there and refusing would invent a failure out of a setting that changes nothing.

Belt as well as braces: an end-of-run summary names exactly the screenshots that went down the re-record path, derived from live state.

[snap_diff] record: :all re-recorded 3 screenshots WITHOUT comparing: cart, payment, review. Review the result before committing -- an unintended change is accepted just as silently.

Additive only (ADR-010)

fail_if_new, pending_if_new and fail_on_difference all keep working exactly as they do. Each warns once per process through the existing Removal channel, from its writer (never its reader, which runs for everyone). Each message names the mode that replaces that setting and nothing more — record: supersedes the missing-baseline half squarely and the other two only obliquely, and a message that overclaims misroutes.

record: is carved out of the options hash before Comparison, rather than added to KNOWN_OPTIONS — a key that hash accepts and nothing downstream reads is the silent no-op ADR-010 exists to stop.

Evidence

  • rake test:unit 682 runs / 0 failures · rake test:canonical 566 runs / 0 failures · standardrb lib test clean.
  • 15 mutations, every one red, each restored by targeted edit and verified byte-identical.
  • Two findings from mutation testing, both fixed in this PR:
    • a record_mode != :all clause in need_to_compare? reddened nothingcheck_base_screenshot already leaves :all with no base file. Deleted; the guard that does hold the invariant now reds 5 tests instead of 1.
    • the :all-under-CI refusal sat in #initialize, so it also fired on capture_screenshot. Moved to build_screenshot_assertion, with a test pinning that #capture under :all + CI=true still works.
  • End-to-end transcripts in a real git repo (real git show HEAD: lookups, real git status) for all three modes, the bulk case, and the CI refusal.

Docs

docs/configuration.md (new Record modes section), docs/UPGRADING.md (warning #5), README.md ("Accepting many at once"), plus architecture.md / snapdiff.md setting counts.

Incidentally fixes a stale claim in docs/ci-integration.md: since #267 the screenshot is written before the raise, so the git add the message names is a command you can actually run.

Does not touch version.rb or CHANGELOG.md.

Summary by Sourcery

Introduce record modes as an explicit workflow for recording, rejecting, or bulk-accepting screenshot baselines while retaining backward-compatible defaults.

New Features:

  • Add configurable VCR-style record modes for accepting new or changed screenshots, including per-screenshot overrides and bulk re-recording with :all.
  • Prevent unsafe bulk re-recording under CI and report the screenshots accepted without comparison.

Bug Fixes:

  • Correct CI integration guidance to reflect that screenshots are written before missing-baseline failures and provide actionable recording instructions.

Enhancements:

  • Define precedence between per-screenshot modes, configured modes, legacy fail_if_new, and CI defaults while preserving existing behavior when no mode is configured.
  • Validate record modes and isolate the workflow option from capture and comparison options.
  • Keep legacy screenshot settings functional while warning once that they will be removed in favor of record modes.

Documentation:

  • Document record modes, bulk acceptance, precedence, migration guidance, and CI usage across the README and project documentation.

Tests:

  • Add comprehensive coverage for record mode behavior, precedence, CI safeguards, reporting, per-screenshot overrides, option handling, validation, and legacy compatibility.

Accepting an intentional UI change is the most frequent action in the
product and had no verb: `grep -rni "def accept|approve|update_baseline"
lib/` returned nothing, both documented recipes were wrong, and two
customer personas ended up reading vcs.rb to work out that the answer is
`git add` + commit.

    SnapDiff.config.record = :once   # default -- record when no baseline
    SnapDiff.config.record = :none   # strict -- a missing baseline fails
    SnapDiff.config.record = :all    # re-record everything, compare nothing

`:all` is the genuinely new capability: the bulk-accept verb for the
redesign that changed forty screenshots at once. Modes rather than a CLI
flag because there is no runner to hang a flag on -- VCR's shape, and
ours.

PRECEDENCE: an explicitly set mode outranks `fail_if_new`; `fail_if_new`
decides only when no mode was set. Same property #267 gave `fail_if_new`
over the CI sniff.

:once IS today, structurally. Config#record reads
`@record || (fail_if_new ? :none : :once)`, so with nothing set
`record_mode == :none` is true exactly when `config.fail_if_new` was, and
the two decision points in ScreenshotMatcher swapped one for the other
1:1. No new branch on the default path; the missing-baseline default is
untouched (CI-only failure, as Jest/AVA/Vitest/testthat all chose).

`:all` refuses to run under CI. It accepts every rendering by design, so
left in a committed config it is a build that compares nothing and passes
forever -- Percy's failure mode. A CI job that needs to record NEW
baselines uses `:once`, which still compares everything that has one.
`#capture` is exempt: it never compares, so the mode is inert there.

Additive only (ADR-010). `fail_if_new`, `pending_if_new` and
`fail_on_difference` keep working for the whole 2.x line; each warns once
per process through the existing Removal channel, naming the mode that
replaces it -- and only where that is true. `record:` is also a
per-screenshot option, carved out before Comparison so it cannot become
an accepted-but-unread key.

Also fixes a stale claim in docs/ci-integration.md: since #267 the
screenshot IS written before the raise.

@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.

Sorry @pftg, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 11 minutes.

View limit details

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

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 95d03a4d-8917-4912-a222-2cca10e7f842

📥 Commits

Reviewing files that changed from the base of the PR and between 60c0f39 and dff24ac.

📒 Files selected for processing (14)
  • README.md
  • docs/UPGRADING.md
  • docs/architecture.md
  • docs/ci-integration.md
  • docs/configuration.md
  • docs/snapdiff.md
  • lib/snap_diff/config.rb
  • lib/snap_diff/legacy_shims.rb
  • lib/snap_diff/removal.rb
  • lib/snap_diff/reporting.rb
  • lib/snap_diff/screenshot_matcher.rb
  • test/test_helper.rb
  • test/unit/record_modes_test.rb
  • test/unit/removed_in_2_1_deprecation_test.rb

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 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces config-driven record modes for strict, normal, and bulk screenshot baseline workflows, including per-screenshot precedence, CI protection for :all, live re-record summaries, legacy deprecation warnings, compatibility coverage, and comprehensive documentation.

Sequence diagram for screenshot record mode resolution and matching

sequenceDiagram
    participant Test
    participant Matcher as ScreenshotMatcher
    participant Config
    participant Snapshot as SnapManager
    participant Reporting

    Test->>Matcher: build_screenshot_assertion()
    Matcher->>Config: record()
    Config-->>Matcher: :once, :none, or :all
    Note over Matcher: Per-screenshot record: overrides config
    alt record_mode == :all
        Matcher->>Matcher: refuse_bulk_record_under_ci!
        Matcher->>Snapshot: capture screenshot
        Matcher->>Snapshot: write baseline
        Matcher->>Reporting: record_rerecorded_baseline(name)
    else baseline exists
        Matcher->>Snapshot: checkout_base_screenshot
        Matcher->>Snapshot: capture screenshot
        Matcher->>Snapshot: compare baseline
    else record_mode == :none and baseline missing
        Matcher->>Snapshot: capture screenshot
        Matcher-->>Test: ExpectationNotMet
    else record_mode == :once and baseline missing
        Matcher->>Snapshot: capture screenshot
        Matcher->>Snapshot: record new screenshot
    end
Loading

Flow diagram for record mode precedence and baseline handling

flowchart TD
    A[assert_matches_screenshot] --> B[Resolve per-screenshot record option]
    B -->|unset| C[Config.record]
    C -->|unset| D[fail_if_new fallback]
    D -->|CI enabled| E[record_mode = none]
    D -->|CI disabled| F[record_mode = once]
    B -->|explicit mode| G[Use explicit mode]
    E --> H{Baseline exists?}
    F --> H
    G --> I{Mode}
    H -->|yes| J[Compare screenshot]
    H -->|no| K[Record screenshot or fail]
    I -->|once| K
    I -->|none| L[Fail with git add guidance]
    I -->|all| M[Re-record without comparison]
    M --> N[Report rerecorded screenshots]
Loading

File-Level Changes

Change Details Files
Adds VCR-style screenshot recording modes with explicit precedence and backward-compatible defaults.
  • Adds validated :once, :none, and :all configuration modes, including nil fallback to fail_if_new.
  • Supports per-screenshot record: overrides with precedence over global configuration.
  • Routes missing-baseline decisions through the resolved mode while preserving existing behavior when no mode is configured.
  • Maps the new setting through legacy configuration shims.
lib/snap_diff/config.rb
lib/snap_diff/legacy_shims.rb
lib/snap_diff/screenshot_matcher.rb
test/unit/record_modes_test.rb
Implements bulk baseline acceptance while preventing unreviewed CI execution.
  • Makes :all write every capture directly to the baseline, skip comparison and VCS lookup, and remove stale checked-out base files.
  • Rejects assertion-based :all execution when CI is set while leaving compare-free #capture unaffected.
  • Tracks re-recorded screenshots across processes and parallel workers, then emits an end-of-run review summary.
lib/snap_diff/screenshot_matcher.rb
lib/snap_diff/reporting.rb
test/test_helper.rb
test/unit/record_modes_test.rb
Preserves and formalizes the legacy boolean migration path.
  • Adds warn-once removal messages for fail_if_new, pending_if_new, and fail_on_difference without changing their 2.x behavior.
  • Documents the 2.1 migration mappings and clarifies that explicit record modes override legacy settings.
  • Adds subprocess coverage for warning behavior, silence, compatibility, and unchanged CI defaults.
lib/snap_diff/config.rb
lib/snap_diff/removal.rb
docs/UPGRADING.md
test/unit/removed_in_2_1_deprecation_test.rb
Documents the new accept workflow and corrects CI guidance.
  • Documents all modes, per-screenshot overrides, precedence, CI safeguards, and the git-based review/commit workflow.
  • Adds bulk acceptance examples to the README and updates configuration, architecture, and setting counts.
  • Corrects the CI integration claim to reflect that screenshots are written before missing-baseline failures.
README.md
docs/configuration.md
docs/UPGRADING.md
docs/architecture.md
docs/snapdiff.md
docs/ci-integration.md

Assessment against linked issues

Issue Objective Addressed Explanation
#259 Write a captured screenshot to disk when its baseline is missing while still failing the test, preserving an honest non-green result.
#259 Provide an explicit accept workflow that re-runs screenshots, verifies the result, and promotes intentional changes, including filtering or accepting selected failures. The PR adds configurable :once, :none, and :all record modes, but :all re-records every screenshot without comparing or re-running a verification workflow. It does not add the requested rake snap_diff:accept task, FILTER= support, or the safety property that acceptance only promotes a change after it reproduces on a rerun.
#259 Improve honest reporting and diagnostics with live, labelled relative paths and a distinct summary state for new or unverified screenshots. The PR adds a summary for screenshots re-recorded by :all, but it does not implement the requested labelled Expected/Received/Diff path format or the third summary state such as 'new (not verified)'. It also does not substantially provide the requested VCR/WebMock/SimpleCov-style configuration and diagnostic messaging.

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

@github-actions

Copy link
Copy Markdown

Screenshot diffs detected

Artifact Link
HTML report (inline) N/A
Full report with images N/A
All artifacts Browse all

The `:all` examples failed on CI for exactly the reason they exist: `:all`
refuses to run when ENV["CI"] is set, and the runner sets it. They passed
locally because CI is unset there.

The class now clears ENV["CI"] in setup and restores it in teardown, so it
runs as a developer's laptop by default; the CI-refusal cases opt IN through
the existing `with_ci` helper. Verified both ways: 25 runs green with CI=true
and with it unset.

Local green is half the bar -- this repo has been here before.
@pftg
pftg merged commit 4043347 into master Aug 24, 2026
8 checks passed
@pftg
pftg deleted the feat/record-modes branch August 24, 2026 10:57
pftg added a commit that referenced this pull request Aug 24, 2026
These three checks could not have been written before the rebase: the
counts line (#269) and record modes (#274) never met until now.

- The fork-parallel fragment carries BOTH tallies, so a worker that
  counts assertions AND re-records a baseline must hand back both and
  neither may cost the other. The main merge case now does both.
- A fragment with none of the keys added since #266 still merges. The
  fragments directory is keyed by pid under the system temp dir, so a
  recycled pid can hand the merge a fragment written by an older version
  of the gem; every key but "missing_baselines" is read with a default
  for exactly that. Mutation-checked: dropping one default fails the
  merge with `TypeError: nil can't be coerced into Integer`.
- `record: :all` end to end, on a finished process. It re-records
  without comparing, which through the summary path is neither verified
  nor changed, and NOT "new" either -- there was a baseline, it just was
  not consulted.
pftg added a commit that referenced this pull request Aug 24, 2026
…on (#272, #269, #270) (#275)

* fix: skip_area selectors that match nothing no longer block for 5s (#272)

Capybara's `all` defaults to `minimum: 1` and blocks in `synchronize`
until that count is satisfied, so every `skip_area` (or `crop`) selector
matching nothing burned a full `Capybara.default_max_wait_time` -- 5s by
default, per selector, per screenshot. Measured with a real browser at
Capybara's shipped 5s default, `%w[picture img]` against an image-less
page: 10.012s before, 0.009s after. One project reported that exact
scenario as 44% of their whole suite.

The cost is only half of it. `skip_area` is a MASK -- "exclude whatever
is currently there". Waiting for an element to appear is the wrong
semantic: a selector matching nothing has nothing to mask, and that
answer is available immediately.

`all_visible_regions_for` is the only Capybara finder in lib/; the rest
of BrowserHelpers is execute_script/evaluate_script and driver
introspection, none of which carry a count expectation to block on. So
this is the class of bug, not one instance of it.

The guard uses a REAL browser session. Every existing test of this path
stubs the browser, and a stub answers instantly whether or not the
selector matches -- which is exactly why a 5-second wait went unnoticed
for years. Its budget is derived from the live
`default_max_wait_time` rather than hardcoded, so lowering the suite's
wait cannot quietly turn it into an assertion that passes while broken.

* fix: the end-of-run summary prints without a reporter registered (#269)

`SnapDiff::Reporting.register` appears exactly once in the whole gem, at
reporters/html.rb:140. So the honest summary line shipped bundled with
the HTML report, and the documented Rails setup registered nothing:

    $ ruby -e 'require "snap_diff/integrations/minitest"
               puts SnapDiff::Reporting.reporters.size'
    0

That line exists to catch the failure modes no per-assertion rule can
see -- a run where zero system tests executed, or where an inherited
GIT_DIR redirected every baseline lookup. `0 verified` is the only
signal for either, and it was behind an opt-in require.

Separates the two concerns: counting is core honesty, writing an HTML
file is a feature. Reporting owns `verified`/`changed` and prints
`counts_summary` unconditionally from `finalize!`; Reporters::HTML keeps
the report file, stays opt-in, and its `summary` is now just the path of
the file it wrote -- on its own line, and nil when it wrote nothing. So
the counts print exactly once whether or not the reporter is loaded, and
`0 verified` still shouts NOTHING WAS VERIFIED.

The fork-parallel merge (#266) carries the new counters in the same
fragment as the missing-baseline names, and its guard now goes through
`Reporting.notify` -- the real path -- so it checks both halves the
worker has to hand back.

`count` warns and skips rather than raising: `notify` runs inside every
test's teardown, and a raise there aborts SnapDiff.reset before it
clears the registry, leaking one test's assertions into the next. Same
contract the reporter loop already applies, and just as loud
(unconditional, not DEBUG-gated). Adding it surfaced three test doubles
that never implemented Comparison's `difference` reader -- they had been
blowing up unnoticed inside HTML#record, which swallows under
`if ENV["DEBUG"]`.

docs: `configuration.md` recommended the LEGACY `Capybara::Screenshot.enabled`
spelling inside the canonical config reference, in three places.

* fix: a disabled screenshot no longer counts as an assertion (#270)

`integrations/minitest.rb` incremented the counter before the `active?`
guard inside `super`, which returns false immediately when screenshots
are disabled. So a test whose only assertion was a screenshot reported
`1 runs, 1 assertions, 0 failures` -- nothing captured, nothing
compared, and a green line claiming otherwise.

Counting only when active hands the alarm to Rails for free. Rails
unconditionally prepends ActiveSupport::Testing::TestsWithoutAssertions
into every ActiveSupport::TestCase (test_case.rb:205), so those tests
now warn:

    Test is missing assertions: `test_it` .../my_test.rb:12

Guarded in both directions with the real Rails module, not a stand-in:
the alarm fires for a test whose sole assertion was a disabled
screenshot, and stays quiet both for a test that asserts something else
and for a screenshot that actually ran.

Audited the other two adapters, neither needs a change:

- RSpec's matcher returns a literal `true`, which is correct rather than
  the same bug: returning `assert_matches_screenshot`'s false would fail
  the example over a config switch the user set on purpose, and a real
  mismatch raises rather than returning false. RSpec has no assertion
  count to correct and no missing-assertion alarm to trigger, so only
  the end-of-run `0 verified` line can see a disabled run. Said so at
  the call site, since the next reader will otherwise "fix" it.
- Cucumber counts nothing; `SnapDiff::DSL#assert_matches_screenshot`
  already returns false when inactive.

* test: pin the #274 record-modes interactions the rebase created

These three checks could not have been written before the rebase: the
counts line (#269) and record modes (#274) never met until now.

- The fork-parallel fragment carries BOTH tallies, so a worker that
  counts assertions AND re-records a baseline must hand back both and
  neither may cost the other. The main merge case now does both.
- A fragment with none of the keys added since #266 still merges. The
  fragments directory is keyed by pid under the system temp dir, so a
  recycled pid can hand the merge a fragment written by an older version
  of the gem; every key but "missing_baselines" is read with a default
  for exactly that. Mutation-checked: dropping one default fails the
  merge with `TypeError: nil can't be coerced into Integer`.
- `record: :all` end to end, on a finished process. It re-records
  without comparing, which through the summary path is neither verified
  nor changed, and NOT "new" either -- there was a baseline, it just was
  not consulted.
pftg added a commit that referenced this pull request Aug 24, 2026
…289)

`jruby-10.0/rails80` timed out on master run 32763256451 and took the whole
run red. Tests were still printing dots when SIGTERM landed, so the cell was
SLOW, not hung -- and the same cell had passed the previous three runs.

The 15-minute per-attempt budget was sized from run 32643567648, where a clean
JRuby attempt was 545-713s: "~26% headroom over the slowest", as the comment
says. That measurement is stale. Re-measured on run 32758898367, the last green
one before this bit:

    rails71   652s
    rails81   740s
    rails80   870s
    rails72   881s   <- against a 900s cap

The suite grew from 646 to 757 runs in between (#274, #277, #278, #279, #283),
and the headroom went with it: 881/900 is 2%. The cells have been passing by
seconds, which is why this looked stable for three runs and then was not.

20 minutes restores the ~26% margin this was originally sized for, and the job
cap follows to keep the arithmetic true: 1 + 20 + 20 = 41. Both numbers move
together on purpose -- a per-attempt timeout that does not fit the cap kills
the last attempt mid-run and reports `cancelled`, which reads as an absence
rather than a failure.

MRI is unchanged: 128s against 3 minutes.

Costs nothing on a green run; it is a ceiling, not a sleep.
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.

Accept-workflow and honest reporting: adopt what Playwright, VCR and SimpleCov already proved

1 participant