Skip to main content

Demos

Every demo below is one end-to-end test. The commands are the ones the test ran against a real simulator, and the output is what idb printed, with run-specific values replaced so two runs read the same. A demo whose test fails is never published.

Crash an app on purpose and read its crash report

Swift injected into an app schedules a fatal error and returns. Once the app has crashed, idb lists the crash report the simulator wrote, shows the report, and deletes it.

  1. 1 of 4

    Schedule a crash with injected Swift

    The Swift returned before the crash it scheduled, so idb-repl printed its result and exited normally.

    ReplHost will crash in a second
    
    Exited 0 after 2.20s; show the command and all 5 lines it printed
    $ idb-repl app --new-session 'import Foundation
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
      fatalError("Crashed on purpose by Swift that idb-repl injected")
    }
    return "ReplHost will crash in a second"
    '
    Result:
    ReplHost will crash in a second
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    
  2. 2 of 4

    List the app's crash reports

    The simulator wrote a crash report for com.facebook.idb.replhost, and idb lists it with the process that crashed and when.

      "bundle_id": "com.facebook.idb.replhost",
    
    Exited 0 after 0.33s; show the command and all 9 lines it printed
    $ idb crash list --bundle-id com.facebook.idb.replhost
    {
      "name": "ReplHost-2026-10-02-152011.ips",
      "bundle_id": "com.facebook.idb.replhost",
      "process_name": "ReplHost",
      "parent_process_name": "launchd_sim",
      "process_identifier": 12965,
      "parent_process_identifier": 1292,
      "timestamp": 1790954387
    }
    
  3. 3 of 4

    Read the crash report

    The crashed thread stopped in the closure the injected Swift scheduled, on the breakpoint trap fatalError raises.

      "exception" : {"codes":"0xADDRESS, 0xADDRESS","rawCodes":[1,6847668752],"type":"EXC_BREAKPOINT","signal":"SIGTRAP"},
      "threads" : [{"triggered":true,"id":42245,"threadState":{"x":[{"value":9223372041265617360},{"value":0},{"value":18446744073709551552},{"value":4358360192},{"value":4358360320},{"value":6131845504},{"value":100},{"value":0},{"value":0},{"value":4290371383},{"value":5295892249059528},{"value":5278192688830672},{"value":644245094400},{"value":13},{"value":0},{"value":4342857728},{"value":18446726483666796544},{"value":18446744072367376383},{"value":0},{"value":8381071296,"symbolLocation":0,"symbol":"_dispatch_main_q"},{"value":0},{"value":4353865472},{"value":0},{"value":0},{"value":8447},{"value":8447},{"value":0},{"value":8447},{"value":272}],"flavor":"ARM_THREAD_STATE64","lr":{"value":6847668752},"cpsr":{"value":4096},"fp":{"value":6131845920},"sp":{"value":6131845792},"esr":{"value":4060086273,"description":"(Breakpoint) brk 1"},"pc":{"value":6847668752,"matchesCrashFrame":1},"far":{"value":0}},"queue":"com.apple.main-thread","frames":[{"imageOffset":1053200,"symbol":"_assertionFailure(_:_:file:line:flags:)","symbolLocation":152,"imageIndex":71},{"imageOffset":3312,"symbol":"closure #1 in userCode_0()","symbolLocation":128,"imageIndex":58},{"imageOffset":3364,"symbol":"thunk for @escaping @callee_guaranteed () -> ()","symbolLocation":48,"imageIndex":58},{"imageOffset":114328,"symbol":"_dispatch_client_callout","symbolLocation":12,"imageIndex":72},{"imageOffset":24680,"symbol":"_dispatch_continuation_pop","symbolLocation":776,"
    
    Exited 0 after 0.20s; show the command and all 50 lines it printed
    $ idb crash show ReplHost-2026-10-02-152011.ips
    {"app_name":"ReplHost","timestamp":"TIMESTAMP +0000","app_version":"1.0","slice_uuid":"00000000-0000-0000-0000-000000000000","build_version":"1","platform":7,"bundleID":"com.facebook.idb.replhost","share_with_app_devs":0,"is_first_party":0,"bug_type":"309","os_version":"macOS 26.6.2 (25G83)","roots_installed":0,"name":"ReplHost","incident_id":"00000000-0000-0000-0000-000000000000"}
    {
      "uptime" : 330,
      "procRole" : "Foreground",
      "version" : 2,
      "userID" : 501,
      "deployVersion" : 210,
      "modelCode" : "VirtualMac2,1",
      "coalitionID" : 902,
      "osVersion" : {
        "train" : "macOS 26.6.2",
        "build" : "25G83",
        "releaseType" : "User"
      },
      "captureTime" : "TIMESTAMP +0000",
      "codeSigningMonitor" : 0,
      "incident" : "00000000-0000-0000-0000-000000000000",
      "pid" : 12965,
      "translated" : false,
      "cpuType" : "ARM-64",
      "procLaunch" : "TIMESTAMP +0000",
      "procStartAbsTime" : 7967849717,
      "procExitAbsTime" : 8038871497,
      "procName" : "ReplHost",
      "procPath" : "\/Users\/USER\/*\/ReplHost.app\/ReplHost",
      "bundleInfo" : {"CFBundleShortVersionString":"1.0","CFBundleVersion":"1","CFBundleIdentifier":"com.facebook.idb.replhost"},
      "storeInfo" : {"deviceIdentifierForVendor":"00000000-0000-0000-0000-000000000000","thirdParty":true},
      "parentProc" : "launchd_sim",
      "parentPid" : 1292,
      "coalitionName" : "com.apple.CoreSimulator.SimDevice.UDID",
      "crashReporterKey" : "00000000-0000-0000-0000-000000000000",
      "appleIntelligenceStatus" : {"reasons":["deviceNotCapable"],"state":"unavailable"},
      "developerMode" : 1,
      "responsiblePid" : 1235,
      "responsibleProc" : "SimulatorTrampoline",
      "codeSigningID" : "com.facebook.idb.replhost",
      "codeSigningTeamID" : "",
      "codeSigningFlags" : 570425857,
      "codeSigningValidationCategory" : 10,
      "codeSigningTrustLevel" : 4294967295,
      "codeSigningAuxiliaryInfo" : 0,
      "instructionByteStream" : {"beforePC":"IAAg1IEF+LfpAwC54gMDquMDBKrkAwWq5QMGquYDB6rnAwiqlAIAlA==","atPC":"IAAg1IgSgFL\/EwC56AcA+UgAgFLoAwA5QBYA0AAQCZFDFgDQY8ATkQ=="},
      "bootSessionUUID" : "00000000-0000-0000-0000-000000000000",
      "sip" : "disabled",
      "exception" : {"codes":"0xADDRESS, 0xADDRESS","rawCodes":[1,6847668752],"type":"EXC_BREAKPOINT","signal":"SIGTRAP"},
      "termination" : {"flags":0,"code":5,"namespace":"SIGNAL","indicator":"Trace\/BPT trap: 5","byProc":"exc handler","byPid":12965},
      "os_fault" : {"process":"ReplHost"},
      "extMods" : {"caller":{"thread_create":0,"thread_set_state":0,"task_for_pid":0},"system":{"thread_create":0,"thread_set_state":0,"task_for_pid":0},"targeted":{"thread_create":0,"thread_set_state":0,"task_for_pid":0},"warnings":0},
      "faultingThread" : 0,
      "threads" : [{"triggered":true,"id":42245,"threadState":{"x":[{"value":9223372041265617360},{"value":0},{"value":18446744073709551552},{"value":4358360192},{"value":4358360320},{"value":6131845504},{"value":100},{"value":0},{"value":0},{"value":4290371383},{"value":5295892249059528},{"value":5278192688830672},{"value":644245094400},{"value":13},{"value":0},{"value":4342857728},{"value":18446726483666796544},{"value":18446744072367376383},{"value":0},{"value":8381071296,"symbolLocation":0,"symbol":"_dispatch_main_q"},{"value":0},{"value":4353865472},{"value":0},{"value":0},{"value":8447},{"value":8447},{"value":0},{"value":8447},{"value":272}],"flavor":"ARM_THREAD_STATE64","lr":{"value":6847668752},"cpsr":{"value":4096},"fp":{"value":6131845920},"sp":{"value":6131845792},"esr":{"value":4060086273,"description":"(Breakpoint) brk 1"},"pc":{"value":6847668752,"matchesCrashFrame":1},"far":{"value":0}},"queue":"com.apple.main-thread","frames":[{"imageOffset":1053200,"symbol":"_assertionFailure(_:_:file:line:flags:)","symbolLocation":152,"imageIndex":71},{"imageOffset":3312,"symbol":"closure #1 in userCode_0()","symbolLocation":128,"imageIndex":58},{"imageOffset":3364,"symbol":"thunk for @escaping @callee_guaranteed () -> ()","symbolLocation":48,"imageIndex":58},{"imageOffset":114328,"symbol":"_dispatch_client_callout","symbolLocation":12,"imageIndex":72},{"imageOffset":24680,"symbol":"_dispatch_continuation_pop","symbolLocation":776,"
    

    Output continues past what is shown; 44687 bytes in all.

  4. 4 of 4

    Delete the crash report

    idb no longer lists the report it deleted.

    Exited 0 after 0.30s; show the command and all 9 lines it printed
    $ idb crash delete ReplHost-2026-10-02-152011.ips
    {
      "name": "ReplHost-2026-10-02-152011.ips",
      "bundle_id": "com.facebook.idb.replhost",
      "process_name": "ReplHost",
      "parent_process_name": "launchd_sim",
      "process_identifier": 12965,
      "parent_process_identifier": 1292,
      "timestamp": 1790954387
    }
    

Add a control to a running app with injected Swift, then drive it

Compile a few lines of Swift on the host and run them inside a running app, adding a button the app was never built with. idb finds the new button in the accessibility tree like any other control and taps it. Its label counts the taps, and asking the same live process how many it counted gives the same answer.

  1. 1 of 7

    Add a button to a running app with injected Swift

    idb-repl launched the host app with the REPL injected, compiled the Swift on the host and ran it inside the app. The button is plain UIKit, added while the app runs.

    Added a button to com.facebook.idb.replhost
    
    Exited 0 after 13.28s; show the command and all 5 lines it printed
    $ idb-repl app --new-session 'import UIKit
    return await MainActor.run { () -> String in
      let window = UIApplication.shared.connectedScenes
        .compactMap { ($0 as? UIWindowScene)?.keyWindow }.first
      guard let window else { return "No window to add to" }
      let button = UIButton(configuration: .borderedProminent())
      button.configuration?.title = "Taps: 0"
      button.accessibilityIdentifier = "injected-counter"
      button.addAction(UIAction { action in
        guard let button = action.sender as? UIButton else { return }
        button.tag += 1
        button.configuration?.title = "Taps: \(button.tag)"
      }, for: .primaryActionTriggered)
      button.translatesAutoresizingMaskIntoConstraints = false
      window.addSubview(button)
      NSLayoutConstraint.activate([
        button.centerXAnchor.constraint(equalTo: window.centerXAnchor),
        button.bottomAnchor.constraint(
          equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: -48),
      ])
      return "Added a button to \(Bundle.main.bundleIdentifier ?? "the app")"
    }'
    Result:
    Added a button to com.facebook.idb.replhost
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    
  2. 2 of 7

    Find the new button by its accessibility identifier

    The button is a Button labelled 'Taps: 0', 79×34 points at (161, 758). idb reads it like any control the app shipped with.

          "identifier": "injected-counter",
          "label": "Taps: 0",
        "value": "injected-counter",
    
    Exited 0 after 0.18s; show the command and all 54 lines it printed
    $ idb ui describe injected-counter --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 34.33333333333337,
            "width": 79.33333333333334,
            "x": 161.33333333333334,
            "y": 757.6666666666666
          },
          "help": null,
          "identifier": "injected-counter",
          "label": "Taps: 0",
          "pid": 6880,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "Button"
          ],
          "type": "Button",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "injected-counter",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  3. 3 of 7

    Tap the new button (1 of 3)

    Exited 0 after 0.24s, printing nothing; show the command
    $ idb ui tap injected-counter --match-key AXUniqueId
  4. 4 of 7

    Tap the new button (2 of 3)

    Exited 0 after 0.37s, printing nothing; show the command
    $ idb ui tap injected-counter --match-key AXUniqueId
  5. 5 of 7

    Tap the new button (3 of 3)

    Exited 0 after 0.31s, printing nothing; show the command
    $ idb ui tap injected-counter --match-key AXUniqueId
  6. 6 of 7

    Read the button's label after 3 taps

    The label reads 'Taps: 3': each tap ran the action the injected Swift attached.

          "label": "Taps: 3",
    
    Exited 0 after 0.19s; show the command and all 54 lines it printed
    $ idb ui describe injected-counter --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 34.33333333333337,
            "width": 79.33333333333334,
            "x": 161.33333333333334,
            "y": 757.6666666666666
          },
          "help": null,
          "identifier": "injected-counter",
          "label": "Taps: 3",
          "pid": 6880,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "Button"
          ],
          "type": "Button",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "injected-counter",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  7. 7 of 7

    Ask the running app how many taps it counted

    idb-repl attached to the same process rather than relaunching it, so the button and its count of 3 were still there.

    The button counted 3 taps
    
    Exited 0 after 0.75s; show the command and all 5 lines it printed
    $ idb-repl app 'import UIKit
    return await MainActor.run { () -> String in
      func counter(in view: UIView) -> UIButton? {
        if view.accessibilityIdentifier == "injected-counter" { return view as? UIButton }
        return view.subviews.lazy.compactMap { counter(in: $0) }.first
      }
      let windows = UIApplication.shared.connectedScenes
        .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
      guard let button = windows.lazy.compactMap({ counter(in: $0) }).first
      else { return "The button is gone" }
      return "The button counted \(button.tag) taps"
    }'
    Result:
    The button counted 3 taps
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    

Scroll a list down from its General row and back up from a row that scrolled into view, then tap General to open its page, naming every element by its accessibility identifier. idb performs the platform's page-scroll action on the list that contains the element and activates the row directly, so there are no screen coordinates, swipe velocities or inertial deceleration to account for. Reading the accessibility tree before and after each scroll shows how far every row moved.

  1. 1 of 5

    Scroll down from the General row

    All 40 rows moved up 724 points.

    com.facebook.idb.replhost.row.row-11 is now the top row on screen. The scroll back up starts from com.facebook.idb.replhost.row.row-19, near the middle of the screen.

    Exited 0 after 0.63s, printing nothing; show the command
    $ idb ui scroll down com.facebook.idb.replhost.row.general --match-key AXUniqueId
  2. 2 of 5

    Scroll back up from com.facebook.idb.replhost.row.row-19

    All 40 rows moved down 724 points.

    Exited 0 after 0.57s, printing nothing; show the command
    $ idb ui scroll up com.facebook.idb.replhost.row.row-19 --match-key AXUniqueId
  3. 3 of 5

    Find the General row by accessibility identifier

    The row is a Cell labelled 'General', with accessibility identifier com.facebook.idb.replhost.row.general. It is 362×53 points at (20, 180) on a 402×874 screen.

          "identifier": "com.facebook.idb.replhost.row.general",
          "label": "General",
        "value": "com.facebook.idb.replhost.row.general",
    
    Exited 0 after 0.48s; show the command and all 56 lines it printed
    $ idb ui describe com.facebook.idb.replhost.row.general --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 53,
            "width": 362,
            "x": 20,
            "y": 180
          },
          "help": null,
          "identifier": "com.facebook.idb.replhost.row.general",
          "label": "General",
          "pid": 4179,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "TextOperationsAvailable",
            "Button",
            "Scrollable"
          ],
          "type": "Cell",
          "value": ""
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "com.facebook.idb.replhost.row.general",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  4. 4 of 5

    Tap the General row by accessibility identifier

    idb resolved com.facebook.idb.replhost.row.general to the row's centre, (201, 206), and pressed it.

    Exited 0 after 0.72s, printing nothing; show the command
    $ idb ui tap com.facebook.idb.replhost.row.general --match-key AXUniqueId
  5. 5 of 5

    Read the navigation bar of the page that opened

    The navigation bar reads 'General'. It is 402×54 points at (0, 62) on a 402×874 screen.

          "identifier": "General",
          "type": "NavigationBar",
        "value": "General",
    
    Exited 0 after 0.26s; show the command and all 54 lines it printed
    $ idb ui describe General --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 54,
            "width": 402,
            "x": 0,
            "y": 62
          },
          "help": null,
          "identifier": "General",
          "label": null,
          "pid": 4179,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "Scrollable"
          ],
          "type": "NavigationBar",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "General",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    

Check one screen in dark mode, at a large text size, and with more contrast

Swift injected into an app shows a screen that reports the appearance, text size and contrast it is drawn with. idb switches the simulator to dark mode, turns the text size up and increases contrast, one at a time, and reads the screen after each change while the app keeps running.

  1. 1 of 8

    Show a screen that reports its own settings, with injected Swift

    The screen reads the appearance, text size and contrast from its trait collection, and rereads them whenever one changes.

    Showing light, large, standard contrast
    
    Exited 0 after 1.76s; show the command and all 5 lines it printed
    $ idb-repl app --new-session 'import UIKit
    return await MainActor.run { () -> String in
      let window = UIApplication.shared.connectedScenes
        .compactMap { ($0 as? UIWindowScene)?.keyWindow }.first
      guard let window else { return "No window to show it in" }
      func settings(of traits: UITraitCollection) -> String {
        let appearance = traits.userInterfaceStyle == .dark ? "dark" : "light"
        let size = [
          UIContentSizeCategory.large: "large",
          .accessibilityExtraLarge: "accessibility-extra-large",
        ][traits.preferredContentSizeCategory]
          ?? traits.preferredContentSizeCategory.rawValue
        let contrast = traits.accessibilityContrast == .high ? "high" : "standard"
        return "\(appearance), \(size), \(contrast) contrast"
      }
      let label = UILabel()
      label.accessibilityIdentifier = "screen-settings"
      label.font = .preferredFont(forTextStyle: .title1)
      label.adjustsFontForContentSizeCategory = true
      label.numberOfLines = 0
      label.textAlignment = .center
      let swatches = UIStackView(arrangedSubviews: [
        UIColor.systemRed, .systemOrange, .systemGreen, .systemBlue,
      ].map { colour in
        let swatch = UIView()
        swatch.backgroundColor = colour
        swatch.layer.cornerRadius = 24
        swatch.widthAnchor.constraint(equalToConstant: 48).isActive = true
        swatch.heightAnchor.constraint(equalToConstant: 48).isActive = true
        return swatch
      })
      swatches.spacing = 12
      let column = UIStackView(arrangedSubviews: [swatches, label])
      column.axis = .vertical
      column.alignment = .center
      column.spacing = 24
      column.translatesAutoresizingMaskIntoConstraints = false
      let screen = UIView(frame: window.bounds)
      screen.backgroundColor = .systemBackground
      screen.autoresizingMask = [.flexibleWidth, .flexibleHeight]
      screen.addSubview(column)
      NSLayoutConstraint.activate([
        column.centerXAnchor.constraint(equalTo: screen.centerXAnchor),
        column.centerYAnchor.constraint(equalTo: screen.centerYAnchor),
        label.widthAnchor.constraint(lessThanOrEqualTo: screen.widthAnchor, constant: -48),
      ])
      window.addSubview(screen)
      label.text = settings(of: screen.traitCollection)
      let traits: [UITrait] = [
        UITraitUserInterfaceStyle.self,
        UITraitPreferredContentSizeCategory.self,
        UITraitAccessibilityContrast.self,
      ]
      screen.registerForTraitChanges(traits) { (screen: UIView, _: UITraitCollection) in
        label.text = settings(of: screen.traitCollection)
      }
      return "Showing \(label.text ?? "nothing")"
    }'
    Result:
    Showing light, large, standard contrast
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    
  2. 2 of 8

    Switch to dark mode

    Exited 0 after 0.29s, printing nothing; show the command
    $ idb set appearance dark
  3. 3 of 8

    Read the screen in dark mode

    The running app redrew itself for dark mode: 'dark, large, standard contrast'.

          "label": "dark, large, standard contrast",
    
    Exited 0 after 0.25s; show the command and all 54 lines it printed
    $ idb ui describe screen-settings --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 33.666666666666686,
            "width": 346.3333333333333,
            "x": 28,
            "y": 456.3333333333333
          },
          "help": null,
          "identifier": "screen-settings",
          "label": "dark, large, standard contrast",
          "pid": 12123,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "StaticText"
          ],
          "type": "StaticText",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "screen-settings",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  4. 4 of 8

    Turn the text size up

    Exited 0 after 0.27s, printing nothing; show the command
    $ idb set content-size accessibility-extra-large
  5. 5 of 8

    Read the screen at that size

    The label is now 285 points tall, up from 34: its font followed the text size.

          "label": "dark, accessibility-extra-large, standard contrast",
    
    Exited 0 after 0.18s; show the command and all 54 lines it printed
    $ idb ui describe screen-settings --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 285.33333333333337,
            "width": 271,
            "x": 65.66666666666669,
            "y": 330.33333333333337
          },
          "help": null,
          "identifier": "screen-settings",
          "label": "dark, accessibility-extra-large, standard contrast",
          "pid": 12123,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "StaticText"
          ],
          "type": "StaticText",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "screen-settings",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  6. 6 of 8

    Increase contrast

    Exited 0 after 0.46s, printing nothing; show the command
    $ idb set increase-contrast enable
  7. 7 of 8

    Read the screen with more contrast

    All three settings changed without relaunching the app.

          "label": "dark, accessibility-extra-large, high contrast",
    
    Exited 0 after 0.20s; show the command and all 54 lines it printed
    $ idb ui describe screen-settings --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 228.33333333333337,
            "width": 340.66666666666674,
            "x": 30.666666666666657,
            "y": 359
          },
          "help": null,
          "identifier": "screen-settings",
          "label": "dark, accessibility-extra-large, high contrast",
          "pid": 12123,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "StaticText"
          ],
          "type": "StaticText",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "screen-settings",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  8. 8 of 8

    Read a setting back from the simulator

    idb reads settings as well as setting them, which is how the test puts back what it found.

    accessibility-extra-large
    
    Exited 0 after 0.32s; show the command
    $ idb get content-size

Deliver a push notification and open the app from its banner

Grant an app notification permission directly, so there is no system prompt to automate, and deliver a push notification to it while it isn't running. Its banner drops in over the home screen, and tapping the banner opens the app. Clearing the app's notifications leaves the simulator clean for whatever runs next.

  1. 1 of 7

    Grant notification permission without showing a prompt

    com.apple.news can now receive notifications. idb granted the permission directly, so no prompt appeared.

    Exited 0 after 0.29s, printing nothing; show the command
    $ idb approve com.apple.news notification
  2. 2 of 7

    Go to the home screen

    Exited 0 after 0.18s, printing nothing; show the command
    $ idb ui button HOME
  3. 3 of 7

    Check what is where the banner will appear

    (201, 90) is on the home screen, not News's icon, so nothing there opens News until a banner covers it.

    Exited 0 after 0.42s; show the command and all 25 lines it printed
    $ idb ui describe-point 201 90 --json
    {
      "help": null,
      "AXValue": null,
      "type": "Group",
      "role": "AXGroup",
      "frame": {
        "y": 0,
        "width": 401.99999999999994,
        "height": 874,
        "x": 0
      },
      "AXLabel": null,
      "content_required": false,
      "title": null,
      "AXUniqueId": null,
      "role_description": "Group",
      "enabled": true,
      "traits": [
        "Scrollable"
      ],
      "custom_actions": [],
      "AXFrame": "{{0, 0}, {401.99999999999994, 874}}",
      "subrole": null,
      "pid": 1306
    }
    
  4. 4 of 7

    Deliver a notification while the app is not running

    Exited 0 after 0.34s, printing nothing; show the command
    $ idb send-notification com.apple.news '{"aps": {"alert": {"title": "Breaking", "body": "idb delivered this without the app running."}}}'
  5. 5 of 7

    Tap the banner

    com.apple.news is running: the tap landed on the notification's banner, which opened the app.

    Exited 0 after 0.38s, printing nothing; show the command
    $ idb ui tap 201 90
  6. 6 of 7

    Clear the app's delivered notifications

    Exited 0 after 0.51s, printing nothing; show the command
    $ idb notification clear com.apple.news
  7. 7 of 7

    List notifications after clearing them

    The app has no delivered notifications.

    Exited 0 after 0.61s, printing nothing; show the command
    $ idb notification list com.apple.news

Read and navigate web content in Safari, including off-screen elements

Read a web page's own elements, not just Safari's toolbar, and find a heading several screens below the visible area without scrolling to it. Then type a new address into Safari's address bar, submit it with a key press, and find a label drawn inside one of the new page's diagrams.

  1. 1 of 7

    Open idb's documentation in Safari

    Exited 0 after 0.54s, printing nothing; show the command
    $ idb open https://fbidb.io/docs/idb/fbsimulatorcontrol
  2. 2 of 7

    Find a single heading in the page's accessibility tree

    idb searched 1008 elements of the page and found the heading at y=5291, about 6 screens down the page. Nothing was scrolled.

              "label": "Functionality beyond Apple's tools"
              "label": "Direct link to Functionality beyond Apple's tools"
          "label": "Functionality beyond Apple's tools Direct link to Functionality beyond Apple's tools"
        "walked": 1008
    
    Exited 0 after 3.20s; show the command and all 71 lines it printed
    $ idb ui describe-all --api axbridge --format complete --match 'Functionality beyond Apple' --key AXLabel --key AXFrame --json
    {
      "automation": {
        "asserted": false,
        "enabled": true
      },
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [
            {
              "children": [],
              "frame": {
                "height": 60,
                "width": 325,
                "x": 16,
                "y": 5291
              },
              "label": "Functionality beyond Apple's tools"
            },
            {
              "children": [],
              "frame": {
                "height": 30,
                "width": 24,
                "x": 71,
                "y": 5321
              },
              "label": "Direct link to Functionality beyond Apple's tools"
            }
          ],
          "frame": {
            "height": 61,
            "width": 370,
            "x": 16,
            "y": 5291
          },
          "label": "Functionality beyond Apple's tools Direct link to Functionality beyond Apple's tools"
        }
      ],
      "frames": {
        "framed": 3,
        "total": 3,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": {
        "filter": "all",
        "ignore_case": false,
        "match": "Functionality beyond Apple",
        "match_key": "AXLabel",
        "matched": 3,
        "walked": 1008
      },
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "frontmost",
        "match_key": null,
        "pid": null,
        "value": null,
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  3. 3 of 7

    Tap the address bar by accessibility identifier

    Safari's address bar has accessibility identifier TabBarItemTitle, so idb taps it like any native control.

    Exited 0 after 3.97s, printing nothing; show the command
    $ idb ui tap TabBarItemTitle --match-key AXUniqueId
  4. 4 of 7

    Type the address of a second page

    Exited 0 after 1.02s, printing nothing; show the command
    $ idb ui text https://fbidb.io/docs/idb/accessibility
  5. 5 of 7

    Read the address bar's value back

    The address bar reads 'https://fbidb.io/docs/idb/accessibility', exactly what was typed.

          "value": "https://fbidb.io/docs/idb/accessibility"
    
    Exited 0 after 0.49s; show the command and all 57 lines it printed
    $ idb ui describe URL --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 48,
            "width": 222,
            "x": 46,
            "y": 792
          },
          "help": null,
          "identifier": "URL",
          "label": "Address",
          "pid": 5227,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "IsEditing",
            "TextEntry",
            "Scrollable",
            "TextOperationsAvailable"
          ],
          "type": "TextField",
          "value": "https://fbidb.io/docs/idb/accessibility"
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "URL",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  6. 6 of 7

    Submit the address with a key press

    Exited 0 after 0.52s, printing nothing; show the command
    $ idb ui key 40
  7. 7 of 7

    Find SimulatorFrameworkBridge on the page that loaded

    The page mentions SimulatorFrameworkBridge in 7 places. The smallest is 100×8 points at (164, 4605) on a 402×874 screen: text inside a diagram, readable like any other element.

          "label": "SimulatorFrameworkBridge"
          "label": "SimulatorFrameworkBridge"
          "label": "SimulatorFrameworkBridge"
          "label": "SimulatorFrameworkBridge (guest)"
          "label": "SimulatorFrameworkBridge (guest)"
          "label": "SimulatorFrameworkBridge"
          "label": "The SimulatorFrameworkBridge guest binary was not found in the companion Resources directory"
        "match": "SimulatorFrameworkBridge",
    
    Exited 0 after 4.92s; show the command and all 110 lines it printed
    $ idb ui describe-all --api axbridge --format complete --match SimulatorFrameworkBridge --key AXLabel --key AXFrame --json
    {
      "automation": {
        "asserted": false,
        "enabled": true
      },
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "frame": {
            "height": 13,
            "width": 119,
            "x": 223,
            "y": 1615
          },
          "label": "SimulatorFrameworkBridge"
        },
        {
          "children": [],
          "frame": {
            "height": 19,
            "width": 209,
            "x": 155,
            "y": 2649
          },
          "label": "SimulatorFrameworkBridge"
        },
        {
          "children": [],
          "frame": {
            "height": 19,
            "width": 209,
            "x": 31,
            "y": 3652
          },
          "label": "SimulatorFrameworkBridge"
        },
        {
          "children": [],
          "frame": {
            "height": 8,
            "width": 100,
            "x": 164,
            "y": 4605
          },
          "label": "SimulatorFrameworkBridge (guest)"
        },
        {
          "children": [],
          "frame": {
            "height": 9,
            "width": 100,
            "x": 164,
            "y": 4434
          },
          "label": "SimulatorFrameworkBridge (guest)"
        },
        {
          "children": [],
          "frame": {
            "height": 19,
            "width": 210,
            "x": 94,
            "y": 5685
          },
          "label": "SimulatorFrameworkBridge"
        },
        {
          "children": [],
          "frame": {
            "height": 19,
            "width": 798,
            "x": 32,
            "y": 5899
          },
          "label": "The SimulatorFrameworkBridge guest binary was not found in the companion Resources directory"
        }
      ],
      "frames": {
        "framed": 7,
        "total": 7,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": {
        "filter": "all",
        "ignore_case": false,
        "match": "SimulatorFrameworkBridge",
        "match_key": "AXLabel",
        "matched": 7,
        "walked": 1274
      },
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "frontmost",
        "match_key": null,
        "pid": null,
        "value": null,
        "x": null,
        "y": null
      },
      "truncated": false
    }
    

Seed photos and a location, and watch an app pick them up

Give a simulator a known photo library and location: clear its photos, add three, let an app read them, and put the simulator in Menlo Park. Swift injected into the app shows the photos and asks for the location, idb answers the system prompt, and when idb moves the simulator to London the app follows it there.

  1. 1 of 9

    Start from an empty photo library

    The simulator's sample photos are gone.

    Exited 0 after 0.66s, printing nothing; show the command
    $ idb photos clear
  2. 2 of 9

    Add three photos

    Three one-colour images, written by the test, are now the whole photo library.

    Exited 0 after 0.28s, printing nothing; show the command
    $ idb add-media '$IDB_E2E_DIR/seeded-photos/sunrise.png' '$IDB_E2E_DIR/seeded-photos/sea.png' '$IDB_E2E_DIR/seeded-photos/leaf.png'
  3. 3 of 9

    Let the app read photos

    Granted ahead of time, so the app never asks for the photos.

    Exited 0 after 0.38s, printing nothing; show the command
    $ idb approve com.facebook.idb.replhost photos
  4. 4 of 9

    Put the simulator in Menlo Park

    Exited 0 after 0.22s, printing nothing; show the command
    $ idb set-location 37.4848 -122.1484
  5. 5 of 9

    Show the photos and follow the location, with injected Swift

    The injected Swift read 3 photos from the library, put them on screen, and left a task running in the app that follows the location.

    Showing 3 photos, and following the location
    
    Exited 0 after 3.29s; show the command and all 5 lines it printed
    $ idb-repl app --new-session 'import CoreLocation
    import Photos
    import UIKit
    let options = PHImageRequestOptions()
    options.isSynchronous = true
    var found: [UIImage] = []
    PHAsset.fetchAssets(with: .image, options: nil).enumerateObjects { asset, _, _ in
      PHImageManager.default().requestImage(
        for: asset, targetSize: CGSize(width: 192, height: 192),
        contentMode: .aspectFill, options: options
      ) { image, _ in image.map { found.append($0) } }
    }
    let photos = found
    return await MainActor.run { () -> String in
      let window = UIApplication.shared.connectedScenes
        .compactMap { ($0 as? UIWindowScene)?.keyWindow }.first
      guard let window else { return "No window to show them in" }
      let row = UIStackView(arrangedSubviews: photos.map { photo in
        let view = UIImageView(image: photo)
        view.layer.cornerRadius = 16
        view.clipsToBounds = true
        view.widthAnchor.constraint(equalToConstant: 96).isActive = true
        view.heightAnchor.constraint(equalToConstant: 96).isActive = true
        return view
      })
      row.spacing = 12
      let label = UILabel()
      label.accessibilityIdentifier = "seeded-library"
      label.text = "\(photos.count) photos, waiting for a location"
      let column = UIStackView(arrangedSubviews: [row, label])
      column.axis = .vertical
      column.alignment = .center
      column.spacing = 24
      column.backgroundColor = .systemBackground
      column.frame = window.bounds
      column.autoresizingMask = [.flexibleWidth, .flexibleHeight]
      column.isLayoutMarginsRelativeArrangement = true
      window.addSubview(column)
      Task {
        for try await update in CLLocationUpdate.liveUpdates() {
          guard let place = update.location?.coordinate else { continue }
          label.text = String(
            format: "%d photos at %.4f, %.4f",
            photos.count, place.latitude, place.longitude)
        }
      }
      return "Showing \(photos.count) photos, and following the location"
    }'
    Result:
    Showing 3 photos, and following the location
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    
  6. 6 of 9

    Answer the location prompt

    The app asked to use the location, and idb pressed the button on the system's prompt by its label.

    Exited 0 after 0.41s, printing nothing; show the command
    $ idb ui tap 'Allow While Using App'
  7. 7 of 9

    Read what the app shows

    The app reads '3 photos at 37.4848, -122.1484': the photos idb added and the location idb set.

          "label": "3 photos at 37.4848, -122.1484",
    
    Exited 0 after 0.19s; show the command and all 54 lines it printed
    $ idb ui describe seeded-library --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 658,
            "width": 240.33333333333337,
            "x": 81.00000000000001,
            "y": 182
          },
          "help": null,
          "identifier": "seeded-library",
          "label": "3 photos at 37.4848, -122.1484",
          "pid": 11876,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "StaticText"
          ],
          "type": "StaticText",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "seeded-library",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  8. 8 of 9

    Move the simulator to London

    Exited 0 after 0.17s, printing nothing; show the command
    $ idb set-location 51.5072 -0.1276
  9. 9 of 9

    Read what the app shows now

    The app now reads '3 photos at 51.5072, -0.1276'. Nothing was relaunched: the task the Swift left running saw the location change.

          "label": "3 photos at 51.5072, -0.1276",
    
    Exited 0 after 0.18s; show the command and all 54 lines it printed
    $ idb ui describe seeded-library --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 658,
            "width": 218,
            "x": 92,
            "y": 182
          },
          "help": null,
          "identifier": "seeded-library",
          "label": "3 photos at 51.5072, -0.1276",
          "pid": 11876,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "StaticText"
          ],
          "type": "StaticText",
          "value": null
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "seeded-library",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    

Spin Safari's address bar, and tap it anyway

Inject Swift into Safari, one of Apple's own apps, and set its address bar spinning with Core Animation. The spin only changes what is drawn, so the accessibility tree still reports the bar exactly where it was, and idb taps it mid-spin. Then stop it.

  1. 1 of 7

    Launch Safari with Swift injected, and ask who it is

    The Swift ran inside Safari, which answers com.apple.mobilesafari. Nothing about Safari was rebuilt or re-signed.

    com.apple.mobilesafari
    
    Exited 0 after 4.35s; show the command and all 5 lines it printed
    $ idb-repl app --bundle-id com.apple.mobilesafari --new-session 'return Bundle.main.bundleIdentifier ?? "no bundle"'
    Result:
    com.apple.mobilesafari
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    
  2. 2 of 7

    Open a page in the same Safari

    Exited 0 after 0.48s, printing nothing; show the command
    $ idb open http://127.0.0.1:PORT/docs/idb/fbsimulatorcontrol
  3. 3 of 7

    Find the address bar

    The address bar has accessibility identifier TabBarItemTitle and is 66×20 points at (168, 806).

          "identifier": "TabBarItemTitle",
          "type": "SFUnifiedTabBarItemTitleContainerView",
        "value": "TabBarItemTitle",
    
    Exited 0 after 0.46s; show the command and all 55 lines it printed
    $ idb ui describe TabBarItemTitle --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 20.33333333333337,
            "width": 66,
            "x": 168,
            "y": 806
          },
          "help": null,
          "identifier": "TabBarItemTitle",
          "label": "Address",
          "pid": 10159,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "TextEntry",
            "Scrollable"
          ],
          "type": "SFUnifiedTabBarItemTitleContainerView",
          "value": "‎127.0.0.1"
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "TabBarItemTitle",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  4. 4 of 7

    Set the address bar spinning

    The Swift climbed from the address text to the bar around it and added a rotation that repeats forever.

    Spinning a SFCapsuleView, 222×48 points
    
    Exited 0 after 1.29s; show the command and all 5 lines it printed
    $ idb-repl app --bundle-id com.apple.mobilesafari 'import UIKit
    return await MainActor.run { () -> String in
      func addressText(in view: UIView) -> UIView? {
        if view.accessibilityIdentifier == "TabBarItemTitle" { return view }
        return view.subviews.lazy.compactMap { addressText(in: $0) }.first
      }
      let windows = UIApplication.shared.connectedScenes
        .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
      guard let text = windows.lazy.compactMap({ addressText(in: $0) }).first,
        let window = text.window
      else { return "No address bar to spin" }
      // The identified view is only the address text; the bar is the widest
      // view around it that is still inset from the window'\''s edges.
      var bar = text
      while let parent = bar.superview, parent.bounds.width < window.bounds.width {
        bar = parent
      }
      let spin = CABasicAnimation(keyPath: "transform.rotation.z")
      spin.byValue = 2 * Double.pi
      spin.duration = 4
      spin.repeatCount = .infinity
      bar.layer.add(spin, forKey: "idb-spin")
      Thread.main.threadDictionary["idb-spin"] = bar
      let size = bar.bounds.size
      return "Spinning a \(type(of: bar)), \(Int(size.width))×\(Int(size.height)) points"
    }'
    Result:
    Spinning a SFCapsuleView, 222×48 points
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
    
  5. 5 of 7

    Find the address bar while it spins

    It is still 66×20 points at (168, 806). Core Animation spins what is drawn, not where UIKit lays the view out, and the accessibility tree reports the layout.

          "identifier": "TabBarItemTitle",
          "type": "SFUnifiedTabBarItemTitleContainerView",
        "value": "TabBarItemTitle",
    
    Exited 0 after 0.51s; show the command and all 55 lines it printed
    $ idb ui describe TabBarItemTitle --match-key AXUniqueId --api axbridge --format complete --json
    {
      "automation": null,
      "backend": "axbridge-exclusive",
      "coverage": null,
      "elements": [
        {
          "children": [],
          "content_required": false,
          "custom_actions": [],
          "enabled": null,
          "frame": {
            "height": 20.33333333333337,
            "width": 66,
            "x": 168,
            "y": 806
          },
          "help": null,
          "identifier": "TabBarItemTitle",
          "label": "Address",
          "pid": 10159,
          "role_description": null,
          "subrole": null,
          "title": null,
          "traits": [
            "TextEntry",
            "Scrollable"
          ],
          "type": "SFUnifiedTabBarItemTitleContainerView",
          "value": "‎127.0.0.1"
        }
      ],
      "frames": {
        "framed": 1,
        "total": 1,
        "zero_frame": 0
      },
      "interaction": null,
      "modal": null,
      "narrowing": null,
      "profile": null,
      "screen": {
        "coordinate_space": "screen",
        "height": 874,
        "width": 402
      },
      "target": {
        "kind": "marker",
        "match_key": "AXUniqueId",
        "pid": null,
        "value": "TabBarItemTitle",
        "x": null,
        "y": null
      },
      "truncated": false
    }
    
  6. 6 of 7

    Tap the spinning address bar

    The tap landed where the bar is laid out, and Safari gave the address field the cursor.

    Exited 0 after 1.19s, printing nothing; show the command
    $ idb ui tap TabBarItemTitle --match-key AXUniqueId
  7. 7 of 7

    Stop the spin

    idb-repl attached to the same live Safari, so this Swift found the view the first injection left in the main thread's dictionary and removed its animation.

    Stopped the SFCapsuleView
    
    Exited 0 after 0.55s; show the command and all 5 lines it printed
    $ idb-repl app --bundle-id com.apple.mobilesafari 'import UIKit
    return await MainActor.run { () -> String in
      guard let bar = Thread.main.threadDictionary["idb-spin"] as? UIView
      else { return "Nothing is spinning" }
      Thread.main.threadDictionary.removeObject(forKey: "idb-spin")
      bar.layer.removeAnimation(forKey: "idb-spin")
      return "Stopped the \(type(of: bar))"
    }'
    Result:
    Stopped the SFCapsuleView
    

    Errors

    idb-repl: received generated interface(s):
      IDBAPI
    idb-repl: compiling injected code for arm64-apple-ios26.5-simulator