updated tests

Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
Matt Bruce 2026-02-08 09:33:51 -06:00
parent 7b3a8903a8
commit 86e4382cc2

View File

@ -10,32 +10,375 @@ import XCTest
final class TheNoiseClockUITests: XCTestCase {
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
// 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.
}
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.
func testAppStoreScreenshots_iPhone69() throws {
let app = XCUIApplication()
app.launchArguments += [
"-onboarding.TheNoiseClock.hasCompletedWelcome", "YES",
"-onboarding.TheNoiseClock.hasLaunched", "YES"
]
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
dismissOnboardingIfNeeded(app)
ensureKeepAwakeEnabled(app)
ensureAlarmExistsAndEnabled(app)
captureNoiseViewPortraitAndLandscape(app)
captureClockPortraitAndLandscape(app)
captureAlarmsViewPortraitAndLandscape(app)
captureSettingsViewPortraitAndLandscape(app)
XCUIDevice.shared.orientation = .portrait
}
// MARK: - Capture Steps
@MainActor
private func captureClockPortraitAndLandscape(_ app: XCUIApplication) {
XCUIDevice.shared.orientation = .portrait
sleep(1)
openTab(named: "Clock", in: app)
waitForClockFullscreenTransition()
saveScreenshot(named: "01-clock-view-portrait.png")
XCUIDevice.shared.orientation = .landscapeLeft
sleep(2)
openTab(named: "Clock", in: app)
waitForClockFullscreenTransition()
saveScreenshot(named: "02-clock-view-landscape.png")
}
@MainActor
func testLaunchPerformance() throws {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
private func captureAlarmsViewPortraitAndLandscape(_ app: XCUIApplication) {
XCUIDevice.shared.orientation = .portrait
sleep(1)
openTab(named: "Alarms", in: app)
sleep(1)
saveScreenshot(named: "03-alarms-view-portrait.png")
XCUIDevice.shared.orientation = .landscapeLeft
sleep(2)
openTab(named: "Alarms", in: app)
sleep(1)
saveScreenshot(named: "04-alarms-view-landscape.png")
}
@MainActor
private func captureNoiseViewPortraitAndLandscape(_ app: XCUIApplication) {
XCUIDevice.shared.orientation = .portrait
sleep(1)
openTab(named: "Noise", in: app)
ensureNoiseSoundSelectedAndPlaying(app)
sleep(1)
saveScreenshot(named: "05-noise-view-portrait.png")
XCUIDevice.shared.orientation = .landscapeLeft
sleep(2)
openTab(named: "Noise", in: app)
ensureNoiseSoundSelectedAndPlaying(app)
sleep(1)
saveScreenshot(named: "06-noise-view-landscape.png")
}
@MainActor
private func captureSettingsViewPortraitAndLandscape(_ app: XCUIApplication) {
XCUIDevice.shared.orientation = .portrait
sleep(1)
openTab(named: "Settings", in: app)
sleep(1)
saveScreenshot(named: "07-clock-settings-portrait.png")
XCUIDevice.shared.orientation = .landscapeLeft
sleep(2)
openTab(named: "Settings", in: app)
sleep(1)
saveScreenshot(named: "08-clock-settings-landscape.png")
}
@MainActor
private func ensureNoiseSoundSelectedAndPlaying(_ app: XCUIApplication) {
// Select a known sound so controls become visible.
let soundNames = [
"White Noise",
"Brown Noise",
"Heavy Rain",
"Atmospheric Pad",
"Fan Heater"
]
var didSelectSound = false
for _ in 0..<8 {
for soundName in soundNames {
let sound = app.staticTexts[soundName]
if sound.exists && sound.isHittable {
sound.tap()
didSelectSound = true
break
}
}
if didSelectSound {
break
}
app.swipeUp()
usleep(250_000)
}
let playButton = app.buttons["Play Sound"]
if playButton.waitForExistence(timeout: 4) {
playButton.tap()
}
}
// MARK: - State Setup
@MainActor
private func dismissOnboardingIfNeeded(_ app: XCUIApplication) {
if app.buttons["Skip"].waitForExistence(timeout: 3) {
app.buttons["Skip"].tap()
waitForMainTabs(app)
return
}
// Fallback: walk onboarding pages if skip is not available.
for _ in 0..<6 {
if hasMainTabs(app) { return }
let next = app.buttons["Next"]
let getStarted = app.buttons["Get Started"]
if getStarted.exists {
getStarted.tap()
waitForMainTabs(app)
return
}
if next.exists {
next.tap()
usleep(300_000)
}
}
}
@MainActor
private func ensureKeepAwakeEnabled(_ app: XCUIApplication) {
openTab(named: "Settings", in: app)
let keepAwakeLabel = app.staticTexts["Keep Awake"]
for _ in 0..<8 {
if keepAwakeLabel.exists { break }
app.swipeUp()
usleep(200_000)
}
guard keepAwakeLabel.waitForExistence(timeout: 3) else {
XCTFail("Could not find Keep Awake toggle in Settings.")
return
}
let labelY = keepAwakeLabel.frame.midY
let switchCandidates = app.switches.allElementsBoundByIndex.filter { element in
abs(element.frame.midY - labelY) < 90
}
if let keepAwakeSwitch = switchCandidates.first {
if !isSwitchOn(keepAwakeSwitch) {
keepAwakeSwitch.tap()
sleep(1)
}
return
}
// Fallback: tap the right side of the Keep Awake row where the switch is rendered.
let appFrame = app.frame
let switchTap = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
.withOffset(CGVector(dx: appFrame.maxX - 24, dy: labelY))
switchTap.tap()
sleep(1)
// Verify we can now observe an "on" switch near this row.
let postTapCandidates = app.switches.allElementsBoundByIndex.filter { element in
abs(element.frame.midY - labelY) < 90
}
if let keepAwakeSwitch = postTapCandidates.first, !isSwitchOn(keepAwakeSwitch) {
keepAwakeSwitch.tap()
sleep(1)
}
}
@MainActor
private func ensureAlarmExistsAndEnabled(_ app: XCUIApplication) {
openTab(named: "Alarms", in: app)
// If no alarms exist, create one with defaults.
if app.staticTexts["No Alarms Set"].exists || app.buttons["Add Your First Alarm"].exists {
let addFirstAlarm = app.buttons["Add Your First Alarm"]
if addFirstAlarm.exists {
addFirstAlarm.tap()
} else {
tapNavigationPlusButton(in: app)
}
let saveButton = app.buttons["Save"]
XCTAssertTrue(saveButton.waitForExistence(timeout: 5), "Add Alarm sheet did not appear.")
saveButton.tap()
sleep(1)
}
// Make sure at least one alarm is enabled.
let firstSwitch = app.switches.firstMatch
if firstSwitch.waitForExistence(timeout: 3), !isSwitchOn(firstSwitch) {
firstSwitch.tap()
sleep(1)
}
}
// MARK: - Helpers
@MainActor
private func openTab(named tabName: String, in app: XCUIApplication) {
let expectedIndex: Int? = switch tabName {
case "Clock": 0
case "Alarms": 1
case "Noise": 2
case "Settings": 3
default: nil
}
func tapByCoordinates(_ element: XCUIElement) {
let coordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
.withOffset(CGVector(dx: element.frame.midX, dy: element.frame.midY))
coordinate.tap()
}
func tapLabeledButtonAnywhere() -> Bool {
let matches = app.buttons.matching(NSPredicate(format: "label == %@", tabName))
.allElementsBoundByIndex
.filter(\.exists)
guard !matches.isEmpty else { return false }
if let hittable = matches.first(where: \.isHittable) {
hittable.tap()
return true
}
tapByCoordinates(matches[0])
return true
}
func tapLabeledButton() -> Bool {
let query = app.tabBars.buttons.matching(NSPredicate(format: "label == %@", tabName))
let matches = query.allElementsBoundByIndex.filter(\.exists)
guard !matches.isEmpty else { return false }
if let hittable = matches.first(where: \.isHittable) {
hittable.tap()
return true
}
tapByCoordinates(matches[0])
return true
}
func tapIndexedButton() -> Bool {
guard let expectedIndex else { return false }
let tabBar = app.tabBars.firstMatch
guard tabBar.exists else { return false }
let buttons = tabBar.buttons
guard buttons.count > expectedIndex else { return false }
let button = buttons.element(boundBy: expectedIndex)
guard button.exists else { return false }
button.tap()
return true
}
if tapLabeledButton() || tapIndexedButton() {
return
}
// Fallback for cases where tab buttons are not exposed under tabBars.
if tapLabeledButtonAnywhere() {
return
}
// Reveal tab bar when clock is in auto full-screen mode.
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
usleep(300_000)
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.98)).tap()
usleep(300_000)
if tapLabeledButton() || tapIndexedButton() || tapLabeledButtonAnywhere() {
return
}
let buttonLabels = app.buttons.allElementsBoundByIndex
.prefix(16)
.map(\.label)
.joined(separator: ", ")
XCTFail("Could not open tab '\(tabName)' by label or index. tabBars=\(app.tabBars.count), buttons=[\(buttonLabels)]")
}
@MainActor
private func tapNavigationPlusButton(in app: XCUIApplication) {
let plusCandidates = [
app.navigationBars.buttons["plus"],
app.navigationBars.buttons["Add"],
app.buttons["plus"]
]
for candidate in plusCandidates where candidate.exists {
candidate.tap()
return
}
XCTFail("Could not find add (+) button.")
}
private func isSwitchOn(_ toggle: XCUIElement) -> Bool {
guard let value = toggle.value as? String else { return false }
return value == "1" || value.lowercased() == "on"
}
private func waitForClockFullscreenTransition() {
// Clock auto-hides chrome after 5 seconds of inactivity.
sleep(6)
}
private func saveScreenshot(named fileName: String) {
let screenshot = XCUIScreen.main.screenshot()
let data = screenshot.pngRepresentation
let destination = screenshotDirectory().appendingPathComponent(fileName)
do {
try data.write(to: destination)
} catch {
XCTFail("Failed to write screenshot to \(destination.path): \(error)")
}
let attachment = XCTAttachment(data: data, uniformTypeIdentifier: "public.png")
attachment.name = fileName
attachment.lifetime = .keepAlways
add(attachment)
}
private func screenshotDirectory() -> URL {
let env = ProcessInfo.processInfo.environment["SCREENSHOT_OUTPUT_DIR"]
let path = (env?.isEmpty == false) ? env! : NSTemporaryDirectory()
let url = URL(fileURLWithPath: path, isDirectory: true)
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
return url
}
private func waitForMainTabs(_ app: XCUIApplication) {
for _ in 0..<20 {
if hasMainTabs(app) { return }
usleep(200_000)
}
}
private func hasMainTabs(_ app: XCUIApplication) -> Bool {
let labels = ["Clock", "Alarms", "Noise", "Settings"]
if app.tabBars.count > 0 { return true }
return labels.contains(where: { app.buttons[$0].exists })
}
}