Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 SwiftDeveloper Tools

WWDC25 · 25 min · Swift / Developer Tools

Record, replay, and review: UI automation with Xcode

Learn to record, run, and maintain XCUIAutomation tests in Xcode. Replay your XCTest UI tests in dozens of locales, device types, and system conditions using test plan configurations. Review your test results using the Xcode test report, and download screenshots and videos of your runs. We’ll also cover best practices for preparing your app for automation with Accessibility and writing stable, high-quality automation code.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

  • 0:00 — Introduction and agenda
  • 3:58 — UI automation overview
  • 6:26 — Prepare your app for automation
  • 11:32 — Record your interactions
  • 17:30 — Replay in multiple configurations
  • 20:54 — Review videos and results
  • 24:16 — Next steps

Code shown on screen · 11 snippets

Adding accessibility identifiers in SwiftUI swift · at 7:52 ↗
// Adding accessibility identifiers in SwiftUI
import SwiftUI

struct LandmarkDetailView: View {
  let landmark: Landmark
  var body: some View {
    VStack {
      Image(landmark.backgroundImageName)
        .accessibilityIdentifier("LandmarkImage-\(landmark.id)")
      
      Text(landmark.description)
        .accessibilityIdentifier("LandmarkDescription-\(landmark.id)")
    }
  }
}
Adding accessibility identifiers in UIKit swift · at 8:19 ↗
// Adding accessibility identifiers in UIKit
import UIKit

struct LandmarksListViewController: UIViewController {
  let landmarks: [Landmark] = [landmarkGreatBarrier, landmarkCairo]

  override func viewDidLoad() {
    super.viewDidLoad()

    for landmark in landmarks {
      let button = UIButton(type: .custom)
      setupButtonView()
                
      button.accessibilityIdentifier = "LandmarkButton-\(landmark.id)"
      
      view.addSubview(button)
    }
  }
}
Best practice: Prefer accessibility identifiers over localized strings swift · at 13:54 ↗
// Example SwiftUI view
struct CollectionDetailDisplayView: View {
  var body: some View {
    ScrollView {
      Text(collection.name)
        .font(.caption)
        .accessibilityIdentifier("Collection-\(collection.id)")
    }
  }
}

// Example of a worse XCUIElementQuery
XCUIApplication().staticTexts["Max's Australian Adventure"]

// Example of a better XCUIElementQuery
XCUIApplication().staticTexts["Collection-1"]
Best practice: Keep queries as concise as possible swift · at 14:09 ↗
// Example SwiftUI view
struct CollectionDetailDisplayView: View {
  var body: some View {
    ScrollView {
      Text(collection.name)
        .font(.caption)
        .accessibilityIdentifier("Collection-\(collection.id)")
    }
  }
}

// Example of a worse XCUIElementQuery
XCUIApplication().scrollViews.staticTexts["Collection-1"]

// Example of a better XCUIElementQuery
XCUIApplication().staticTexts["Collection-1"]
Best practice: Prefer generic queries for dynamic content swift · at 14:21 ↗
// Example SwiftUI view
struct CollectionDetailDisplayView: View {
  var body: some View {
    ScrollView {
      Text(collection.name)
        .font(.caption)
        .accessibilityIdentifier("Collection-\(collection.id)")
    }
  }
}

// Example of a worse XCUIElementQuery
XCUIApplication().staticTexts["Max's Australian Adventure"]

// Example of a better XCUIElementQuery
XCUIApplication().staticTexts.firstMatch
Add validations to a test case swift · at 15:49 ↗
// Add validations to the test case
import XCTest

class LandmarksUITests: XCTestCase {

  func testGreatBarrierAddedToFavorites() {
    let app = XCUIApplication()
    app.launch()
    app.cells["Landmark-186"].tap()
    XCTAssertTrue(
      app.staticTexts["Landmark-186"].waitForExistence(timeout: 10.0)),
      "Great Barrier exists"
    )

    let favoriteButton = app.buttons["Favorite"]
    favoriteButton.tap()
    XCTAssertTrue(
      favoriteButton.wait(for: \.value, toEqual: true, timeout: 10.0),
      "Great Barrier is a favorite"
    )
  }
}
Set up your device for test execution swift · at 16:36 ↗
// Set up your device for test execution
import XCTest
import CoreLocation

class LandmarksUITests: XCTestCase {

  override func setUp() {
    continueAfterFailure = false
    
    XCUIDevice.shared.orientation = .portrait
    XCUIDevice.shared.appearance = .light
      
    let simulatedLocation = CLLocation(latitude: 28.3114, longitude: -81.5535)
    XCUIDevice.shared.location = XCUILocation(location: simulatedLocation)
  }
  
}
Launch your app with environment variables and arguments swift · at 16:54 ↗
// Launch your app with environment variables and arguments
import XCTest

class LandmarksUITests: XCTestCase {

  func testLaunchWithDefaultCollection() {
    let app = XCUIApplication()
    app.launchArguments = ["ClearFavoritesOnLaunch"]
    app.launchEnvironment = ["DefaultCollectionName": "Australia 🐨 🐠"]
    app.launch()

    app.tabBars.buttons["Collections"].tap()
    XCTAssertTrue(app.buttons["Australia 🐨 🐠"].waitForExistence(timeout: 10.0))
  }
}
Launch your app using custom URL schemes swift · at 17:04 ↗
// Launch your app using custom URL schemes
import XCTest

class LandmarksUITests: XCTestCase {

  func testOpenGreatBarrier() {
    let app = XCUIApplication()
    let customURL = URL(string: "landmarks://great-barrier")!
    app.open(customURL)

    XCTAssertTrue(app.wait(for: .runningForeground, timeout: 10.0))
    XCTAssertTrue(app.staticTexts["Great Barrier Reef"].waitForExistence(timeout: 10.0))
  }
}
Launch your app using custom URL schemes and the system default app swift · at 17:12 ↗
// Launch your app using custom URL schemes
import XCTest

class LandmarksUITests: XCTestCase {

  func testOpenGreatBarrier() {
    let app = XCUIApplication()
    let customURL = URL(string: "landmarks://great-barrier")!
    XCUIDevice.shared.system.open(customURL)

    XCTAssertTrue(app.wait(for: .runningForeground, timeout: 10.0))
    XCTAssertTrue(app.staticTexts["Great Barrier Reef"].waitForExistence(timeout: 10.0))
  }
}
Perform an accessibility audit during an automation swift · at 17:13 ↗
// Perform an accessibility audit during an automation
import XCTest

class LandmarksUITests: XCTestCase {
  
  func testPerformAccessibilityAudit() {
    let app = XCUIApplication()
    try app.performAccessibilityAudit()
  }

}

Resources