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.
div > div:nth-child(3) > button.btn-primary لا يختبر الزر — بل يختبر أن أحدًا لم يعد ترتيب HTML، وسيفعلها أحدهم في الدورة القادمة، لأسباب لا صلة لها بك.Best to worst, and why
| Locator | Breaks when | Verdict |
|---|---|---|
| By role and name — button "Sign in" | The button is renamed or removed | Best — that IS the change |
| By label text — the field labelled Email | The label changes | Best for form fields |
| By test id — data-testid="submit" | Someone deletes the attribute | Good, when there is no visible name |
| By CSS class — .btn-primary | Any restyle | Poor — classes are for appearance |
| By position — nth-child(3) | Anything above it moves | Worst — 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();
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.
getByRole('button', { name: 'Delete' }) تسعة أزرار حذف في جدول، فإما أن ترسب الأداة أو تختار واحدًا — وإن اختارت، حذف اختبارك صفًا عشوائيًا ونجح. فضيّقه بحاويته: جد الصف أولًا ثم الزر بداخله. فالمحدِّد الملتبس ليس مشكلة صغيرة؛ بل اختبار يفعل شيئًا غير ما يقول.getByText('Sign in') شيئًا لحظةَ يعرضه أحدهم بالعربية — وسينجح محليًا ويرسب في خط الإنتاج إن جريا بلغتين مختلفتين. استخدم دورًا مع اسم، أو معرّف اختبار، ودع الكلمات الظاهرة تتغير.Check yourself / اختبر نفسك
1. Why is finding a button by role and visible name better than by CSS class?
A class is an appearance decision and changes freely. A visible name is part of the product: if it changes, a user is affected, so a test failing there is reporting something real rather than being brittle.
2. A locator matches nine Delete buttons in a table. What is the risk?
Deleting an arbitrary row and passing is worse than failing, because the result is trusted. Narrow to the container first — find the row, then the button within it.
3. Why avoid locating by visible text on a bilingual site?
The element is the same; only its words differ. A locator tied to English words is really a locator tied to one language, which is a failure waiting for whichever run happens to use the other.
Score / النتيجة: 0 / 3