szpakkamil/harnesskit
A structured navigation harness for SwiftUI demo apps and component previews. Define a tree of views using `PathProject` and `PathFolder`, render it with `HarnessView`, then navigate to any screen in UI tests with a single call.
Table of Contents
Overview
- Define a
PathProjectas the root of your view hierarchy. - Organize views into
PathFoldersections and subsections. - Render the entire tree with
HarnessView<YourProject>. - Navigate to any view in UI tests via
someCase.navigate(app:).
Implementation
HarnessKit uses two protocols: PathProject for the root and PathFolder for each section.
Define a Project
import HarnessKit
struct MyProject: PathProject {
static let name = "My App"
static let folders: [any PathFolder.Type] = [ButtonsFolder.self, TextFolder.self]
}Define Folders
import SwiftUI
import HarnessKit
enum ButtonsFolder: PathFolder {
typealias ParentSection = MyProject
static let name = "Buttons"
static let folders: [any PathFolder.Type] = []
case primary
case secondary
case destructive
var description: String {
switch self {
case .primary: "Primary Button"
case .secondary: "Secondary Button"
case .destructive: "Destructive Button"
}
}
@ViewBuilder
var view: some View {
switch self {
case .primary: PrimaryButtonPreview()
case .secondary: SecondaryButtonPreview()
case .destructive: DestructiveButtonPreview()
}
}
}Render the Harness
import SwiftUI
import HarnessKit
@main
struct HarnessApp: App {
var body: some Scene {
WindowGroup {
HarnessView<MyProject>()
}
}
}HarnessView builds a NavigationStack from your project tree. Tap any folder to drill down; tap any option to open the associated view.
UI Testing
Add HarnessKitTesting to your UI test target, then call navigate(app:) on any folder case.
import XCTest
import HarnessKitTesting
class ButtonTests: XCTestCase {
func testDestructiveButtonAppears() {
let app = XCUIApplication()
app.launch()
ButtonsFolder.destructive.navigate(app: app)
XCTAssertTrue(app.buttons["Delete"].exists)
}
}navigate(app:) resolves the full path from the project root and taps each level in sequence. On tvOS it uses XCUIRemote directional presses instead of tap gestures.
Resources
- Documentation: API Reference
- GitHub: SzpakKamil/HarnessKit — Track issues and contributions.
- Index: Swift Package Index — View compatibility and release history.
Installation
Swift Package Manager
Add HarnessKit as a package dependency in your Package.swift file.
dependencies: [
.package(url: "https://github.com/SzpakKamil/HarnessKit.git", from: "1.0.0")
]Add HarnessKit to your harness app target and HarnessKitTesting to your UI test target:
targets: [
.target(
name: "MyHarnessApp",
dependencies: ["HarnessKit"]
),
.testTarget(
name: "MyHarnessAppUITests",
dependencies: ["HarnessKitTesting"]
)
]Requirements
- Platforms: iOS 14.0+, macOS 11.0+, tvOS 14.0+, watchOS 10.0+, visionOS 1.0+
- Tools: Swift 5.9+, Xcode 15.0+
License
HarnessKit is released under the MIT license.
Third-Party Notices
The device bezel images fetched at runtime by HarnessKitTransform depict Apple hardware and are the intellectual property of Apple Inc. They are used in accordance with Apple's App Store Marketing Guidelines and may only be used to frame screenshots of your own application for App Store promotion. See NOTICE.md for full details.
Package Metadata
Repository: szpakkamil/harnesskit
Default branch: master
README: README.md