Mobile Development 7 min read

Implementing Lightweight iOS Monkey Testing with SwiftMonkey and XCUITest

This article explains the background, advantages, implementation details, and crash analysis of using SwiftMonkey and XCUITest for lightweight, automated iOS Monkey testing, providing code examples and outlining pending improvements for a more robust testing pipeline.

JD Retail Technology
JD Retail Technology
JD Retail Technology
Implementing Lightweight iOS Monkey Testing with SwiftMonkey and XCUITest

Background: In daily development testing, developers often encounter unreproducible app crashes; automated testing is needed to improve app robustness. Monkey tools, which generate random user events, address this need.

iOS lacks an official Monkey tool, but community solutions like UIAutomation‑based Monkey, XCUITest, SwiftMonkey, and FastMonkey have emerged.

Advantages: random user actions (tap, double‑tap, long press, move) can reveal hidden issues; Disadvantages: randomness can cause meaningless actions or dead loops.

Choosing a lightweight solution, SwiftMonkey combined with XCUITest provides sufficient logging and screenshots without requiring instrumentation.

Practice: Define scenarios such as switching instrumentation, detecting app background events, inserting custom logic, and collecting crash data.

Instrumentation: SwiftMonkey can be added via CocoaPods or manually; to run without instrumentation, set the target app’s bundle identifier and launch it with XCUITest.

let launchApp = XCUIApplication(bundleIdentifier: "com.xxx.xxx")
launchApp.launch()

Event handling: Use XCUITest APIs to monitor app state and react, e.g., activate the app if not in foreground.

if launchApp.state != .runningForeground {
    launchApp.activate()
}

Custom actions: Expose closures for actions like login, allowing business modules to implement specifics.

func actInForeground(_ action: @escaping ActionClosure) -> ActionClosure {
    guard #available(iOS 9.0, *) else {
        return action
    }
    let closure: ActionClosure = {
        if launchApp.state != .runningForeground {
            launchApp.activate()
        }
        generalActionClosure()
        action()
    }
    return {
        if Thread.isMainThread {
            closure()
        } else {
            DispatchQueue.main.async(execute: closure)
        }
    }
}

// login example
func loginAction() {
    if self.currentApp.buttons["短信验证码登录"].exists {
        let textField = self.currentApp.textFields.element
        if !textField.exists { return }
        textField.tap()
        textField.clearAndEnterText(text: "用户名")
        sleep(1)
        let pwdField = self.currentApp.secureTextFields.element
        pwdField.tap()
        pwdField.typeText("******")
        sleep(1)
        let loginBtn = self.currentApp.buttons["登录"]
        loginBtn.tap()
        self.isLogin = true
        sleep(3)
    }
}

Crash analysis: After each Monkey run, collect crash logs (e.g., using idevicecrashreport) and symbolicate them with dSYM using tools like atos.

idevicecrashreport -u
-e -k ~/Desktop/crashlog
atos -o "myapp.app.dSYM" -arch arm64 -l

Pending issues: full automation is not yet achieved; configuration still requires manual steps, and module‑specific execution is not implemented.

Conclusion: The article introduces iOS Monkey testing basics and suggests building a CI pipeline to automate packaging, Monkey execution, and crash collection, while continuing to improve the framework for targeted and efficient testing.

iOSautomationcrash analysisMonkey TestingSwiftMonkeyXCUITest
JD Retail Technology
Written by

JD Retail Technology

Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.