Bromium
Bromium is a Python extension for Windows UI Automation, implemented in Rust. The workspace also includes UI Explore, a desktop tree inspector and XPath tool.
Key Features of python library
- Query a cached UI tree with on-demand, incremental coverage repair
- Launch an application or activate a matching element in an existing window
- Request normal closure of an element supporting the UIA Window pattern
- Interact with UI elements on the current desktop
- Get screen context information (size, scaling, etc.)
- Capture the primary monitor to a PNG file
- Get cursor position coordinates
- Retrieve UI element information at specific coordinates
Coordinate lookup generates reusable XPath candidates such as
/Pane/Window[@Name='resume.txt – Notepad']//Button[@Name='Einstellungen'].
It prefers a unique nonempty Name, then AutomationId, within a semantic window anchor.
The same preference applies to the window anchor. AutomationIds can change across
application restarts (for example in Teams); names can still vary with language or UI state. No generated
automation locator contains runtime IDs, native handles or NodeKey.
element = driver.get_element_by_coordinates(x, y)
print(element.xpath, element.xpath_status) # usually "candidate" (unverified)
# Explicitly expensive: fresh full desktop scan within driver.timeout_ms.
verified = driver.get_element_by_coordinates(x, y, verify_xpath=True)
assert verified.xpath_status == "snapshot_unique"
verify_xpath=True scans the entire exposed desktop (not just window_title),
keeps the original target identity, and fails if it disappears, full coverage
cannot be obtained, or no unique reusable locator is available. It never silently
returns an unverified result. Increase timeout_ms explicitly for slow systems.
Uniqueness is for the captured state, not a guarantee about future sessions:
window titles, language, AutomationIds and layout still need to remain compatible.
Structural fallback below a window requires enumerated siblings; indistinguishable
windows raise a locator-generation error instead of using window positions.
Coordinate lookup supports popup/menu HWNDs nested inside an application's UIA
tree as well as separately exposed menus. A bounded UIA point worker acquires the
hit element and immediate children along its ancestry, without descending into
unrelated branches. These steps share the query deadline; no desktop-wide or
whole-window descendant capture is required. Dismissal or a
native window change during the query raises StaleTreeError, rather than
returning an unrelated control underneath. Menus may inherit their native owner's
title scope; ordinary owned dialogs retain their own scope.
For same-window menu overlays (including VS Code), lookup also checks the current
focus ancestry for an exposed menu covering the point in the same native window.
It inspects only that active menu branch, accepting any descendant control type
(for example a checkbox), rather than trusting a provider hit on content behind
the menu. Focus identifies the active branch, not necessarily the returned item.
The exact focused occurrence may override a contradictory IsOffscreen=true
flag inside an otherwise visible menu; this is not a global visibility override.
Overlapping sibling targets and unstable identities fail conservatively. The
selection is repeated before publication, within the original query deadline,
with a 256-node menu traversal limit and 64-level ancestry/depth limits. Without
that active-menu evidence, the existing provider/native-popup route is retained.
Point lookup certifies the anchored region, not the entire desktop. It does not
force or wait for unrelated desktop membership refreshes. For an uncached window,
UIA ancestry can add that window without enumerating desktop siblings. This does
not clear unrelated dirty regions or weaken XPath query coverage. The point
worker is separate from the background subtree worker. A blocked point-provider
call can still exhaust the deadline; COM calls cannot be forcibly interrupted.
Ambiguous ancestry still raises StaleTreeError. A point-discovered XAML Popup
omitted by its parent's child enumeration may be retained when the popup's parent
and the final hit-to-desktop identity chain are revalidated. The parent is marked
as partial membership, not completely observed; the popup does not certify global
XPath coverage. Locators through that parent are semantic candidates, never
invented sibling positions. A later complete capture can remove
the popup, and live actions retain strict identity checks (they may still fail if
the provider cannot resolve the element). Other missing links remain errors.
Partial/dirty coverage produces xpath_status="candidate"; uniqueness across
uncaptured descendants is not asserted. Internal/debug snapshot locators still
use NodeKey, independently of reusable automation locators.
Enable Debug logging for point_lookup diagnostics: screen coordinates, native
root handle, class and owner chain, selected cache/repair route, UIA ancestor
identities when needed, and the final hit or failure. Provider discovery runs on
one bounded worker; a deadline bounds the caller's wait but cannot cancel an
individual COM call already in progress.
Transient invalidation during point-capture publication permits at most three
acquisition attempts, with 25/50 ms backoffs, within the original query deadline.
Retries remain narrow and pin the original target identity and native window;
they never switch to a different control. point_retry Debug records explain
each invalidation and retry decision. Provider failures, timeouts, and missing or
ambiguous ancestry are not automatically retried.
Debug point_invalidation records the target, capture/current epochs,
invalidation-history floor and the overlapping identity with its epoch and repair
kind. Debug point_capture_timing logs window/target acquisition and each ancestor
capture's begin/end, identity/depth, elapsed time, child count, cumulative captured
node count and errors. A begin record without its matching end at the query
deadline identifies unfinished work; these timings do not extend the deadline.
point_popup_branch records a popup admitted using verified upward ancestry.
For missing/ambiguous ancestry, Debug point_ancestry_mismatch records the
expected occurrence, qualified ancestry, captured parent and immediate children,
and original-versus-cached runtime IDs and metadata from capture (bounded to 128
records per parent). A separately labelled, later read-only probe compares raw
and control-view parent/child relationships, native handles, runtime IDs before
and after BuildUpdatedCache, names, types, bounds and class/framework/provider
information. This probe has a cooperative 500 ms budget within the original
deadline, at most 256 provider operations and 32 children per view. Skips and
incomplete enumeration are explicit. A running COM call cannot be cancelled;
the caller still waits only until its deadline. Diagnostic evidence never
repairs membership or authorizes an action; popup admission uses separate live
identity checks, not the diagnostic samples. No separate diagnostic tool is needed.
For menu hit-testing discrepancies, Debug/Trace also emits point_hit_probe
records with a per-query query ID:
initial_raw/initial_control_view: the original UIA hit and its normalized control-view element;verification_raw/verification_control_viewrepeat the comparison at the existing end-of-capture check.focus: keyboard focus sampled during lookup (read-only, never moved).initial_menu_reconciled/verification_menu_reconciled: the hit after active menu reconciliation.point_menu_overlayrecords the active menu identity, supporting focus identity and traversal count when that route is selected.cached_menu_candidate: captured Menu/MenuItem/MenuBar entries covering the cursor in the selected window, with ancestry, bounds, capturedIsOffscreen, metadata age and child coverage. These can include retained older observations.cached_menus: scan counts and truncation, including when no candidate matches.
Live records include runtime ID, name/type, handle, bounds, control-view membership,
class/framework/provider and IsOffscreen. Metadata is batched through a separate
diagnostic cache request and is never used to replace the selected element.
Extra provider reads share a 500 ms
cooperative diagnostic budget within the query deadline. Errors and skipped reads
are explicit; a running COM call cannot be forcibly cancelled. The snapshot scan
uses no provider calls, visits at most 4,096 nodes, emits at most 16 candidates,
and has a 10 ms cooperative scan budget. None visibility means it was not
captured; IsOffscreen=false is not proof that an element is visually unobscured.
This instrumentation does not select candidates, refresh menu branches, or change
cursor-tracking behaviour. It is disabled below Debug level.
Coordinate lookup defaults to logical menu-heading discovery through known XAML
light-dismiss layers. The gate requires framework XAML, class PopupRoot, type
Button, and AutomationId Light Dismiss (not its localized name), a live
InputSiteWindowClass parent, and a focused visible flyout item in the same
application window. Only immediate menu bars and their headings are inspected;
the pointer must be outside the active dropdown and over a unique exposed heading.
Bounds, visibility and occurrence ancestry are validated live. The overlay and
active-menu identities must survive final verification and invalidation retries.
This bounded search shares the query deadline and never closes the menu or
captures a whole window. Ambiguity and inconsistent identity fail closed.
The returned heading is a logical locator target, not necessarily the layer
that receives a physical click while the menu is open. To disable this specific
reconciliation, use driver.get_element_by_coordinates(x, y, resolve_xaml_overlays=False).
This does not disable the existing menu/Teams reconciliation or bypass freshness
checks; a provider's inconsistent dismissal-overlay ancestry can still raise
StaleTreeError. Debug point_xaml_dismiss records the provider hit, logical
target and active menu; initial_xaml_reconciled / verification_xaml_reconciled
record both capture stages.
Coordinate lookup also reconciles an empty, unnamed Chrome_WidgetWin_* pane
inside the observed TeamsWebView window structure with a unique point-covering
interactive control in its sibling Chrome document. Cached identity paths only
prioritize the search; current provider properties, visibility, downward membership
and upward occurrence ancestry determine the result. Overlapping candidates,
provider errors and incomplete bounded searches fail closed with StaleTreeError.
The search streams point-covering branches (at most 256 nodes, 64 levels and 4,096
provider calls per pass), shares the original query deadline, and is repeated at
final hit verification. It does not capture a whole desktop or select a control
by its name or cached rectangle. Other window classes retain their existing route.
Debug point_host_reconcile records selections; initial_host_reconciled and
verification_host_reconciled identify the results in the hit diagnostics.
For unnamed Chromium host panes (Pane, empty name, Chrome_WidgetWin_* class),
Debug/Trace additionally emits point_host_probe using the same query ID. It
records live raw-view children of the hit and point-covering branches of its
already discovered window, including sibling WebView content. Node records include
runtime ID, native handle, name/type, automation ID, class/framework/provider,
bounds, visibility and an ancestry path of child indices (diagnostic only).
These reads have an additional 250 ms cooperative budget within the original
query deadline, split between host and window surfaces, with at most 64 nodes,
256 calls per surface. Wrapper traversal is limited to 8 levels. A discovered
Chrome Document grants up to 16 further levels within that document, sharing
the same time/node/call budgets; nested documents do not reset this allowance.
document_entry records the transition and remaining budget. truncated and
surface_end records identify wrapper/document depth, node, call or deadline
limits, provider errors and excluded/unverified processes.
Non-covering or unknown-bounds branches are
logged but not deepened. Skips, errors, pruning and limits are reported explicitly;
a running provider call cannot be cancelled. Cached overlapping candidates include
ancestry, metadata age and child coverage (4,096 nodes / 16 candidates / 10 ms).
Neither scan proves absence or occlusion. No desktop scan or selection change is
performed, and no separate tool is needed. Names/provider details may contain
application data; review logs before sharing.
Installation
python -m pip install bromium
Usage
Cached trees and query deadlines
Startup observes desktop/window membership only. Descendant coverage is acquired
on demand and then maintained by incremental property, child-list, and subtree
updates. Set window_title to avoid acquiring unrelated application contents.
The cache uses UIA's control view; it is not a simultaneous snapshot of all providers.
XPath, filtering, membership and point queries wait for relevant dirty or missing
coverage within the query deadline, including when a cached match already exists.
bromium.StaleTreeError extends TimeoutError and exposes reason, scope,
revision, and coverage. A stale query is not a definitive empty result.
Malformed XPath raises ValueError. A singular clean no-match retains
ElementNotFoundError; plural clean no-match returns [].
import bromium
driver = bromium.WinDriver(timeout_ms=5000, window_title="My application")
try:
button = driver.get_element_by_xpath("//Button[@Name='Save']", timeout_ms=500)
except bromium.StaleTreeError as error:
print(error.reason, error.coverage)
print(driver.tree_status)
cached = driver.snapshot_elements() # explicitly permits incomplete/stale data
timeout_ms=0 performs no waiting and raises StaleTreeError if the required
coverage is not usable. A query timeout does not restart for each capture, and
does not cancel useful shared work. Python's GIL is released during waits.
len(driver), iteration, element_count, formatting and
snapshot_elements() describe cached data without provider acquisition. Consult
tree_status for pending/incomplete coverage; use collection queries when a
validated result is required.
refresh(window_title="Other application") persists the title on success only.
Calling refresh() retains the configured scope; set driver.window_title = None
to clear it. refresh_region(element, timeout_ms=500) repairs only that cached
region without changing scope. Manual unscoped refresh explicitly requests all
windows, acquired separately; ordinary repair never walks desktop descendants
as a fallback. Property-only changes read one cached property bundle. Ambiguous
layout/geometry events conservatively repair the affected subtree because child
rectangles cannot safely be inferred by translation.
Missed-event validation currently uses a 2-second desktop membership age and 30-second relevant coverage age. One capture worker bounds blocked-provider capacity; a provider that never returns can leave later queries stale. These are initial policies, not guarantees about unreported changes or target latency.
Actions, snapshots and concurrency
Element properties are snapshots: even after a refresh, an already-returned
object keeps its captured name and rectangle. Query again for updated properties.
Driver-created elements retain cached lifetime identity for action validation.
Actions release the GIL during resolution/provider work, then invalidate the
affected region. They remain synchronous and have no enforced execution timeout;
timeout_ms does not cancel a blocked action.
The public Element(...) constructor remains supported. A nonzero HWND is
validated against the runtime ID; without a HWND the legacy runtime-ID search is
used. These manually constructed objects do not own a driver cache and cannot
directly invalidate one. Prefer elements returned by a driver.
launch_or_activate_app() validates coverage before deciding whether to launch.
Discovery, process creation, polling, and activation share driver.timeout_ms.
Stale discovery raises StaleTreeError, malformed XPath raises ValueError, and
other deadline expiry raises TimeoutError. A native operation already started
may finish after caller expiry; timeout is not a promise that no app was launched
or focused. The configured title scope is preserved.
Use timeout_ms=5000 for five seconds, not 5. tree_timeout_secs controls manual
refresh and provider-job budgets (default 120 seconds); it does not extend query
deadlines. Startup itself has a 120-second shallow-membership budget.
Serialize calls on a shared WinDriver with a Python lock. Concurrent mutable
calls can raise PyO3's RuntimeError for an already-borrowed object; the API does
not promise simultaneous queries on one Python driver. Rust shares in-flight
repair internally. Separate drivers own separate services. Released GIL permits
other Python work, but does not make concurrent input to one application safe.
Quickstart
The following inspects the element under the cursor; clicking it is deliberately left commented out because the cursor may be over a destructive control.
import bromium
# Initialize logging (optional, but helpful for debugging)
bromium.init_logging(log_level="Info", enable_console=True)
# Create a WinDriver — observes window membership; descendants remain lazy
driver = bromium.WinDriver(timeout_ms=5000, window_title=None)
print(f"Cached elements (possibly incomplete): {len(driver)}")
# Get cursor position and find the element under it
x, y = driver.get_cursor_pos()
element = driver.get_element_by_coordinates(x, y)
print(f"Element at cursor: {element.name} ({element.control_type})")
# Look up an element by XPath (retries until timeout_ms if not found)
found = driver.get_element_by_xpath(element.xpath)
print(f"Found: {found.name}")
# Click the element
# found.send_click() # opt in only after checking the target
App Launch Example
Teams must be installed and locators must match its language and current UI. Discovery uses the XPath within the configured title scope, not the executable name or process ID. A locator that misses an existing app can therefore launch another instance. This example activates and inspects; it does not sign in.
import bromium
bromium.init_logging(log_level="Info", enable_console=True)
driver = bromium.WinDriver(timeout_ms=15000, window_title="Microsoft Teams")
print(f"Driver has {driver.element_count} elements.")
# Launch or activate an application
app_path = r"ms-teams.exe"
xpath = "//Window[contains(@Name,'Microsoft Teams')]"
try:
app_window = driver.launch_or_activate_app(app_path, xpath)
print(f"App window: {app_window.name}")
# Subsequent queries repair relevant coverage; no routine refresh is needed.
# Find a button without changing accounts or submitting credentials
login_btn = driver.get_element_by_xpath("//Button[@Name='Sign in']", timeout_ms=3000)
print(f"Sign-in control: {login_btn.name}")
except bromium.ElementNotFoundError:
print("Element not found — app may already be logged in.")
except bromium.StaleTreeError as e:
print(f"Discovery/query coverage is stale: {e.reason}")
except TimeoutError as e:
print(f"Launch/activation deadline expired: {e}")
except bromium.AutomationError as e:
print(f"Automation error: {e}")
Iterating & Filtering Elements
import bromium
driver = bromium.WinDriver(timeout_ms=5000)
# Collection protocols
print(f"Cached elements: {len(driver)}")
ok_xpath = '//Button[@Name="OK"]'
print(f"XPath exists: {ok_xpath in driver}") # may raise StaleTreeError
# Iterate cached elements only; missing coverage is not acquired
for elem in driver:
if elem.control_type == "Button":
print(f" Button: {elem.name}")
# Filter with find_elements (case-insensitive substring match)
buttons = driver.find_elements(control_type="Button")
edits = driver.find_elements(control_type="Edit", name="Search")
Closing a window
Element.close() -> None checks the resolved live element's Window pattern,
not just its cached control type. It requests closure of that element only;
there is no automatic ancestor selection, Alt+F4 fallback, or process termination.
launch_or_activate_app() can return a descendant if its XPath selects one, so
use a locator for the actual window when you intend to close it.
import bromium
driver = bromium.WinDriver(timeout_ms=5000, window_title="My test application")
window = driver.get_element_by_xpath("//Window[contains(@Name,'My test application')]")
try:
window.close() # Only use this on a window you intend to close.
except bromium.ElementNotFoundError as error:
print(f"Window identity no longer available: {error}")
except bromium.AutomationError as error:
print(f"Closure unsupported or provider failed: {error}")
Unsupported elements raise AutomationError with a Window-pattern diagnostic.
Provider lookup and close failures preserve their error details. Identity
resolution rejects removed/replaced targets with ElementNotFoundError.
A successful return does not guarantee that the window has disappeared or that
its process has exited; the application may display a prompt or handle the
request without exiting. This operation does not answer prompts or force closure.
For driver-created elements, affected coverage and the parent's immediate
membership are invalidated automatically. Subsequent queries repair them without
a manual refresh. The old Python object's properties remain snapshots. Manually
constructed elements retain the same compatibility limitations as other actions.
Like other actions, close() releases the GIL but has no enforced execution
timeout; driver.timeout_ms does not interrupt an already-running close call.
API Reference
Module-level Functions
These are the recommended entry points for logging configuration:
WinDriver construction logs the received parameters and effective timeouts at
Info level. Library-raised StaleTreeError, ElementNotFoundError,
AutomationError, and TreeConstructionError log their details at Error level.
Enable Debug logging to diagnose repair failures: each rejected commit records
its target identity, capture kind, revision, and rejection reason. Runtime-ID
collisions include the shared ID and both elements' names, control types, and
ancestry from the capture root. The one-based child positions distinguish even
identically named siblings; these diagnostic paths are not XPath locators.
The ancestry diagnostics use only captured data. At Debug level, the first
colliding pair with the same full occurrence identity in each capture also emits
tree_collision_probe records. Different-handle aliases are accepted without
additional probe calls. This
worker-side probe re-samples each original element's runtime ID before and after
BuildUpdatedCache, the updated element's ID and cached properties, and up to
16 immediate control-view children (no recursive capture). Records include names,
control types, native handles, class/framework/provider information and bounds.
This is a later diagnostic sample, not an atomic snapshot of the original
collision. Errors, deadline skips and truncated child lists are reported rather
than treated as valid empty data. Each occurrence has a one-second cooperative
budget, also bounded by the capture deadline and cancellation; an individual
provider call cannot be interrupted. The probe does not repair or publish data,
and makes no additional provider calls when Debug logging is disabled or no
collision is detected. The latest
failure is retained per dirty region until recovery or removal; unrelated
successful repairs do not erase it. Deadline errors include retained failures
relevant to the query, and tree_status includes outstanding region errors.
init_logging(log_path=None, log_level=None, enable_console=None, enable_file=None) -> None: Initialize logging.log_pathis a directory; defaults are%USERPROFILE%/.bromium, Info, console off, file on. Initialization opens a log file even if file output is disabled.get_version() -> str: Returns the current bromium version string.get_log_file() -> str: Returns the current log file path, opening a default file if needed; returns an empty string if that fails.set_log_file(log_file: str) -> None: Sets the full path for the log file. Creates parent directories if needed.get_log_level() -> str: Returns the current logging level as a string.set_log_level(log_level: str) -> None: Sets the logging level ("Off", "Error", "Warn", "Info", "Debug", "Trace").set_log_directory(log_directory: str) -> None: Sets a custom directory for log files. A timestamped file is created automatically.enable_console_logging(enable: bool) -> None: Enable or disable console logging.enable_file_logging(enable: bool) -> None: Enable or disable file logging.reset_log_file() -> None: Truncates the current log file; raisesValueErrorif none is set, orOSErroron an I/O failure.
Logging level arguments are strings (case-insensitive), not LogLevel objects.
Unrecognized strings fall back to Info. File/directory setters can raise
OSError; initialization reports file setup failures on stderr instead.
Exceptions
ElementNotFoundError: Raised when a UI element cannot be located (by xpath, coordinates, or runtime ID).AutomationError: Raised when a UI Automation operation fails (click, send_keys, etc.).TreeConstructionError(extendsTimeoutError): Initial desktop membership could not be acquired.StaleTreeError(extendsTimeoutError): Relevant query/refresh/discovery coverage remained stale at the deadline.
WinDriver
The main class for interacting with the Windows UI Automation tree.
Constructor
WinDriver(timeout_ms: Optional[int] = None, window_title: Optional[str] = None): Creates a shallow cache. None uses a 120000 ms query/launch budget; descendants remain lazy. Titles are case-sensitive substring filters.
Properties
| Property | Type | Access | Description |
|---|---|---|---|
timeout_ms |
int |
read/write | Default timeout in milliseconds for element lookup retries |
tree_timeout_secs |
int |
read/write | Manual refresh and subsequent provider-job budget, default 120 seconds |
tree_status |
str |
read-only | Scope and service-wide revision, dirty/unobserved counts, retained errors per dirty region |
element_count |
int |
read-only | Cached count in scope; may be incomplete or stale |
window_title |
Optional[str] |
read/write | The window title filter, if set |
Collection Protocols
len(driver)— cached count in scope; no acquisitionfor elem in driver— cached snapshot iterator; no acquisitionxpath in driver— repairs coverage withintimeout_ms, then returns a bool; clean absence is immediately False, invalid XPath and stale errors propagate
Methods
get_cursor_pos() -> tuple[int, int]: Returns the current cursor position as (x, y) coordinates.get_element_by_coordinates(x: int, y: int) -> Element: Returns the UI element at the given screen coordinates.get_element_by_xpath(xpath: str, timeout_ms: Optional[int] = None) -> Element: Validates relevant coverage and retries clean misses within one deadline. None uses the driver budget; zero waits for nothing and raises stale if coverage is unusable.get_elements_by_xpath(xpath: str) -> list[Element]: Repairs relevant coverage withintimeout_ms, then returns matches or[]; does not wait for a clean absence to become a match.find_elements(control_type: Optional[str] = None, name: Optional[str] = None) -> list[Element]: Filters elements by case-insensitive substring match on control type and/or name. Returns an empty list if none match.refresh(window_title: Optional[str] = None) -> None: Explicit membership reconciliation and scoped repair withintree_timeout_secs; a successful title override persists. None retains scope. RaisesStaleTreeErroron incomplete coverage.refresh_ui_tree(window_title: Optional[str] = None) -> None: Compatibility alias forrefresh.refresh_region(element: Element, timeout_ms: Optional[int] = None) -> None: Requests subtree repair by the element's occurrence identity (including native-ancestor context) in this driver's cache. None uses the driver query budget; scope is unchanged. Missing cached identity raisesValueError, incomplete repair raisesStaleTreeError.snapshot_elements() -> list[Element]: Returns cached elements in scope without waiting for coverage.launch_or_activate_app(app_path: str, xpath: str) -> Element: Launches or activates an application, returning the element matching the XPath.get_screen_context() -> ScreenContext: Returns information about all connected display screens.take_screenshot() -> str: Captures the primary monitor to a PNG in%TEMP%/bromium_screenshotsand returns its path; errors raiseAutomationError.pretty_print_ui_tree() -> None: Prints the cached scoped tree to stdout without acquisition.
Element
Captured UI Automation metadata with live action methods. Properties are read-only.
Element(name, xpath, handle, control_type, runtime_id, bounding_rectangle) is
also public; see the manual-element limitations above. Equality and hashing use
runtime ID, native window handle and, for driver-created handle-less elements,
the full parent identity chain back to the nearest native anchor, not cached
incarnation, name, rectangle or sibling position. Runtime IDs are not
permanent identifiers across removal/replacement.
The cached tree, live-element cache and action-resolution paths use the same
occurrence identity. The native anchor is qualified by its runtime ID and handle,
and every intervening handle-less parent contributes its identity. Repeated menu
subtrees under different popup hosts and repeated fields under different rows in
one native table remain distinct. Row reordering does not change occurrence
identity; removal or reparenting invalidates the old occurrence.
Public runtime_id and handle are unchanged; ancestor context
is internal and is not inferred by the manual Element(...) constructor.
A raw @RtID XPath may match multiple occurrences; use a generated locator or
ancestor-qualified XPath. @NativeWindowHandle alone cannot distinguish two
handle-less occurrences. Runtime-ID-only events with ambiguous identity reconcile the owning window;
manual runtime-ID-only actions reject ambiguous results. Missing-ID elements and
duplicate observations with the same full occurrence identity are preserved using
snapshot-only tokens, without erasing valid siblings or rejecting their capture.
These tokens are replaced on fresh observation; equality/hashing include them.
Their actions, and actions on handle-less descendants dependent on them, raise
ElementNotFoundError. Names, bounds and sibling positions never re-identify an
action target. Repair requests for snapshot-only occurrences target their nearest
resolvable parent instead. Handle-less live
resolution follows the qualified path from its native ancestor, rather than
accepting an arbitrary provider-ID match.
The XML IdentityStatus attribute distinguishes provider from snapshot-only
occurrences. Public runtime IDs are never fabricated to fill missing IDs.
When coordinate lookup rejects an ancestry element with an empty runtime ID,
Debug/Trace logging also emits point_msaa_probe with the point query ID and
ancestry depth. It samples the rejected element, its control-view parent, and up
to 16 immediate children: UIA metadata, legacy MSAA pattern availability, child
ID, name, role, state, and IAccessible availability. Comparisons report canonical
COM IUnknown equality and child-ID equality, plus UIA CompareElements, and
resample the rejected element afterwards. These are diagnostic evidence, not
proof of a persistent identity; no pointer or child ID becomes a locator.
The probe issues at most 256 calls, starts no further calls after two seconds or
the query deadline (whichever comes first), and records errors, skipped reads and
truncation. An in-flight provider call cannot be interrupted. It does not invoke
actions, modify the tree, scan the desktop, or change the missing-identity policy.
No separate tool is needed; enable Debug or Trace logging and repeat the lookup.
Debug tree_missing_identity diagnostics report captured ancestry, element name,
type, native handle, class/framework/provider and original-before, cached, and
original-after runtime IDs. tree_identity_diagnostic and
tree_snapshot_occurrence explain tolerated identity defects. Property/capture
failures other than missing identity remain errors; they are not treated as empty
child lists.
Properties
| Property | Type | Description |
|---|---|---|
name |
str |
The name of the UI element |
xpath |
str |
The XPath locator for this element |
xpath_status |
str |
candidate (unverified), snapshot_unique, provided, or unavailable |
handle |
int |
The native window handle (HWND) |
control_type |
str |
The UI Automation control type (e.g. "Button", "Edit") |
runtime_id |
list[int] |
The unmodified provider runtime ID; not necessarily unique, even with handle |
bounding_rectangle |
tuple[int, int, int, int] |
Bounding rectangle as (left, top, right, bottom) |
Methods
close() -> None: Requests closure using the live Window pattern. Unsupported capability/provider failures raiseAutomationError; unresolved/obsolete identity raisesElementNotFoundError. See Closing a window.send_click() -> None: Uses Invoke, else SelectionItem, else a mouse click at the live center. A failed supported pattern raises rather than trying another action.send_double_click() -> None: Sends a double-click at the element center.send_right_click() -> None: Sends a right-click at the element center.hold_click(holdkeys: str) -> None: Clicks while holding modifier keys ("ctrl", "shift", "alt").send_keys(keys: str) -> None: Sends keyboard input. Special keys use{}syntax (e.g.,{ctrl}{alt}{delete}). Groups with()(e.g.,{ctrl}(AB)for Ctrl+A+B).send_text(text: str) -> None: Sends plain text (uses Value pattern if available, otherwise simulated keystrokes).hold_send_keys(holdkeys: str, keys: str, interval: int) -> None: Sends keys while holding modifiers with a custom interval in milliseconds.show_context_menu() -> None: Shows the context menu for this element.
ScreenContext
ScreenContext() captures connected displays on construction. Its properties
are snapshots. Enumeration failure or no displays raises RuntimeError.
If no display is flagged primary, primary_screen falls back to the first display.
Properties
| Property | Type | Description |
|---|---|---|
primary_screen |
ScreenInfo |
The primary display screen |
screens |
list[ScreenInfo] |
All available display screens |
ScreenInfo
Information about a single display screen.
Properties
| Property | Type | Description |
|---|---|---|
id |
int |
Unique identifier associated with the display |
name |
str |
The display name |
friendly_name |
str |
The display friendly name |
x |
int |
The display x coordinate |
y |
int |
The display y coordinate |
width |
int |
The display pixel width |
height |
int |
The display pixel height |
width_mm |
int |
Width in millimeters (may be 0) |
height_mm |
int |
Height in millimeters (may be 0) |
rotation |
float |
Rotation in clock-wise degrees (0, 90, 180, 270) |
scale_factor |
float |
Pixel scale factor |
frequency |
float |
Refresh rate |
is_primary |
bool |
Whether this is the primary display |
LogLevel
Exported enum values: LogLevel.Error, LogLevel.Warn, LogLevel.Info, LogLevel.Debug, LogLevel.Trace, LogLevel.Off. Logging functions accept strings, not these enum instances.
Bromium (Legacy)
A static-method-only class that mirrors the module-level functions above. Prefer using bromium.init_logging(...) directly instead of Bromium.init_logging(...).
It additionally exposes Bromium.get_win_driver(timeout_ms=None, window_title=None),
equivalent to constructing WinDriver with those arguments.
Requirements
- Python 3.12 or higher
- Windows operating system
Local validation used CPython 3.12 on Windows x64. Package metadata permits additional interpreters/versions, but those are not all validated. A compatible wheel or working source-build environment is required.
Building from Source
To build the project from source, you'll need:
- Rust toolchain (cargo, rustc)
- Python 3.12+
- maturin (for building Python wheels)
# Clone the repository
git clone https://github.com/dangrazh/bromium.git
cd bromium
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install "maturin>=1.8,<2.0"
# Build the project using maturin
maturin build --release --manifest-path crates/bromium/Cargo.toml --out target/wheels
# Install in development mode
maturin develop --release --manifest-path crates/bromium/Cargo.toml
Run these commands from the workspace root, with MSVC build tools and the Windows
SDK available. From crates/bromium, omit --manifest-path instead.
See regression test instructions. Desktop tests
are opt-in; Teams interaction requires a separate opt-in. Local tests are not
representative slow-target performance acceptance. Do not use the workspace's
ci.ps1 for ordinary checks: it includes release/publication operations.
License
Apache License 2.0
Metadata
Release files for bromium 0.8.15
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bromium-0.8.15-cp312-cp312-win_amd64.whl | CPython 3.12 | CPython 3.12 | Windows x86-64 | Details for bromium-0.8.15-cp312-cp312-win_amd64.whl |
Release files / bromium-0.8.15-cp312-cp312-win_amd64.whl
| Download URL | bromium-0.8.15-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
2d566d3fe0acb6b461d43ce7fb8c53d32adafb19eb3a5fc886910010be0f8686
|
|
BLAKE2b-256 checksum How to use checksums |
97ad076500d4cb4b5b9561f8c9d95b907a20a3c16a4c90eac7a24624ee40216d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.14.1
|
Security for bromium 0.8.15
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.