Good and bad locators
The same order summary twice: once built for testing, once without. Switch to version 2 to see which locators survive a redesign.
A test finds an element by its locator. If the locator depends on things that change — the layout, the text, generated class names — the test breaks with the next release, even though nothing is wrong with the application. Map both sections below, run your tests, then switch to version 2.
data-test attributes did not change; the layout, the texts, the order of the items and the
generated class names did.
A. Built for testing
An id on every single element, data-test on the ones that repeat.
John Smith · john.smith@example.com
Your order 1042
Paid| Item | Qty | Price |
|---|---|---|
| Ceramic mug | 1 | €9.50 |
| Paper filters | 3 | €6.00 |
| Coffee beans, 1 kg | 2 | €24.00 |
Total €39.50
B. Built without testing in mind
Generated class names, nested divs, text only, and an id that changes on every load.
Locators, from the most to the least stable
| Locator | Why |
|---|---|
id, data-test | Made for finding the element; a redesign leaves them alone. data-test where an id does not fit, e.g. rows of a list. |
name, label, ARIA (role, aria-label) | Part of the form and of accessibility, so they change rarely. |
| CSS class | Made for the look; a new design changes it. |
| Text | Gets reworded and translated, and is rarely unique: both sections above have a "Cancel" button, and a locator by text finds the first one. |
Position, absolute XPath (/html/body/div[2]/…) | Breaks when anything above or around the element moves. |
Generated id or class (input-3f9a1c, css-1x9f3k) | Changes with every build, or even with every page load. |
What to practise
- Map section A: the order, the customer, the items, the total, the note and the two buttons. Write a test that saves a note and cancels the order.
- Map section B the same way, with whatever locators you can find, and run the same test.
- Switch to version 2 and run both again: A still passes; see which locators of B break, and why.
- Reload the page and look at the id of B's note field. Then find the best locator B allows (an ARIA role, a label, text inside a row) — and notice that B cannot be made stable without changing the application. Ask the developers for an
idor adata-test.
Page objects: guide, section 1.
Tested in VAF-Demo
A test of this page from VAF-Demo: the whole VAF chain, from the page to the element, is in the test.
[TestCase("/practice/mapping")]
[TestCase("/practice/mapping?version=2")]
public void StableLocatorsSurviveTheRedesign(string path)
{
SharedContext.LetsOpen(path);
SharedContext.App.Should().HaveSection<MappingPage.Order>()
.That.Should().HaveElement<MappingPage.Order.Total>()
.That.AsLabelText().Should().HaveValue("€39.50")
.AndAlso().Should().HaveElement<MappingPage.Order.Note>()
.That.AsTextInputField().LetsInsert("Ring twice")
.AndAlso().Should().HaveElement<MappingPage.Order.SaveNote>()
.That.AsButton().LetsClick()
.AndAlso().Should().HaveElement<MappingPage.Order.Cancel>()
.That.AsButton().LetsClick()
.AndAlso().Should().HaveElement<MappingPage.Order.Status>()
.That.AsLabelText().Should().HaveValue("Cancelled");
}
On GitHub (opens in a new tab)
-
Page object
—
MappingPage.cs: the locators of this page -
Fragile page object
—
MappingPageFragile.cs: the same screen mapped the way not to, with the reason on every locator -
Native tests
—
MappingTests.cs: the whole VAF chain in each test - The whole project and its README Latest test run (GitHub Actions)