Skip to content

Fix incomplete UI Automation descendant searches - #842

Merged
Nikola Metulev (nmetulev) merged 2 commits into
mainfrom
nmetulev-fix-incomplete-ui-subtree-traversal
Sep 17, 2026
Merged

Nikola Metulev (nmetulev) merged 2 commits into
mainfrom
nmetulev-fix-incomplete-ui-subtree-traversal

Conversation

@nmetulev

@nmetulev Nikola Metulev (nmetulev) commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Summary

  • complete nonzero-but-partial FindAll(TreeScope.Descendants) results with the existing Control View traversal
  • share the completion logic across SearchAsync, FindSingleElementAsync, and popup/owned-window lookups
  • preserve exact AutomationId precedence, including IDs omitted by a provider's bulk descendant query
  • replace the fallback's depth-25 recursion with iterative traversal so deep realized descendants are not silently excluded
  • preserve current-main resolved-window/DPI context when a completed walk recovers an exact ID
  • add static-seam regressions for zero results, partial nonzero results, exact-ID precedence at result caps, deep traversal, cancellation, and PID-only/stale-HWND recovery

Performance and completeness tradeoff

Exact AutomationId lookup in FindSingleElementAsync retains its FindFirst fast path. Exact-ID bulk searches also retain the cap-satisfied fast path when the provider returns a result.

When the exact-ID bulk probe misses, ordinary Name/substring lookup deliberately completes one Control View walk before applying the caller's result cap. This is required because an initialized, visible WebView2 provider can return a nonzero but incomplete bulk result, and a result count at the cap does not prove the omitted exact ID or later substring matches are absent. A node/time budget or WebView-only heuristic would knowingly reintroduce #823; provider metadata did not provide a reliable completeness signal.

Published native binary measurements against the same pre-optimization head remained sub-second while removing the redundant exact-only walk:

  • Microsoft Edge Name lookup: 894.52 ms -> 626.63 ms
  • initialized-visible WebView2 Name lookup after the provider boundary: 691.92 ms -> 545.78 ms
  • omitted exact WebView2 ID: remained correct, 445.96 ms -> 532.01 ms (the expected cost of broad completion replacing the exact-only walk)

Validation

  • focused traversal and current-main window-context integration tests: 16 passed
  • recovered exact-ID PID-only/stale-HWND regression plus focused neighbors: 5 passed
  • full UI Automation suite: 420 passed, 2 explicitly skipped
  • scripts\build-cli.ps1 -SkipTests: passed end to end, including native publish, npm, NuGet, MSIX, schema, and plugin validation
  • independent multi-dimensional and different-model review completed; the only remaining concern was the deliberate completeness cost documented above

Fixes #823

Copilot AI balanced review requested due to automatic review settings September 14, 2026 23:18
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed

Copilot AI 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.

🟡 Changes recommended

Real WinUI/provider validation and coverage of traversal beyond depth 25 remain missing.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Completes partial UI Automation descendant searches by supplementing bulk results with Control View traversal.

Changes:

  • Shares bulk-plus-walker completion across search paths.
  • Replaces depth-limited recursion with iterative traversal.
  • Adds regression tests for partial results and capped fast paths.
File summaries
File Description
UiAutomationService.cs Adds descendant result completion and iterative traversal.
RealUiAutomationTests.Coverage.cs Adds seam-based traversal regressions.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/winapp-CLI/WinApp.UIAutomation.Tests/RealUiAutomationTests.Coverage.cs Outdated
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs

Copilot AI 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.

🔵 Needs a closer look

Quadratic COM-based deduplication can severely degrade non-exact searches on large UI trees.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs:1755

  • What is wrong: Deduplication is quadratic and each comparison crosses the UIA COM boundary. FindSingleElementAsync passes int.MaxValue, so even when bulk FindAll is complete, a non-exact selector walks the whole tree and compares the first manual match once, the second twice, and so on.

Show me: If bulk and Control View each return the same 1,000 matches, this loop performs about 500,000 CompareElements calls instead of linear work.

Why it matters: Common text selectors on large application trees can turn an ambiguity check into a long-running or effectively hung action; the reported 128-element fixture does not exercise this growth.

Smallest fix: Use a stable element identity key (for example, a successfully retrieved runtime ID) to deduplicate in constant time, with CompareElements only as a fallback for elements whose identity cannot be read.

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Fixed

Copilot AI 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.

🟡 Changes recommended

Exact AutomationId searches can return incorrect capped results, and runtime-ID SAFEARRAYs leak during repeated searches.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs:478

  • Popup searches have the same exact-AutomationId regression. When the main window has no match and a provider cannot evaluate AutomationId substring conditions, an unrelated popup element whose Name matches can fill the remaining cap and prevent the manual walk from finding the exact AutomationId. Restore exact AutomationId lookup ahead of the substring/completion path for owned windows as well.
                        var condition = BuildCondition(selector);
                        if (condition is not null)
                        {
                            var windowFound = FindAllDescendantMatches(
                                windowRoot,
                                condition,
                                selector.Query,
                                maxResults - mainResults.Count);
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Outdated
Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Outdated
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 48.44 MB 48.51 MB 📈 +65.9 KB (+0.13%)
CLI (x64) 48.29 MB 48.35 MB 📈 +61.4 KB (+0.12%)
MSIX (ARM64) 19.96 MB 19.99 MB 📈 +34.8 KB (+0.17%)
MSIX (x64) 21.16 MB 21.17 MB 📈 +17.8 KB (+0.08%)
NPM Package 41.53 MB 41.58 MB 📈 +52.7 KB (+0.12%)
NuGet Package 41.66 MB 41.72 MB 📈 +57.8 KB (+0.14%)

Test Results

✅ 6851 passed, 37 skipped out of 6888 tests in 873.0s (+32 tests, -304.0s vs. baseline)

Test Coverage

✅ 85.9% line coverage, 79.9% branch coverage · ✅ +0.1% vs. baseline

CLI Startup Time

63ms median (x64, winapp --version) · ⚠️ +11ms vs. baseline

Try This Build

Installs the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing.

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 842
Switching between builds often?

Put the tool on your PATH once:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPath

Then this build is just:

winapp-pr 842

Run winapp-pr with no arguments to pick from a list of open PRs.


Updated 2026-09-17 02:01:15 UTC · commit 0db2e3d · workflow run

Copilot AI 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.

🟡 Changes recommended

The unbounded traversal ignores cancellation, preventing timeouts or Ctrl+C from stopping slow searches.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs

Copilot AI 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.

🟢 Approval recommended

The prior findings are addressed, the project builds cleanly, and all 123 live fixture tests passed.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@nmetulev

Copy link
Copy Markdown
Member Author

Copilot review

Copilot AI 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.

🟡 Changes recommended

Capped substring results can still override an omitted exact AutomationId match.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs

Copilot AI 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.

🟢 Approval recommended

The implementation is consistent and the solution build plus all 411 UI automation tests completed successfully.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Rebase the reviewed traversal fix onto current main while preserving exact AutomationId precedence and complete Control View fallback behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🟡 Changes recommended

A recovered exact match can retain a stale HWND, causing subsequent UI commands to reject it.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@zateutsch Zach Teutsch (zateutsch) 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.

Review clean, approving.

Copilot AI 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.

🔵 Needs a closer look

Ordinary capped Name searches still perform full traversal, contradicting the stated fast-path behavior.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs:1995

  • The cap-satisfied fast path is not preserved for ordinary Name queries. When the exact-ID bulk probe misses and the substring bulk query returns one result for maxResults = 1, this int.MaxValue makes FindAllDescendantMatches walk the entire Control View anyway; the new SearchAsync_ExactBulkMiss_UsesOneCompletedSubstringWalkAndPreservesExactPrecedence test confirms that path. This contradicts the PR's performance claim that completion runs only when bulk results have not satisfied the requested cap, and makes common Name searches pay the measured completion overhead. Either retain the caller's cap here with a narrower exact-ID recovery strategy, or explicitly document and benchmark this always-completed path.
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@nmetulev

Copy link
Copy Markdown
Member Author

Copilot review

Copilot AI 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.

🟢 Approval recommended

The implementation addresses the reported incomplete-provider behavior and the expanded tests cover the previously identified regressions.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Copilot review

🤖 AI-generated review (winappcli pr-review skill) — verify before acting.

Review clean; no blocking findings. The focused real UI Automation fixture passed 137/137 tests.

@nmetulev
Nikola Metulev (nmetulev) merged commit 18c43fc into main Sep 17, 2026
0 checks passed
@nmetulev
Nikola Metulev (nmetulev) deleted the nmetulev-fix-incomplete-ui-subtree-traversal branch September 17, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Investigate incomplete WinUI subtree traversal

4 participants