EarlGrey vs XCUITest: Choosing the Right iOS UI Testing Framework

EarlGrey vs XCUITest: Choosing the Right iOS UI Testing Framework

Both EarlGrey and XCUITest are the two serious options for iOS UI test automation. XCUITest is Apple's official framework. EarlGrey is Google's open-source alternative. They have fundamentally different architectures that affect how you write tests and how reliable those tests are.

Architecture

XCUITest

XCUITest runs in a separate process from your app. The test runner communicates with the app through the accessibility API — the same API used by screen readers. This means:

  • Tests interact with the app through the accessibility layer only
  • Your test code cannot directly access app state, memory, or objects
  • Every interaction involves IPC between the test process and the app process

EarlGrey 2

EarlGrey 2 runs in the same process as your app (it subclasses XCTestCase and uses XCTest infrastructure, but the test code and app share a process). This means:

  • Tests can access app state, singletons, and internal APIs directly
  • Synchronization is precise — EarlGrey reads app internals to know when it's idle
  • No IPC overhead for each interaction

Synchronization: The Critical Difference

This is where EarlGrey has a clear advantage.

XCUITest requires explicit waits:

// XCUITest — manual wait
let result = app.staticTexts["resultLabel"]
let exists = result.waitForExistence(timeout: 5.0)
XCTAssertTrue(exists)
XCTAssertEqual(result.label, "Loaded")

You estimate how long things take and set timeouts accordingly. Too short = flaky test. Too long = slow test suite.

EarlGrey tracks app activity automatically:

// EarlGrey — automatic wait
EarlGrey.selectElement(with: grey_accessibilityID("resultLabel"))
    .assert(grey_text("Loaded"))

EarlGrey knows the app made a network request after the button tap and waits for it to complete before checking the assertion. No manual timeout needed.

Code Comparison

Login Flow

XCUITest:

func testLogin() {
    let app = XCUIApplication()
    app.launch()

    let emailField = app.textFields["emailTextField"]
    XCTAssert(emailField.waitForExistence(timeout: 5))
    emailField.tap()
    emailField.typeText("user@example.com")

    let passwordField = app.secureTextFields["passwordTextField"]
    passwordField.tap()
    passwordField.typeText("password123")

    app.buttons["loginButton"].tap()

    let dashboard = app.otherElements["dashboardView"]
    XCTAssert(dashboard.waitForExistence(timeout: 10))
}

EarlGrey:

func testLogin() {
    EarlGrey.selectElement(with: grey_accessibilityID("emailTextField"))
        .perform(grey_tap())
        .perform(grey_typeText("user@example.com"))

    EarlGrey.selectElement(with: grey_accessibilityID("passwordTextField"))
        .perform(grey_tap())
        .perform(grey_typeText("password123"))

    EarlGrey.selectElement(with: grey_accessibilityID("loginButton"))
        .perform(grey_tap())

    EarlGrey.selectElement(with: grey_accessibilityID("dashboardView"))
        .assert(grey_sufficientlyVisible())
}

The EarlGrey version is shorter because the synchronization is implicit. The XCUITest version requires explicit waitForExistence calls and manual timeout values.

Feature Comparison

Feature XCUITest EarlGrey 2
Process isolation Separate process In-process
App state access No Yes
Automatic synchronization Limited Comprehensive
Manual waits needed Frequently Rarely
Setup complexity None (built-in) Additional dependency
Accessibility tree access Yes Yes
Customizable idle detection No Yes (idling resources)
Apple official support Yes Community (Google OSS)
Swift/Obj-C support Both Both
Simulator and device Both Both
Network tracking No Yes
Animation tracking Partial Yes

When XCUITest Is the Better Choice

No additional dependencies. XCUITest comes with Xcode. No CocoaPods, no SPM dependencies, no framework version management. For teams that want minimal dependency surface, this matters.

Cross-app testing. XCUITest can test multiple apps in one test run, including system apps. EarlGrey's in-process design limits it to one app.

Apple feature support. New iOS features (widgets, App Clips, etc.) get XCUITest support at launch. EarlGrey may lag.

Simpler architecture. The separate-process model is easier to reason about. In-process tests can affect app state in unexpected ways if not careful.

Existing investment. If your team has a large XCUITest suite with helper classes and patterns, migrating to EarlGrey is a significant cost without guaranteed proportional benefit.

When EarlGrey Is the Better Choice

Complex async flows. Apps with multiple concurrent network requests, animations, and state updates are where EarlGrey's synchronization pays dividends. Tests that flake regularly in XCUITest often stabilize with EarlGrey.

White-box testing. When tests need to verify internal app state or manipulate app internals for setup, EarlGrey's in-process access is necessary.

Speed matters. EarlGrey tests run faster because synchronization resolves as soon as the app is truly idle, not after a fixed timeout expires.

Heavy animation. Apps with complex UI animations that XCUITest struggles to time correctly.

Migration Considerations

Migrating from XCUITest to EarlGrey is not a drop-in replacement. The APIs are different. For existing suites:

  • Migrate high-value, high-flake tests first
  • Keep stable XCUITest tests as-is
  • Write new tests in EarlGrey

Both frameworks can coexist in the same project (different test targets). You don't need to commit to one exclusively.

Conclusion

For new iOS projects with complex UI and async flows, EarlGrey's automatic synchronization will produce a more reliable test suite faster. For simpler apps or teams that value zero dependencies and Apple-native tooling, XCUITest is perfectly capable.

The deciding factor is usually flakiness. If your XCUITest suite has tests that fail intermittently due to timing issues, EarlGrey is worth the setup cost. If your XCUITest suite runs reliably, don't switch.

Read more

Start now free