Lesson 9 / الدرس 9

Finding things the way a person would / إيجاد الأشياء كما يجدها الإنسان

How you find an element decides whether your test survives next month. Locators tied to the page's structure break when anyone touches the markup; locators tied to what the user sees break only when the product genuinely changes.

طريقة إيجادك للعنصر تقرر: أينجو اختبارك الشهر القادم؟ فالمحدِّدات المربوطة ببنية الصفحة تنكسر حين يمسّ أحدٌ الوسوم؛ والمربوطة بما يراه المستخدم لا تنكسر إلا حين يتغير المنتج حقًا.

This is the single largest source of unreliable browser tests, and it is entirely under your control. A test that finds a button by div > div:nth-child(3) > button.btn-primary is not testing the button — it is testing that nobody has rearranged the HTML, which somebody will, next sprint, for reasons that have nothing to do with you.

Best to worst, and why

LocatorBreaks whenVerdict
By role and name — button "Sign in"The button is renamed or removedBest — that IS the change
By label text — the field labelled EmailThe label changesBest for form fields
By test id — data-testid="submit"Someone deletes the attributeGood, when there is no visible name
By CSS class — .btn-primaryAny restylePoor — classes are for appearance
By position — nth-child(3)Anything above it movesWorst — tests the markup, not the product

The top two are best because they fail for the right reason. If a button's visible name changes from "Sign in" to "Log in", a user is affected — a test that notices is doing its job, not being brittle.

// Brittle: three ways to break without the product changing at all.
await page.locator('div.card > form > button.btn.btn-primary').click();
await page.locator('#root > div:nth-child(2) input').fill('sara@example.com');

// Durable: the same two elements, found the way a person finds them.
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByLabel('Email').fill('sara@example.com');

// And when there is genuinely nothing visible to go on:
await page.getByTestId('basket-count').click();
The durable pair reads like a description of what a person does, which is not a coincidence — it is the point. It also means the test doubles as documentation, because the line says what the user sees.

A locator that finds two things is a bug in your test

If getByRole('button', { name: 'Delete' }) matches nine delete buttons in a table, the tool will either fail or pick one — and if it picks one, your test deletes a row at random and passes. Narrow it by its container instead: find the row first, then the button inside it. An ambiguous locator is not a small problem; it is a test doing something other than what it says.

Check yourself / اختبر نفسك

1. Why is finding a button by role and visible name better than by CSS class?

2. A locator matches nine Delete buttons in a table. What is the risk?

3. Why avoid locating by visible text on a bilingual site?