A recorded control does not become a screenshot or a set of coordinates. It becomes a locator, the accessibility tree's own fields, proven against the live desktop before it is written down. This page follows one control from the point you indicate it to the target a run reads.
#The locator
A desktop locator carries three fields. name is the control's accessible Name, what the user sees on screen.
automationId is the application's own stable hook. controlType is what the control is, such as Button, Edit
or Window. There is no coordinate field, no rectangle, no image and no positional index anywhere in it, because none
of those is an identity: a coordinate survives no resize, no DPI change and no theme change.
At least one of name and automationId must be present. A control whose accessible Name is its value, such as a
display, a status line or a counter, is addressed by its AutomationId and never by that moving name.
{
"controlType": "Button",
"automationId": "num8Button",
"name": "Eight",
"within": { "controlType": "Window", "name": "Calculator" }
}
within is the window the control was recorded in, carried on every recorded target. A target's name is matched
exactly, never case folded. A scope's name is matched as a substring, because a browser moves its active tab into the
window title and an exact scope would refuse a window that is plainly up. A step recorded in a window with neither a
Name nor an AutomationId is refused, because this recorder does not emit an unscoped target.
#Anchors
after addresses the first element of the target's own controlType following a named anchor, in document order
inside the anchor's parent. It exists for a control that cannot name itself: a value with no AutomationId whose name
changes every run, or a web grid that exposes no name at all. The anchor is proved and the value is read and the
value never enters the locator.
{
"controlType": "Text",
"after": { "controlType": "Text", "name": "This is your unicorn name" },
"within": { "controlType": "Document", "name": "Edge" }
}
The relation skips siblings until one matches the target's type. That was measured rather than assumed: Edge injects an image between the label and the value, so a naive next sibling would have addressed the wrong element while looking entirely reasonable. An anchor that is itself anchored is refused, because a chain of relations is a path and a path is a position.
#The chain from the window to the control
Each recorded control also carries a selector chain, top down, from the top of the window to the control itself. Every level holds whatever that ancestor has: a class, a name, an automation id. The chain is what makes a control that names nothing addressable, because an address is a path where "the first Table in this window" is a hope. The outermost level of the chain is the scope and for a browser it prefers the page Document, since the browser's own chrome sits above the page and a scope that names the browser is refused.
The chain is carried, not yet resolved. Nothing downstream walks a path today, so it is evidence for the next slice rather than a claim that a path target is addressable now.
#Save to bindings
Every capture has a destination, the recorder's answer to UiPath's "Save to". A read produces a string and a table
produces a DataTable and the defaults follow UiPath's own auto names: strResult for a read and dtResult for a
table. You can rename either. A choice carries no binding, because it acts on the application rather than producing a
value.
The emitted automation binds the read to that name:
const strResult = await ctx.desktop.text({
controlType: 'Edit',
automationId: 'customerName',
within: { controlType: 'Window', name: 'Customer record' },
});
ctx.log.info('read the customer name', { strResult });
A table capture is a TODO for now, because the facade has no table verb yet, but the TODO names the binding so the destination is already decided.
#Why a recorded target is safe to re-run
The prover resolves the declaration against the live desktop twice and requires exactly one control each time, with the same identity across both walks: process, automationId, controlType and path. Uniqueness alone can be a coincidence and a count can agree while the control has moved, so the second walk is compared on identity rather than on a number. The engine's match is exact on every declared field, never partial and never case folded.
The proof travels with the target, so a reader can always ask where a claim came from. A definition that cannot be
addressed at all, with no name, no automationId and no relation, is refused when the target file is built, because
such a definition can only fail as a search of the whole desktop. At run time an ambiguous locator is still refused
as DESKTOP_MULTIPLE_MATCHES rather than resolved by taking the first.
Two limits are worth knowing. The prover cannot tell whether a unique locator is semantically right, only that it is unique and stable. And a proof holds for the tree state it was made in, which is why re-proving it in the gate is what catches a change to the application.