Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
2f85c334cb
commit
0ed1eee242
@ -503,7 +503,7 @@
|
|||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
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;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@ -525,7 +525,7 @@
|
|||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
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;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,10 +21,11 @@ struct RootView: View {
|
|||||||
|
|
||||||
/// Whether to show the paywall (shared between views)
|
/// Whether to show the paywall (shared between views)
|
||||||
@State private var showPaywall = false
|
@State private var showPaywall = false
|
||||||
|
private let isScreenshotUITest = ProcessInfo.processInfo.arguments.contains("-ui-testing-screenshots")
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if hasCompletedOnboarding {
|
if isScreenshotUITest || hasCompletedOnboarding {
|
||||||
// Main app content
|
// Main app content
|
||||||
ContentView()
|
ContentView()
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ struct ContentView: View {
|
|||||||
@State private var settings = SettingsViewModel()
|
@State private var settings = SettingsViewModel()
|
||||||
@State private var showSettings = false
|
@State private var showSettings = false
|
||||||
@State private var showPaywall = false
|
@State private var showPaywall = false
|
||||||
|
private let isScreenshotUITest = ProcessInfo.processInfo.arguments.contains("-ui-testing-screenshots")
|
||||||
|
|
||||||
@State private var capturedPhoto: CapturedPhoto?
|
@State private var capturedPhoto: CapturedPhoto?
|
||||||
@State private var showPhotoReview = false
|
@State private var showPhotoReview = false
|
||||||
@ -32,15 +33,25 @@ struct ContentView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
// Camera view - wrapped in EquatableView to prevent re-evaluation on settings changes
|
// Camera view - wrapped in EquatableView to prevent re-evaluation on settings changes
|
||||||
if !showPhotoReview {
|
if !showPhotoReview {
|
||||||
EquatableView(content: CameraContainerView(
|
if isScreenshotUITest {
|
||||||
settings: settings,
|
ScreenshotCameraPlaceholder(
|
||||||
sessionKey: cameraSessionKey,
|
showSettings: $showSettings,
|
||||||
cameraPosition: settings.cameraPosition,
|
ringWidth: 70.0,
|
||||||
onImageCaptured: { image in
|
ringColor: .white,
|
||||||
handlePhotoCaptured(image)
|
ringOpacity: 0.7
|
||||||
}
|
)
|
||||||
))
|
.ignoresSafeArea()
|
||||||
.ignoresSafeArea() // Only camera ignores safe area to fill screen
|
} else {
|
||||||
|
EquatableView(content: CameraContainerView(
|
||||||
|
settings: settings,
|
||||||
|
sessionKey: cameraSessionKey,
|
||||||
|
cameraPosition: settings.cameraPosition,
|
||||||
|
onImageCaptured: { image in
|
||||||
|
handlePhotoCaptured(image)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.ignoresSafeArea() // Only camera ignores safe area to fill screen
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Photo review overlay - handles its own safe area
|
// Photo review overlay - handles its own safe area
|
||||||
@ -67,7 +78,7 @@ struct ContentView: View {
|
|||||||
)
|
)
|
||||||
// Settings button overlay - only show when NOT in photo review mode
|
// Settings button overlay - only show when NOT in photo review mode
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
if !showPhotoReview {
|
if !showPhotoReview && !isScreenshotUITest {
|
||||||
Button {
|
Button {
|
||||||
showSettings = true
|
showSettings = true
|
||||||
} label: {
|
} label: {
|
||||||
@ -83,6 +94,13 @@ struct ContentView: View {
|
|||||||
.padding(.top, Design.Spacing.small)
|
.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)
|
.animation(.easeInOut(duration: Design.Animation.quick), value: showPhotoReview)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// Initialize tracking of camera position
|
// Initialize tracking of camera position
|
||||||
@ -220,7 +238,3 @@ struct CameraContainerView: View, Equatable {
|
|||||||
.startSession()
|
.startSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -85,11 +85,11 @@ struct ScreenshotCameraPlaceholder: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.accessibilityIdentifier("screenshot-green-screen")
|
.accessibilityIdentifier("screenshot-camera-placeholder")
|
||||||
.overlay(alignment: .topLeading) {
|
.overlay(alignment: .topLeading) {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: 1, height: 1)
|
.frame(width: 1, height: 1)
|
||||||
.accessibilityIdentifier("screenshot-green-screen")
|
.accessibilityIdentifier("screenshot-camera-placeholder")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,34 +8,125 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class SelfieCamUITests: XCTestCase {
|
final class SelfieCamUITests: XCTestCase {
|
||||||
|
private var app: XCUIApplication!
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
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
|
continueAfterFailure = false
|
||||||
|
app = XCUIApplication()
|
||||||
|
addUIInterruptionMonitor(withDescription: "System Permission Alerts") { alert in
|
||||||
|
let preferredButtons = [
|
||||||
|
"Allow",
|
||||||
|
"Allow While Using App",
|
||||||
|
"OK",
|
||||||
|
"Continue"
|
||||||
|
]
|
||||||
|
|
||||||
// In UI tests it’s 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 {
|
override func tearDownWithError() throws {
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func testExample() throws {
|
func testAppStorePortraitScreenshots() throws {
|
||||||
// UI tests must launch the application that they test.
|
app.launchArguments = ["-ui-testing-screenshots"]
|
||||||
let app = XCUIApplication()
|
app.launchEnvironment = [
|
||||||
|
"ENABLE_DEBUG_PREMIUM": "1"
|
||||||
|
]
|
||||||
app.launch()
|
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
|
@MainActor
|
||||||
func testLaunchPerformance() throws {
|
func testLaunchPerformance() throws {
|
||||||
// This measures how long it takes to launch your application.
|
|
||||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||||
XCUIApplication().launch()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user