Skip to content

feat(testing): add MeshtasticUITests accessibility-tree navigation driver - #2180

Open
bruschill wants to merge 6 commits into
meshtastic:mainfrom
bruschill:feat/meshtastic-ui-tests
Open

feat(testing): add MeshtasticUITests accessibility-tree navigation driver#2180
bruschill wants to merge 6 commits into
meshtastic:mainfrom
bruschill:feat/meshtastic-ui-tests

Conversation

@bruschill

@bruschill bruschill commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What changed?

Adds a new MeshtasticUITests target (XCUITest, wired into the Meshtastic scheme's test action) built around a small, reusable accessibility-tree-driven navigator:

  • AccessibilityDriver.swiftNavigationStep (tab, tapIdentifier, tapButtonLabeled, waitForIdentifier, pause) and a run(_:app:) that drives XCUIApplication through real element queries and taps — the same path VoiceOver and a real user's touches take. A step whose target never appears fails the test loudly (not silently skipped) — an unreachable target is itself a finding.
  • TabBarNavigationUITests.swift — a real regression test built on the driver: walks all five root tabs (Messages, Nodes, Map, Settings, Connect) and asserts each is reachable through the accessibility tree.
  • ContentView.swift — tags each root tab bar button with .accessibilityIdentifier("tab-*"). While building this I confirmed (by dumping the live accessibility hierarchy during a real run) that SwiftUI's value-based Tab(value:) API does not currently propagate that identifier down to the underlying UITabBarButton on Xcode 26.6/iOS 18 — the driver's .tab step therefore matches on the visible title as a documented, working fallback. The identifiers are left in place: harmless today, and picked up automatically if a future SwiftUI version fixes the gap.
  • project.yml — new MeshtasticUITests target (bundle.ui-testing, TEST_TARGET_NAME: Meshtastic). Regenerated Meshtastic.xcodeproj with the pinned XcodeGen 2.46.0.
  • docs/developer/testing.md — new "UI Tests (MeshtasticUITests)" section covering what the target is for, why it deliberately uses XCTestCase instead of Swift Testing (XCUITest has no Swift Testing equivalent — this is an intentional, documented exception to the "no XCTAssert*" rule above it, not drift from it), a usage example, the Tab(value:) identifier gap, and how to run it.

This is deliberately not the same mechanism as MarketingCapture (Meshtastic/Persistence/MarketingCapture.swift), which navigates in-process via Router — fast and great for a fixed, curated App Store screenshot list, but it bypasses real touch dispatch, hit-testing, and accessibility traits entirely.

Why did it change?

This is infrastructure: a reusable way to drive the app through its actual accessibility tree rather than in-process shortcuts, so a passing UI test also proves the target is genuinely reachable and tappable the way VoiceOver and a real user experience it — catching hit-testing/accessibility-trait regressions that router-driven navigation can't. TabBarNavigationUITests is the first real consumer and gives permanent regression coverage for the root tab bar. (A follow-up PR builds a PR-review screenshot-capture pipeline on top of this driver — kept separate since this piece stands on its own.)

How is this tested?

  • xcodebuild build-for-testing succeeds for the Meshtastic scheme.
  • TabBarNavigationUITests.testAllRootTabsAreReachable run against a real simulator (iPhone 17): passes, all 5 tabs reached via real accessibility-tree taps.
  • Full MeshtasticTests suite run for regression: same 3 pre-existing failures (CoreDataMigrationServiceTests x2, IntervalConfigurationDetailedTests.allCases_count) reproduce identically on a clean upstream/main checkout, confirming they're unrelated to this change.
  • xcodegen generate with the pinned 2.46.0 verified to produce zero drift against the committed project both before and after this change.
  • SwiftLint clean on all new/changed files.

Screenshots/Videos (when applicable)

N/A — this PR adds test infrastructure, not user-visible UI.

Checklist

  • My code adheres to the project's coding and style guidelines.
  • I have conducted a self-review of my code.
  • I have commented my code, particularly in complex areas.
  • I have verified whether these changes require updates to the in-app documentation under docs/user/ or docs/developer/, and updated accordingly (see copilot-instructions.md for the view → doc page mapping). If no doc update is needed, add the skip-docs-check label.
  • I have tested the change to ensure that it works as intended.

Summary by CodeRabbit

  • New Features

    • Added a dedicated UI testing bundle target and included it in the default Xcode scheme alongside existing tests.
    • Added accessibility-driven navigation support by introducing stable accessibility identifiers for the root tabs.
  • Tests

    • Added UI regression coverage to verify all root tabs are reachable.
    • Added a reusable accessibility-based UI navigation helper that fails immediately when a step can’t be found.
  • Documentation

    • Updated developer testing guidance with UI test running instructions (including xcodebuild commands) and how accessibility-based tab navigation works.

…iver

Adds a new UI test target for driving the app through real
XCUIApplication element queries — the same path VoiceOver and a real
user's taps take — rather than in-process shortcuts.

- MeshtasticUITests/AccessibilityDriver.swift: NavigationStep enum
  (tab, tapIdentifier, tapButtonLabeled, waitForIdentifier, pause) +
  a runner that fails loudly (not silently) when a step's target
  never appears, since an unreachable target is itself a finding
  (broken navigation or an accessibility regression), not something
  to skip past.
- MeshtasticUITests/TabBarNavigationUITests.swift: a real regression
  test exercising the driver — walks all five root tabs and asserts
  each is reachable through the accessibility tree. Bootstraps via
  the existing --meshtastic-marketing-seed launch mode (skips
  onboarding, disables BLE discovery/autoconnect) so tab switches
  aren't racing a connect attempt or blocked behind a first-launch
  sheet.
- ContentView.swift: tag each root tab bar button with a
  .accessibilityIdentifier("tab-*"). Confirmed via a live accessibility
  hierarchy dump during development that SwiftUI's value-based
  Tab(value:) API does not currently propagate this identifier to the
  underlying UITabBarButton (Xcode 26.6/iOS 18) — NavigationStep.tab
  therefore matches on the visible title as a documented, working
  fallback. The identifiers are left in place: harmless today, and
  picked up automatically if a future SwiftUI version fixes the gap.
- project.yml: new MeshtasticUITests target (bundle.ui-testing,
  TEST_TARGET_NAME: Meshtastic) wired into the Meshtastic scheme's
  test action. Regenerated Meshtastic.xcodeproj with the pinned
  XcodeGen 2.46.0 (verified zero drift both before and after this
  change).

Verified: build-for-testing succeeds, TabBarNavigationUITests passes
end-to-end against a real simulator run (all 5 tabs reached). Full
MeshtasticTests suite run for regression: same 3 pre-existing failures
(CoreDataMigrationServiceTests x2, IntervalConfigurationDetailedTests
.allCases_count) reproduce identically on a clean upstream/main
checkout, confirming they're unrelated to this change.
Adds a "UI Tests (MeshtasticUITests)" section to
docs/developer/testing.md: what the target is for, why it deliberately
uses XCTestCase instead of Swift Testing (XCUITest has no Swift Testing
equivalent), a usage example for AccessibilityDriver.run(), the known
SwiftUI Tab(value:) identifier-propagation gap that makes tab lookup
match on visible title today, and how to run the target.

Regenerated the bundled docs via
`bash scripts/build-docs.sh --output Meshtastic/Resources/docs` per
the repo's docs-staleness convention.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b86aa0ef-5bcc-460e-804d-68555287b37a

📥 Commits

Reviewing files that changed from the base of the PR and between 68fb50c and d63d78d.

📒 Files selected for processing (1)
  • Meshtastic/Resources/docs/index.json

📝 Walkthrough

Walkthrough

Adds the MeshtasticUITests Xcode target, integrates it into the shared scheme, provides accessibility identifiers and a navigation driver, adds root-tab regression coverage, and documents UI test usage and execution.

Changes

UI testing

Layer / File(s) Summary
UI test target wiring
project.yml, Meshtastic.xcodeproj/project.pbxproj, Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme
Registers MeshtasticUITests as an iOS UI-testing bundle dependent on Meshtastic, with Debug/Release configurations and scheme test integration.
Accessibility navigation path
Meshtastic/Views/ContentView.swift, MeshtasticUITests/AccessibilityDriver.swift
Adds stable root-tab accessibility identifiers and an AccessibilityDriver supporting tab, identifier, button, wait, and pause steps.
Root tab regression coverage
MeshtasticUITests/TabBarNavigationUITests.swift
Launches the app and verifies that all five root tabs are reachable through the accessibility tree.
UI testing documentation
docs/developer/testing.md, Meshtastic/Resources/docs/markdown/developer/testing.md, Meshtastic/Resources/docs/developer/testing.html, Meshtastic/Resources/docs/index.json
Documents UI test conventions, accessibility navigation, execution commands, and updated documentation metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TabBarNavigationUITests
  participant AccessibilityDriver
  participant Meshtastic
  TabBarNavigationUITests->>Meshtastic: launch app
  TabBarNavigationUITests->>AccessibilityDriver: run tab steps
  AccessibilityDriver->>Meshtastic: query and tap root tabs
  Meshtastic-->>AccessibilityDriver: expose accessibility elements
  AccessibilityDriver-->>TabBarNavigationUITests: report missing targets via XCTFail
Loading

Suggested reviewers: garthvh

Poem

A rabbit hops through tabs with cheer,
Messages, Nodes, Map draw near.
Settings, Connect answer the call,
Accessibility guides them all.
Tests leap softly, failures clear—
UI paths now bloom this year!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the new MeshtasticUITests accessibility-tree navigation driver and related testing infrastructure.
Description check ✅ Passed The description matches the template and covers what changed, why, how it was tested, screenshots, and checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Meshtastic/Resources/docs/markdown/developer/testing.md`:
- Around line 141-151: Reconcile the contradictory CLI guidance in the testing
documentation by narrowing the earlier “no CLI test runner” statement or
replacing it with the existing xcodebuild command reference. Keep the UI test
instructions consistent, then regenerate the bundled documentation after
updating the testing content.
- Around line 121-139: Pin the UI test locale to English by adding
-AppleLanguages and -AppleLocale launch arguments to testSomeFlowIsReachable,
and configure the Meshtastic test scheme/action with the same settings. Keep
NavigationStep.tab title matching unchanged unless migrating the tabs to stable
identifiers.

In `@MeshtasticUITests/TabBarNavigationUITests.swift`:
- Around line 28-35: Pin the UI test locale to English before launching the app
in the setup containing the --meshtastic-marketing-seed launch argument, so
NavigationStep.tab can reliably resolve the visible English titles. Keep the
existing title-based tab lookup and navigation assertions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d4cfd16-c14a-4ce2-8589-b5700b404093

📥 Commits

Reviewing files that changed from the base of the PR and between 196e8ce and e53329a.

📒 Files selected for processing (10)
  • Meshtastic.xcodeproj/project.pbxproj
  • Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme
  • Meshtastic/Resources/docs/developer/testing.html
  • Meshtastic/Resources/docs/index.json
  • Meshtastic/Resources/docs/markdown/developer/testing.md
  • Meshtastic/Views/ContentView.swift
  • MeshtasticUITests/AccessibilityDriver.swift
  • MeshtasticUITests/TabBarNavigationUITests.swift
  • docs/developer/testing.md
  • project.yml

Comment thread Meshtastic/Resources/docs/markdown/developer/testing.md
Comment thread Meshtastic/Resources/docs/markdown/developer/testing.md
Comment thread MeshtasticUITests/TabBarNavigationUITests.swift Outdated
CodeRabbit flagged two issues on PR meshtastic#2180:

- docs/developer/testing.md's "Running Tests" section claimed "there is
  no CLI test runner — tests require Xcode", directly contradicting the
  new "Running UI Tests" section a few paragraphs down, which documents
  the xcodebuild test invocation actually used to verify this PR and
  run CI. Reworded to point at that section instead of denying a CLI
  path exists.

- NavigationStep.tab matches tab bar buttons by visible English title
  (a documented SwiftUI Tab(value:) accessibilityIdentifier propagation
  gap), which made testAllRootTabsAreReachable() locale-dependent —
  it'd flake on a non-English simulator/device. Pinned the test's
  launch to English via -AppleLanguages "(en)" and -AppleLocale en_US
  alongside the existing --meshtastic-marketing-seed argument, and
  updated the AccessibilityDriver doc example to show the same pattern.

Regenerated bundled docs via scripts/build-docs.sh. Verified with
xcodebuild test against MeshtasticUITests/TabBarNavigationUITests
(iPhone 17 simulator) — passes. swiftlint lint clean on the changed
test file.
…tests

# Conflicts:
#	Meshtastic/Resources/docs/index.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant