How to test drag and drop in UITests

Issue #583

In UITests, we can use press from XCUIElement to test drag and drop

1
2
3
4
5
let fromCat = app.buttons["cat1"].firstMatch
let toCat = app.buttons["cat2"]
let fromCoordinate = fromCat.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let toCoordinate = toCat.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: -0.5))
fromCoordinate.press(forDuration: 1, thenDragTo: toCoordinate)

and then take screenshot

1
2
3
4
5
let screenshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.lifetime = .keepAlways
attachment.name = name
add(attachment)

Screenshot capturing happens after the action, so it may be too late. One way is to inject launch arguments, like app.launchArguments.append("--dragdrop") to alter some code in the app.

We can also swizzle gesture recognizer to alter behavior

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
extension UILongPressGestureRecognizer {
@objc var uiTests_state: UIGestureRecognizer.State {
let state = self.uiTests_state
if state == .ended {
return .changed
} else {
return state
}
}
}

let originalSelector = #selector(getter: UILongPressGestureRecognizer.state)
let swizzledSelector = #selector(getter: UILongPressGestureRecognizer.uiTests_state)

let originalMethod = class_getInstanceMethod(UILongPressGestureRecognizer.self, originalSelector)!
let swizzledMethod = class_getInstanceMethod(UILongPressGestureRecognizer.self, swizzledSelector)!

let didAddMethod = class_addMethod(UILongPressGestureRecognizer.self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

if didAddMethod {
class_replaceMethod(UILongPressGestureRecognizer.self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}

Comments