Summary
With XcodeBuildMCP 2.6.0 runtime UI automation, SwiftUI TabView tab bar items can collapse into a single non-actionable Tab Bar element in snapshot-ui. The individual tabs are not exposed as targetable elementRefs, so agents still have to rely on an app-side UIKit accessibility/automation bridge or coordinates to switch tabs.
This may be an AXe capture limitation rather than a bug in the JS runtime-snapshot normalizer, but it shows up as an XcodeBuildMCP UI automation gap because wait-for-ui and tap --element-ref can only act on elements present in the runtime snapshot.
Environment
- XcodeBuildMCP: 2.6.0 (Homebrew)
- Xcode: 26.5 / build 17F42
- Simulator profile: clean disposable iOS simulator
- App under test: generic SwiftUI iOS app using
TabView(selection:) with .tabItem { Label(...) }, .tag(...), and .accessibilityIdentifier("tab_reports") on the tab content view
xcodebuildmcp doctor doctor --output json --style minimal reported:
serverVersion: 2.6.0
axe: Available: Yes; UI automation: Yes; Video capture: Yes
manifest-tools: Total tools: 82; Workflows: 15
Reproduction
- Build and launch a SwiftUI app with a standard tab view:
TabView(selection: $selectedTab) {
NavigationStack { HomeView() }
.accessibilityIdentifier("screen_home")
.tabItem { Label("Home", systemImage: "house") }
.tag(Tab.home)
.accessibilityIdentifier("tab_home")
NavigationStack { ReportsView() }
.accessibilityIdentifier("screen_reports")
.tabItem { Label("Reports", systemImage: "chart.bar") }
.tag(Tab.reports)
.accessibilityIdentifier("tab_reports")
}
- Capture a verbose runtime snapshot:
xcodebuildmcp ui-automation snapshot-ui \
--simulator-id <UDID> \
--output json \
--style minimal \
--verbose
- Try waiting for the tab identifier or a tab role/label:
xcodebuildmcp ui-automation wait-for-ui \
--simulator-id <UDID> \
--predicate exists \
--identifier tab_reports \
--timeout-ms 7000 \
--output json \
--style minimal \
--verbose
xcodebuildmcp ui-automation wait-for-ui \
--simulator-id <UDID> \
--predicate exists \
--label Reports \
--role tab \
--timeout-ms 7000 \
--output json \
--style minimal \
--verbose
Actual result
Both waits time out with no candidates. The runtime snapshot contains a single Tab Bar element but no child tab controls and no actionable Reports target:
{
"elementCount": 7,
"targetCount": 0,
"screenHash": "1aztj8k"
}
Relevant elements from data.capture.elements:
[
{
"ref": "e1",
"role": "application",
"label": "SampleTabApp",
"frame": { "x": 0, "y": 0, "width": 440, "height": 956 },
"actions": []
},
{
"ref": "e3",
"role": "other",
"label": "Home",
"actions": ["longPress", "touch"]
},
{
"ref": "e7",
"role": "other",
"label": "Tab Bar",
"frame": { "x": 0, "y": 873, "width": 440, "height": 83 },
"actions": ["longPress", "touch"]
}
]
The action hints confirm there is no tappable tab target:
{"action":"longPress","elementRef":"e7","label":"Tab Bar"}
{"action":"touch","elementRef":"e7","label":"Tab Bar"}
wait-for-ui --identifier tab_reports and wait-for-ui --label Reports --role tab both return:
{
"code": "WAIT_TIMEOUT",
"message": "Timed out after 7000ms waiting for UI predicate 'exists'.",
"candidates": []
}
Expected result
For a standard SwiftUI TabView, the runtime snapshot should expose each visible tab item as a targetable element, ideally with:
- role
tab or an equivalent actionable role;
- label such as
Reports;
- any available accessibility identifier from the tab item/content association, when iOS exposes it;
- a
tap action and stable elementRef.
That would let agents switch tabs using the v2.6 runtime workflow instead of coordinates or app-side UIKit overlays.
Workaround
An app-side UIKit overlay of transparent UIButtons at the tab bar frame works. In the same app/session, adding buttons with accessibilityIdentifier values like tab_reports produced runtime refs such as:
{
"ref": "e11",
"role": "button",
"label": "Reports",
"identifier": "tab_reports",
"actions": ["tap", "longPress", "touch"]
}
Then tap --element-ref e11 successfully navigated to the Reports screen and the refreshed snapshot reported tab_reports as selected.
That workaround is undesirable for SwiftUI-first apps, so it would be useful if XcodeBuildMCP/AXe could expose standard SwiftUI tab items directly.
Summary
With XcodeBuildMCP 2.6.0 runtime UI automation, SwiftUI
TabViewtab bar items can collapse into a single non-actionableTab Barelement insnapshot-ui. The individual tabs are not exposed as targetableelementRefs, so agents still have to rely on an app-side UIKit accessibility/automation bridge or coordinates to switch tabs.This may be an AXe capture limitation rather than a bug in the JS runtime-snapshot normalizer, but it shows up as an XcodeBuildMCP UI automation gap because
wait-for-uiandtap --element-refcan only act on elements present in the runtime snapshot.Environment
TabView(selection:)with.tabItem { Label(...) },.tag(...), and.accessibilityIdentifier("tab_reports")on the tab content viewxcodebuildmcp doctor doctor --output json --style minimalreported:serverVersion:2.6.0axe:Available: Yes; UI automation: Yes; Video capture: Yesmanifest-tools:Total tools: 82; Workflows: 15Reproduction
Actual result
Both waits time out with no candidates. The runtime snapshot contains a single
Tab Barelement but no child tab controls and no actionableReportstarget:{ "elementCount": 7, "targetCount": 0, "screenHash": "1aztj8k" }Relevant elements from
data.capture.elements:[ { "ref": "e1", "role": "application", "label": "SampleTabApp", "frame": { "x": 0, "y": 0, "width": 440, "height": 956 }, "actions": [] }, { "ref": "e3", "role": "other", "label": "Home", "actions": ["longPress", "touch"] }, { "ref": "e7", "role": "other", "label": "Tab Bar", "frame": { "x": 0, "y": 873, "width": 440, "height": 83 }, "actions": ["longPress", "touch"] } ]The action hints confirm there is no tappable tab target:
{"action":"longPress","elementRef":"e7","label":"Tab Bar"} {"action":"touch","elementRef":"e7","label":"Tab Bar"}wait-for-ui --identifier tab_reportsandwait-for-ui --label Reports --role tabboth return:{ "code": "WAIT_TIMEOUT", "message": "Timed out after 7000ms waiting for UI predicate 'exists'.", "candidates": [] }Expected result
For a standard SwiftUI
TabView, the runtime snapshot should expose each visible tab item as a targetable element, ideally with:tabor an equivalent actionable role;Reports;tapaction and stableelementRef.That would let agents switch tabs using the v2.6 runtime workflow instead of coordinates or app-side UIKit overlays.
Workaround
An app-side UIKit overlay of transparent
UIButtons at the tab bar frame works. In the same app/session, adding buttons withaccessibilityIdentifiervalues liketab_reportsproduced runtime refs such as:{ "ref": "e11", "role": "button", "label": "Reports", "identifier": "tab_reports", "actions": ["tap", "longPress", "touch"] }Then
tap --element-ref e11successfully navigated to the Reports screen and the refreshed snapshot reportedtab_reportsas selected.That workaround is undesirable for SwiftUI-first apps, so it would be useful if XcodeBuildMCP/AXe could expose standard SwiftUI tab items directly.