A sidebar navigation entry that a product rename moved out from under the suite
Ten scenarios, one stale label: "Fleet" became "AGENTS"
What makes it hard
Ten of ten scenarios in a module failed, all on the same navigation step. The heal log makes the cause unambiguous:
{
"action": "click",
"originalSelector": "getByRole('link', { name: 'Fleet' })",
"candidates": [
{ "strategy": "role", "count": 0 },
{ "strategy": "text-exact", "count": 0 },
{ "strategy": "text-partial", "count": 0 }
],
"succeeded": false
}Every strategy returned zero. The string was not on the page in any form — the section had been renamed. Self-healing cannot invent a name that does not exist; it recovers from a moved element, not from a deleted one.
Two details made it worse than a rename:
- the project YAML still carried
title: "Fleet"for the module, so the report labelled the failure with a name nobody in the product would recognise - the destination was no longer a link at all. It had become a collapsible section header showing a count. Even the correct name would not have satisfied a click.
Ten heal attempts, zero heals, across the whole run. Which is the one piece of good news: no pass in that run was carried by a healed selector.
How to cover it
This is the locator-priority rule presenting its bill. Visible copy is the most volatile thing on a page — it changes for a product decision, a marketing decision or a translation, none of which anyone tells the test team about. A test id survives all three; getByRole('link', { name: 'Fleet' }) survives none of them.
Concretely:
- Move navigation onto a test id and ask for
data-testid="nav-<key>"per row. Navigation is the single highest-fan-in locator in any suite; it is worth an attribute. - Give the heal context every handle you have, and one you do not yet have:
readonly agentsNav = this.heal.locator(
this.page.getByTestId('nav-agents'),
{ role: 'link', name: 'Agents', testId: 'nav-agents', description: 'agents nav entry' },
);- Read the heal report before you re-run anything.
sdods heal report --lastsummarises the original selector, the strategy that won, the score and the suggested locator. Zero heals over ten attempts told the whole story in one command;--write-historypersistsheal-history.jsonso future runs bias toward strategies that have actually worked on this application. - Keep
sdods.project.yamlin step. A moduletitle:that no longer matches the product turns every report into a translation exercise. - Re-check the scenario's intent, not only its selector. An expandable section header is not a link, so the fix here was not a new name — it was a different interaction.
analyze_locators over the MCP server, or the reviewer agent, will find the CSS-only and copy-bound locators in a project before the next rename does.
Scenario sketch
@ui @smoke
Scenario: The agents section is reachable from the workspace shell
Given I use a leased user with role "member"
When I navigate to the "workspace" page
And I click the element with test id "nav-agents"
Then the page URL should contain "/agents"
And the element with test id "fleet-list" should be visiblesdods heal report --lastMore on the scoring and the probe order in self-healing.
Other use cases
- Testing an eight-screen auth funnel that has no test idsLogin, signup, email verification, 2FA, the device flow, OAuth consent, invitation acceptance and password reset
- A 403 that a shared cache is free to replayAuthorisation refusals on an API served through a CDN
- Plan caps, spending limits, and a gate that never firedBilling, usage and spending-limit routes behind plan entitlements
- A workflow canvas SDODS cannot dragA node-graph editor: 193 block types, edges, handles, sub-block inputs and subflow containers