Fallback selectors (self-healing)¶
UI elements sometimes change - a developer renames an AutomationId, or a new build ships
with a slightly different control hierarchy. Fallback selectors let Dolphin try alternative
criteria automatically before failing a test.
The problem¶
A locator breaks when the primary selector stops matching:
# Works today - AutomationId "btnSubmit"
btn = win.get_by_automation_id("btnSubmit")
# Next sprint: developer renamed it to "btnSave" - test fails
Adding a fallback¶
Pass a fallback list when creating the locator.
Dolphin tries the primary criteria first; if that times out, it tries each fallback in order:
btn = win.locator(
auto_id="btnSubmit",
fallback=[
{"auto_id": "btnSave"},
{"title": "Submit", "control_type": "Button"},
]
)
btn.click()
If the primary fails and auto_id="btnSave" matches, Dolphin clicks that element and logs
a self-healing event.
Image-based fallback¶
When UIA criteria all fail, fall back to template matching:
from dolphin_desktop import ImageLocator
btn = win.locator(
auto_id="btnSubmit",
fallback=[{"auto_id": "btnSave"}],
image_fallback=ImageLocator("templates/submit_button.png"),
)
btn.click()
The template image is matched against the live screen using OpenCV.
Requires pip install dolphin-desktop[vision].
Inspecting self-healing events¶
Last 3 self-healing event(s):
[2024-06-10 14:32:01] tests/test_submit.py::test_submit_form
primary: {'auto_id': 'btnSubmit'}
fallback: {'auto_id': 'btnSave'}
This shows which selectors degraded and helps you update them before they break completely.
Best practices¶
- Prefer
auto_idas primary - AutomationId is set by developers and is the most stable identifier. - Add a title-based fallback - titles change with locale but rarely disappear.
- Use image fallback as last resort - image matching is slower and more fragile than UIA.
- Review
selfheal-statsafter every release - a self-healed test is a warning, not a pass.
Using fallbacks in Page Objects¶
class FormPage:
def __init__(self, window):
self._win = window
@property
def _submit_button(self):
return self._win.locator(
auto_id="btnSubmit",
fallback=[
{"auto_id": "btnSave"},
{"title": "Submit"},
],
)
def submit(self):
self._submit_button.click()
Next steps¶
- Tracing and debugging - record traces to diagnose selector failures
- API Reference: Locator -
fallbackandimage_fallbackparameters