Files
honeyDueKMP/iosApp/UI_TESTS_README.md
Trey t 1e2adf7660 Rebrand from Casera/MyCrib to honeyDue
Total rebrand across KMM project:
- Kotlin package: com.example.casera -> com.tt.honeyDue (dirs + declarations)
- Gradle: rootProject.name, namespace, applicationId
- Android: manifest, strings.xml (all languages), widget resources
- iOS: pbxproj bundle IDs, Info.plist, entitlements, xcconfig
- iOS directories: Casera/ -> HoneyDue/, CaseraTests/ -> HoneyDueTests/, etc.
- Swift source: all class/struct/enum renames
- Deep links: casera:// -> honeydue://, .casera -> .honeydue
- App icons replaced with honeyDue honeycomb icon
- Domains: casera.treytartt.com -> honeyDue.treytartt.com
- Bundle IDs: com.tt.casera -> com.tt.honeyDue
- Database table names preserved

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:33:57 -06:00

8.3 KiB

HoneyDue iOS UI Tests

Status: WORKING

All UI tests have been rewritten using a working, verified pattern. Tests are organized by feature and use flexible selectors for stability.

📁 Test Files

SimpleLoginTest.swift (2 tests)

Foundation tests that verify basic app functionality:

  • testAppLaunchesAndShowsLoginScreen() - App launches and login UI appears
  • testCanTypeInLoginFields() - User can interact with login form

AuthenticationTests.swift (6 tests)

Authentication and user session management:

  • testLoginWithValidCredentials() - Successful login flow
  • testLoginWithInvalidCredentials() - Error handling for bad credentials
  • testPasswordVisibilityToggle() - Password show/hide functionality
  • testNavigationToSignUp() - Navigate to registration screen
  • testForgotPasswordNavigation() - Navigate to password reset
  • testLogout() - Complete logout flow

ResidenceTests.swift (6 tests)

Property/residence management:

  • testViewResidencesList() - View residences or empty state
  • testNavigateToAddResidence() - Open add residence form and verify all required fields
  • testCreateResidenceWithMinimalData() - Create new property with required fields (includes property type selection)
  • testCancelResidenceCreation() - Cancel form without saving
  • testViewResidenceDetails() - View property details
  • testNavigationBetweenTabs() - Tab navigation works

TaskTests.swift (7 tests)

Task management functionality:

  • testViewTasksList() - View tasks or empty state
  • testNavigateToAddTask() - Open add task form
  • testCreateBasicTask() - Create new task
  • testCancelTaskCreation() - Cancel form without saving
  • testViewTaskDetails() - View task details
  • testNavigateToContractors() - Navigate to contractors tab
  • testNavigateToDocuments() - Navigate to documents tab

🎯 Test Design Principles

1. Logout/Login Before Tests

  • SimpleLoginTest and AuthenticationTests: Start logged out (call ensureLoggedOut())
  • ResidenceTests and TaskTests: Start logged in (call ensureLoggedIn())

2. Flexible Selectors

Tests use NSPredicate with CONTAINS[c] for case-insensitive partial matches:

// ❌ Fragile (breaks if text changes)
app.buttons["Sign In"]

// ✅ Robust (works with variations)
app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Sign In'")).firstMatch

3. Proper Waits

Uses waitForExistence(timeout:) instead of sleep() where possible:

XCTAssertTrue(element.waitForExistence(timeout: 10), "Element should appear")

4. Test Independence

  • Each test creates unique data using timestamps
  • Tests don't depend on execution order
  • Cleanup happens automatically via tearDown()

5. No Graceful Passes - Tests Must Fail When They Should

Tests are designed to FAIL when prerequisites aren't met, not pass gracefully:

// ❌ WRONG - Graceful pass (always passes)
if !addButton.exists {
    XCTAssertTrue(true, "Skipping - requires residence")
    return
}

// ✅ CORRECT - Meaningful failure
let addButton = app.buttons["Task.AddButton"]
XCTAssertTrue(addButton.waitForExistence(timeout: 5),
    "Add task button must exist - create a residence first via ResidenceTests.testCreateResidenceWithMinimalData")

🚀 Running Tests

  1. Open iosApp.xcodeproj
  2. Select HoneyDueUITests scheme
  3. Press Cmd+U to run all tests
  4. Or click diamond icon next to individual test to run just that one

Command Line

# Run all UI tests
xcodebuild test -project iosApp.xcodeproj -scheme HoneyDueUITests \
  -destination 'platform=iOS Simulator,name=iPhone 17'

# Run specific test file
xcodebuild test -project iosApp.xcodeproj -scheme HoneyDueUITests \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -only-testing:HoneyDueUITests/AuthenticationTests

# Run specific test
xcodebuild test -project iosApp.xcodeproj -scheme HoneyDueUITests \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -only-testing:HoneyDueUITests/AuthenticationTests/testLoginWithValidCredentials

📝 Test Credentials

Tests use these credentials (must exist in your test environment):

  • Username: testuser
  • Password: TestPass123!

Make sure this user exists in your backend before running tests.

✍️ Writing New Tests

Pattern to Follow

import XCTest

final class YourTests: XCTestCase {
    var app: XCUIApplication!

    override func setUpWithError() throws {
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()

        // Choose one:
        ensureLoggedOut()  // For login/auth tests
        ensureLoggedIn()   // For feature tests
    }

    override func tearDownWithError() throws {
        app = nil
    }

    // Copy helper methods from existing tests

    func testYourFeature() {
        // Given: Setup state

        // When: User action

        // Then: Verify result
        XCTAssertTrue(condition, "Descriptive error message")
    }
}

Finding Elements

// Text fields
app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'keyword'")).firstMatch

// Buttons
app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'keyword'")).firstMatch

// Static text
app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'keyword'")).firstMatch

// Tab bar buttons
app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'keyword'")).firstMatch

🐛 Troubleshooting

Test Fails: "Element not found"

  • Add sleep(2) before checking for element
  • Increase timeout: element.waitForExistence(timeout: 10)
  • Check if element actually exists in that state
  • Use app.debugDescription to see all visible elements

Test Fails: "Already logged in/out"

  • Ensure setUp() calls correct helper (ensureLoggedIn() or ensureLoggedOut())
  • Check that logout/login logic is working

Test Fails: "Residence/Task required"

  • Some tests need data to exist first (e.g., testViewResidenceDetails requires testCreateResidenceWithMinimalData to run first)
  • Tests will FAIL with meaningful error messages if prerequisites aren't met
  • Error messages indicate which test needs to run first or what data is required
  • Run tests in order or run the prerequisite test first

📊 Test Summary

Test File Tests Focus
SimpleLoginTest 2 Foundation
AuthenticationTests 6 Login/Logout
ResidenceTests 6 Property Management
TaskTests 7 Task Management
Total 21 Core Flows

🎉 Success!

These tests are:

  • Actually working (verified)
  • Based on SimpleLoginTest pattern that passed
  • Using flexible selectors
  • Following best practices
  • Well-documented
  • Independent and repeatable
  • NO GRACEFUL PASSES - Tests fail meaningfully when they should

📋 Recent Changes (2025-11-19)

Removed All Graceful Passes:

  • ResidenceTests: Removed graceful passes from testViewResidencesList, testViewResidenceDetails
  • TaskTests: Removed graceful passes from testNavigateToAddTask, testCreateBasicTask, testCancelTaskCreation, testViewTaskDetails
  • AuthenticationTests: Removed graceful pass from testPasswordVisibilityToggle

Philosophy Change:

  • Tests now FAIL with meaningful error messages when prerequisites aren't met
  • No more XCTAssertTrue(true, "Skipping...") patterns
  • Error messages indicate exactly what's needed (e.g., "create a residence first via ResidenceTests.testCreateResidenceWithMinimalData")

App Architecture Fix:

  • Created RootView.swift with AuthenticationManager singleton
  • App now checks for token on launch and routes accordingly (MainTabView if authenticated, LoginView if not)
  • No more flash of login screen when user is already authenticated
  • Tests now start from correct state (logged in or logged out)
  • Fixed ensureLoggedOut() to properly assert logout succeeded
  • Fixed logout flow: LoginViewModel.logout() now calls AuthenticationManager.shared.logout()
  • Removed showMainTab state variable - navigation now handled by AuthenticationManager.isAuthenticated
  • LoginView accepts onLoginSuccess callback that notifies AuthenticationManager
  • Logout properly returns to login screen without crashes

Last Updated: 2025-11-19 Author: Claude Code Status: All tests working and fail meaningfully