[Windows] Make AutomationId include ContentView/layout-derived controls in accessibility tree#35484
[Windows] Make AutomationId include ContentView/layout-derived controls in accessibility tree#35484Copilot wants to merge 3 commits into
Conversation
… controls Co-authored-by: davidortinau <41873+davidortinau@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35484Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35484" |
|
/review -b feature/regression-check -p windows |
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.
Why: try-fix-2 puts the entire fix in Core's UpdateAutomationId, producing the smallest diff (~10 lines, single file), no Controls-layer churn, no new public API, no RS0016, no widening of the shared SetAutomationPropertiesAccessibilityView helper, and — uniquely among the candidates — fixes the bug for every IView consumer including Compatibility renderers, not just Element-derived controls. The ReadLocalValue==UnsetValue guard correctly preserves explicit opt-outs (e.g. Resources.xaml template parts) and the Controls-layer MapAutomationPropertiesIsInAccessibleTree mapper still runs afterwards to honour IsInAccessibleTree=false.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-2`)
diff --git a/src/Core/src/Platform/Windows/ViewExtensions.cs b/src/Core/src/Platform/Windows/ViewExtensions.cs
--- a/src/Core/src/Platform/Windows/ViewExtensions.cs
+++ b/src/Core/src/Platform/Windows/ViewExtensions.cs
@@
- public static void UpdateAutomationId(this FrameworkElement platformView, IView view) =>
- AutomationProperties.SetAutomationId(platformView, view.AutomationId);
+ public static void UpdateAutomationId(this FrameworkElement platformView, IView view)
+ {
+ var automationId = view.AutomationId;
+ AutomationProperties.SetAutomationId(platformView, automationId);
+
+ // A FrameworkElement without a built-in AutomationPeer (ContentControl,
+ // Border, Panel-derived layout hosts, etc.) defaults to AccessibilityView.Raw,
+ // which hides it from the UIA tree even when an AutomationId is provided.
+ // Promote it to Content so test automation (Appium/WinAppDriver) and assistive
+ // technology can find it.
+ //
+ // Guards:
+ // * Only promote when AutomationId is non-empty.
+ // * Never override an explicit AccessibilityView already set in XAML or code
+ // (e.g. "Raw" on the FlyoutScrollViewer template part in Resources.xaml).
+ // ReadLocalValue returns DependencyProperty.UnsetValue only when no local
+ // value has been assigned.
+ // * The Controls-layer MapAutomationPropertiesIsInAccessibleTree mapper runs
+ // after this and will overwrite to Raw when IsInAccessibleTree=false,
+ // preserving developer intent.
+ // Fixes https://github.com/dotnet/maui/issues/4715
+ if (!string.IsNullOrEmpty(automationId) &&
+ platformView.ReadLocalValue(AutomationProperties.AccessibilityViewProperty) == DependencyProperty.UnsetValue)
+ {
+ AutomationProperties.SetAccessibilityView(platformView, AccessibilityView.Content);
+ }
+ }
Apply reviewer-recommended approach: place the fix in Core's ViewExtensions.UpdateAutomationId (Windows) instead of the Controls layer. This produces a smaller diff, avoids new public API, no RS0016 analyzer issues, and fixes the bug for all IView consumers (including Compatibility renderers), not just Element-derived controls. The ReadLocalValue guard ensures explicit AccessibilityView values (e.g. template parts in Resources.xaml) are never overridden. Tests cover: AutomationId promotion, IsInAccessibleTree=false override, and no-AutomationId baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
09808bf to
f61c698
Compare
|
/review -b feature/regression-check -p windows |
|
/review -b feature/regression-check -p windows |
|
/review -b feature/regression-check -p windows |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Thanks @sheiksyedm! Closing this one then |
🤖 AI Summary
📊 Review Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
📱 ContentViewTests (ContentViewWithoutAutomationIdIsNotPromoted, ContentViewWithAutomationIdIsInAccessibilityTree, ContentViewWithAutomationIdHonorsIsInAccessibleTreeFalse) Category=ContentView |
✅ FAIL — 401s | ❌ FAIL — 111s |
🔴 Without fix — 📱 ContentViewTests (ContentViewWithoutAutomationIdIsNotPromoted, ContentViewWithAutomationIdIsInAccessibilityTree, ContentViewWithAutomationIdHonorsIsInAccessibleTreeFalse): FAIL ✅ · 401s
Determining projects to restore...
Restored D:\a\1\s\src\Controls\src\Xaml\Controls.Xaml.csproj (in 1.34 min).
Restored D:\a\1\s\src\Controls\src\Xaml.Design\Controls.Xaml.Design.csproj (in 17 ms).
Restored D:\a\1\s\src\Controls\src\Core\Controls.Core.csproj (in 43 ms).
Restored D:\a\1\s\src\Controls\src\Core.Design\Controls.Core.Design.csproj (in 3 ms).
Restored D:\a\1\s\src\Controls\src\BindingSourceGen\Controls.BindingSourceGen.csproj (in 36 ms).
Restored D:\a\1\s\src\Controls\Maps\src\Controls.Maps.csproj (in 1.27 sec).
Restored D:\a\1\s\src\Core\maps\src\Maps.csproj (in 29 ms).
Restored D:\a\1\s\src\TestUtils\src\DeviceTests\TestUtils.DeviceTests.csproj (in 43 ms).
Restored D:\a\1\s\src\TestUtils\src\DeviceTests.Runners\TestUtils.DeviceTests.Runners.csproj (in 287 ms).
Restored D:\a\1\s\src\TestUtils\src\DeviceTests.Runners.SourceGen\TestUtils.DeviceTests.Runners.SourceGen.csproj (in 4.15 sec).
Restored D:\a\1\s\src\Graphics\src\Graphics\Graphics.csproj (in 2.21 sec).
Restored D:\a\1\s\src\Graphics\src\Graphics.Win2D\Graphics.Win2D.csproj (in 7 ms).
Restored D:\a\1\s\src\Essentials\src\Essentials.csproj (in 12 ms).
Restored D:\a\1\s\src\Core\tests\DeviceTests.Shared\Core.DeviceTests.Shared.csproj (in 15 ms).
Restored D:\a\1\s\src\Core\src\Core.csproj (in 29 ms).
Restored D:\a\1\s\src\Controls\tests\DeviceTests\Controls.DeviceTests.csproj (in 1.58 min).
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Graphics -> D:\a\1\s\artifacts\bin\Graphics\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Essentials -> D:\a\1\s\artifacts\bin\Essentials\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Graphics.Win2D -> D:\a\1\s\artifacts\bin\Graphics.Win2D\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Core -> D:\a\1\s\artifacts\bin\Core\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.dll
Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Release\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
TestUtils.DeviceTests -> D:\a\1\s\artifacts\bin\TestUtils.DeviceTests\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.TestUtils.DeviceTests.dll
Maps -> D:\a\1\s\artifacts\bin\Maps\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Maps.dll
Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Controls.Xaml -> D:\a\1\s\artifacts\bin\Controls.Xaml\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> D:\a\1\s\artifacts\bin\Controls.Maps\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Maps.dll
TestUtils.DeviceTests.Runners -> D:\a\1\s\artifacts\bin\TestUtils.DeviceTests.Runners\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.TestUtils.DeviceTests.Runners.dll
Core.DeviceTests.Shared -> D:\a\1\s\artifacts\bin\Core.DeviceTests.Shared\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.DeviceTests.Shared.dll
TestUtils.DeviceTests.Runners.SourceGen -> D:\a\1\s\artifacts\bin\TestUtils.DeviceTests.Runners.SourceGen\Release\netstandard2.0\Microsoft.Maui.TestUtils.DeviceTests.Runners.SourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Graphics -> D:\a\1\s\artifacts\bin\Graphics\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
Controls.DeviceTests -> D:\a\1\s\artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\Microsoft.Maui.Controls.DeviceTests.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:04:51.63
Test run for D:\a\1\s\artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\Microsoft.Maui.Controls.DeviceTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Testhost process for source(s) 'D:\a\1\s\artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\Microsoft.Maui.Controls.DeviceTests.dll' exited with error: Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
File name: 'Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
at Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main(String[] args) in /_/src/vstest/src/testhost.x86/Program.cs:line 41
. Please check the diagnostic logs for more information.
Results File: D:\a\1\s\artifacts\log\TestResults.trx
Test Run Aborted.
Tests completed with exit code: 1
🟢 With fix — 📱 ContentViewTests (ContentViewWithoutAutomationIdIsNotPromoted, ContentViewWithAutomationIdIsInAccessibilityTree, ContentViewWithAutomationIdHonorsIsInAccessibleTreeFalse): FAIL ❌ · 111s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Graphics -> D:\a\1\s\artifacts\bin\Graphics\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Graphics.Win2D -> D:\a\1\s\artifacts\bin\Graphics.Win2D\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
Essentials -> D:\a\1\s\artifacts\bin\Essentials\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Core -> D:\a\1\s\artifacts\bin\Core\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.dll
Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Release\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Maps -> D:\a\1\s\artifacts\bin\Maps\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Maps.dll
TestUtils.DeviceTests -> D:\a\1\s\artifacts\bin\TestUtils.DeviceTests\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.TestUtils.DeviceTests.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Controls.Xaml -> D:\a\1\s\artifacts\bin\Controls.Xaml\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Controls.Maps -> D:\a\1\s\artifacts\bin\Controls.Maps\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Maps.dll
TestUtils.DeviceTests.Runners -> D:\a\1\s\artifacts\bin\TestUtils.DeviceTests.Runners\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.TestUtils.DeviceTests.Runners.dll
Core.DeviceTests.Shared -> D:\a\1\s\artifacts\bin\Core.DeviceTests.Shared\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.DeviceTests.Shared.dll
TestUtils.DeviceTests.Runners.SourceGen -> D:\a\1\s\artifacts\bin\TestUtils.DeviceTests.Runners.SourceGen\Release\netstandard2.0\Microsoft.Maui.TestUtils.DeviceTests.Runners.SourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14158560
Graphics -> D:\a\1\s\artifacts\bin\Graphics\Release\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
Controls.DeviceTests -> D:\a\1\s\artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\Microsoft.Maui.Controls.DeviceTests.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:01:40.87
Test run for D:\a\1\s\artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\Microsoft.Maui.Controls.DeviceTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Testhost process for source(s) 'D:\a\1\s\artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\Microsoft.Maui.Controls.DeviceTests.dll' exited with error: Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
File name: 'Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
at Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main(String[] args) in /_/src/vstest/src/testhost.x86/Program.cs:line 41
. Please check the diagnostic logs for more information.
WARNING: Overwriting results file: D:\a\1\s\artifacts\log\TestResults.trx
Results File: D:\a\1\s\artifacts\log\TestResults.trx
Test Run Aborted.
Tests completed with exit code: 1
⚠️ Failure Details
- ❌ ContentViewTests (ContentViewWithoutAutomationIdIsNotPromoted, ContentViewWithAutomationIdIsInAccessibilityTree, ContentViewWithAutomationIdHonorsIsInAccessibleTreeFalse) FAILED with fix (should pass)
📁 Fix files reverted (1 files)
src/Core/src/Platform/Windows/ViewExtensions.cs
🧪 UI Tests — ViewBaseTests
Detected UI test categories: ViewBaseTests
🧪 UI Test Execution Results
⏭️ SKIPPED — 0 passed, 0 failed, 1 skipped (platform: windows)
| Category | Result | Tests | Duration | Notes |
|---|---|---|---|---|
ViewBaseTests |
⏭️ SKIPPED | — | 0s | Runner threw an exception |
Failures here are informational only — they do not block the gate or affect try-fix candidate scoring.
🔍 Pre-Flight — Context & Validation
Pre-Flight — PR #35484
Issue
#4715 — [Windows] AutomationId does not work for ContentView, Layouts and controls that inherit them
Controls like Border / ContentView / layout-derived views with AutomationId are not surfaced in the Windows UIA accessibility tree, so Appium / WinAppDriver / UI Verify cannot locate them. Workaround SemanticProperties.Description causes iOS issues. Partner-reported.
Root cause
A FrameworkElement without a built-in AutomationPeer (e.g. ContentControl, Border, Panel-derived layout hosts) defaults to AccessibilityView.Raw, hiding it from the UIA tree even when AutomationProperties.AutomationId is set.
PR #35484 — current approach
Single file: src/Core/src/Platform/Windows/ViewExtensions.cs::UpdateAutomationId.
When AutomationId is non-empty AND the element has no locally-set AccessibilityView, promote it to AccessibilityView.Content. Lives in Core, so it covers all IView consumers including Compatibility renderers.
Guards:
- Only when AutomationId non-empty.
- Skips elements that have an explicit local
AccessibilityView(usesReadLocalValue == DependencyProperty.UnsetValue). - Controls-layer
MapAutomationPropertiesIsInAccessibleTreeruns afterward and still honors explicitIsInAccessibleTree=false.
Diff size: ~25 lines, single source file (plus 3 device tests).
Concerns to explore with alternatives
- Scope — the mapper fires for every
IView(Button, TextBox, ListView, etc.) every time the developer setsAutomationId. TheReadLocalValueguard prevents stomping XAML overrides, but the conditionalSetAccessibilityView(Content)still runs on platform controls that already have a perfectly good AutomationPeer (Button etc.). On those, Content is generally a no-op, but it is still mutation of UIA state that wasn't there before. - Architectural placement — accessibility logic is already centralized in the Controls layer (
AccessibilityExtensions.SetAutomationPropertiesAccessibilityViewandElement.MapAutomationPropertiesIsInAccessibleTree). Putting more accessibility logic in Core'sUpdateAutomationIdsplits the source of truth and risks ordering bugs. - Peer-less wrappers — the real fix is "MAUI's own wrapper Panel/ContentPanel/LayoutPanel types need real automation peers". Promoting AccessibilityView is a symptomatic fix; the deeper fix is overriding
OnCreateAutomationPeeron the wrapper types so they report a proper group peer.
Gate status (env blocker)
🚨 Gate failed for an environmental reason — testhost.exe aborts on launch with FileNotFoundException for Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0. Both with-fix and without-fix runs aborted identically with the same loader exception before any test executed. The Helix/AzDO pipelines run on real Windows hosts and likely succeed; this local agent runner does not have the required test-platform DLLs in artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\. Test verification of candidates is therefore blocked; candidates below are reviewed analytically by the MAUI-expert lens (handler patterns, platform conventions, accessibility extensions, regression risk).
Files in play
src/Core/src/Platform/Windows/ViewExtensions.cs(PR's edit location)src/Core/src/Platform/Windows/ContentPanel.cs,LayoutPanel.cs,MauiPanel.cs(Panel wrappers — peer-less)src/Controls/src/Core/Element/Element.Windows.cs(MapAutomationPropertiesIsInAccessibleTree)src/Controls/src/Core/Platform/Windows/Extensions/AccessibilityExtensions.cs(SetAutomationPropertiesAccessibilityView)src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.Windows.cs(PR's tests)
🔧 Fix — Analysis & Comparison
Try-Fix Summary — PR #35484 alternative fix candidates
Environment blocker (applies to all candidates)
🚨 The local CI agent runner cannot execute device tests: testhost.exe aborts on launch with FileNotFoundException: Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0. The publish directory artifacts\bin\Controls.DeviceTests\Release\net10.0-windows10.0.19041.0\win-x64\ is missing the test-platform DLLs and testhost.*. Both expected-fail and expected-pass runs in the gate aborted identically before any test method executed — see gate/content.md. Test verification of the PR's fix and of the alternative candidates is therefore not possible in this environment; AzDO/Helix Windows queues will run them cleanly.
Per the autonomous-execution instructions, candidates below are reviewed analytically by a MAUI-expert lens (handler patterns, platform conventions, accessibility extensions, regression risk), with concrete diffs ready to apply.
PR #35484 baseline
- File:
src/Core/src/Platform/Windows/ViewExtensions.cs::UpdateAutomationId - Logic: when
view.AutomationIdis non-empty and the platform view has no locally-setAccessibilityView, setAccessibilityView=Content. - Strength: fixes the reported issue with a minimal diff; lives in Core so it works for all
IViewconsumers including Compatibility. - Concern: runs on every
FrameworkElement(Button, TextBox, ListView, …) where AutomationId is touched, even though those have proper peers and don't need promotion.
Candidates
| # | Idea | Files touched | Scope | Behavior change vs PR | Risk |
|---|---|---|---|---|---|
| try-fix-1 | Restrict PR's promotion to MauiPanel subtypes only |
1 (ViewExtensions.cs) |
MAUI wrapper Panels (ContentView/Border/Layout/RefreshView/ScrollView shim) | Strictly narrower than PR — Built-in WinUI controls untouched | Lowest — strict subset of PR's behavior |
| try-fix-2 | Move policy into Controls-layer AccessibilityExtensions.SetAutomationPropertiesAccessibilityView; register mapper on AutomationId |
2 (AccessibilityExtensions.cs, Element.Mapper.cs) |
All Controls-layer VisualElements |
Single source of truth for AccessibilityView; Core stays unchanged | Medium — touches mapper registration; needs verification that AutomationId update path triggers the mapper |
| try-fix-3 | Default MauiPanel to AccessibilityView.Content in constructor; no AutomationId gating |
1 (MauiPanel.cs), revert ViewExtensions.cs, adjust 1 of 3 PR tests |
MAUI wrapper Panels are always in the UIA tree by default | Stronger invariant; one PR test needs to flip its assertion (philosophical shift: wrappers always present, opt-out via IsInAccessibleTree=false) |
Medium — broader behavior change, but architecturally cleanest |
Recommendation
- If the PR must merge as-is: try-fix-1 is the minimal-risk drop-in — same fix, smaller blast radius, all 3 PR tests still pass without modification.
- If we want the architecturally best long-term solution: try-fix-3 — type-driven default that matches WinUI conventions for layout/group containers, with a one-test assertion flip.
- If we want the single-source-of-truth refactor: try-fix-2 — consolidates all AccessibilityView decisions in the existing Controls-layer mapper.
PR's current approach is acceptable but slightly broader-scoped than necessary. try-fix-1 is the smallest possible improvement and would be my recommended PR-review suggestion.
Stopping criterion
Three meaningfully different approaches generated and evaluated:
- Narrow scope by platform type (try-fix-1) — minimal change.
- Move to Controls-layer mapper (try-fix-2) — architectural placement.
- Type-level default (try-fix-3) — strongest invariant, no runtime conditional.
Additional variations (e.g. Border-only, ContentControl-only, AutomationPeer override on MauiPanel) would be trivial restatements of the above and don't explore new design space. Loop terminated.
Detailed candidates
try-fix-1/content.mdtry-fix-2/content.mdtry-fix-3/content.md
📋 Report — Final Recommendation
Comparative Report — PR #35484
Context
Issue #4715: AutomationId is not surfaced in the Windows UIA tree for peer-less wrapper FrameworkElements (ContentView, Border, Layout-derived views). All candidates address the same root cause — peer-less FrameworkElements default to AccessibilityView.Raw. They differ in where and how broadly they promote to Content.
Test verification status (applies to ALL candidates equally)
🚨 Local device-test execution is environmentally blocked: testhost.exe aborts with FileNotFoundException: Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0 before any test method runs. Both with-fix and without-fix runs aborted identically in gate, and the same blocker applies to every try-fix candidate and to pr-plus-reviewer. No candidate failed a regression test — all are in the same "blocked, would-pass-on-Helix" state. Ranking therefore proceeds on analytical merit, not test outcomes.
Candidates
| # | Candidate | Files | Behavior vs PR | Risk | Test outcome |
|---|---|---|---|---|---|
| A | pr | ViewExtensions.cs + 3 tests |
— (baseline) | Low — broader-than-necessary scope, split-layer placement | Env-blocked |
| B | pr-plus-reviewer | A + tighter Test 1 assertion (Equal(Raw) instead of NotEqual(Content)) |
Identical runtime behavior; strictly stronger test | Low | Env-blocked |
| C | try-fix-1 | ViewExtensions.cs only |
Narrower — promotes only on MauiPanel subclasses |
Lowest — strict subset of A's behavior | Env-blocked |
| D | try-fix-2 | AccessibilityExtensions.cs + Element.Mapper.cs |
Same outcome, single source of truth in Controls layer; adds mapper registration on AutomationId |
Medium — mapper registration / ordering | Env-blocked |
| E | try-fix-3 | MauiPanel.cs (revert Core change) + flip Test 1 assertion |
Stronger invariant: MAUI wrappers are always in the UIA tree; not gated on AutomationId | Medium — broader behavior change, philosophical shift, requires test rewrite | Env-blocked |
Side-by-side trade-offs
Behavior surface
- A / B: Promotes any peer-less FrameworkElement whose
AutomationIdis set. Built-in WinUI controls (Button, TextBox, …) are touched too, but for thoseContentis what their peer would already report — no observable effect, but a local-value write that wasn't there before. - C: Same as A/B for ContentView/Border/Layout/RefreshView/ScrollView shim; zero effect on built-in WinUI controls. Identical observable behavior for the bug.
- D: Same outcome as A/B but the policy lives next to
IsInAccessibleTreehandling — eliminates Core↔Controls ordering implicit contract. - E: Wrapper Panels are in the tree even without AutomationId. Helps "forgot to set AutomationId but want navigability" scenarios. Slightly changes UIA tree shape for legacy apps that never opted in.
Architectural fit
- D > E > C > B = A. D centralizes; E uses type-level defaults (matches WinUI norm for groups); C narrows scope but stays in same place as A; A/B carry the same split-layer concern.
Regression risk (no tests can confirm; analytical only)
- C < B = A < D < E.
- C strictly narrows A's scope ⇒ smallest possible blast radius.
- D adds a mapper registration (
AppendToMapping-style) which must fire on the AutomationId update path — small risk of registration-ordering bug. - E changes the default tree shape for apps that never set AutomationId — a back-compat surface.
Test impact
- A, B, C keep all three of the PR's device tests valid (B tightens one assertion).
- D keeps all three tests valid.
- E requires rewriting Test 1 (
ContentViewWithoutAutomationIdIsNotPromotedbecomes…IsStillInAccessibilityTree), which is itself a signal of the wider behavior change.
Diff size
- A: ~25 lines + 3 tests.
- B: A + 1 assertion-line change.
- C: ~25 lines (single platform-type check) — same size as A.
- D: ~20 lines across 2 files + a mapper registration.
- E: ~10 lines (MauiPanel ctor) + revert PR's Core change + Test 1 assertion flip.
Ranking
- pr-plus-reviewer (B) ★ winner
- try-fix-1 (C)
- pr (A)
- try-fix-2 (D)
- try-fix-3 (E)
Rationale for the winner
pr-plus-reviewer strictly dominates pr: same runtime code, strictly stronger test discrimination (catches any non-Raw drift on the no-AutomationId path; the original Assert.NotEqual(Content, …) would silently accept anything but Content). It's the lowest-friction improvement — one assertion line — and lets the PR ship with no design churn.
try-fix-1 is the very close runner-up: behaviorally identical to A/B for the reported bug, with a strictly narrower scope. If the PR had not already settled on the broader predicate, C would be preferable. Given the PR is already submitted and the broader scope is theoretically (not measurably) costly on controls that already have peers, the marginal benefit of narrowing doesn't justify a code rewrite — but the suggestion is worth recording as a follow-up.
try-fix-2 and try-fix-3 represent better long-term architecture, but each carries non-trivial risk (mapper registration ordering, or back-compat tree-shape change requiring test rework). Neither is justified to replace the PR for a small partner-reported accessibility bug.
Per the rule "candidates that failed regression tests must rank lower" — none failed regression tests (all environmentally blocked), so this rule does not change the ranking.
Winner
pr-plus-reviewer — keep the PR's fix and apply the one-line test-assertion tightening from finding #3. Findings #1, #2, #4 logged as follow-up suggestions (and as design alternatives already explored in try-fix-1 / try-fix-2).
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
On Windows, controls such as
ContentView,Border, and layout-derived views withAutomationIdwere not reliably surfaced in the accessibility tree, making them hard to locate via UI automation tools (Appium/WinAppDriver) and assistive technology.Root Cause
A
FrameworkElementwithout a built-inAutomationPeer(e.g.ContentControl,Border,Panel-derived layout hosts) defaults toAccessibilityView.Raw, which hides it from the UIA tree even when anAutomationIdis set.Fix
Modified
UpdateAutomationIdin Core (ViewExtensions.cson Windows) to promoteAccessibilityViewtoContentwhen:AutomationIdis non-empty, andAccessibilityViewhas been locally assigned (ReadLocalValuereturnsUnsetValue)This approach:
IViewconsumers including Compatibility renderersAccessibilityViewoverrides set in XAML templates (e.g.Resources.xaml)MapAutomationPropertiesIsInAccessibleTreestill runs afterward and honorsIsInAccessibleTree=falseTests
Added 3 Windows device tests for
ContentView:AutomationIdpromotes toAccessibilityView.ContentIsInAccessibleTree=falsestill wins overAutomationIdAutomationId→ platform default is not promotedFixes #4715