If you can't touch this, it's Hammer time!
Hammer is a touch, stylus and keyboard synthesis library for emulating user interaction events. It enables better ways of triggering UI actions in unit tests, replicating a real world environment as much as possible.
- Real events: touches and key presses go through UIKit like a user's, so gesture recognizers, controls and SwiftUI handle them as they would on a device.
- Tests: tap, swipe, drag, pinch and type in XCTest or Swift Testing, finding views by accessibility identifier, label, value or traits.
- Running apps: the
hammercommand line tool and AI agents can drive an app that runs Hammer's agent, see what's on screen and try view changes live.
Hammer requires Swift 6.0 and iOS 17.0 or later. Add it with SwiftPM:
.package(url: "https://github.com/lyft/Hammer.git", from: "2.0.0")Add the Hammer product to your test target, and HammerAgent to your app to drive it with the command line tool. CocoaPods is deprecated.
Hammer tests need a host application to send touches. Create an EventGenerator for the view, view controller, SwiftUI view or window you test, then send it events:
@MainActor
final class SignInTests: XCTestCase {
func testSignIn() async throws {
let eventGenerator = try await EventGenerator(viewController: SignInViewController())
try await eventGenerator.fingerTap(at: "email_field")
try await eventGenerator.keyType("[email protected]")
try await eventGenerator.fingerTap(at: .label("Sign In", traits: .button))
try await eventGenerator.waitUntilVisible("home_title")
}
}Locations can be an accessibility identifier, an AccessibilityQuery, a view or a point. A few more of what you can do:
try await eventGenerator.fingerSwipe(at: "list", direction: .up)
try await eventGenerator.fingerEdgeSwipe(direction: .right)
try await eventGenerator.fingerDrag(from: "card", to: "trash", duration: 0.5)
try await eventGenerator.fingerPinch(fromDistance: 100, toDistance: 50, duration: 0.5)
try await eventGenerator.keyPress(.returnOrEnter)
try await eventGenerator.waitUntilHittable("checkout_button")Learn more:
- Getting started
- Touches and keyboard
- Finding views and SwiftUI
- Waiting and Swift Testing
- Troubleshooting
Start the agent in a debug build of your app. By default it only runs in the simulator and only accepts connections from the same Mac:
#if DEBUG
import HammerAgent
let agent = HammerAgent()
try await agent.start()
#endifInstall the prebuilt hammer tool from the latest release:
mkdir -p ~/.local/bin
curl -fsSL https://github.com/lyft/Hammer/releases/latest/download/hammer-macos.zip -o /tmp/hammer-macos.zip
unzip -o /tmp/hammer-macos.zip -d ~/.local/binThen see what's on screen and act on it:
hammer describe # Elements on screen, each with a ref such as e12
hammer tap --ref e12
hammer type-text --identifier email --text "[email protected]"
hammer wait-for --identifier home_title --condition visible
hammer screenshot --output screen.png
hammer help swipe # Arguments and examples of a commandTo give an AI agent the same commands as tools, run hammer mcp as an MCP server. For example, with Claude Code:
claude mcp add hammer -- ~/.local/bin/hammer mcpLearn more:
- Adding the agent and security, including tokens and physical devices
- Using the command line tool and every command
- Live editing views before changing them in code
- Custom commands for your app
- MCP server and agent skills
Every guide is in docs.
Hammer is released under the Apache License. See LICENSE

