reset: prep for new test suite
This commit is contained in:
@@ -1,86 +0,0 @@
|
||||
//
|
||||
// AccessibilityUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Mike Freno on 1/8/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class AccessibilityUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--skip-onboarding")
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
app = nil
|
||||
}
|
||||
|
||||
func testMenuBarAccessibilityLabels() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
for button in app.buttons.allElementsBoundByIndex {
|
||||
XCTAssertFalse(button.label.isEmpty, "Button should have accessibility label")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyboardNavigation() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
app.typeKey(XCUIKeyboardKey.tab, modifierFlags: [])
|
||||
|
||||
let focusedElement = app.descendants(matching: .any).element(matching: NSPredicate(format: "hasKeyboardFocus == true"))
|
||||
XCTAssertTrue(focusedElement.exists || app.buttons.count > 0)
|
||||
}
|
||||
}
|
||||
|
||||
func testAllButtonsAreHittable() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
sleep(1)
|
||||
|
||||
let buttons = app.buttons.allElementsBoundByIndex
|
||||
for button in buttons where button.exists && button.isEnabled {
|
||||
XCTAssertTrue(button.isHittable || !button.isEnabled, "Enabled button should be hittable: \(button.label)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testVoiceOverElementsHaveLabels() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let staticTexts = app.staticTexts.allElementsBoundByIndex
|
||||
for text in staticTexts where text.exists {
|
||||
XCTAssertFalse(text.label.isEmpty, "Static text should have label")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testImagesHaveAccessibilityTraits() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let images = app.images.allElementsBoundByIndex
|
||||
for image in images where image.exists {
|
||||
XCTAssertFalse(image.label.isEmpty, "Image should have accessibility label")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
//
|
||||
// EnhancedOnboardingUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Gaze Team on 1/13/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class EnhancedOnboardingUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--reset-onboarding")
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
app = nil
|
||||
}
|
||||
|
||||
func testOnboardingCompleteFlowWithUserTimers() throws {
|
||||
// Navigate through the complete onboarding flow
|
||||
let continueButtons = app.buttons.matching(identifier: "Continue")
|
||||
let nextButtons = app.buttons.matching(identifier: "Next")
|
||||
|
||||
var currentStep = 0
|
||||
let maxSteps = 15
|
||||
|
||||
while currentStep < maxSteps {
|
||||
if continueButtons.firstMatch.exists && continueButtons.firstMatch.isHittable {
|
||||
continueButtons.firstMatch.tap()
|
||||
currentStep += 1
|
||||
sleep(1)
|
||||
} else if nextButtons.firstMatch.exists && nextButtons.firstMatch.isHittable {
|
||||
nextButtons.firstMatch.tap()
|
||||
currentStep += 1
|
||||
sleep(1)
|
||||
} else if app.buttons["Get Started"].exists {
|
||||
app.buttons["Get Started"].tap()
|
||||
break
|
||||
} else if app.buttons["Done"].exists {
|
||||
app.buttons["Done"].tap()
|
||||
break
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Verify onboarding completed successfully
|
||||
XCTAssertLessThan(currentStep, maxSteps, "Onboarding flow should complete")
|
||||
|
||||
// Verify main application UI is visible (menubar should be active)
|
||||
XCTAssertTrue(app.menuBarItems.firstMatch.exists, "Menubar should be available after onboarding")
|
||||
}
|
||||
|
||||
func testUserTimerCreationInOnboarding() throws {
|
||||
// Reset to fresh onboarding state
|
||||
app.terminate()
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--reset-onboarding")
|
||||
app.launch()
|
||||
|
||||
// Navigate to user timer setup section (assumes it's at the end)
|
||||
let continueButtons = app.buttons.matching(identifier: "Continue")
|
||||
let nextButtons = app.buttons.matching(identifier: "Next")
|
||||
|
||||
// Skip through initial screens
|
||||
var currentStep = 0
|
||||
while currentStep < 8 && (continueButtons.firstMatch.exists || nextButtons.firstMatch.exists) {
|
||||
if continueButtons.firstMatch.exists && continueButtons.firstMatch.isHittable {
|
||||
continueButtons.firstMatch.tap()
|
||||
currentStep += 1
|
||||
sleep(1)
|
||||
} else if nextButtons.firstMatch.exists && nextButtons.firstMatch.isHittable {
|
||||
nextButtons.firstMatch.tap()
|
||||
currentStep += 1
|
||||
sleep(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Look for timer creation UI or related elements
|
||||
let timerSetupElement = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Timer' OR label CONTAINS 'Custom'")).firstMatch
|
||||
XCTAssertTrue(timerSetupElement.exists, "User timer setup section should be available during onboarding")
|
||||
|
||||
// If we can create a timer in onboarding, test that flow
|
||||
if app.buttons["Add Timer"].exists {
|
||||
app.buttons["Add Timer"].tap()
|
||||
|
||||
// Fill out timer details - this would be specific to the actual UI structure
|
||||
let titleField = app.textFields["Timer Title"]
|
||||
if titleField.exists {
|
||||
titleField.typeText("Test Timer")
|
||||
}
|
||||
|
||||
let intervalField = app.textFields["Interval (minutes)"]
|
||||
if intervalField.exists {
|
||||
intervalField.typeText("10")
|
||||
}
|
||||
|
||||
// Submit the timer
|
||||
app.buttons["Save"].tap()
|
||||
}
|
||||
}
|
||||
|
||||
func testSettingsPersistenceAfterOnboarding() throws {
|
||||
// Reset to fresh onboarding state
|
||||
app.terminate()
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--reset-onboarding")
|
||||
app.launch()
|
||||
|
||||
// Complete onboarding flow
|
||||
let continueButtons = app.buttons.matching(identifier: "Continue")
|
||||
let nextButtons = app.buttons.matching(identifier: "Next")
|
||||
|
||||
while continueButtons.firstMatch.exists || nextButtons.firstMatch.exists {
|
||||
if continueButtons.firstMatch.exists && continueButtons.firstMatch.isHittable {
|
||||
continueButtons.firstMatch.tap()
|
||||
sleep(1)
|
||||
} else if nextButtons.firstMatch.exists && nextButtons.firstMatch.isHittable {
|
||||
nextButtons.firstMatch.tap()
|
||||
sleep(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Get to the end and complete onboarding
|
||||
app.buttons["Get Started"].tap()
|
||||
|
||||
// Verify that settings are properly initialized
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
XCTAssertTrue(menuBar.exists, "Menubar should exist after onboarding")
|
||||
|
||||
// Re-launch the app to verify settings persistence
|
||||
app.terminate()
|
||||
let newApp = XCUIApplication()
|
||||
newApp.launchArguments.append("--skip-onboarding")
|
||||
newApp.launch()
|
||||
|
||||
XCTAssertTrue(newApp.menuBarItems.firstMatch.exists, "Application should maintain state after restart")
|
||||
newApp.terminate()
|
||||
}
|
||||
|
||||
func testOnboardingNavigationEdgeCases() throws {
|
||||
// Test that navigation buttons work properly at each step
|
||||
let continueButton = app.buttons["Continue"]
|
||||
if continueButton.waitForExistence(timeout: 2) {
|
||||
continueButton.tap()
|
||||
|
||||
// Verify we moved to the next screen
|
||||
let nextScreen = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Setup' OR label CONTAINS 'Configure'")).firstMatch
|
||||
XCTAssertTrue(nextScreen.exists, "Should navigate to next screen on Continue")
|
||||
}
|
||||
|
||||
// Test back navigation
|
||||
let backButton = app.buttons["Back"]
|
||||
if backButton.waitForExistence(timeout: 1) {
|
||||
backButton.tap()
|
||||
|
||||
// Should return to previous screen
|
||||
XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Welcome'")).firstMatch.exists)
|
||||
}
|
||||
|
||||
// Test that we can go forward again
|
||||
let continueButton2 = app.buttons["Continue"]
|
||||
if continueButton2.waitForExistence(timeout: 1) {
|
||||
continueButton2.tap()
|
||||
XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Setup'")).firstMatch.exists)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
//
|
||||
// GazeUITests.swift
|
||||
// GazeUITests
|
||||
// ExampleUITests.swift
|
||||
// Gaze
|
||||
//
|
||||
// Created by Mike Freno on 1/7/26.
|
||||
// Created by AI Assistant on 1/15/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
final class GazeUITests: XCTestCase {
|
||||
final class ExampleUITests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
@@ -15,7 +15,7 @@ final class GazeUITests: XCTestCase {
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
continueAfterFailure = false
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
@@ -23,12 +23,17 @@ final class GazeUITests: XCTestCase {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testExample() throws {
|
||||
func testExampleOfUITesting() throws {
|
||||
// UI tests must launch the application that they test.
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
// For example:
|
||||
// XCTAssertEqual(app.windows.count, 1)
|
||||
// XCTAssertTrue(app.buttons["Start"].exists)
|
||||
|
||||
XCTAssertTrue(true, "UI testing example - this would verify UI elements")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -38,4 +43,4 @@ final class GazeUITests: XCTestCase {
|
||||
XCUIApplication().launch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// GazeUITestsLaunchTests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Mike Freno on 1/7/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
final class GazeUITestsLaunchTests: XCTestCase {
|
||||
|
||||
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testLaunch() throws {
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||
// such as logging into a test account or navigating somewhere in the app
|
||||
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.name = "Launch Screen"
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
//
|
||||
// MenuBarUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Mike Freno on 1/8/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class MenuBarUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--skip-onboarding")
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
app = nil
|
||||
}
|
||||
|
||||
func testMenuBarExtraExists() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
XCTAssertTrue(menuBar.waitForExistence(timeout: 5))
|
||||
}
|
||||
|
||||
func testMenuBarCanBeOpened() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let gazeTitle = app.staticTexts["Gaze"]
|
||||
XCTAssertTrue(gazeTitle.waitForExistence(timeout: 2) || app.staticTexts.count > 0)
|
||||
}
|
||||
}
|
||||
|
||||
func testMenuBarHasTimerStatus() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let activeTimersText = app.staticTexts["Active Timers"]
|
||||
let hasTimerInfo = activeTimersText.exists || app.staticTexts.count > 3
|
||||
|
||||
XCTAssertTrue(hasTimerInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func testMenuBarHasPauseResumeControl() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let pauseButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Pause' OR label CONTAINS 'Resume'")).firstMatch
|
||||
XCTAssertTrue(pauseButton.waitForExistence(timeout: 2))
|
||||
}
|
||||
}
|
||||
|
||||
func testMenuBarHasSettingsButton() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let settingsButton = app.buttons["Settings"]
|
||||
let settingsMenuItem = app.menuItems["Settings"]
|
||||
|
||||
XCTAssertTrue(settingsButton.exists || settingsMenuItem.exists)
|
||||
}
|
||||
}
|
||||
|
||||
func testMenuBarHasQuitButton() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let quitButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Quit'")).firstMatch
|
||||
XCTAssertTrue(quitButton.waitForExistence(timeout: 2))
|
||||
}
|
||||
}
|
||||
|
||||
func testPauseResumeToggle() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let pauseButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Pause'")).firstMatch
|
||||
let resumeButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Resume'")).firstMatch
|
||||
|
||||
if pauseButton.exists && pauseButton.isHittable {
|
||||
pauseButton.tap()
|
||||
XCTAssertTrue(resumeButton.waitForExistence(timeout: 2))
|
||||
} else if resumeButton.exists && resumeButton.isHittable {
|
||||
resumeButton.tap()
|
||||
XCTAssertTrue(pauseButton.waitForExistence(timeout: 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCompleteOnboardingButtonVisibleWhenOnboardingIncomplete() throws {
|
||||
// Relaunch app without skip-onboarding flag
|
||||
app.terminate()
|
||||
let newApp = XCUIApplication()
|
||||
newApp.launchArguments.append("--reset-onboarding")
|
||||
newApp.launch()
|
||||
|
||||
let menuBar = newApp.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let completeOnboardingButton = newApp.buttons["Complete Onboarding"]
|
||||
XCTAssertTrue(
|
||||
completeOnboardingButton.waitForExistence(timeout: 2),
|
||||
"Complete Onboarding button should be visible when onboarding is incomplete"
|
||||
)
|
||||
}
|
||||
|
||||
newApp.terminate()
|
||||
}
|
||||
|
||||
func testCompleteOnboardingButtonNotVisibleWhenOnboardingComplete() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
let completeOnboardingButton = app.buttons["Complete Onboarding"]
|
||||
XCTAssertFalse(
|
||||
completeOnboardingButton.exists,
|
||||
"Complete Onboarding button should not be visible when onboarding is complete"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
//
|
||||
// OnboardingUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Mike Freno on 1/8/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class OnboardingUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--reset-onboarding")
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
app = nil
|
||||
}
|
||||
|
||||
func testOnboardingWelcomeScreen() throws {
|
||||
XCTAssertTrue(app.staticTexts["Welcome to Gaze"].exists || app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Welcome'")).firstMatch.exists)
|
||||
}
|
||||
|
||||
func testOnboardingNavigationFromWelcome() throws {
|
||||
let continueButton = app.buttons["Continue"]
|
||||
|
||||
if continueButton.waitForExistence(timeout: 2) {
|
||||
continueButton.tap()
|
||||
|
||||
let nextScreen = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Setup' OR label CONTAINS 'Configure'")).firstMatch
|
||||
XCTAssertTrue(nextScreen.waitForExistence(timeout: 2))
|
||||
}
|
||||
}
|
||||
|
||||
func testOnboardingBackNavigation() throws {
|
||||
let continueButton = app.buttons["Continue"]
|
||||
if continueButton.waitForExistence(timeout: 2) {
|
||||
continueButton.tap()
|
||||
|
||||
let backButton = app.buttons["Back"]
|
||||
if backButton.waitForExistence(timeout: 1) {
|
||||
backButton.tap()
|
||||
|
||||
XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Welcome'")).firstMatch.waitForExistence(timeout: 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testOnboardingCompleteFlow() throws {
|
||||
let continueButtons = app.buttons.matching(identifier: "Continue")
|
||||
let nextButtons = app.buttons.matching(identifier: "Next")
|
||||
|
||||
var currentStep = 0
|
||||
let maxSteps = 10
|
||||
|
||||
while currentStep < maxSteps {
|
||||
if continueButtons.firstMatch.exists && continueButtons.firstMatch.isHittable {
|
||||
continueButtons.firstMatch.tap()
|
||||
currentStep += 1
|
||||
sleep(1)
|
||||
} else if nextButtons.firstMatch.exists && nextButtons.firstMatch.isHittable {
|
||||
nextButtons.firstMatch.tap()
|
||||
currentStep += 1
|
||||
sleep(1)
|
||||
} else if app.buttons["Get Started"].exists {
|
||||
app.buttons["Get Started"].tap()
|
||||
break
|
||||
} else if app.buttons["Done"].exists {
|
||||
app.buttons["Done"].tap()
|
||||
break
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertLessThan(currentStep, maxSteps, "Onboarding flow should complete")
|
||||
}
|
||||
|
||||
func testOnboardingHasRequiredElements() throws {
|
||||
let hasText = app.staticTexts.count > 0
|
||||
let hasButtons = app.buttons.count > 0
|
||||
|
||||
XCTAssertTrue(hasText, "Onboarding should have text elements")
|
||||
XCTAssertTrue(hasButtons, "Onboarding should have buttons")
|
||||
}
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
//
|
||||
// OverlayReminderUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by OpenCode on 1/13/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
/// Comprehensive UI tests for overlay and reminder system
|
||||
///
|
||||
/// NOTE: macOS MenuBarExtra UI testing limitations:
|
||||
/// - MenuBarExtras created with MenuBarExtra {} don't reliably appear in XCUITest accessibility hierarchy
|
||||
/// - This is a known limitation of XCUITest with SwiftUI MenuBarExtra
|
||||
/// - Therefore, these tests focus on what can be tested: window lifecycle, dismissal, and cleanup
|
||||
///
|
||||
/// These tests verify:
|
||||
/// - No overlays get stuck on screen
|
||||
/// - Window cleanup happens properly
|
||||
/// - App remains responsive after overlay cycles
|
||||
@MainActor
|
||||
final class OverlayReminderUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--skip-onboarding")
|
||||
app.launchArguments.append("--ui-testing")
|
||||
app.launch()
|
||||
|
||||
// Wait for app to be ready
|
||||
sleep(UInt32(2))
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Ensure app is terminated cleanly
|
||||
app.terminate()
|
||||
app = nil
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
/// Verifies that no overlay is currently visible
|
||||
private func verifyNoOverlay() {
|
||||
let overlayTexts = ["Look Away", "Blink", "Posture", "User Reminder"]
|
||||
|
||||
for text in overlayTexts {
|
||||
XCTAssertFalse(
|
||||
app.staticTexts[text].exists,
|
||||
"Overlay '\(text)' should not be visible"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Counts the number of windows
|
||||
private func countWindows() -> Int {
|
||||
return app.windows.count
|
||||
}
|
||||
|
||||
// MARK: - App Lifecycle Tests
|
||||
|
||||
func testAppLaunchesSuccessfully() throws {
|
||||
// Basic test to ensure app launches and is responsive
|
||||
XCTAssertTrue(app.exists, "App should launch successfully")
|
||||
|
||||
// Verify no stuck overlays from previous sessions
|
||||
verifyNoOverlay()
|
||||
}
|
||||
|
||||
func testAppRemainsResponsiveAfterLaunch() throws {
|
||||
// Wait a bit and verify app didn't crash
|
||||
sleep(UInt32(3))
|
||||
|
||||
XCTAssertTrue(app.exists, "App should remain running")
|
||||
|
||||
// Verify no unexpected overlays appeared
|
||||
verifyNoOverlay()
|
||||
}
|
||||
|
||||
func testNoStuckWindowsAfterAppLaunch() throws {
|
||||
let initialWindowCount = countWindows()
|
||||
|
||||
// Wait to ensure no delayed windows appear
|
||||
sleep(UInt32(5))
|
||||
|
||||
let finalWindowCount = countWindows()
|
||||
|
||||
// Window count should be stable (menu bar doesn't create visible windows)
|
||||
XCTAssertLessThanOrEqual(
|
||||
finalWindowCount,
|
||||
initialWindowCount + 1, // Allow for menu bar if it appears
|
||||
"No unexpected windows should appear after launch"
|
||||
)
|
||||
|
||||
verifyNoOverlay()
|
||||
}
|
||||
|
||||
// MARK: - Window Lifecycle Tests
|
||||
|
||||
func testWindowCleanupVerification() throws {
|
||||
let initialWindowCount = countWindows()
|
||||
|
||||
// Let the app run for a while
|
||||
sleep(UInt32(10))
|
||||
|
||||
let finalWindowCount = countWindows()
|
||||
|
||||
// Ensure window count hasn't grown unexpectedly
|
||||
XCTAssertLessThanOrEqual(
|
||||
finalWindowCount,
|
||||
initialWindowCount + 2, // Allow some leeway for system windows
|
||||
"Window count should remain stable during normal operation"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Overlay Detection Tests
|
||||
|
||||
func testNoOverlaysAppearWithoutTrigger() throws {
|
||||
// Run for a period and ensure no overlays appear
|
||||
// (with our UI testing timers disabled or set to very long intervals)
|
||||
|
||||
for i in 1...5 {
|
||||
print("Checking for stuck overlays - iteration \(i)/5")
|
||||
sleep(UInt32(2))
|
||||
verifyNoOverlay()
|
||||
}
|
||||
|
||||
print("✅ No stuck overlays detected during test period")
|
||||
}
|
||||
|
||||
func testAppStabilityOverTime() throws {
|
||||
// Extended stability test - run for 30 seconds
|
||||
let testDuration: Int = 30
|
||||
let checkInterval: Int = 5
|
||||
let iterations = testDuration / checkInterval
|
||||
|
||||
for i in 1...iterations {
|
||||
print("Stability check \(i)/\(iterations)")
|
||||
sleep(UInt32(checkInterval))
|
||||
|
||||
XCTAssertTrue(app.exists, "App should continue running")
|
||||
verifyNoOverlay()
|
||||
}
|
||||
|
||||
print("✅ App remained stable for \(testDuration) seconds")
|
||||
}
|
||||
|
||||
// MARK: - Regression Tests
|
||||
|
||||
func testNoStuckOverlaysAfterAppStart() throws {
|
||||
// This test specifically checks for the bug where overlays don't dismiss
|
||||
|
||||
// Wait for initial app startup
|
||||
sleep(UInt32(3))
|
||||
|
||||
verifyNoOverlay()
|
||||
|
||||
// Check multiple times to ensure stability
|
||||
for i in 1...10 {
|
||||
sleep(UInt32(1))
|
||||
verifyNoOverlay()
|
||||
|
||||
if i % 3 == 0 {
|
||||
print("No stuck overlays detected - check \(i)/10")
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
app.exists,
|
||||
"App should still be running after extended monitoring"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Documentation Tests
|
||||
|
||||
func testDocumentedLimitations() throws {
|
||||
// This test documents the UI testing limitations we discovered
|
||||
|
||||
print("""
|
||||
|
||||
==================== UI Testing Limitations ====================
|
||||
|
||||
MenuBarExtra Accessibility:
|
||||
- SwiftUI MenuBarExtra items don't reliably appear in XCUITest
|
||||
- This is a known Apple limitation as of macOS 13+
|
||||
- MenuBarItem queries return system menu bars (Apple, etc.) not app extras
|
||||
|
||||
Workarounds Attempted:
|
||||
- Searching by index (unreliable, system dependent)
|
||||
- Using accessibility identifiers (not exposed for MenuBarExtra)
|
||||
- Iterating through menu bar items (finds wrong items)
|
||||
|
||||
What We Can Test:
|
||||
- App launch and stability
|
||||
- Window lifecycle and cleanup
|
||||
- No stuck overlays appear unexpectedly
|
||||
- App remains responsive
|
||||
|
||||
What Requires Manual Testing:
|
||||
- Overlay appearance when triggered
|
||||
- ESC/Space/Button dismissal methods
|
||||
- Countdown functionality
|
||||
- Rapid trigger/dismiss cycles
|
||||
- Multiple reminder types in sequence
|
||||
|
||||
Recommendation:
|
||||
- Use unit tests for TimerEngine logic
|
||||
- Use integration tests for reminder triggering
|
||||
- Use manual testing for UI overlay behavior
|
||||
- Use these UI tests for regression detection of stuck overlays
|
||||
|
||||
================================================================
|
||||
|
||||
""")
|
||||
|
||||
XCTAssertTrue(true, "Limitations documented")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Manual Test Checklist
|
||||
/*
|
||||
Manual testing checklist for overlay reminders:
|
||||
|
||||
Look Away Overlay:
|
||||
☐ Appears when triggered
|
||||
☐ Shows countdown
|
||||
☐ Dismisses with ESC key
|
||||
☐ Dismisses with Space key
|
||||
☐ Dismisses with X button
|
||||
☐ Auto-dismisses after countdown
|
||||
☐ Doesn't appear when timers paused
|
||||
|
||||
User Timer Overlay:
|
||||
☐ Appears when triggered
|
||||
☐ Shows custom message
|
||||
☐ Shows correct color
|
||||
☐ Dismisses properly with all methods
|
||||
|
||||
Subtle Reminders (Blink, Posture, User Timer Subtle):
|
||||
☐ Appear in corner
|
||||
☐ Auto-dismiss after 3 seconds
|
||||
☐ Don't block UI interaction
|
||||
|
||||
Edge Cases:
|
||||
☐ Rapid triggering (10x in a row)
|
||||
☐ Trigger while countdown active
|
||||
☐ Trigger while paused
|
||||
☐ System sleep during overlay
|
||||
☐ Multiple monitors
|
||||
☐ Window cleanup after dismissal
|
||||
|
||||
Regression:
|
||||
☐ No overlays get stuck on screen
|
||||
☐ All dismissal methods work reliably
|
||||
☐ Window count returns to baseline after dismissal
|
||||
☐ App remains responsive after many overlay cycles
|
||||
*/
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
//
|
||||
// PerformanceUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Mike Freno on 1/8/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class PerformanceUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--skip-onboarding")
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
app = nil
|
||||
}
|
||||
|
||||
func testAppLaunchPerformance() throws {
|
||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||
app.launch()
|
||||
app.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
func testMenuBarOpenPerformance() throws {
|
||||
app.launch()
|
||||
|
||||
measure {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
_ = app.staticTexts["Gaze"].waitForExistence(timeout: 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSettingsWindowOpenPerformance() throws {
|
||||
app.launch()
|
||||
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
menuBar.click()
|
||||
|
||||
measure {
|
||||
let settingsButton = app.menuItems["Settings"]
|
||||
if settingsButton.waitForExistence(timeout: 2) {
|
||||
settingsButton.click()
|
||||
|
||||
let settingsWindow = app.windows["Settings"]
|
||||
_ = settingsWindow.waitForExistence(timeout: 3)
|
||||
|
||||
if settingsWindow.exists {
|
||||
let closeButton = settingsWindow.buttons[XCUIIdentifierCloseWindow]
|
||||
if closeButton.exists {
|
||||
closeButton.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuBar.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMemoryUsageDuringOperation() throws {
|
||||
app.launch()
|
||||
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.waitForExistence(timeout: 5) {
|
||||
measure(metrics: [XCTMemoryMetric()]) {
|
||||
for _ in 0..<5 {
|
||||
menuBar.click()
|
||||
sleep(1)
|
||||
|
||||
menuBar.click()
|
||||
sleep(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
//
|
||||
// SettingsUITests.swift
|
||||
// GazeUITests
|
||||
//
|
||||
// Created by Mike Freno on 1/8/26.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class SettingsUITests: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
app = XCUIApplication()
|
||||
app.launchArguments.append("--skip-onboarding")
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
app = nil
|
||||
}
|
||||
|
||||
func testOpenSettingsWindow() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.exists {
|
||||
menuBar.click()
|
||||
|
||||
let settingsButton = app.menuItems["Settings"]
|
||||
if settingsButton.waitForExistence(timeout: 2) {
|
||||
settingsButton.click()
|
||||
|
||||
let settingsWindow = app.windows["Settings"]
|
||||
XCTAssertTrue(settingsWindow.waitForExistence(timeout: 3))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSettingsWindowHasTimerControls() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.exists {
|
||||
menuBar.click()
|
||||
|
||||
let settingsButton = app.menuItems["Settings"]
|
||||
if settingsButton.waitForExistence(timeout: 2) {
|
||||
settingsButton.click()
|
||||
|
||||
sleep(1)
|
||||
|
||||
let hasSliders = app.sliders.count > 0
|
||||
let hasTextFields = app.textFields.count > 0
|
||||
let hasSwitches = app.switches.count > 0
|
||||
|
||||
let hasControls = hasSliders || hasTextFields || hasSwitches
|
||||
XCTAssertTrue(hasControls, "Settings should have timer controls")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSettingsWindowCanBeClosed() throws {
|
||||
let menuBar = app.menuBarItems.firstMatch
|
||||
if menuBar.exists {
|
||||
menuBar.click()
|
||||
|
||||
let settingsButton = app.menuItems["Settings"]
|
||||
if settingsButton.waitForExistence(timeout: 2) {
|
||||
settingsButton.click()
|
||||
|
||||
let settingsWindow = app.windows["Settings"]
|
||||
if settingsWindow.waitForExistence(timeout: 3) {
|
||||
let closeButton = settingsWindow.buttons[XCUIIdentifierCloseWindow]
|
||||
if closeButton.exists {
|
||||
closeButton.click()
|
||||
|
||||
XCTAssertFalse(settingsWindow.exists)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user