Skip to content

SnapshotTesting 2.0 #1003

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft

SnapshotTesting 2.0 #1003

wants to merge 9 commits into from

Conversation

o-nnerb
Copy link

@o-nnerb o-nnerb commented Jun 21, 2025

This is a preview of what I've been working on over the past few months.

It all started with #760, #742 and #973. I began by testing the proposed solution but soon realized it didn’t fully address my needs. There were, in fact, two key problems:

  1. Missing Delay Support: SnapshotTesting lacked a clean way to capture screenshots after a delay on UIWindow.
  2. View Rendering Issues: Even with workarounds like wait, there was no guarantee views were fully rendered before snapshots were taken.

Solving the First Problem: Delay Support

Adding delays programmatically can be done in multiple ways, but the existing SnapshotTesting architecture made it difficult to inject delays mid-flow. To resolve this, I restructured the core logic of the Snapshotting API, inspired by Swift’s functional programming paradigms.

I split the Async type into two distinct components:

  • Sync: For synchronous operations within the same execution context.
  • Async: For asynchronous workflows using Swift’s async/await.

Building on functional pipelines (input → processing → output), I redesigned Snapshotting to enforce a clear transformation chain:

UIView -> UIViewController + UIWindow -> Delay -> snapshot() -> UIImage

Each step in this pipeline processes input and passes the result to the next, enabling modular and composable snapshot logic.


Solving the Second Problem: Reliable View Rendering

This challenge required deeper exploration of UIKit’s lifecycle. The existing UIWindow initialization (init()) was outdated—though not officially deprecated—since the introduction of UISceneDelegate. To ensure compatibility with both UIKit and SwiftUI, I updated the implementation to use UIWindow(windowScene:).

This change resolved rendering inconsistencies, particularly for UINavigationController and NavigationStack. Additionally, I discovered that relying on keyWindow (now deprecated) was unnecessary, as the new approach works seamlessly with or without scene delegate support.


Key Improvements & Learnings

  • Cross-Platform Support: Added testing capabilities for visionOS, watchOS, tvOS, iOS, and macOS (though macOS requires further refinement).
  • Swift 6.0 Compliance: Modernized the codebase with async/await, Sendable, and Swift Package Manager (SPM) compatibility.
  • Modular Architecture: Split the library into two modules:
    • SnapshotTesting: Adapters for integrating with Swift’s new Testing framework.
    • XCTSnapshot: Core snapshot logic (e.g., rendering pipelines, environment configuration).
      • Inspired by XCTest’s XCT... naming convention, this layer ensures compatibility with Swift Testing’s test runner.
  • Snapshot Environment: Introduced SnapshotEnvironment and SnapshotEnvironmentKey to pass contextual parameters (e.g., delays, platform-specific configs).
  • Object-Oriented Design: Restructured the codebase for clarity, maintainability, and scalability.
  • Backward Compatibility: Deprecated legacy APIs (marked with @available) to ease migration.

Remaining Tasks

  • Add documentation for newly implemented methods.
  • Update existing documentation to reflect changes.
  • Fix outdated tests to avoid confusion for contributors.
  • Test rendering across platforms (macOS currently has issues).
  • Evaluate dynamic UIWindow sizing requirements.

Final Notes

This PR is a draft, and I welcome feedback or suggestions. Given the scope of the refactoring, I completely understand if the project maintainers decide not to merge it.

What began as an attempt to fix a delay issue evolved into a complete overhaul. Regardless of the outcome, I’m deeply grateful to the community for the foundation and the opportunity to contribute over these months.

Thank you for your time and consideration! 🙏


P.S. Contributions to address the remaining tasks are highly encouraged!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant