UI Testing Implementation
Overview
This document tracks the implementation of UI automation testing with screenshots.
Key Requirements:
- Tests run with isolated test storage (not user’s real data)
- Screenshots captured at each step for visual regression
- Fixtures look like real person’s data (not “test1”, “test2”)
- Each step follows the Backlog Processing Workflow (see 910-backlog)
Backlog Processing Workflow
For each implementation step:
- Plan Agent (thorough) - Deep analysis, identify files, design approach
- Review Agent (thorough) - Verify plan, check edge cases, suggest improvements
- Implement Agent - Write code following approved plan
- Test/Document Agent - Verify it works, update docs
Implementation Steps
Step 1: App-Side Test Storage Support
- Modify StorageLocationManager to accept
--test-storage=launch argument - Ensure override happens before AppState initialization
- Status: Completed
- Files modified:
TimeTracker/Sources/Services/StorageLocationManager.swift
Step 2: Add UI Test Target
- Update project.yml with TimeTrackerUITests target
- Create directory structure (UITests/, UITests/Flows/, UITests/Fixtures/)
- Note: swift-snapshot-testing not used (Xcode 26 compatibility issue), using XCTest native screenshots
- Status: Completed
- Files modified:
TimeTracker/project.yml
Step 3: Create Test Fixtures
- Create realistic fixture data (real person’s usage pattern)
- empty/ - Clean slate with empty tags.json
- standard/ - Tags and records for a freelance developer
- Status: Completed
- Files created:
UITests/Fixtures/empty/,UITests/Fixtures/standard/
Step 4: Add Accessibility Identifiers
- Add identifiers to key interactive elements
- Status: Completed
- Identifiers added:
floatingPlayButton- Main play buttonsettingsButton- Settings gearstopTimerButton- Stop button in running timer (Note: inherits section identifier due to SwiftUI behavior)runningTimersSection- Running timers sectionuseDefaultLocationButton- Storage setup button
Step 5: Base Test Class
- Create TimeTrackerUITests.swift with fixture copying
- Add snapshot helper methods using XCTest native screenshots
- Add element waiting utilities
- Status: Completed
- Files created:
UITests/TimeTrackerUITests.swift
Step 6: First Test - Timer Flow
- Implement TimerFlowTests.swift
- Verify screenshots work
- Generate baseline snapshots
- Status: Completed
- Files created:
UITests/Flows/TimerFlowTests.swift - Snapshots saved to:
UITests/__Snapshots__/TimerFlowTests/
Step 7: CI Integration
- GitHub Actions workflow for UI tests
- Artifact upload for failed snapshots
- Status: Not started
Running UI Tests
# Run all UI tests
cd TimeTracker && xcodebuild test -scheme TimeTracker -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:TimeTrackerUITests
# Run specific test
xcodebuild test -scheme TimeTracker -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:TimeTrackerUITests/TimerFlowTests/testStartAndStopTimer Test Architecture
Test Storage Isolation
- Tests pass
--test-storage=/path/to/tempas launch argument StorageLocationManagerdetects this and uses the path instead of user’s Documents- Each test gets a unique temp directory that’s cleaned up after
Fixture System
- Fixtures stored in
UITests/Fixtures/ - Each fixture is a directory with
tags.jsonand optionalrecords/subdirectory - Tests override
fixtureNameproperty to select fixture - Base class copies fixture to temp directory before app launch
Screenshots
- Saved to
UITests/__Snapshots__/{TestClassName}/{name}.png - Also attached to Xcode test results for viewing
- Use
snapshot("name")helper method
Fixture Data Design
Fixtures should look like a real person’s time tracking data.
Character: Alex (Freelance Developer)
- Works on client projects: “Acme Corp”, “StartupXYZ”
- Has personal projects: “Side Project”, “Learning”
- Uses comments like real notes, not test strings
Tags (standard fixture)
| Name | Color | Notes |
|---|---|---|
| Acme Corp | oklch(0.5579 0.1147 240) | Main client (blue) |
| StartupXYZ | oklch(0.5579 0.1147 145) | Secondary client (green) |
| Side Project | oklch(0.5579 0.1147 300) | Personal coding (purple) |
| Learning | oklch(0.5579 0.1147 45) | Courses, reading (orange) |
| Admin | oklch(0.5579 0.1147 0) | Emails, planning (gray) |
Sample Records Pattern
- Morning: Acme Corp work session (2-3 hours)
- Midday: StartupXYZ meeting or work
- Afternoon: Mixed tasks
- Evening: Side project or learning
- Comments reference real-sounding tasks
Known Issues and Solutions
SwiftUI Accessibility Identifier Inheritance
When a Section has an accessibilityIdentifier, child elements may inherit it.
Solution: Find elements by label instead of identifier when needed.
// Instead of:
let stopButton = app.buttons["stopTimerButton"]
// Use:
let stopButton = app.buttons.matching(NSPredicate(format: "label == 'Stop'")).firstMatch Xcode 26 and swift-snapshot-testing
The swift-snapshot-testing package has compatibility issues with Xcode 26 beta.
Solution: Using XCTest’s native XCUIScreen.main.screenshot() instead.
Progress Log
2025-12-18
- Created implementation document
- Implemented Steps 1-6 (all core functionality)
- First UI test (TimerFlowTests) passing
- Screenshots saving correctly to
UITests/__Snapshots__/ - Discovered SwiftUI accessibility identifier inheritance issue and documented workaround
Related
- 602-ui-automation-plan - Original plan and framework selection
- 910-backlog - Backlog processing workflow
- 601-testing-guide - General testing guide