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 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 secondExited 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 secondErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator - 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 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 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 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.replhostExited 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.replhostErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator - 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 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 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 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 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 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 tapsExited 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 tapsErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator
Scroll a list and open a row by accessibility identifier
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 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 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 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 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 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 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 contrastExited 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 contrastErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator - 2 of 8
Switch to dark mode
Exited 0 after 0.29s, printing nothing; show the command
$ idb set appearance dark - 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 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 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 of 8
Increase contrast
Exited 0 after 0.46s, printing nothing; show the command
$ idb set increase-contrast enable - 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 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-largeExited 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 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 of 7
Go to the home screen
Exited 0 after 0.18s, printing nothing; show the command
$ idb ui button HOME - 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 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 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 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 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 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 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": 1008Exited 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 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 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 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 of 7
Submit the address with a key press
Exited 0 after 0.52s, printing nothing; show the command
$ idb ui key 40 - 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 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 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 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 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 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 locationExited 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 locationErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator - 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 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 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 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 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.mobilesafariExited 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.mobilesafariErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator - 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 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 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 pointsExited 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 pointsErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator - 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 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 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 SFCapsuleViewExited 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 SFCapsuleViewErrors
idb-repl: received generated interface(s): IDBAPI idb-repl: compiling injected code for arm64-apple-ios26.5-simulator