barredewe / prefire Goto Github PK
View Code? Open in Web Editor NEW๐ฅ A library based on SwiftUI Preview, for easy generation: Playbook view, Snapshot and Accessibility tests
License: Apache License 2.0
๐ฅ A library based on SwiftUI Preview, for easy generation: Playbook view, Snapshot and Accessibility tests
License: Apache License 2.0
Just make a very simple form view like this:
static var previews: some View {
Form {
Section("Test") {
Text("Row 1")
Text("Row 2")
}
}
}
The preview that SwiftUI Canvas shows is the following:
The generated previews instead is:
Is very subtle but if you check the edges on the sides of the two rows you will see that the rounding happens PER ROW in the preview snapshot, instead of happening only at the end and at the start of the row like in the SwiftUI preview.
Hello I just added the Test Plugin to my UnitTests as a Build Tool Plug-in.
However I get this error saying that multiple command produce the Unit Tests product.
Am I doing something wrong in the setup?
I have added in the project Prefire, incuded it in the base target, and then as a build tool plugin only for the tests (I do not need the Playbook)
Generated snapshots for a test name are numbered:
test_authView_Preview.1.png
test_authView_Preview.2.png
test_authView_Preview.3.png
test_authView_Preview.4.png
Would be better if we supply the preview provider display name so that generated png's have more context:
test_prefireView_Preview.PrefireView.png
I would like to custom the snapshot image file name with custom prefix. e.g. with Swift snapshot testing, I can change the file name like test_snapshots.EU-staging-pt_PT
I checked PreviewTests.stencil
, and found the file name is created from preview.displayName
. Here is my code to do it with swift snapshot testing
.assertSnapshots(as: .image(precision: precision,
layout: .device(config: config)),
named: LocaleHelper.deviceLanguage + "_" + LocaleHelper.regionCode,
record: recording,
snapshotDirectory: XCTestCase.snapshotDirectoryUrl(file: file, pathPrefix: pathPrefix).path,
file: file,
testName: testName,
line: line
)
I am thinking about editing PreviewTests.stencil
, or add a new option to allow custom prefix?
While attempting to capture a view that includes AsyncImage or revealing a component after a specified duration, it's been noticed that the resulting snapshot doesn't include images loaded via URL or views that appear with a slight delay. Even when using .snapshot(delay: 1.0)
, there's no noticeable improvement in this scenario.
To overcome this challenge, it would be helpful to incorporate an option to wait before taking the snapshot. This way, we can ensure that the snapshot accounts for the asynchronous loading of images and views that emerge after a delay.
Example of a view that isn't properly captured in the snapshot:
struct DummyView: View {
@State var isButtonHidden = true
var body: some View {
VStack {
Text("Some text")
Text("Some text with delay")
.opacity(isButtonHidden ? 0 : 1)
}.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.isButtonHidden = false
}
}
}
}
Inside PreviewModels.stencil
we have a conditional import statement:
{% if argument.mainTarget %}
@testable import {{ argument.mainTarget }}
{% endif %}
Where as the PrefireTestsPlugin
can be provided with a mainTarget
argument.
It seems the PrefirePlaybookPlugin
has no support for passing the mainTarget
?
Think this might be as simple as passing:
"--args",
"mainTarget=\(targetName)",
to the PrefireTestsPlugin.Command
Setup the Prefire example project to use a - template_file_path: PreviewTests.stencil
, where PreviewTests.stencil
is just a copy of the default template next to PreFireExample.xcodeproj
Specifying a template_file_path
doesn't seem to be working anymore resulting in a does not exist or is not readable
error
Let's try to fix it maybe ๐
The README specifies NO_PLAYBOOK
as the name of the setting used to exclude PreviewModels
but the template uses PLAYBOOK_DISABLED
Prefire/Templates/PreviewModels.stencil
Line 16 in 237f400
We are using different local SPM packages and I added Prefire to one of them. PreviewTests is generated but it seems it doesn't use the .prefire.yml file to have a custom device and simulator version?
Specifying module like this struct FooPreviews: ModuleA.PrefierProvider { ... }
I guess such specification (that can take place in multimodular projects) shouldn't affect snapshot tests generation
I suppose Prefire parses project files and collects previews marked with PrefireProvider
, if that's the case โ then parser should use mask [space]*.PrefireProvider
to avoid the issue
The README specifies the command to configure permissions on CI as
defaults write com.apple.dt.Xcode ideskippackagepluginfingerprintvalidationbool YES
but it should be:
defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES
As per Apple's documentation - https://developer.apple.com/documentation/xcode/previewing-your-apps-interface-in-xcode #Preview
supports other forms of views such as UIViewController, NSView, UIView. Right now, unless you exclude those the generated tests from the plugin won't compile.
It would be amazing if we can add support for more view types other than just SwiftUI views.
In the PreviewLoader, it assumes that it's a SwiftUI view and always wraps it in AnyView
I wonder if we could add another function assertSnapshots
to the stencil that takes in the other view types like:
private func assertSnapshots(matching view: UIView,
name: String?, isScreen: Bool,
device: ViewImageConfig,
testName: String = #function,
traits: UITraitCollection = .init()) -> String? {
// ...
}
private func assertSnapshots(matching view: UIViewController,
name: String?, isScreen: Bool,
device: ViewImageConfig,
testName: String = #function,
traits: UITraitCollection = .init()) -> String? {
// ...
}
And then instead of always casting to AnyView
, use a more generic any View
parameter for the assertSnapshots
function and then allow it to dynamically call the appropriate function based on the classes implementation type?
Happy to help further with an implementation but I think this would be a great addition!
Swift package:
let package = Package(
name: "ModuleTypes",
platforms: [.iOS(.v15)],
products: [
.library(
name: "ModuleTypes",
targets: ["ModuleTypes"]
)
],
dependencies: [
.package(path: "../Asset"),
.package(path: "../Common"),
.package(path: "../Components"),
.package(url: "https://github.com/BarredEwe/Prefire.git", branch: "main"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.11.0")
],
targets: [
.target(
name: "ModuleTypes",
dependencies: [
"Prefire",
.product(name: "Assets", package: "Asset"),
.product(name: "Common", package: "Common"),
.product(name: "Components", package: "Components")
],
path: "./",
exclude: ["Tests"],
plugins: [
// For Snapshot Tests
.plugin(name: "PrefireTestsPlugin", package: "Prefire")
]
),
.testTarget(
name: "SnapshotTests",
dependencies: [
"ModuleTypes",
.product(name: "TestAssets", package: "Asset"),
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
]
)
]
)
I've placed a .prefire.yml
in multiple places up the directory chain.
Seems that StaticString
is conditional of argument.file
but the assertSnapshot
argument condition is not resolving correctly.
In our views we have UIImageView
which fetches from an image url.
For testing purposes these test assets are loaded from load bundle:
public enum TestResource {
public static func imageURL(named: String, ext: String) -> URL? {
Bundle.module.url(
forResource: named,
withExtension: ext,
subdirectory: nil
But for snapshots to work there needs to be a minor delay for completion closures to be called in which to display the image.
i.e:
let failure = verifySnapshot(
matching: try value(),
as: .wait(
for: wait,
on: .image(
drawHierarchyInKeyWindow: false,
precision: defaultPerceptualPrecision,
size: size
)
),
Potentially need to use shared Artifact bundle:
krzysztofzablocki/Sourcery#1155
Most of our previews look fine in preview without any additional attributes:
#Preview {
ShowMetaStack(
showModel: ShowModel.featureMockData,
nowNextModel: .success(.init()),
actionsRowModel: .withReminderMockData,
playButtonAction: {
},
actions: .init(
shareAction: nil,
watchlistAction: nil,
reminderAction: nil
)
)
.preferredColorScheme(.dark)
}
Our generated snapshots appear with white background and default device resolution:
Without needing to apply preview layout and background colours too all our previews:
.previewLayout(.sizeThatFits)
.background(.red)
I wonder if can add to the config in order to apply global attributes to all our previews which generate snapshots
I'm integrating Prefire 2.2.1 in a SwiftUI Design System package.
A space in the preview name isn't escaped or replaced with an underscore, causing compile errors in the generated code.
Compile error: Found an unexpected second identifier in function declaration; is there an accidental break?
Example:
#Preview("Redacted regular") {
...
}
Generates:
func test_Redacted regular_Preview() {
let preview = {
...
}
if let failure = assertSnapshots(matching: AnyView(preview()), name: "Redacted regular", isScreen: true, device: deviceConfig) {
XCTFail(failure)
}
}
Attempting to generate snapshot tests.
Using #Preview for SwiftUI previews.
.prefire.yml:
test_configuration:
- target: RedactedTests
- simulator_device: "iPhone15,4"
- required_os: 16
- preview_default_enabled: true
- imports:
- Foundation
- UIKit
- testable_imports:
- Prefire
The generated code (PreviewTests.generated.swift) throws error No such module Prefire
upon line import Prefire
.
I'm integrating Prefire 2.2.1 in a SwiftUI Design System package.
A common approach in our previews is the use of a VStack
or HStack
to display multiple instantiations of a view. This comes in handy for the (small) components of our design system, being able to see several variations at once.
Example:
#Preview {
VStack {
Redacted(.horizontal)
Redacted(.vertical)
}
}
This setup causes Prefire to use the same function name (test_VStack_Preview
and test_HStack_Preview
) for every generated preview test which results in invalid redeclaration compile errors.
func test_VStack_Preview() {
let preview = {
VStack {
...
}
}
if let failure = assertSnapshots(matching: AnyView(preview()), name: "VStack", isScreen: true, device: deviceConfig) {
XCTFail(failure)
}
}
Setting a custom previewDisplayName
or previewUserStory
isn't picked up either by Prefire, which could have been a workaround although I'd rather not have to specify a custom name/story.
I've got Prefire up & running in a package on my own machine. I'm trying to get the tests running on Xcode Cloud but it seems like the snapshots tests aren't run. The plain XCTest included in the package tests is being run though.
I have added a ci_post_clone.sh
script which resolved the "PrefireTestsPlugin" is disabled
error I initially had. So again, it all seems set up correctly.
Before I dig deeper & post more info, can anyone confirm it has actually worked on Xcode Cloud?
Would be nice to have the preview of the tests be device or height independent in some way.
What I mean is that it seems that there is no way to generate the previews so that they match exactly their fitting size.
This would be very useful to render scroll views for example, or very huge vertical stack layouts that do not fit a single iPhone screen.
Maybe the test code could only pick the device width (and not be completely device independent), and then have the height get rendered based on a size that fits it.
Or maybe in the configuration you could specify a specific device width (either from a device family like iPhone X or giving the px/pts value directly).
I am actually playing around myself with the .stencil code to see if I can achieve something like that but so far I did not find a solution that works generally.
Just updated from 1.5.0 to 2.3.0. I use playbook and tests plugins for my target.
Configuration file uses - preview_default_enabled: false
to manually choose previews for snapshots and playbook view.
.prefire.yml
content:
test_configuration:
- preview_default_enabled: false
- target: [Redacted]
- simulator_device: "iPhone15,2"
- required_os: 16
- snapshot_devices:
- iPhone 14 Pro
prefire_configuration:
- preview_default_enabled: false
We have rather big project with some local SPM local packages. In those packages we have SwiftUI screens and components.
I added Prefire to the project and package and updated a SwiftUI preview in the local package to use PrefireProvider.
But then the project generated the PreviewModels file but it has no access to
AppointmentDetailsScreen_Previews is part of Presentation package and that package isn't imported or cannot be accessed.. How can I do that?
Can I update the PreviewModels template file?
I'm integrating Prefire 2.1.1 in a SwiftUI Design System package.
The #Preview macro causes the PreviewTests.generated
file to contain compile errors. It seems that in some cases the #Preview macro breaks the generated code.
Error: Expected '(' in argument list of function declaration
.
When I remove the #Preview macro and create the SwiftUI preview with the old method, the compiler will fail on another one of these errors, but for a different preview. It seems like Prefires parsing of the #Preview macro is incorrectly adding a curly brace to the name in the generated code.
Example #Preview:
#Preview {
VStack {
Redacted("Hello, World!", .style1, .color1)
}
}
The generated code in PreviewTests.generated
, note the VStack{
reference in the name.
func test_VStack{_Preview() {
let preview = {
VStack {
Redacted("Hello, World!", .style1, .color1)
}
}
if let failure = assertSnapshots(matching: AnyView(preview()), name: "VStack{", isScreen: true, device: deviceConfig) {
XCTFail(failure)
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.