Broken selectors are the #1 reason RPA automations fail in production. This lesson teaches you how to read, edit, and build resilient UI element selectors in Power Automate Desktop — covering dynamic IDs, wildcard matching, timing strategies, and a systematic debugging workflow that works on real enterprise applications.

You've recorded a flow, it runs perfectly in the demo, and you're feeling good. Then it runs on Monday morning against the live application and fails spectacularly — clicking the wrong button, typing into a field that doesn't exist, or timing out while staring at a loading spinner. The recording worked because PAD captured a precise snapshot of the UI at that exact moment. But UIs are living things: windows resize, dynamic IDs change, applications update their layouts, and server-side rendering can reorder DOM elements between page loads.
The difference between RPA automations that survive in production and ones that need constant babysitting comes down almost entirely to how you define and maintain your UI elements. Selectors are the addressing system your bot uses to find controls on screen — get them right, and your automation is resilient. Get them wrong, and you're stuck in a cycle of recording, breaking, fixing, repeat. This lesson moves past the recording basics to give you a systematic understanding of how selectors work, when to use each strategy, and how to build the kind of robust UI targeting that holds up against real-world application chaos.
By the end of this lesson, you will have genuine, hands-on competence in crafting and maintaining selectors that survive application updates, dynamic content, and environment changes.
What you'll learn:
This lesson assumes you're comfortable navigating the PAD designer, have recorded at least one desktop flow, and understand the basics of variables and flow control. If you're just getting started, work through Getting Started with Power Automate Desktop: Installing, Recording, and Running Your First Desktop Flow first. You should also have some familiarity with Variables, Lists, and Data Tables in Power Automate Desktop, since selector conditions often feed into larger data-handling logic.
Before you can fix a broken selector, you need to understand what a selector actually is. When PAD records or captures a UI element, it doesn't store a pixel coordinate. Instead, it stores a structured description of where that control lives in the application's element tree, expressed as a series of attribute-value conditions at each level of the hierarchy.
Think of the element tree like a file system path. To reach a specific file, you traverse: Computer > Drive > Folder > Subfolder > File. To reach a button in a desktop app, PAD might traverse: Application Window > Panel > Tab Control > Tab Page > Button.
Each node in that path has attributes — things like the window title, the control type (button, text box, combo box), the automation ID, the name, and the class. PAD captures these attributes and assembles them into the selector. At runtime, it walks that tree, matching attributes at each level, until it either finds the element or fails.
This is why a selector can break for reasons that have nothing to do with the control you're targeting. If the window title changes (say, a document filename appears in the title bar), PAD can't even get past the first node. If a parent container gets a new CSS class or the tab reorders, every child selector becomes unreachable.
Key insight
A selector is a path through a tree, not just a description of one control. Breakage anywhere in the path breaks the whole selector, even if the target element itself is perfectly stable.
The most important skill in this entire lesson is knowing how to read and edit the selector editor. You get there by opening the UI Elements panel in PAD (look for the element tree icon on the right side of the designer), right-clicking any captured element, and choosing Edit selector. Or, within an action like "Click UI Element," click the selector display to open it.
The selector editor shows a table-like interface with rows representing each level of the hierarchy and columns showing the attributes being matched at that level. A typical desktop selector for a "Submit" button in a form application might look like this:
Level 1 (Window):
Type: Window
Process Name: InvoiceApp
Title: Invoice Entry - [CORP-ERP v4.2]
Level 2 (Pane):
Type: Pane
Class: WindowsForms10.Window.8...
AutomationId: splitContainer1
Level 3 (Pane):
Type: Pane
AutomationId: tabControl1
Level 4 (TabItem):
Type: TabItem
Name: Details
Level 5 (Button):
Type: Button
Name: Submit
AutomationId: btnSubmit
Reading this, you can immediately spot potential trouble. Level 1 includes a version number in the title. When the application updates, that title changes, and the selector fails before it even looks at anything else. Level 2 includes a Windows Forms internal class name that differs between 32-bit and 64-bit installations. Level 4 depends on the tab being named "Details" — which might be localized differently on machines with different regional settings.
The selector editor lets you check and uncheck individual attributes and toggle whether each level is required or optional. Understanding how to use those controls is how you go from "it works on my machine" to "it works on every machine, every time."
Power Automate Desktop uses UI Automation (UIA) as its primary framework for interacting with Windows applications — WPF apps, Windows Forms apps, UWP, and modern Win32. For older applications that don't expose a UIA tree, PAD falls back to other mechanisms including MSAA (Microsoft Active Accessibility).
Not all attributes are created equal. Here's a practical ranking from most to least reliable:
AutomationId is your best friend. Good developers assign stable, meaningful automation IDs to controls that never change unless the developer deliberately changes them. In well-developed enterprise apps, btnSubmit is btnSubmit forever. If an element has an AutomationId, lean on it heavily.
Control Type (Button, Edit, ComboBox, etc.) is always reliable because it reflects the fundamental nature of the control. It changes only if the developer replaces one control type with another.
Name is reliable when it's static — a button labeled "Submit" will reliably be named "Submit." But watch out when the name includes dynamic content, like "Invoice #1047 - Open" where 1047 changes per record.
Class Name is moderately reliable for native Win32/WPF controls but fragile for Windows Forms apps, where class names include internal versioning strings like WindowsForms10.Button.app.0.141b42a_r9_ad1. These strings change between framework versions.
Index (the nth control of a given type) is the last resort. It works when everything else fails, but it's completely meaningless if anything is ever added before or after the target control.
Warning
Never rely on Index as your primary selector attribute for controls in dynamic forms. An index of 3 today might be index 4 after a UI redesign adds a new field above your target. Use it only within a very narrow, stable parent context, and only when no other attribute is available.
One of the most effective improvements you can make to a captured selector is shortening the hierarchy. PAD's recorder captures everything, which makes selectors more specific than they need to be. A button with a globally unique AutomationId doesn't need seven levels of ancestry — it could be found with just:
Level 1 (Window):
Type: Window
Process Name: InvoiceApp
Level 2 (Button):
Type: Button
AutomationId: btnSubmit
In the selector editor, you can delete intermediate levels when the target has uniquely identifying attributes. The rule of thumb: include an ancestry level only when its removal would cause ambiguity (i.e., the same element appears in multiple containers and you need the container to distinguish between them).
When you automate browsers using PAD's web automation actions, selectors work on the HTML DOM rather than the UIA tree, but the same principles apply. PAD captures a hierarchy of HTML elements with their attributes — tag name, id, class, name, type, innertext, and position-based attributes.
The selector for a login button might be captured as:
Level 1 (Browser Window):
Title: Customer Portal - Login
URL: https://portal.contoso.com/login
Level 2 (html):
Tag: html
Level 3 (body > main > div):
Tag: div
Class: auth-container
Level 4 (form):
Tag: form
Id: loginForm
Level 5 (button):
Tag: button
Type: submit
Innertext: Sign In
The most common web selector problem in enterprise applications is dynamic IDs — frameworks like Angular, React, and legacy Java-based portals often generate element IDs at runtime. An ID like j_id_2c:formPanel:submitBtn_4f7a looks stable in your recording session but is completely different next time the page loads.
The fix is to exclude the id attribute from that level and rely on structural attributes instead:
Level 5 (button):
Tag: button
Type: submit
Innertext: Sign In
No id. This works as long as there's only one submit button with "Sign In" text in the form, which is a stable structural fact.
Tip
When you open the selector editor for a web element, look for any attribute value that contains numbers, hashes, or long alphanumeric strings. These are usually dynamic. Uncheck them and test whether the selector still uniquely identifies the element.
CSS class names in modern JavaScript frameworks often include hashed suffixes for cache-busting: button--primary--a3f9d2. After a production deployment, that suffix changes, and your selector fails.
The fix is to use partial class matching in the selector editor. Instead of matching the entire class attribute exactly, use the Contains operator:
classContainsbutton--primaryThis survives the hash changing while still being specific enough to target the right control. In the PAD selector editor, click on the operator dropdown (which defaults to "Equal to") next to any attribute to access Contains, Starts with, Ends with, and Regular expression options.
Partial matching isn't just for CSS classes — it's the universal tool for dealing with dynamic content in any attribute at any level of the hierarchy.
The most common place you'll need wildcards is in window titles. Applications frequently append document names, record numbers, or status indicators to their title bars:
Invoice Entry - Invoice #1047Invoice Entry - Invoice #1048Invoice Entry - Invoice #1049 (Modified)If you match the full title, every record breaks the selector. Instead, match just the stable prefix:
TitleStarts withInvoice EntryThis works for the entire application, regardless of which record is open.
For even more complex patterns — like titles that contain a record number somewhere in the middle — use Regular expression matching:
TitleMatches regexInvoice Entry - Invoice #\d+The \d+ matches any sequence of digits, so the selector works for any invoice number.
Note
Regular expression matching in PAD selectors uses .NET regex syntax. The same patterns you'd use in C# work here. If you're not comfortable with regex, the Contains operator covers most practical cases — matching Invoice Entry with Contains handles the title bar scenario without regex at all.
Sometimes you genuinely can't find a unique attribute on the target element. A classic example: a table of rows where each row has a "Delete" button, but all the Delete buttons have the same attributes except for their position in the table.
In this case, use a relative selector — identify the row first by a stable attribute (like the text of a neighboring cell), then navigate to the button relative to that row. PAD supports this through the hierarchy: capture the row element by matching the text content, then add a child level targeting the button by type and name.
This is exactly how you'd handle something like an accounts payable queue where you need to click "Approve" on the row for a specific vendor:
Level 3 (DataGrid):
Type: DataGrid
AutomationId: invoiceGrid
Level 4 (DataItem):
Type: DataItem
Name: Contains: "Contoso Supplies" ← matches the row by vendor name
Level 5 (Button):
Type: Button
Name: Approve
Now your selector finds the Approve button for Contoso Supplies specifically, regardless of which row number it's in.
Once your flow has more than a few dozen UI elements, the UI Elements panel becomes critical for organization and maintenance. Here's how to work with it effectively.
PAD generates default names like Button 'Submit' and Text field 'InvoiceNumber'. These are fine for simple flows, but in a production automation with 50+ elements, you'll want descriptive names that include context:
InvoiceEntry_SubmitButton (not Button 'Submit')InvoiceEntry_InvoiceNumberField (not Text field 'InvoiceNumber')VendorPortal_SearchBox_Main (not Edit)Rename elements by right-clicking them in the UI Elements panel. Consistent naming conventions pay dividends when a selector breaks and you need to find it quickly, or when a colleague needs to maintain your flow.
One of the most underused features in PAD is that UI elements are defined once and can be referenced by multiple actions. If your flow clicks a field, clears it, types a value, and then validates it — all four actions can reference the same captured element. This means if you need to update the selector (because the application changed), you update it in one place, and all four actions pick up the change automatically.
When you're recording or building a flow, look for opportunities to reference existing elements rather than capturing new ones. In the element picker that appears when you click the target icon in any action, you can switch from "capture new" to "select existing" and pick from your element library.
Tip
Treat UI elements like functions: define them once, use them everywhere. A flow that re-captures the same element five times needs five fixes when the selector breaks. A flow that defines it once needs one.
Selectors break. It's not a question of if — it's when. Here's a repeatable debugging workflow that will save you hours.
Before touching the selector, verify that the application is in the exact state it needs to be. The element might genuinely not exist yet — maybe a form is still loading, a tab hasn't been activated, or a modal dialog hasn't appeared. Run the flow step-by-step using PAD's debug mode (the step-through controls in the designer toolbar) and pause before the failing action to observe the actual screen state.
In the selector editor, use the Highlight button to ask PAD to find the element right now and draw a box around it. If it highlights correctly, the selector is actually fine and your problem is a timing or application-state issue. If it highlights the wrong element, you know the selector is ambiguous. If it can't find anything, you know the selector is broken.
Remove selector levels from the top down, starting with the window level. After each removal, click Highlight to see if PAD can still find the element. When you find the level that's causing the failure, focus your fix on that level's attributes.
With the selector editor open and the application running, use the Recapture element option to capture the element fresh and compare the old attributes against the new ones. Differences are your culprits. This is dramatically faster than guessing.
Warning
Don't just re-record over a broken element without understanding why it broke. If the root cause is a dynamic attribute (like a generated ID), the new recording will capture the same unstable attribute and break again at the next session. Fix the type of problem, not just the instance.
Once you think your selector is fixed, test it before re-running the whole flow. In the designer, right-click the action that uses the element and choose "Run from here" with a single-step so you can observe just that action's behavior. This avoids having to run 20 setup steps every time you iterate.
| Failure Pattern | Symptom | Fix |
|---|---|---|
| Version number in window title | Breaks after every app update | Use Starts with operator on stable prefix |
| Dynamic ID in web app | Breaks between sessions | Remove ID attribute, use tag + text instead |
| Tab order changed | Wrong element found | Add parent container to disambiguate |
| Application in wrong state | Element not found | Add Wait for UI Element before action |
| Control renamed in localized version | Element not found on different-language machine | Use AutomationId instead of Name |
| React/Angular re-render | Stale reference | Use Starts with on class, remove hashed suffix |
Timing failures look exactly like selector failures: the action says it can't find the element. But if you manually walk through the flow slowly enough, it works fine. The element exists — it's just not there yet when the action fires.
PAD's default timeout for UI element actions is short (typically 10 seconds), and it doesn't distinguish between "element doesn't exist" and "element doesn't exist yet." For dynamic applications — anything with AJAX loading, server-side rendering, or heavy JavaScript — you need to explicitly wait.
The right tool is Wait for UI element on screen (found in the UI Automation action group). Place it before any action that targets a dynamically appearing element:
Action: Wait for UI element on screen
UI Element: [InvoiceEntry_SubmitButton]
Wait for element to: Appear
Timeout: 30 seconds
On Timeout: Fail (or continue and handle in error block)
This is especially important in multi-step form workflows: after clicking "Next," wait for the next page's first element to appear before trying to interact with it. Doing otherwise means your flow races ahead and tries to click elements that haven't rendered yet.
For applications that use loading overlays (a spinner or grayed-out panel that appears during processing), add a second wait:
Action: Wait for UI element on screen
UI Element: [LoadingOverlay]
Wait for element to: Disappear
Timeout: 60 seconds
Now your flow waits for both the overlay to appear (confirming the operation started) and disappear (confirming it completed) before proceeding. This pattern is far more reliable than fixed Wait delays, which are both brittle and slow.
Key insight
Fixed delays (Wait 3 seconds) are always wrong in production RPA. They're either too short (breaks on slow days) or too long (wastes time on fast days). Wait for UI Element adapts to actual application behavior and is both faster and more reliable.
Let's put this together with a realistic scenario. Imagine you've inherited a flow that automates data entry into a legacy vendor management portal. The flow was recorded against a test environment but breaks in production because:
Vendor Portal - jsmith@contoso.comYour task: Repair the flow's selectors to be production-resilient.
Step 1: Fix the Window Title Selector
Open the UI Elements panel. Find every element whose Level 1 (browser window) matches the full title. Edit each selector:
Vendor Portal - jsmith@contoso.com to Vendor PortalClick Highlight for each one to confirm PAD can still find it.
Step 2: Fix the Dynamic Row Selector
Find the selector for the table row that PAD captured. It likely has a Level matching something like id: result_row_4f7a29.
id attribute at that levelinnertext Contains [VendorName] where VendorName is a variable holding the vendor you're searching forThis makes the row selector dynamically match the specific vendor, not the specific row position.
Step 3: Handle the Conditional "Update Vendor" Button
The button doesn't exist on page load — it only appears after selection. Add a Wait for UI element on screen action immediately after the "click vendor row" action:
Action: Wait for UI element on screen
UI Element: [VendorPortal_UpdateVendorButton]
Wait for element to: Appear
Timeout: 15 seconds
Then add the Click action targeting that same element.
Validation: Run the flow against a production vendor record. Then run it against a different vendor to confirm the dynamic row matching works. Check that the wait action properly handles both fast and slow server responses by deliberately testing on a throttled network connection if possible.
"My selector works in the recorder but fails at runtime"
The most common cause is the recording session leaving the application in a state (like a specific tab being active) that doesn't exist at the start of a fresh automation run. Add setup steps that explicitly navigate to the correct state, and use Wait for UI Element to confirm each state transition.
"The selector finds the wrong element" Your selector is not specific enough. Open the selector editor and add more attributes at the ambiguous level, or add an additional parent level to narrow the context. Use Highlight to verify before saving.
"Adding the AutomationId makes it fail" Some applications expose unreliable AutomationIds that change with every instantiation (common in older WPF apps). Counterintuitively, removing the AutomationId and relying on Name plus Type is more stable. Test both and use whichever Highlight consistently shows the right element.
"My flow fails only on some machines but not others" Check for class name attributes that include internal versioning strings (common in Windows Forms apps). These change between framework versions. Switch to AutomationId or Name-based matching. Also check window DPI settings — on high-DPI displays, some applications render differently, which can affect element hierarchy.
"I have to re-capture elements after every application update" You're relying on too many fragile attributes. Systematically review every selector in your UI Elements panel and prune anything that looks like a generated value, version number, or position index. Invest the time once; save yourself from recurring maintenance.
If you're building flows that interact with Excel alongside other applications, the same selector stability principles apply — see Automating Excel with Power Automate Desktop: Reading, Writing, and Running Macros for Excel-specific element patterns. And for broader RPA architecture patterns, including how to structure flows that interact with legacy systems where UIA support is minimal, Desktop Flows: Automate Legacy Applications with RPA in Power Automate covers the full toolkit.
UI elements and selectors are the foundation that everything else in RPA sits on. A well-designed flow with fragile selectors is an unreliable flow. The skills you've built here — reading selector hierarchies, understanding attribute reliability, applying partial matching and wildcards, debugging systematically, and using wait-based timing — are what separate automations that need constant care from ones that run unattended for months.
The core principles to carry forward:
Your immediate next steps: audit the selectors in any existing flows you own. Identify every attribute that includes numbers, hashes, or version strings, and replace them with partial-match alternatives. Run your flows with deliberate application slowness to expose timing gaps, and add wait actions to close them.
Once your selectors are solid, you're ready to build more complex automation patterns. The next logical frontier is Web Automation in Power Automate Desktop: Browser Actions, Form Filling, and Data Extraction, which builds on stable selectors to create full end-to-end web workflows. And when your flows grow to the point where you need to coordinate multiple sub-flows with shared logic, the patterns in Variables, Lists, and Data Tables in Power Automate Desktop will help you manage state cleanly across that complexity.