Setup
I've been experimenting with migrating to Xcode Cloud for our CI but I'm struggling with getting Snapshot Testing to work.
I've been involved in some discussions here... https://github.com/pointfreeco/swift-snapshot-testing/discussions/553
And I saw this site also which explains a little... https://wojciechkulik.pl/xcode/xcode-cloud-overview-and-setup
But so far I've been unsuccessful in getting it to work at all.
I'm using the swift-snapshot-testing library from PointFree https://github.com/pointfreeco/swift-snapshot-testing and I have overridden the assertSnapshot function to change the snapshot testing directory.
The function looks like this...
func snapshotDirectory(
for file: StaticString,
ciScriptsPathComponent: String = "ci_scripts",
relativePathComponent: String = "Tests"
) -> String {
var sourcePathComponents = URL(fileURLWithPath: "\(file)").pathComponents
if let indexFolder = sourcePathComponents.firstIndex(of: relativePathComponent) {
sourcePathComponents.insert("resources", at: indexFolder)
sourcePathComponents.insert(ciScriptsPathComponent, at: indexFolder)
}
var pathsComponents: [String] = sourcePathComponents.dropLast()
let fileUrl = URL(fileURLWithPath: "\(file)", isDirectory: false)
let folderName = fileUrl.deletingPathExtension().lastPathComponent
pathsComponents.append("__Snapshots__")
pathsComponents.append(folderName)
let directory = String(pathsComponents.joined(separator: "/").dropFirst())
return directory
}
public func assertSnapshot<Value, Format>(
matching value: @autoclosure () throws -> Value,
as snapshotting: Snapshotting<Value, Format>,
named name: String? = nil,
record recording: Bool = false,
timeout: TimeInterval = 5,
file: StaticString = #file,
testName: String = #function,
line: UInt = #line
) {
let failure = verifySnapshot(
matching: try value(),
as: snapshotting,
named: name,
record: recording,
snapshotDirectory: snapshotDirectory(for: file),
timeout: timeout,
file: file,
testName: testName
)
guard let message = failure else { return }
XCTFail("\(message) snap: \(snapshotDirectoryUrl) file: \(file) ", file: file, line: line)
}
Essentially this takes my test file path... repoRoot/Tests/FeatureTests/FeatureTestFile.swift.
And injects some path component into it so that you end up with a directory path... repoRoot/ci_scripts/resources/Tests/FeatureTests/__Snapshots__/FeatureTestFile/.
And then the snapshot file will be located in that directory using the name of the test function with a suffix of .1.txt or .2.txt (etc... for each subsequent snapshot in each function). i.e. testSnapshotStuff.1.txt, testSnapshotStuff.2.txt.
Problem
This all works locally. And all the files are checked into GitHub.
But, when I run this on Xcode Cloud it fails the tests and tells me the files are not there.
Having added some logging in it is writing new snapshot files to where I am expecting them to be so it just looks like those files are not available to the Test environment.
This is where I read about putting them into the ci_scripts file at the root of the repo. Which is what I've done. Files in this directory are supposed to be copied into the test environments so that they can be accessed... but it seems that they're not being.
I have tried using ci_scripts/resources and ci_scripts/Artifacts but it's always the same. The files aren't there and the Xcode Cloud tests write those files there over time.
I'm running out of options of what to do with this now. I just want a way that I can access these snapshot files in the test environment on Xcode Cloud.
Any help would be much appreciated.
Thanks
Post
Replies
Boosts
Views
Activity
Xcode 14.1
Running on iPhone 14 Pro max simulator 16.1
Code...
import SwiftUI
struct ContentView: View {
@State var loggedIn: Bool = false
var body: some View {
switch loggedIn {
case false:
Button("Login") {
loggedIn = true
}
.onAppear {
print("π Login on appear")
}
.onDisappear {
print("π Login on disappear")
}
case true:
TabView {
NavigationView {
Text("Home")
.navigationBarTitle("Home")
.onAppear {
print("π Home on appear")
}
.onDisappear {
print("π Home on disappear")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Logout") {
loggedIn = false
}
}
}
}
.tabItem {
Image(systemName: "house")
Text("Home")
}
NavigationView {
Text("Savings")
.navigationBarTitle("Savings")
.onAppear {
print("π Savings on appear")
}
.onDisappear {
print("π Savings on disappear")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Logout") {
loggedIn = false
}
}
}
}
.tabItem {
Image(systemName: "dollarsign.circle")
Text("Savings")
}
NavigationView {
Text("Profile")
.navigationBarTitle("Profile")
.onAppear {
print("π Profile on appear")
}
.onDisappear {
print("π Profile on disappear")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Logout") {
loggedIn = false
}
}
}
}
.tabItem {
Image(systemName: "person")
Text("Profile")
}
}
.onAppear {
print("π Tabview on appear")
}
.onDisappear {
print("π Tabview on disappear")
}
}
}
}
Video of bug... https://youtu.be/oLKjRGL2lX0
Example steps...
Launch app
Tap Login
Tap Savings tab
Tap Home tab
Tap Logout
Expected Logs...
π Login on appear
π Tabview on appear
π Home on appear
π Login on disappear
π Savings on appear
π Home on disappear
π Home on appear
π Savings on disappear
π Login on appear
π Home on disappear
π Tabview on disappear
Actual logs...
π Login on appear
π Tabview on appear
π Home on appear
π Login on disappear
π Savings on appear
π Home on disappear
π Home on appear
π Savings on disappear
π Login on appear
π Savings on appear
π Home on disappear
π Savings on disappear
π Tabview on disappear
Error...
10 and 12 in the actual logs should not be there at all.
For each tab that you have visited (that is not the current tab) it will call onAppear and onDisappear for it when the tab view is removed.
I have created a small project to show this bug...
https://github.com/oliverfoggin/BrokenImagePickers/tree/main
When run on device the app allows you to tap a button to show a UIImagePickerController or a PHPickerViewController (depending on the button).
If you just select a picture from there then everything works as expected.
But as soon as you enter into the search bar the UIImagePickerController will just crash back to the app. And the PHPickerViewController will display an error "Unable to Load Photos".
I'm running iOS 15 on an iPhone 11 Pro Max if that changes anything.
Would just like to know if I'm doing anything wrong with this. Or if I should file a radar for it?
Thanks