Skip to content

Runtime snapshot does not expose SwiftUI TabView tab items as actionable refs #439

@dpearson2699

Description

@dpearson2699

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

  1. 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")
}
  1. Capture a verbose runtime snapshot:
xcodebuildmcp ui-automation snapshot-ui \
  --simulator-id <UDID> \
  --output json \
  --style minimal \
  --verbose
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions