Skip to content

Commit

Permalink
rework data entry (#14)
Browse files Browse the repository at this point in the history
Co-authored-by: Paul Schmiedmayer <[email protected]>
  • Loading branch information
lukaskollmer and PSchmiedmayer authored Jan 20, 2025
1 parent 6e9344a commit 9f33d81
Show file tree
Hide file tree
Showing 14 changed files with 698 additions and 351 deletions.
26 changes: 15 additions & 11 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@ on:
workflow_dispatch:

jobs:
build_and_test-spm:
name: Build and Test Swift Package
uses: StanfordBDHG/.github/.github/workflows/build-and-test-xcodebuild-spm.yml@v1
buildandtest_ios:
name: Build and Test Swift Package iOS
uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: XCTHealthKit
build_and_test-uitests:
name: Build and Test UITest App
uses: StanfordBDHG/.github/.github/workflows/build-and-test-xcodebuild.yml@v1
artifactname: XCTHealthKit.xcresult
buildandtestuitests_ios:
name: Build and Test UI Tests iOS
uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
xcodeprojname: Tests/UITests/UITests.xcodeproj
runsonlabels: '["macOS", "self-hosted"]'
path: Tests/UITests
scheme: TestApp
create-and-upload-coverage-report:
name: Create and Upload Coverage Report
needs: [build_and_test-spm, build_and_test-uitests]
uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v1
artifactname: TestApp.xcresult
uploadcoveragereport:
name: Upload Coverage Report
needs: [buildandtest_ios, buildandtestuitests_ios]
uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: TestApp.xcresult XCTHealthKit.xcresult
secrets:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
jobs:
reuse_action:
name: REUSE Compliance Check
uses: StanfordBDHG/.github/.github/workflows/reuse.yml@v1
uses: StanfordBDHG/.github/.github/workflows/reuse.yml@v2
swiftlint:
name: SwiftLint
uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v1
uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v2
82 changes: 82 additions & 0 deletions Sources/XCTHealthKit/HealthAppCategory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// This source file is part of the Stanford XCTHealthKit open-source project
//
// SPDX-FileCopyrightText: 2025 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


import XCTest


/// A category in the health app
public enum HealthAppCategory: String, Hashable {
case activity = "Activity"
case bodyMeasurements = "Body Measurements"
case cycleTracking = "Cycle Tracking"
case hearing = "Hearing"
case heart = "Heart"
case medications = "Medications"
case mentalWellbeing = "Mental Wellbeing"
case mobility = "Mobility"
case nutrition = "Nutrition"
case respiratory = "Respiratory"
case sleep = "Sleep"
case symptoms = "Symptoms"
case vitals = "Vitals"
case otherData = "Other Data"


/// The category's english-language display title in the Health app's "Browse" tab.
public var healthAppDisplayTitle: String {
rawValue
}


/// Navigates in the health app to the category, and selects it.
public func navigateToPage(in healthApp: XCUIApplication) throws {
try healthApp.assertIsHealthApp()

let categoryTitle = self.rawValue

// Dismiss any sheets that may still be open
if healthApp.navigationBars["Browse"].buttons["Cancel"].exists {
healthApp.navigationBars["Browse"].buttons["Cancel"].tap()
}

let browseTabBarButton = healthApp.tabBars["Tab Bar"].buttons["Browse"]

if !browseTabBarButton.waitForExistence(timeout: 2) && browseTabBarButton.isHittable {
XCTFail("Unable to find 'Browse' tab bar item")
return
}

browseTabBarButton.tap() // tap once to select the tab (if necessary)
browseTabBarButton.tap() // tap again to pop all VC off the navigation stack (if necessary)

// Find category:
let categoryStaticTextPredicate = NSPredicate(format: "label CONTAINS[cd] %@", categoryTitle)
let categoryStaticText = healthApp.staticTexts.element(matching: categoryStaticTextPredicate).firstMatch

if categoryStaticText.waitForExistence(timeout: 30), !categoryStaticText.isHittable {
healthApp.swipeUp()

if !categoryStaticText.isHittable {
healthApp.swipeUp()
}
}

categoryStaticText.tap()

// Retry ...
if !healthApp.navigationBars.staticTexts[categoryTitle].waitForExistence(timeout: 20) {
categoryStaticText.tap()
}

guard healthApp.navigationBars.staticTexts[categoryTitle].waitForExistence(timeout: 20) else {
logger.notice("Failed to find category: \(healthApp.staticTexts.allElementsBoundByIndex)")
throw XCTestError(.failureWhileWaiting)
}
}
}
144 changes: 0 additions & 144 deletions Sources/XCTHealthKit/HealthAppDataType.swift

This file was deleted.

101 changes: 101 additions & 0 deletions Sources/XCTHealthKit/HealthAppSampleType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// This source file is part of the XCTHealthKit open-source project
//
// SPDX-FileCopyrightText: 2025 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


import HealthKit
import XCTest


/// A Sample type within the Health app.
public struct HealthAppSampleType: Hashable {
public let category: HealthAppCategory
public let sampleType: HKSampleType
public let healthAppDisplayTitle: String

/// Creates a new sample type, with the specified fields
public init(category: HealthAppCategory, sampleType: HKSampleType, healthAppDisplayTitle: String) {
self.category = category
self.sampleType = sampleType
self.healthAppDisplayTitle = healthAppDisplayTitle
}
}


// MARK: Some Well-Known Sample Types

extension HealthAppSampleType {
/// The active energy subpage of the Apple Health app. Corresponds to `HKQuantityType(.activeEnergyBurned)` samples.
public static let activeEnergy = Self(
category: .activity,
sampleType: HKQuantityType(.activeEnergyBurned),
healthAppDisplayTitle: "Active Energy"
)
/// The resting heart rate subpage of the Apple Health app. Corresponds to `HKQuantityType(.restingHeartRate)` samples.
public static let restingHeartRate = Self(
category: .heart,
sampleType: HKQuantityType(.restingHeartRate),
healthAppDisplayTitle: "Resting Heart Rate"
)
/// The electrocardiograms subpage of the Apple Health app. Corresponds to `HKQuantityType.electrocardiogramType()` samples.
public static let electrocardiograms = Self(
category: .heart,
sampleType: .electrocardiogramType(),
healthAppDisplayTitle: "Electrocardiograms (ECG)"
)
/// The steps subpage of the Apple Health app. Corresponds to `HKQuantityType(.stepCount)` samples.
public static let steps = Self(
category: .activity,
sampleType: HKQuantityType(.stepCount),
healthAppDisplayTitle: "Steps"
)
/// The pushes subpage of the Apple Health app. Corresponds to `HKQuantityType(.pushCount)` samples.
public static let pushes = Self(
category: .activity,
sampleType: HKQuantityType(.pushCount),
healthAppDisplayTitle: "Pushes"
)

/// All currently well-known sample types.
public static let all: [Self] = [
.activeEnergy, .restingHeartRate, .electrocardiograms, .steps, .pushes
]
}


// MARK: XCTest Navigation

extension HealthAppSampleType {
/// Navigates the Health app to the sample type's page.
public func navigateToPage(in healthApp: XCUIApplication, assumeAlreadyInCategory: Bool) throws {
if !assumeAlreadyInCategory {
try category.navigateToPage(in: healthApp)
}

let elementStaticTextPredicate = NSPredicate(format: "label CONTAINS[cd] %@", healthAppDisplayTitle)
let elementStaticText = healthApp.staticTexts.element(matching: elementStaticTextPredicate).firstMatch

guard elementStaticText.waitForExistence(timeout: 30), elementStaticText.isHittable else {
healthApp.swipeUp()
if elementStaticText.waitForExistence(timeout: 10), elementStaticText.isHittable {
elementStaticText.tap()
return
}

healthApp.swipeUp()
if elementStaticText.waitForExistence(timeout: 10), elementStaticText.isHittable {
elementStaticText.tap()
return
}

logger.notice("Failed to find element in category: \(healthApp.staticTexts.allElementsBoundByIndex)")
throw XCTestError(.failureWhileWaiting)
}

elementStaticText.tap()
}
}
12 changes: 12 additions & 0 deletions Sources/XCTHealthKit/XCTHealthKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// This source file is part of the XCTHealthKit open-source project
//
// SPDX-FileCopyrightText: 2025 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import OSLog


let logger = Logger(subsystem: "XCTHealthKit", category: "")
Loading

0 comments on commit 9f33d81

Please sign in to comment.