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.

A. Built for testing

An id on every single element, data-test on the ones that repeat.

Order #1042

Paid

John Smith · john.smith@example.com

ItemQtyPrice
Coffee beans, 1 kg 2 €24.00
Ceramic mug 1 €9.50
Paper filters 3 €6.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.

Order #1042
Paid
John Smith · john.smith@example.com
Coffee beans, 1 kg
2
€24.00
Ceramic mug
1
€9.50
Paper filters
3
€6.00
Total €39.50
Note for the courier

Locators, from the most to the least stable

LocatorWhy
id, data-testMade 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 classMade for the look; a new design changes it.
TextGets 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

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)