{"id":20149072,"url":"https://github.com/emergetools/snapshotpreviews","last_synced_at":"2025-12-11T23:01:10.937Z","repository":{"id":180756341,"uuid":"663230812","full_name":"EmergeTools/SnapshotPreviews","owner":"EmergeTools","description":"📸 Automatic snapshots from Xcode previews. Supports UIKit/AppKit/SwiftUI on iOS/macOS/watchOS/visionOS/tvOS. Browse previews in-app with the Preview Gallery, or save them to PNGs with an XCTest","archived":false,"fork":false,"pushed_at":"2025-11-14T19:26:57.000Z","size":2626,"stargazers_count":380,"open_issues_count":13,"forks_count":18,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-11-18T09:30:01.324Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.emergetools.com","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EmergeTools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-07-06T21:07:06.000Z","updated_at":"2025-11-17T14:43:58.000Z","dependencies_parsed_at":"2023-10-12T04:20:02.995Z","dependency_job_id":"d4d58e56-8208-43fb-b8b8-7e66c3ec85eb","html_url":"https://github.com/EmergeTools/SnapshotPreviews","commit_stats":null,"previous_names":["emergetools/etbrowser","emergetools/snapshotpreviews"],"tags_count":69,"template":false,"template_full_name":null,"purl":"pkg:github/EmergeTools/SnapshotPreviews","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EmergeTools%2FSnapshotPreviews","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EmergeTools%2FSnapshotPreviews/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EmergeTools%2FSnapshotPreviews/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EmergeTools%2FSnapshotPreviews/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EmergeTools","download_url":"https://codeload.github.com/EmergeTools/SnapshotPreviews/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EmergeTools%2FSnapshotPreviews/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27571234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-07T02:00:07.896Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T22:41:14.615Z","updated_at":"2025-12-11T23:01:10.361Z","avatar_url":"https://github.com/EmergeTools.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📸 SnapshotPreviews\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools%2FSnapshotPreviews%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/EmergeTools/SnapshotPreviews)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools%2FSnapshotPreviews%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/EmergeTools/SnapshotPreviews)\n[![](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.emergetools.com%2Fapi%2Fv2%2Fpublic_new_build%3FexampleId%3Dsnapshotpreviews-ios.PreviewGallery%26platform%3Dios%26badgeOption%3Dversion_and_max_install_size%26buildType%3Drelease\u0026query=$.badgeMetadata\u0026label=PreviewGallery\u0026logo=apple)](https://www.emergetools.com/app/example/ios/snapshotpreviews-ios.PreviewGallery/release?utm_campaign=badge-data)\n[![](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.emergetools.com%2Fapi%2Fv2%2Fpublic_new_build%3FexampleId%3Dsnapshotpreviews-ios.SnapshottingTests%26platform%3Dios%26badgeOption%3Dversion_and_max_install_size%26buildType%3Drelease\u0026query=$.badgeMetadata\u0026label=SnapshottingTests\u0026logo=apple)](https://www.emergetools.com/app/example/ios/snapshotpreviews-ios.SnapshottingTests/release?utm_campaign=badge-data)\n\nAn all-in-one snapshot testing solution built on Xcode previews. Automatic browsable gallery of previews, and no-code snapshot generation with XCTest. Supports SwiftUI and UIKit previews using `PreviewProvider` or `#Preview` and works on all Apple platforms (iOS/macOS/watchOS/tvOS/visionOS).\n\n- 🖼️ Browse previews on device as part of your app using the `PreviewGallery`, no Xcode required.\n- 📸 Snapshot Xcode previews automatically in a XCTest without writing any test code.\n- ♿ Run accessibility audits on all your previews in a XCUITest, still without writing any test code.\n\n# Features\n\n## Preview Gallery\n\n`PreviewGallery` is an interactive UI built on top of snapshot extraction. It turns your Xcode previews into a gallery of components and features you can access from your application, for example in an internal settings screen. **Xcode is not required to view the previews.** You can use it to preview individual components (buttons/rows/icons/etc) or even entire interactive features.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/master/images/image1.png\" /\u003e\n\u003c/p\u003e\n\nThe public API of PreviewGallery is a single SwiftUI `View` named `PreviewGallery`. Displaying this view gives you access to the full gallery. For example, you could add a button to open the gallery like this:\n\n```swift\nimport SwiftUI\nimport PreviewGallery\n\nstruct InternalSettingsView: View {\n  var body: some View {\n    NavigationStack {\n      Form {\n        Section(\"Previews\") {\n          NavigationLink(\"Open Gallery\") { PreviewGallery() }\n        }\n      }\n    }\n    .navigationTitle(\"Internal Settings\")\n  }\n}\n```\n\n## Local Snapshot Generation\n\nGenerate PNGs for each Xcode preview with no code as part of an XCTest. Link your XCTest target to `SnapshottingTests` and create a test that inherits from `SnapshotTest` like this:\n\n```swift\nimport SnapshottingTests\n\nclass DemoAppPreviewTest: SnapshotTest {\n\n  // Return the type names of previews like \"MyApp.MyView._Previews\" to selectively render only some previews\n  override class func snapshotPreviews() -\u003e [String]? {\n    return nil\n  }\n\n  // Use this to exclude some previews from generating\n  override class func excludedSnapshotPreviews() -\u003e [String]? {\n    return nil\n  }\n}\n```\n\nNote that there are no test functions; they are automatically added at runtime by `SnapshotTest`. You can return a list of previews from the `snapshotPreviews()` function based on what preview you are trying to locally validate. The snapshots will be added as attachments in Xcode’s test results.\n\n\u003e [!NOTE]\n\u003e When you use Preview macros (`#Preview(\"Display Name\")`) the name of the snapshot uses the file path and the name, for example: \"MyModule/MyFile.swift:Display Name\"\n\n![Screenshot of Xcode test output](https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/master/images/testOutput.png)\n\nThe [EmergeTools snapshot testing service](https://docs.emergetools.com/docs/snapshot-testing) generates snapshots and diffs them in the cloud to control for sources of flakiness, store images outside of git, and optimize test performance. `SnapshotTest` is for locally debugging these snapshot tests. You can also use `PreviewLayoutTest` to get code coverage of all previews in your unit test without generating PNGs. This will validate that previews do not crash (such as a missing @EnvironmentObject) but runs faster because it does not render the views to images.\n\n## Accessibility Audits\n\nXcode [accessibility audits](https://developer.apple.com/documentation/xctest/xcuiapplication/4191487-performaccessibilityaudit) can also be run locally on any preview. They are run in a UI test (not unit test). To enable these, inherit from `AccessibilityPreviewTest`. To customize the behavior you can override the following functions in your test:\n\n```swift\nimport SnapshottingTests\nimport Snapshotting\n\nclass DemoAppAccessibilityPreviewTest: AccessibilityPreviewTest {\n\n  override func auditType() -\u003e XCUIAccessibilityAuditType {\n    return .all\n  }\n\n  override func handle(issue: XCUIAccessibilityAuditIssue) -\u003e Bool {\n    return false\n  }\n}\n```\n\nSee the demo app for a full example.\n\n\u003cdetails\u003e\n  \u003csummary\u003eHow does it work?\u003c/summary\u003e\n\n  The XCTest dynamically inserts test functions by creating functions using the Objective-C runtime and overriding XCTest’s `testInvocations` function.\n\n  Previews are discovered in the binary by parsing the `__swift5_proto` Mach-O section to see what types conform to `PreviewProvider` (and similar protocols generated by the #Preview macro). Details of how this works in the Swift runtime can be found in our [blog post](https://www.emergetools.com/blog/posts/SwiftProtocolConformance).\n\u003c/details\u003e\n\n# Installation\n\nAdd the package dependency to your Xcode project using the URL of this repository (https://github.com/EmergeTools/SnapshotPreviews).\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/master/images/image2.png\" /\u003e\n\u003c/p\u003e\n\nLink your app to `PreviewGallery` and (optionally) to `SnapshotPreferences` to customize the behavior of snapshot generation.\nLink your XCTest target to `SnapshottingTests`.\n\n# Tips\n\n### Unique names\n\nIt’s strongly encouraged to use a display name for every preview, for example:\n\n```swift\nstruct MyView_Previews: PreviewProvider {\n  var previews: some View {\n    MyView().previewDisplayName(\"My Display Name\")\n    // Note if you had more than one view here they should all have different display names.\n  }\n}\n\n#Preview(\"My Display Name\") {\n  MyView()\n}\n```\n\nThe display name will show up in XCTest results and the EmergeTools UI. Display names should be unique within each PreviewProvider or within files in the case of preview macros.\n\n### Environment variables\n\nIt’s recommended to set the environment variable `EMERGE_IS_RUNNING_FOR_SNAPSHOTS` to `1` in your unit test scheme. This is also set when snapshots are generated from the EmergeTools snapshot testing service. Combine it with the Xcode previews variable like this:\n\n```swift\nextension ProcessInfo {\n  var isRunningPreviews: Bool {\n    environment[\"EMERGE_IS_RUNNING_FOR_SNAPSHOTS\"] == \"1\" || environment[\"XCODE_RUNNING_FOR_PREVIEWS\"] == \"1\"\n  }\n}\n```\n\nCheck `ProcessInfo.isRunningPeviews` to disable behavior you don’t want in previews such as emitting logging data.\n\n### Variants\n\n\u003e [!TIP]\n\u003e Using PreviewVariants greatly simplifies snapshot testing, by ensuring a consistent set of variants and that every view is provided a name.\n\nUsing multiple variants of the same view can ensure test coverage of all the ways users interact with your UI. Most are provided by SwiftUI, eg: `.dynamicTypeSize(.xxxLarge)`. There is one built into the package: `.emergeAccessibility(true)`. This function adds a visualization of voice over elements to your snapshot. You can automatically add variants using the [`PreviewVariants` View](https://github.com/EmergeTools/SnapshotPreviews/blob/main/Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift) that is demonstrated in the example app. It adds RTL, landscape, accessibility, dark mode and large text variants. You can use it like this:\n\n```swift\nstruct MyView_Previews: PreviewProvider {\n  static var previews: some View {\n    PreviewVariants(layout: .sizeThatFits) {\n      MyView(mode: .loaded)\n        // PreviewVariants requires that every view has a name, so you can’t create one without a display name\n        .previewVariant(named: \"My View - Loaded\")\n      \n      MyView(mode: .loading)\n        .previewVariant(named: \"My View - Loading\")\n      \n      MyView(mode: .error)\n        .previewVariant(named: \"My View - Error\")\n    }\n  }\n}\n```\n\n# Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=EmergeTools/SnapshotPreviews\u0026type=Date)](https://star-history.com/#EmergeTools/SnapshotPreviews\u0026Date)\n\n# Related Reading\n- [How to use VariadicView, SwiftUI's Private View API](https://www.emergetools.com/blog/posts/how-to-use-variadic-view): VariadicView is a core part of how multiple images are rendered for one PreviewProvider.\n- [The Surprising Cost of Protocol Conformances in Swift](https://www.emergetools.com/blog/posts/SwiftProtocolConformance): Details of how protocol conformances work in the runtime, which is how previews are discovered in app binaries.\n- [Emerge Android](https://github.com/EmergeTools/emerge-android): The android SDK for similar preview based snapshot testing, along with other EmergeTools features.\n- [ETTrace](https://github.com/EmergeTools/ETTrace): Another open source iOS project from EmergeTools.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femergetools%2Fsnapshotpreviews","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femergetools%2Fsnapshotpreviews","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femergetools%2Fsnapshotpreviews/lists"}