Skip to content

Commit c054c3b

Browse files
authored
Merge pull request #70 from kmatzen/feat/visual-regression-testing
Add visual regression testing with swift-snapshot-testing
2 parents e93d71c + 11404c6 commit c054c3b

10 files changed

Lines changed: 179 additions & 15 deletions

Facett.xcodeproj/project.pbxproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
objectVersion = 77;
77
objects = {
88

9+
/* Begin PBXBuildFile section */
10+
/* End PBXBuildFile section */
11+
912
/* Begin PBXContainerItemProxy section */
1013
D8C4C2022D345CE90093AB00 /* PBXContainerItemProxy */ = {
1114
isa = PBXContainerItemProxy;
@@ -208,6 +211,8 @@
208211
);
209212
mainGroup = D8C4C1E82D345CE50093AB00;
210213
minimizedProjectReferenceProxies = 1;
214+
packageReferences = (
215+
);
211216
preferredProjectObjectVersion = 77;
212217
productRefGroup = D8C4C1F22D345CE50093AB00 /* Products */;
213218
projectDirPath = "";
@@ -591,6 +596,12 @@
591596
defaultConfigurationName = Release;
592597
};
593598
/* End XCConfigurationList section */
599+
600+
/* Begin XCRemoteSwiftPackageReference section */
601+
/* End XCRemoteSwiftPackageReference section */
602+
603+
/* Begin XCSwiftPackageProductDependency section */
604+
/* End XCSwiftPackageProductDependency section */
594605
};
595606
rootObject = D8C4C1E92D345CE50093AB00 /* Project object */;
596607
}

Facett/BLEManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
17221722

17231723
/// Refresh discovered cameras without disconnecting connected ones
17241724
func refreshDiscoveredCameras() {
1725+
guard !FacettApp.isDemoMode else { return }
17251726
DispatchQueue.main.async {
17261727
// Clear only discovered cameras (not connected ones)
17271728
self.discoveredGoPros.removeAll()

Facett/CameraGroupManagementViews.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ struct CameraGroupEditorView: View {
184184
showingCameraSelector = true
185185
}
186186
.buttonStyle(.bordered)
187+
.accessibilityIdentifier("AddCameraButton")
187188
}
188189
}
189190
}

Facett/CameraGroupViewComponents.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct CameraGroupRowView: View {
7373
.foregroundColor(.secondary)
7474
.padding(8)
7575
}
76+
.accessibilityLabel("Group Actions")
7677
.onTapGesture {} // Prevent menu tap from triggering row selection
7778
}
7879
}

Facett/CameraViews.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ struct ActiveGroupSummaryView: View {
350350
@ObservedObject var bleManager: BLEManager
351351
@ObservedObject var configManager: ConfigManager
352352
@State private var selectedCamera: GoPro?
353-
@State private var showingCameraDetails = false
354353

355354
// Simple computed properties - no caching needed
356355
private var status: GroupStatus {
@@ -417,7 +416,6 @@ struct ActiveGroupSummaryView: View {
417416
)
418417
.onTapGesture {
419418
selectedCamera = connectedCamera
420-
showingCameraDetails = true
421419
}
422420
} else {
423421
// Camera is either discovered but not connected, or not found at all
@@ -433,14 +431,12 @@ struct ActiveGroupSummaryView: View {
433431
.padding()
434432
.background(Color(.systemGray6))
435433
.cornerRadius(12)
436-
.sheet(isPresented: $showingCameraDetails) {
437-
if let selectedCamera = selectedCamera {
438-
CameraDetailsSheet(
439-
camera: selectedCamera,
440-
bleManager: bleManager,
441-
cameraGroupManager: cameraGroupManager
442-
)
443-
}
434+
.sheet(item: $selectedCamera) { camera in
435+
CameraDetailsSheet(
436+
camera: camera,
437+
bleManager: bleManager,
438+
cameraGroupManager: cameraGroupManager
439+
)
444440
}
445441
}
446442
}

Facett/ConfigManagementView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,9 @@ struct ConfigRowView: View {
185185
} label: {
186186
Image(systemName: "ellipsis.circle")
187187
.foregroundColor(.secondary)
188-
.padding(8) // Increase tap area for menu
188+
.padding(8)
189189
}
190+
.accessibilityLabel("Config Actions")
190191
.onTapGesture {} // Prevent menu tap from triggering row selection
191192
}
192193
}

Facett/ContentView.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ struct ContentView: View {
8888
GeometryReader { geometry in
8989
let isLandscape = geometry.size.width > geometry.size.height
9090

91-
NavigationStack {
92-
ZStack {
91+
ZStack {
9392
VStack(spacing: 0) {
9493
// Recording Controls at the very top
9594
RecordingControlsView(
@@ -204,7 +203,6 @@ struct ContentView: View {
204203
autoSyncTimer = nil
205204
bleManager.onCameraStatusUpdated = nil
206205
}
207-
}
208206
}
209207
}
210208

Facett/FacettApp.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,25 @@ enum DemoDataProvider {
103103
let mainGroup = CameraGroup(name: "Main Setup", cameraSerials: serials, configId: configManager.configs.first?.id)
104104
cameraGroupManager.cameraGroups = [mainGroup]
105105
cameraGroupManager.setActiveGroup(mainGroup)
106+
107+
let extraCameras: [(name: String, serial: String, battery: Int)] = [
108+
("GoPro 1122", "GP1122", 45),
109+
("GoPro 3344", "GP3344", 90),
110+
("GoPro 5566", "GP5566", 62),
111+
]
112+
for extra in extraCameras {
113+
let uuid = UUID()
114+
let gopro = GoPro(identifier: uuid, name: extra.name)
115+
gopro.hasReceivedInitialStatus = true
116+
gopro.status.batteryPercentage = extra.battery
117+
gopro.status.batteryLevel = extra.battery / 25
118+
gopro.status.isBatteryPresent = true
119+
gopro.status.isReady = true
120+
gopro.status.wifiBars = 2
121+
gopro.status.apSSID = extra.serial
122+
CameraSerialResolver.shared.storeUUID(uuid, forSerial: extra.serial)
123+
CameraIdentityManager.shared.storeCameraName(extra.name, forSerial: extra.serial)
124+
bleManager.discoveredGoPros[uuid] = gopro
125+
}
106126
}
107127
}

Facett/QRCodeView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ struct QRCodeView: View {
141141
.background(Color.white)
142142
.cornerRadius(12)
143143
.shadow(color: Color.black.opacity(0.1), radius: 8, x: 0, y: 4)
144+
.accessibilityIdentifier("QRCodeImage")
144145
}
145146
}
146147
.onAppear(perform: setMaxBrightness)

FacettUITests/SnapshotTests.swift

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final class SnapshotTests: XCTestCase {
77

88
override func setUp() {
99
super.setUp()
10-
continueAfterFailure = false
10+
continueAfterFailure = true
1111
app = XCUIApplication()
1212
app.launchArguments += ["-DEMO_MODE"]
1313
setupSnapshot(app)
@@ -22,6 +22,9 @@ final class SnapshotTests: XCTestCase {
2222
func testScreenshots() {
2323
snapshot("01-Dashboard")
2424

25+
app.swipeUp()
26+
sleep(1)
27+
2528
let configurationsText = app.staticTexts["Configurations"]
2629
if configurationsText.exists {
2730
configurationsText.tap()
@@ -45,8 +48,139 @@ final class SnapshotTests: XCTestCase {
4548
snapshot("04-BugReport")
4649
dismissSheet()
4750
}
51+
52+
app.swipeDown()
53+
sleep(1)
54+
55+
let cameraCard = app.staticTexts["GoPro 0841"]
56+
if cameraCard.exists {
57+
cameraCard.tap()
58+
sleep(2)
59+
snapshot("05-CameraDetails")
60+
dismissSheet()
61+
}
62+
}
63+
64+
func testEditConfigScreenshot() {
65+
app.swipeUp()
66+
sleep(1)
67+
68+
let configurationsText = app.staticTexts["Configurations"]
69+
if configurationsText.exists {
70+
configurationsText.tap()
71+
sleep(1)
72+
73+
let actionsButton = app.buttons.matching(identifier: "Config Actions").firstMatch
74+
if actionsButton.waitForExistence(timeout: 3) {
75+
actionsButton.tap()
76+
sleep(1)
77+
let editButton = app.buttons["Edit"]
78+
if editButton.waitForExistence(timeout: 3) {
79+
editButton.tap()
80+
sleep(1)
81+
snapshot("06-EditConfiguration")
82+
}
83+
}
84+
}
85+
}
86+
87+
func testEditCameraGroupScreenshot() {
88+
app.swipeUp()
89+
sleep(1)
90+
91+
let cameraGroupsText = app.staticTexts["Camera Groups"]
92+
if cameraGroupsText.exists {
93+
cameraGroupsText.tap()
94+
sleep(1)
95+
96+
let ellipsisButton = app.buttons["Group Actions"]
97+
if ellipsisButton.waitForExistence(timeout: 3) {
98+
ellipsisButton.tap()
99+
sleep(1)
100+
let editButton = app.buttons["Edit"]
101+
if editButton.waitForExistence(timeout: 3) {
102+
editButton.tap()
103+
sleep(1)
104+
snapshot("07-EditCameraGroup")
105+
}
106+
}
107+
}
48108
}
49109

110+
func testAddCameraScreenshot() {
111+
app.swipeUp()
112+
sleep(2)
113+
114+
let cameraGroupsText = app.staticTexts["Camera Groups"]
115+
guard cameraGroupsText.waitForExistence(timeout: 5) else { return }
116+
cameraGroupsText.tap()
117+
sleep(2)
118+
119+
let ellipsisButton = app.buttons["Group Actions"]
120+
guard ellipsisButton.waitForExistence(timeout: 5) else { return }
121+
ellipsisButton.tap()
122+
sleep(2)
123+
124+
let editButton = app.buttons["Edit"]
125+
guard editButton.waitForExistence(timeout: 5) else { return }
126+
editButton.tap()
127+
sleep(2)
128+
129+
let addCameraButton = app.buttons["AddCameraButton"]
130+
guard addCameraButton.waitForExistence(timeout: 5) else { return }
131+
addCameraButton.tap()
132+
sleep(2)
133+
snapshot("08-AddCamera")
134+
}
135+
136+
func testAddCameraGroupScreenshot() {
137+
app.swipeUp()
138+
sleep(2)
139+
140+
let cameraGroupsText = app.staticTexts["Camera Groups"]
141+
guard cameraGroupsText.waitForExistence(timeout: 5) else { return }
142+
cameraGroupsText.tap()
143+
sleep(2)
144+
145+
let addGroupButton = app.buttons["Add Group"]
146+
guard addGroupButton.waitForExistence(timeout: 5) else { return }
147+
addGroupButton.tap()
148+
sleep(2)
149+
snapshot("09-AddCameraGroup")
150+
}
151+
152+
func testVoiceNotificationsScreenshot() {
153+
app.swipeUp()
154+
sleep(1)
155+
156+
let voiceText = app.staticTexts["Voice Notifications"]
157+
if voiceText.exists {
158+
voiceText.tap()
159+
sleep(1)
160+
snapshot("10-VoiceNotifications")
161+
}
162+
}
163+
164+
func testQRCodeScreenshot() {
165+
app.swipeUp()
166+
sleep(1)
167+
app.swipeUp()
168+
sleep(1)
169+
170+
let qrCodesButton = app.staticTexts["QR Codes"]
171+
if qrCodesButton.exists {
172+
qrCodesButton.tap()
173+
sleep(2)
174+
app.swipeUp()
175+
sleep(1)
176+
app.swipeUp()
177+
sleep(1)
178+
snapshot("11-QRCodes")
179+
}
180+
}
181+
182+
// MARK: - Helpers
183+
50184
private func dismissSheet() {
51185
if app.navigationBars.buttons.firstMatch.exists {
52186
app.navigationBars.buttons.firstMatch.tap()

0 commit comments

Comments
 (0)