If you can't touch this, it's Hammer time!
Table of Contents
Hammer is a touch and keyboard synthesis library for emulating user interaction events. It enables new ways of triggering UI actions in unit tests, replicating a real world environment as much as possible.
With SwiftPM
.package(url: "https://github.com/lyft/Hammer.git", from: "0.9.0")
Hammer allows you to simulate fingers, stylus and keyboard events. It also provides various convenience methods to simulate higher level user interactions.
To be able to send events to a view you must first create an EventGenerator
:
// Initialize for an existing UIWindow, ensure that the window is key and visible.
let eventGenerator = EventGenerator(window: myWindow)
// Initialize for a UIView, automatically wrapping it in a temporary window.
let eventGenerator = EventGenerator(view: myView)
// Initialize for a UIViewController, automatically wrapping it in a temporary window.
let eventGenerator = EventGenerator(viewController: myViewController)
When simulating finger or stylus touches, there are multiple ways of specifying a touch location:
- Default: If you don't specify a location it will use the center of the screen.
- Point: A CGPoint in screen coordinates.
- View: A reference to a UIView or UIViewController, the location will be the center of the visible part of the view.
- Identifier: An accessibility identifier string of a view, the location will be the center of the visible part of the view.
By default, Hammer will display simulated touches over the view. You can change this behavior for your event generator.
eventGenerator.showTouches = false
Fingers are the most common method of user interaction on iOS. Hammer supports handling multiple fingers on the screen simultaneously, up to the limit on the device. You can specify the specific finger index you would like to use, if unspecified it will choose the most appropriate one automatically.
Primitive events are the basic building blocks of user interactions, they can be combined together to create full gestures. Some methods will allow you to specify a duration and will interpolate the changes during that time.
try eventGenerator.fingerDown(at: CGPoint(x: 10, y: 10))
try eventGenerator.fingerMove(to: CGPoint(x: 20, y: 10), duration: 0.5)
try eventGenerator.fingerUp()
For convenience, Hammer provides many higher level gestures. If you don't specify a location it will automatically default to the center of the view.
try eventGenerator.fingerTap()
try eventGenerator.fingerDoubleTap()
try eventGenerator.fingerLongPress()
try eventGenerator.twoFingerTap()
Many advanced gestures are also available.
try eventGenerator.fingerDrag(from: CGPoint(x: 10, y: 10), to: CGPoint(x: 20, y: 10), duration: 0.5)
try eventGenerator.fingerPinch(fromDistance: 100, toDistance: 50, duration: 0.5)
try eventGenerator.fingerRotate(angle: .pi, duration: 0.5)
Stylus is available when running on an iPad. It allows for additional properties like pressure, altitude and azimouth to be specified.
Similar to fingers, primitive events are the basic building blocks of stylus interactions.
try eventGenerator.stylusDown(at: CGPoint(x: 10, y: 10), azimuth: 0, altitude: 0, pressure: 0.5)
try eventGenerator.stylusMove(to: CGPoint(x: 20, y: 10), duration: 0.5)
try eventGenerator.stylusUp()
Hammer also provides many higher level gestures for Stylus. If you don't specify a location it will automatically default to the center of the view.
try eventGenerator.stylusTap()
try eventGenerator.stylusDoubleTap()
try eventGenerator.stylusLongPress()
Keyboard methods take an explicit KeyboardKey
object or a Character
. Characters will be mapped to their closest keybaord key, you must wrap them with a shift key modifier if needed. This means that specifying a lowercase "a" character is equivalent to speciifying an uppercase "A", this is also true for keys with symbols.
// Explicit `KeyboardKey`
try eventGenerator.keyDown(.letterA)
try eventGenerator.keyUp(.letterA)
// Automatic `Character` mapping
try eventGenerator.keyDown("a")
try eventGenerator.keyUp("a")
// Convenience key down and up events
try eventGenerator.keyPress(.letterA)
try eventGenerator.keyPress("a")
To type characters or longer strings and get automatic shift wrapping you can use the keyType()
methods.
try eventGenerator.keyType("This will type the string as specified, including symbols!")
When running on a full screen app or testing navigation, specifying a CGPoint in screen coordinates can be difficult. For this, Hammer provides convenience methods to find views in the hierarchy by their accessibility identifier.
let myButton = try eventGenerator.viewWithIdentifier("my_button", ofType: UIButton.self)
This method will throw an error if the view was not found in the hierarchy. If you're testing navigation or screen changes and you need to wait until the view appears, you can add a timeout. This will wait until the hierarchy has updated and return the view.
let myButton = try eventGenerator.viewWithIdentifier("my_button", ofType: UIButton.self, timeout: 1)
You will often need to wait for the simulator to finish displaying something on the screen or for an animation to end. Hammer provides multiple methods to wait until a view is visible on screen or if a control is hittable
try eventGenerator.wait(untilVisible: "myLabel", timeout: 1)
try eventGenerator.wait(untilHittable: "myButton", timeout: 1)
Hammer is released under the Apache License. See LICENSE