Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-02-09 22:23:44 -06:00
parent 2f85c334cb
commit 0ed1eee242
5 changed files with 135 additions and 29 deletions

View File

@ -503,7 +503,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SelfieCam.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SelfieCam";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Selfie Cam.app/Selfie Cam";
};
name = Debug;
};
@ -525,7 +525,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SelfieCam.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SelfieCam";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Selfie Cam.app/Selfie Cam";
};
name = Release;
};

View File

@ -21,10 +21,11 @@ struct RootView: View {
/// Whether to show the paywall (shared between views)
@State private var showPaywall = false
private let isScreenshotUITest = ProcessInfo.processInfo.arguments.contains("-ui-testing-screenshots")
var body: some View {
ZStack {
if hasCompletedOnboarding {
if isScreenshotUITest || hasCompletedOnboarding {
// Main app content
ContentView()
.preferredColorScheme(.dark)

View File

@ -13,6 +13,7 @@ struct ContentView: View {
@State private var settings = SettingsViewModel()
@State private var showSettings = false
@State private var showPaywall = false
private let isScreenshotUITest = ProcessInfo.processInfo.arguments.contains("-ui-testing-screenshots")
@State private var capturedPhoto: CapturedPhoto?
@State private var showPhotoReview = false
@ -32,6 +33,15 @@ struct ContentView: View {
ZStack {
// Camera view - wrapped in EquatableView to prevent re-evaluation on settings changes
if !showPhotoReview {
if isScreenshotUITest {
ScreenshotCameraPlaceholder(
showSettings: $showSettings,
ringWidth: 70.0,
ringColor: .white,
ringOpacity: 0.7
)
.ignoresSafeArea()
} else {
EquatableView(content: CameraContainerView(
settings: settings,
sessionKey: cameraSessionKey,
@ -42,6 +52,7 @@ struct ContentView: View {
))
.ignoresSafeArea() // Only camera ignores safe area to fill screen
}
}
// Photo review overlay - handles its own safe area
if showPhotoReview, let photo = capturedPhoto {
@ -67,7 +78,7 @@ struct ContentView: View {
)
// Settings button overlay - only show when NOT in photo review mode
.overlay(alignment: .topTrailing) {
if !showPhotoReview {
if !showPhotoReview && !isScreenshotUITest {
Button {
showSettings = true
} label: {
@ -83,6 +94,13 @@ struct ContentView: View {
.padding(.top, Design.Spacing.small)
}
}
.overlay(alignment: .topLeading) {
if isScreenshotUITest {
Color.clear
.frame(width: 1, height: 1)
.accessibilityIdentifier("screenshot-mode-active")
}
}
.animation(.easeInOut(duration: Design.Animation.quick), value: showPhotoReview)
.onAppear {
// Initialize tracking of camera position
@ -220,7 +238,3 @@ struct CameraContainerView: View, Equatable {
.startSession()
}
}
#Preview {
ContentView()
}

View File

@ -85,11 +85,11 @@ struct ScreenshotCameraPlaceholder: View {
}
}
.ignoresSafeArea()
.accessibilityIdentifier("screenshot-green-screen")
.accessibilityIdentifier("screenshot-camera-placeholder")
.overlay(alignment: .topLeading) {
Color.clear
.frame(width: 1, height: 1)
.accessibilityIdentifier("screenshot-green-screen")
.accessibilityIdentifier("screenshot-camera-placeholder")
}
}
}

View File

@ -8,34 +8,125 @@
import XCTest
final class SelfieCamUITests: XCTestCase {
private var app: XCUIApplication!
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
app = XCUIApplication()
addUIInterruptionMonitor(withDescription: "System Permission Alerts") { alert in
let preferredButtons = [
"Allow",
"Allow While Using App",
"OK",
"Continue"
]
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
for title in preferredButtons where alert.buttons[title].exists {
alert.buttons[title].tap()
return true
}
if let firstButton = alert.buttons.allElementsBoundByIndex.first {
firstButton.tap()
return true
}
return false
}
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
func testAppStorePortraitScreenshots() throws {
app.launchArguments = ["-ui-testing-screenshots"]
app.launchEnvironment = [
"ENABLE_DEBUG_PREMIUM": "1"
]
app.launch()
if !waitForCameraScreen(timeout: 8) {
app.terminate()
app.launch()
}
// Use XCTAssert and related functions to verify your tests produce the correct results.
saveScreenshot(named: "01-launch-screen")
XCTAssertTrue(waitForCameraScreen(timeout: 25), "Camera screen did not appear")
XCTAssertTrue(app.otherElements["screenshot-camera-placeholder"].waitForExistence(timeout: 5), "Screenshot camera placeholder did not appear")
RunLoop.current.run(until: Date().addingTimeInterval(0.6))
saveScreenshot(named: "02-camera-ring-light")
let settingsButton = app.buttons["Settings"]
XCTAssertTrue(settingsButton.waitForExistence(timeout: 10), "Settings button not found")
settingsButton.tap()
XCTAssertTrue(app.otherElements["settings-sheet"].waitForExistence(timeout: 8), "Settings sheet did not appear")
RunLoop.current.run(until: Date().addingTimeInterval(0.8))
saveScreenshot(named: "03-settings-popup")
}
@MainActor
func testLaunchPerformance() throws {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
// MARK: - Helpers
@MainActor
private func waitForCameraScreen(timeout: TimeInterval) -> Bool {
let onboardingActions = [
"Get Started",
"Continue",
"Enable Camera",
"Enable Microphone",
"Enable Photos",
"Done"
]
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if app.otherElements["screenshot-mode-active"].exists {
return true
}
_ = tapFirstExistingButton(labels: onboardingActions)
tapFirstSpringboardButton(labels: ["Allow", "Allow While Using App", "OK", "Continue"])
if app.alerts.firstMatch.exists {
app.tap()
}
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
}
return app.otherElements["screenshot-mode-active"].exists
}
@MainActor
private func tapFirstSpringboardButton(labels: [String]) {
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
for label in labels {
let button = springboard.buttons[label]
if button.waitForExistence(timeout: 0.2) {
button.tap()
return
}
}
}
@MainActor
private func tapFirstExistingButton(labels: [String]) -> Bool {
for label in labels {
let button = app.buttons[label]
if button.exists {
button.tap()
return true
}
}
return false
}
@MainActor
private func saveScreenshot(named name: String) {
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = name
attachment.lifetime = .keepAlways
add(attachment)
}
}