Skip to content
This repository has been archived by the owner on Dec 5, 2022. It is now read-only.

fix: use Public API on Xcode10.2 #81

Merged
merged 2 commits into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,36 +102,36 @@ the input event generation. Here is a simple example:

````swift
func testMonkey() {
let application = XCUIApplication()

// Workaround for bug in Xcode 7.3. Snapshots are not properly updated
// when you initially call app.frame, resulting in a zero-sized rect.
// Doing a random query seems to update everything properly.
// TODO: Remove this when the Xcode bug is fixed!
_ = application.descendants(matching: .any).element(boundBy: 0).frame

// Initialise the monkey tester with the current device
// frame. Giving an explicit seed will make it generate
// the same sequence of events on each run, and leaving it
// out will generate a new sequence on each run.
let monkey = Monkey(frame: application.frame)
//let monkey = Monkey(seed: 123, frame: application.frame)

// Add actions for the monkey to perform. We just use a
// default set of actions for this, which is usually enough.
// Use either one of these, but maybe not both.
// XCTest private actions seem to work better at the moment.
// UIAutomation actions seem to work only on the simulator.
monkey.addDefaultXCTestPrivateActions()
//monkey.addDefaultUIAutomationActions()

// Occasionally, use the regular XCTest functionality
// to check if an alert is shown, and click a random
// button on it.
monkey.addXCTestTapAlertAction(interval: 100, application: application)

// Run the monkey test indefinitely.
monkey.monkeyAround()
let application = XCUIApplication()

// Initialise the monkey tester with the current device
// frame. Giving an explicit seed will make it generate
// the same sequence of events on each run, and leaving it
// out will generate a new sequence on each run.
let monkey = Monkey(frame: application.frame)
//let monkey = Monkey(seed: 123, frame: application.frame)

// Add actions for the monkey to perform. We just use a
// default set of actions for this, which is usually enough.
// Use either one of these but maybe not both.

// XCTest private actions seem to work better at the moment.
// before Xcode 10.1, you can use
// monkey.addDefaultXCTestPrivateActions()

// after Xcode 10.1 We can only use public API
monkey.addDefaultXCTestPublicActions()

// UIAutomation actions seem to work only on the simulator.
//monkey.addDefaultUIAutomationActions()

// Occasionally, use the regular XCTest functionality
// to check if an alert is shown, and click a random
// button on it.
monkey.addXCTestTapAlertAction(interval: 100, application: application)

// Run the monkey test indefinitely.
monkey.monkeyAround()
}
````

Expand Down
60 changes: 59 additions & 1 deletion SwiftMonkey/MonkeyXCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import XCTest
*/
@available(iOS 9.0, *)
extension Monkey {

/// Generates a random `CGVector` inside the frame of the app.
public func randomOffset() -> CGVector {
let point = randomPoint()
return CGVector(dx: point.x, dy: point.y)
}
/**
Add an action that checks, at a fixed interval,
if an alert is being displayed, and if so, selects
Expand All @@ -40,4 +44,58 @@ extension Monkey {
}
}
}

public func addDefaultXCTestPublicActions(app: XCUIApplication) {
addXCTestPublicTapAction(app: app, weight: 25)
addXCTestPublicLongPressAction(app: app, weight: 1)
addXCTestPublicDragAction(app: app, weight: 1)
}


/// Add an action that generates a tap, with a possibility for double taps, using the public XCTest API.
/// - Parameter app: The application proxy.
/// - Parameter weight: The relative probability of this event being generated. Can be any value larger than zero. Probabilities
/// will be normalised to the sum of all relative probabilities.
/// - Parameter doubleTapProbability: Probability that a double tap event is used. Between 0 and 1.
public func addXCTestPublicTapAction(app: XCUIApplication,
weight: Double,
doubleTapProbability: Double = 0.05) {
addAction(weight: weight) { [unowned self] in
let doubleTap = self.r.randomDouble() < doubleTapProbability
let coordinate = app.coordinate(withNormalizedOffset: .zero).withOffset(self.randomOffset())

if doubleTap {
coordinate.doubleTap()
} else {
coordinate.tap()
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello and thanks for the feedback! Your implementation does not really utilize multiple touches, as this API is not available on the XCUICoordinate object. Also I noticed that touches have tendency to cluster on the edges of the screen:

I am also concerned by let app = XCUIApplication() inside the action. According to #70 it might cause memory bloat. Here is how I would change the code:

Suggested change
}
/// Add an action that generates a tap, with a possibility for double taps, using the public XCTest API.
/// - Parameter app: The application proxy.
/// - Parameter weight: The relative probability of this event being generated. Can be any value larger than zero. Probabilities
/// will be normalised to the sum of all relative probabilities.
/// - Parameter doubleTapProbability: Probability that a double tap event is used. Between 0 and 1.
public func addXCTestPublicTapAction(app: XCUIApplication,
weight: Double,
doubleTapProbability: Double = 0.05) {
addAction(weight: weight) { [unowned self] in
let doubleTap = self.r.randomDouble() < doubleTapProbability
let coordinate = app.coordinate(withNormalizedOffset: .zero).withOffset(self.randomOffset())
if doubleTap {
coordinate.doubleTap()
} else {
coordinate.tap()
}
}
}

Notice that I've replaced multipleTouchProbability with doubleTapProbability as this is supported directly by the XCUICoordinate class. I haven't tested though whether tapping immediately multiple coordinates would generate a multi-touch event. I would also add the app parameter to the addDefaultXCTestPublicActions method, e.g:

public func addDefaultXCTestPublicActions(app: XCUIApplication) {
    addXCTestPublicTapAction(app: app, weight: 25)
    addXCTestPublicLongPressAction(app: app, weight: 1)
    addXCTestPublicDragAction(app: app, weight: 1)
}

What do you think @a455455b ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to attach the code for randomVector() method (though it's very straightforward):

/// Generates a random `CGVector` inside the frame of the app.
public func randomOffset() -> CGVector {
    let point = randomPoint()
    return CGVector(dx: point.x, dy: point.y)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good, it gave me a lot of inspiration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need me to modify and resubmit?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ideally you could try adapting other methods that you've added (add the app parameter, compute coordinates properly, etc.).

If you wish I can also do it myself by providing code suggestions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi,I submitted a fix.



/// Add an action that generates a long press event using the public XCTest API.
/// - Parameter app: The application proxy.
/// - Parameter weight: The relative probability of this event being generated. Can be any value larger than zero. Probabilities
/// will be normalised to the sum of all relative probabilities.
public func addXCTestPublicLongPressAction(app: XCUIApplication,
weight: Double) {
addAction(weight: weight) { [unowned self] in
let coordinate = app.coordinate(withNormalizedOffset: .zero).withOffset(self.randomOffset())
coordinate.press(forDuration: 0.5)
}
}


/// Add an action that generates a drag event from one random screen position to another using the public XCTest API.
/// - Parameter app: The application proxy.
/// - Parameter weight: The relative probability of this event being generated. Can be any value larger than zero. Probabilities
/// will be normalised to the sum of all relative probabilities.
public func addXCTestPublicDragAction(app: XCUIApplication,
weight: Double) {
addAction(weight: weight) { [unowned self] in
let startCoordinate = app.coordinate(withNormalizedOffset: .zero).withOffset(self.randomOffset())
let endCoordinate = app.coordinate(withNormalizedOffset: .zero).withOffset(self.randomOffset())
startCoordinate.press(forDuration: 0.2, thenDragTo: endCoordinate)
}
}
}
16 changes: 8 additions & 8 deletions SwiftMonkeyExample/AppTests/MonkeyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ class SwiftMonkeyExampleUITests: XCTestCase {
func testMonkey() {
let application = XCUIApplication()

// Workaround for bug in Xcode 7.3. Snapshots are not properly updated
// when you initially call app.frame, resulting in a zero-sized rect.
// Doing a random query seems to update everything properly.
// TODO: Remove this when the Xcode bug is fixed!
_ = application.descendants(matching: .any).element(boundBy: 0).frame

// Initialise the monkey tester with the current device
// frame. Giving an explicit seed will make it generate
// the same sequence of events on each run, and leaving it
Expand All @@ -39,11 +33,17 @@ class SwiftMonkeyExampleUITests: XCTestCase {
// Add actions for the monkey to perform. We just use a
// default set of actions for this, which is usually enough.
// Use either one of these but maybe not both.

// XCTest private actions seem to work better at the moment.
// before Xcode 10.1, you can use
// monkey.addDefaultXCTestPrivateActions()

// after Xcode 10.1 We can only use public API
monkey.addDefaultXCTestPublicActions(app: application)

// UIAutomation actions seem to work only on the simulator.
monkey.addDefaultXCTestPrivateActions()
//monkey.addDefaultUIAutomationActions()

// Occasionally, use the regular XCTest functionality
// to check if an alert is shown, and click a random
// button on it.
Expand Down