pointfreeco / swift-snapshot-testing

📸 Delightful Swift snapshot testing.
https://www.pointfree.co/episodes/ep41-a-tour-of-snapshot-testing
MIT License
3.74k stars 568 forks source link

Unable to snapshot modal UIViewController #279

Open ileitch opened 4 years ago

ileitch commented 4 years ago

In order to present a modal view controller, its parent view controller must already be a member of a window hierarchy. Therefore given that SnapshotTesting prepares its window internally, there's no way to correctly present the modal controller.

I'm new to this project, so I'm not too sure on what the preferred API for achieving this might look like?

stephencelis commented 4 years ago

We discovered this with iOS 13 recently and know how to fix it. Unfortunately there are a few caveats:

  1. It requires a host application, which can slow down tests
  2. It requires some pretty major refactoring of the existing strategies.

Here's a very simple strategy that should get you what you want, though!

extension Snapshotting where Value: UIViewController, Format == UIImage {
   static var windowedImage: Snapshotting {
     return Snapshotting<UIImage, UIImage>.image.asyncPullback { vc in
       Async<UIImage> { callback in
         UIView.setAnimationsEnabled(false)
         let window = UIApplication.shared.windows[0]
         window.rootViewController = vc
         DispatchQueue.main.async {
           let image = UIGraphicsImageRenderer(bounds: window.bounds).image { ctx in
             window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
           }
           callback(image)
           UIView.setAnimationsEnabled(true)
         }
       }
     }
   }
 }

We'd like to get this logic back into the main view controller strategies, but it may be a little while.

ileitch commented 4 years ago

Might an API like this be possible, and also not so difficult to implement?

assertSnapshot(matching: .modal(myModalViewController), as: .image)

Internally it'd just defer presentation until after the window is ready?

ileitch commented 4 years ago

Possibly related to this, I'm also coming across instances where our view controllers require some setup to be performed after they've been added to the window, but before the snapshot is taken, e.g selecting table view cells.

myurieff commented 2 years ago

Sorry to bump up such an old issue, but is anyone having troubles using the windowedImage strategy? I'm getting blank images in my company's project. Even creating a plain UIViewController and giving it a background color results in a blank image. The test target has a host app set in the settings. @stephencelis

ugiacoman commented 2 years ago

Sorry to bump up such an old issue, but is anyone having troubles using the windowedImage strategy? I'm getting blank images in my company's project. Even creating a plain UIViewController and giving it a background color results in a blank image. The test target has a host app set in the settings. @stephencelis

Ran into the same issue, did you figure out what was happening @myurieff?

myurieff commented 2 years ago

Sorry to bump up such an old issue, but is anyone having troubles using the windowedImage strategy? I'm getting blank images in my company's project. Even creating a plain UIViewController and giving it a background color results in a blank image. The test target has a host app set in the settings. @stephencelis

Ran into the same issue, did you figure out what was happening @myurieff?

I didn't have the time to dig deeper into it (so no idea what was the cause), however I made it work by creating a new empty app target that the test suite was using as a host app.

ugiacoman commented 2 years ago

Sorry to bump up such an old issue, but is anyone having troubles using the windowedImage strategy? I'm getting blank images in my company's project. Even creating a plain UIViewController and giving it a background color results in a blank image. The test target has a host app set in the settings. @stephencelis

Ran into the same issue, did you figure out what was happening @myurieff?

I didn't have the time to dig deeper into it (so no idea what was the cause), however I made it work by creating a new empty app target that the test suite was using as a host app.

If anyone else runs into this, our snapshots using the windowed strategy were showing up "blank" because our test suite had opened up Safari. Can't take a snapshot within the hierarchy at that point!

matheusalano commented 1 year ago

Hey! We've been using this extension for snapshotting modal, and we use it with quick/nimble. but since their latest update where they start using async/await, this extension stopped working and now it's timing out. I'm looking into fixing it, but also posting here in case someone has already the answer.

myurieff commented 1 year ago

Hey! We've been using this extension for snapshotting modal, and we use it with quick/nimble. but since their latest update where they start using async/await, this extension stopped working and now it's timing out. I'm looking into fixing it, but also posting here in case someone has already the answer.

Bumping this again.

I've made a separate app target + unit test target and we don't get blank images anymore.

However, I'm getting the same issue as @matheusalano. Using windowedImage strategy, I'm getting timeouts. The strange thing is that if you put a breakpoint on callback(image), you can see that the proper image is being generated. However, that is called after the test has failed with a timeout. Will investigate further.

myurieff commented 1 year ago

Seems like some type of race condition is happening and the main thread is blocked. Since I'm already on the @MainActor in my particular case, removing DispatchQueue.main.async fixes the timing out issue for me.