From wpf-dev-pack
Resolves FlaUI element discovery failures in WPF UI automation like missing AutomationIds in ItemsControls, FindAllDescendants limits, and invisible Shape controls.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
When automating WPF applications with FlaUI, elements you expect to find may be missing from the UIA automation tree. This skill covers the most common causes and their fixes, based on how WPF exposes elements to UI Automation.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
When automating WPF applications with FlaUI, elements you expect to find may be missing from the UIA automation tree. This skill covers the most common causes and their fixes, based on how WPF exposes elements to UI Automation.
Setting AutomationProperties.AutomationId on a content element inside an ItemsControl may not expose it in the UIA tree. The UIA tree reflects the items container, not the content template.
Symptoms:
designer.FindAllDescendants(cf => cf.ByAutomationId("MyNode")) returns emptyAutomationProperties.AutomationId="{Binding Name}"Root cause: In WPF's ItemsControl pattern (e.g., Nodify's NodifyEditor), items are wrapped in containers (ItemContainer, ListBoxItem, TreeViewItem). The UIA tree exposes these containers, not the content templates inside them. Setting AutomationId on a DataTemplate child (e.g., nodify:Node) places it below the container level where UIA may not propagate it.
Fix — set AutomationId on the ItemContainerStyle, not the DataTemplate:
<!-- WRONG: AutomationId on content element inside DataTemplate -->
<DataTemplate DataType="{x:Type local:MyItem}">
<nodify:Node AutomationProperties.AutomationId="{Binding Name}" .../>
</DataTemplate>
<!-- RIGHT: AutomationId on the items container via Style -->
<Style x:Key="NodeStyle" TargetType="{x:Type nodify:ItemContainer}">
<Setter Property="AutomationProperties.AutomationId" Value="{Binding Name}"/>
<Setter Property="Location" Value="{Binding PixelPosition, Mode=TwoWay}"/>
</Style>
This applies to any ItemsControl-based pattern: ListView, ListBox, TreeView, NodifyEditor, etc.
node.FindAllDescendants() may not return all elements in the visual tree. TextBlocks or other elements inside nested ItemsControl containers can be omitted.
Symptoms:
ItemsControlItem IS found as a descendant, but the child itself is notExample: Nodify output connector structure:
ItemContainer (node) ← FindAllDescendants() starts here
└── ItemsControl (PART_Output) ← found
└── ItemsControlItem ← found
└── TextBlock "ZMap3D" ← NOT found in node.FindAllDescendants()
Fix — search children of the specific container directly:
// FindAllDescendants misses deeply nested TextBlocks
AutomationElement? socketTextBlock = null;
// First try: search all descendants (works for shallow elements)
foreach (var desc in node.FindAllDescendants())
{
if (desc.Name?.StartsWith(socketName + "\n") == true)
{
socketTextBlock = desc;
break;
}
}
// If not found: search children of each ItemsControlItem directly
if (socketTextBlock is null)
{
foreach (var desc in node.FindAllDescendants())
{
if (desc.Properties.ClassName.ValueOrDefault != "ItemsControlItem")
{
continue;
}
// FindAllChildren goes one level deep — catches elements
// that FindAllDescendants missed
var children = desc.FindAllChildren();
foreach (var child in children)
{
if (child.Name?.StartsWith(socketName + "\n") == true)
{
socketTextBlock = child;
break;
}
}
if (socketTextBlock is not null)
{
break;
}
}
}
WPF controls inheriting from Shape (Path, Ellipse, Rectangle, Line, and custom shapes) do not provide an AutomationPeer by default. They are invisible to FlaUI's FindAllDescendants().
Affected controls:
BaseConnection / MidpointConnection (extends Shape)ShapePath elements used for connection linesSymptoms:
FindAllDescendants()ClassName.Contains("Connection") or ClassName.Contains("Path") finds nothingImpact on testing:
FindAllDescendants().Length may decrease after connecting (connector structure changes)Workarounds:
Accept that connections can't be verified via UIA. Document this limitation and verify connections indirectly (e.g., the drag operation completed without error).
Add a custom AutomationPeer to the connection control if you control the source code:
public class MidpointConnection : BaseConnection
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new FrameworkElementAutomationPeer(this);
}
}
| Situation | Solution |
|---|---|
ByAutomationId finds nothing | Set AutomationId on the ItemContainerStyle, not on DataTemplate content |
TextBlock missing from FindAllDescendants | Use FindAllChildren() on the parent ItemsControlItem directly |
| Connection/Path not in UIA tree | Shape-based controls have no AutomationPeer — invisible to UIA |
| Element count decreases after action | Connector structure can change on connection — don't rely on count comparison |
| Need to distinguish input/output connectors | Filter ItemsControlItem by position (left half = input, right half = output) |
When elements aren't found, dump the full UIA tree to see what's actually exposed:
var allDescs = designer.FindAllDescendants();
Console.WriteLine($"Total elements: {allDescs.Length}");
foreach (var desc in allDescs)
{
var cls = desc.Properties.ClassName.ValueOrDefault ?? "(null)";
var name = desc.Name ?? "(null)";
var aid = desc.Properties.AutomationId.ValueOrDefault ?? "";
var b = desc.BoundingRectangle;
Console.WriteLine(
$" [{cls}] Name=\"{name}\" AutomationId=\"{aid}\" " +
$"Bounds=({b.Left},{b.Top},{b.Width}x{b.Height})");
}
Check for:
ItemsControlItem vs the control type you expect