Məzmuna keç

Modul 2: Lokatorların mənimsənilməsi

Pis lokator testlərinizi qeyri-sabit edər. Yaxşı lokator isə onları möhkəm saxlayır — stil dəyişikliklərinə, yenidən strukturlaşdırmaya və məlumat yeniləmələrinə dözümlü olur. Lokatorlar sonradan düşünüləcək bir şey deyil — dizayn qərarıdır.

🎬 Video tezliklə əlavə olunacaq

Lokatorlarla yeni tanışsan? Buradan başla

Brauzer avtomatlaşdırma alətindən heç vaxt istifadə etməmisənsə, bütün ideya bir dəqiqədə:

  • Lokator — Playwright-ı səhifədəki elementə yönəltmə üsuludur; onu “Login düyməsi” və ya “email sahəsi” üçün ünvan kimi düşün.
  • O tənbəldir: lokator yaratmaq elementi dərhal tutmur. Playwright hər əməliyyatda onu yenidən tapır (və hazır olmasını gözləyir) — lokatorları vaxt problemlərinə qarşı dayanıqlı edən budur.
  • Hər əməliyyat lokatorla başlayır: page.getByRole('button', { name: 'Log In' }).click()düyməni tap, sonra klik et deməkdir.
  • Lokator bir və ya bir neçə elementə uyğun gələ bilər. Əməliyyat edəndə Playwright onun dəqiq bir elementə uyğun gəlməsini tələb edir — bu strict rejimdir (bu modulda sonra izah olunur).
  • Elementi tapmağın bir neçə yolu var — rol, label, mətn, test-id və ya xalis CSS ilə. Ən yaxşısını seçmək məhz bu modulun öyrətdiyidir.

Bunları əzbərləmək lazım deyil — aşağıdakı nümunələrlə işlədikcə aydınlaşacaq.


Lokator strategiyası niyə vacibdir

Bölmə: “Lokator strategiyası niyə vacibdir”

Playwright-in hər əməliyyatı — click, fill, check, expect — bir lokatorla başlayır. Lokator Playwright-ə hansı elementə təsir etməsini deyir. Yanlış strategiya seçsəniz, ilk dizayner sprintindən sonra testləriniz uğursuz olacaq.

Real kod bazalarından üç zərif lokator nümunəsi:

// ❌ Zərif: dizayner layout-u dəyişdirdə CSS class dəyişir
page.locator('.btn.btn-primary.mt-2').click();
// ❌ Zərif: bir sarıcı <div> əlavə edildikdə sınır
page.locator('div > div > form > button').click();
// ❌ Zərif: dəqiq mətn uyğunluğu boşluq dəyişikliklərindən sınır
page.locator('text="Log In"').click();

Bunları davamlı ekvivalentləri ilə müqayisə edin:

// ✅ Davamlı: köməkçi texnologiyanın səhifəni necə gördüyünə uyğundur
page.getByRole('button', { name: 'Log In' }).click();
// ✅ Davamlı: <label> əlaqəsinə bağlıdır
page.getByLabel('Email').fill('user@test.io');
// ✅ Davamlı: açıq-aşkar, yalnız siz dəyişdirəndə dəyişir
page.getByTestId('submit-btn').click();

Playwright-in tövsiyə etdiyi lokator prioriteti:

  1. getByRole — ilk olaraq bunu üstün tutun
  2. getByLabel — form sahələri üçün
  3. getByPlaceholder — label olmadıqda
  4. getByText — keçidlər və görünən mətn üçün
  5. getByTestId — başqa heç nə sabit deyilsə
  6. CSS / XPath — son çıxış yolu

getByRole — tövsiyə olunan standart

Bölmə: “getByRole — tövsiyə olunan standart”

getByRole elementləri köməkçi texnologiyanın (ekran oxuyucuların) gördüyü kimi uyğunlaşdırır. Elementin gizli ARIA rolundan və əlçatan adından istifadə edir. Sadə dillə: rol elementin nə olduğudur (düymə, link, başlıq), əlçatan ad isə ekran oxuyucusunun onun üçün səsləndirdiyi insan-oxunaqlı etiketdir — adətən onun görünən mətni.

// <button>Log In</button>-ə uyğundur
page.getByRole('button', { name: 'Log In' })
// <a href="/register">Register</a>-a uyğundur
page.getByRole('link', { name: 'Register' })
// <h1>Welcome to TestMarket</h1>-a uyğundur
page.getByRole('heading', { name: 'Welcome to TestMarket' })
// "Category" labeli ilə əlaqəli <select>-ə uyğundur
page.getByRole('combobox', { name: 'Category' })

Ümumi rol xəritəsi:

HTML ElementiARIA Rolu
<button>button
<a href>link
<input type="text">textbox
<input type="checkbox">checkbox
<select>combobox
<h1><h6>heading
<img alt="...">img

name seçimi getByRole-u dəqiq edən şeydir. Onsuz getByRole('button') səhifədəki hər düyməyə uyğun gəlir — bu zəmanətli bir strict mode pozuntusudur (Playwright lokator birdən çox elementə uyğun gələndə əməliyyatdan imtina edir; tam izah aşağıda).

Əlçatan ad bunlardan gəlir:

  • Düymə / keçid mətn məzmunu: <button>Log In</button> → ad 'Log In'-dir
  • aria-label atributu: <button aria-label="Close dialog">X</button> → ad 'Close dialog'-dir
  • Şəkillərdə alt: <img alt="Company logo"> → ad 'Company logo'-dur
  • Form sahələri üçün əlaqəli <label>: <label for="email">Email</label> → ad 'Email'-dir
// ✅ Dəqiq — name seçimi yalnız bir düyməyə daraltır
page.getByRole('button', { name: 'Add to Cart' })
// ❌ Qeyri-dəqiq — bütün düymələrə uyğun gəlir, strict mode pozuntusuna səbəb olur
page.getByRole('button')

Yadda saxla: getByRole standartdır — elementi nə olduğuna (roluna) və əlçatan adına görə tap.


getByText — görünən mətnə uyğunlaşma

Bölmə: “getByText — görünən mətnə uyğunlaşma”

getByText verilmiş mətni ehtiva edən istənilən elementə uyğun gəlir. Keçidlər və müstəqil mətn elementləri üçün istifadə edin, lakin ehtiyatla istifadə edin — DOM-dakı istənilən elementə uyğun gəlir.

// <a href="/products/new">New Product</a>-a uyğundur
page.getByText('New Product')
// Dəqiq uyğunluq — oxşar mətnlər üçün qismən uyğunluğun qarşısını alır
page.getByText('Log In', { exact: true })
// Regex uyğunluğu — hərfə həssas olmayan və ya qismən uyğunluq üçün faydalı
page.getByText(/login/i)

getByText nə vaxt istifadə edilir: başqa rol daha təsviri olmayan keçidlər, ya da görünən olduğunu iddia etmək istədiyiniz müstəqil abzas mətni üçün.

getByText nə vaxt istifadə edilməz: form sahələri üçün (getByLabel istifadə edin), düymələr üçün (getByRole istifadə edin).

Yadda saxla: getByText-i linklər və müstəqil mətn üçün, az-az istifadə et; qismən uyğunluqdan qaçmaq üçün { exact: true } əlavə et.


getByLabel — form sahələri üçün ən yaxşı seçim

Bölmə: “getByLabel — form sahələri üçün ən yaxşı seçim”

getByLabel form daxiletmələrini əlaqəli <label> elementi vasitəsilə uyğunlaşdırır. for / id əlaqəsini, aria-labelledbyaria-label atributlarını hörmət edir.

// <label for="email">Email</label> ilə <input id="email">-a uyğundur
page.getByLabel('Email').fill('customer@test.io')
// <label for="password">Password</label> ilə <input id="password">-a uyğundur
page.getByLabel('Password').fill('customer123')
// "Shipping Name" labeli ilə <input id="shipping_name">-a uyğundur
page.getByLabel('Shipping Name').fill('Jane Smith')

Bu, login formları, ödəniş formları və admin məhsul formları üçün düzgün seçimdir — HTML-in düzgün <label> əlaqəsinə sahib olduğu hər yerdə.

Yadda saxla: görünən label-ı olan form sahəsi → əvvəlcə getByLabel.


getByPlaceholder — label olmadıqda

Bölmə: “getByPlaceholder — label olmadıqda”

Sahənin <label>-i olmasa da placeholder atributu varsa, getByPlaceholder-dan istifadə edin.

// <input placeholder="Search products...">-a uyğundur
page.getByPlaceholder('Search products...')
// <input placeholder="Enter your email address">-a uyğundur
page.getByPlaceholder('Enter your email address')

Bunu yalnız həqiqətən label olmadıqda istifadə edin. Əgər label varsa, getByLabel daha möhkəmdir (labellər placeholder mətndən daha az dəyişir).

Yadda saxla: getByPlaceholder-i yalnız həqiqətən label olmadıqda istifadə et.


getByTestId — sabit, lakin kod dəyişikliyi tələb edir

Bölmə: “getByTestId — sabit, lakin kod dəyişikliyi tələb edir”

getByTestId data-testid atributuna sahib elementlərə uyğun gəlir. Test ID-ləri qəsdən dəyişdirilir, heç vaxt təsadüfən deyil — bu onu ən sabit lokator edir.

// <button data-testid="submit-btn">Place Order</button>-a uyğundur
page.getByTestId('submit-btn').click()
// <div data-testid="product-card">...</div>-a uyğundur
page.getByTestId('product-card')

data-testid nə vaxt əlavə edilir: elementin əlçatan adı, sabit mətni və unikal ID-si olmadıqda. Ümumi nümunələr: ümumi <div> konteynerləri, SVG ikonalar, mürəkkəb vidjet sərhədləri.

Hər elementə data-testid əlavə etməyin. Rol əsaslı lokatorları ilk üstün tutun.

Yadda saxla: getByTestId ən sabitidir, amma data-testid-i yalnız rol/label/mətn elementi tapa bilmədikdə əlavə et.


CSS lokatorları — son çıxış yolu kimi

Bölmə: “CSS lokatorları — son çıxış yolu kimi”

CSS lokatorları pis deyil — sadəcə daha az davamlıdır. Bunları aşağıdakı hallarda istifadə edin:

  • ID ilə seçim edərkən (#email çox sabitdir)
  • Atribut seçiciləri istifadə edərkən (button[type="submit"])
  • Əlçatan rolu olmayan ümumi elementlər üçün
  • Demək olar ki, heç vaxt dəyişməyən struktural sinifləri hədəf alanda
// ID seçici — sabit və sürətli
page.locator('#shipping_name')
// Atribut seçici — sinif əsaslıdan daha yaxşı
page.locator('button[type="submit"]')
// Flash mesajları üçün sinif kompozisiyası — qəbul edilə bilər
page.locator('.alert.alert-success')
// Məhsul kartı grid elementi
page.locator('.product-card')

ID-ləri siniflərdən üstün tutun. CSS sinifləri dizaynerlər stilistikanı yenidən struktur etdikdə dəyişir. ID-lər nadirən dəyişir, çünki onlar həmçinin <label for="..."> əlaqələri və JavaScript hookları üçün də istifadə olunur.

Yadda saxla: CSS son çıxış yoludur — siniflərdən çox ID-lərə üstünlük ver (stil dəyişəndə siniflər dəyişir).


Lokatoru səhifəni yoxlayaraq tap

Bölmə: “Lokatoru səhifəni yoxlayaraq tap”

Lokatoru nadir hallarda təxmin edirsən — onu səhifədən oxuyursan. Üç alət əsas işi görür:

1. Brauzer DevTools. Elementə sağ klik → Inspect (Yoxla). Elements panelində onun teqini, role/aria atributlarını, label-ını və data-testid-ini oxuya bilərsən — məhz getByRole, getByLabelgetByTestId-in əsaslandığı şeyləri.

2. npx playwright codegen — qoy lokatoru Playwright yazsın. İşlət:

Terminal window
npx playwright codegen https://senin-app.test

Brauzer açılır; sən klik etdikcə və yazdıqca Playwright uyğun lokatorları (getByRole/getByLabel-a üstünlük verərək) kopyalaya biləcəyin pəncərəyə yazır. Yaxşı lokatorları öyrənməyin ən sürətli yoludur — hər element üçün tövsiyə olunanı görürsən.

3. page.pause() + Playwright Inspector. Testə await page.pause(); əlavə et və işlət; Inspector Pick locator (Lokator seç) düyməsi ilə açılır. İstənilən elementin üzərinə gəl və Playwright-in təklif etdiyi lokatoru göstərsin — testin ortasında çətin elementi debug etmək üçün əladır.


Zəncirlər DOM ağacını dəqiqliklə keçməyə imkan verir. Geniş başlayın — konteyner tapın — sonra uşaq elementlərə enin.

Ən sadə zəncir iki addımdır — konteyner → uşaq:

// 1. Məhsul kartını tap (konteyner)
const card = page.locator('.product-card').first();
// 2. Həmin kartın içindəki düyməni tap (uşaq)
await card.getByRole('button', { name: 'Add to Cart' }).click();

Hər addım axtarışı əvvəlki nəticənin içinə daraldır. Buradan başlayaraq qur — admin məhsullar cədvəlində daha dolğun nümunə:

// Admin məhsullar cədvəlini tapın, sonra bütün sıraları tapın
const rows = page.locator('table tbody tr.table-row');
// İkinci sıranı götürün (indeks 1)
const secondRow = rows.nth(1);
// İkinci sıradakı məhsul adını (ikinci <td>) götürün
const productName = secondRow.locator('td').nth(1);
// Son sıradakı Edit düyməsini kliklayın
await rows.last().locator('a:has-text("Edit")').click();

Səbət nümunəsi — iki səviyyəli zəncir:

// İlk səbət sırasındakı miqdar daxiletməsini götürün
const firstCartQuantity = page.locator('.cart-table .table-row')
.first()
.locator('input[name="quantity"]');

Performans ipucu: Playwright lokatorları tənbəlliklə qiymətləndirir. Uzun bir zəncir müəyyən etməyin heç bir xərci yoxdur — zəncir yalnız üzərində əməliyyat həyata keçirdiyinizdə həll olunur.

Yadda saxla: geniş → dar zəncirlə (konteyner → uşaq) bir elementi dəqiqləşdir.


filter({ hasText })filter({ has })

Bölmə: “filter({ hasText }) və filter({ has })”

filter uyğun elementlər dəstini əlavə şərti yerinə yetirənlərə daraltır. Mürəkkəb CSS seçicilərindən daha oxunaqlıdır.

Mətn məzmunu (ya da hər hansı nəslin mətni) verilmiş stringi ehtiva edən elementlərə uyğun gəlir. Hərfə həssas deyil, alt-sətir uyğunluğu.

// Admin dashboard: bütün stat kartları eyni görünür — başlıq mətni ilə filtrləyin
const totalProductsCard = page.locator('.card')
.filter({ hasText: 'Total Products' });
// Həmin kartdan say dəyərini götürün
const count = await totalProductsCard.locator('p').textContent();
// "Gaming Keyboard" məhsul kartını tapın
await page.locator('.product-card')
.filter({ hasText: 'Gaming Keyboard' })
.locator('button:has-text("Add to Cart")')
.click();

Verilmiş lokatora uyğun gələn uşaq elementi ehtiva edən elementlərə uyğun gəlir. Mətn uyğunluğu əvəzinə struktur uyğunluğuna ehtiyacınız olduqda istifadə edin.

// <p> elementi ehtiva edən bütün stat kartlarını tapın
const allStatCards = page.locator('.card')
.filter({ has: page.locator('p') });
// "pending" status badge-ı olan bütün sifariş sıralarını tapın
const pendingRows = page.locator('tr.table-row')
.filter({ has: page.locator('.badge-warning') });

Tuz: hasText nəsllərdəki mətni də uyğunlaşdırır. Axtarış mətniniz elementin bir neçə yerində görünürsə, gözlənilməz uyğunluqlar ala bilərsiniz. Bir elementə daraltmaq üçün kifayət qədər spesifik olun.

Yadda saxla: uyğun dəsti əməliyyatdan əvvəl filter({ hasText }) və ya filter({ has }) ilə daralt.


Bir lokator çoxsaylı elementlərə uyğun gəlir və bir spesifik elementə ehtiyacınız olduqda:

const rows = page.locator('table tbody tr.table-row');
await rows.first().locator('td').nth(0).textContent(); // birinci sıra, birinci xana
await rows.last().locator('button:has-text("Delete")').click(); // son sıranı sil
await rows.nth(2).locator('a:has-text("Edit")').click(); // üçüncü sıranı redaktə et

Mətni bildiyiniz zaman .nth()-dən çox filter({ hasText })-i üstün tutun. İndeks əsaslı seçim sıralar yenidən sıralandıqda sınır; məzmun əsaslı filtrləmə davamlıdır.

// ❌ Zərif — sıralama dəyişsə sınır
await rows.nth(0).locator('button:has-text("Edit")').click();
// ✅ Davamlı — sıralama sırası nə olursa olsun düzgün sıranı tapır
await rows.filter({ hasText: 'Wireless Mouse' })
.locator('button:has-text("Edit")')
.click();

Yadda saxla: .nth()-dən çox daha dəqiq lokatora üstünlük ver — indeks əsaslı seçim sıra dəyişəndə pozulur.


Playwright-in strict mode-u lokatoru birdən çox elementə uyğun gəldikdə və click, fill və ya textContent kimi bir əməliyyat həyata keçirdikdə xəta atır. Bu qəsdən edilir — Playwright hansını nəzərdə tutduğunuzu təxmin etməkdən imtina edir.

Error: strict mode violation: page.locator('button') resolved to 15 elements
// ❌ Səhifədə 15 düymə var — hansı?
await page.locator('button').click();
// ❌ Çoxsaylı keçidlər — hansı?
await page.locator('a').click();
// ❌ Səhifədə iki <select> elementi var
await page.locator('select').selectOption('electronics');

Düzəltmə 1 — Rol + adla daha spesifik olun:

// ✅ Yalnız bir düymənin labeli "Log In"-dir
await page.getByRole('button', { name: 'Log In' }).click();
// ✅ Yalnız bir keçidin labeli "Register"-dir
await page.getByRole('link', { name: 'Register' }).click();
// ✅ Yalnız bir select-in labeli "Category"-dir
await page.getByRole('combobox', { name: 'Category' }).selectOption('electronics');

Düzəltmə 2 — Konteynerə zəncirlə:

// ✅ Düyməni valideyn konteynerə məhdudlaşdırın
await page.locator('.login-form').getByRole('button', { name: 'Log In' }).click();
// ✅ Düzgün səbət sırasındakı Remove düyməsini tapın
await page.locator('.cart-table .table-row')
.filter({ hasText: 'Wireless Mouse' })
.getByRole('button', { name: 'Remove' })
.click();

Düzəltmə 3 — Sıra sabitdirsə .first(), .last(), ya da .nth()-dən istifadə edin:

// ✅ Birinci bildiriş bağlama düyməsi — sıra zəmanətlidirsə qəbul edilə bilər
await page.locator('.notification button.close').first().click();

Dinamik ID-lər — strict mode-la əlaqəli problem

Bölmə: “Dinamik ID-lər — strict mode-la əlaqəli problem”

Bəzi səhifələr təsadüfi və ya məlumat əsaslı dəyərlərlə ID-lər yaradır:

<!-- Məhsul detalları səhifəsi — ID-də slug ya da hash -->
<div id="product_abc123f">...</div>
<div id="product_def456a">...</div>

Bu ID-lər məlumat bazası yenidən toxumlandıqda və ya məhsul yenidən yaradıldıqda dəyişir. Qayda:

Dinamik hissə (hash, UUID, slug, zaman damğası) ehtiva edirsə, onu lokator kimi istifadə etməyin.

Bunun əvəzinə sabit alternativlər istifadə edin:

// ❌ Məlumat yeniləməsindən sonra sınır
page.locator('#product_abc123f')
// ✅ Sabit — sinif ya da rol dəyişməz
page.locator('.product-card').filter({ hasText: 'Wireless Mouse' })
page.getByRole('article').filter({ hasText: 'Wireless Mouse' })
page.getByTestId('product-card').filter({ hasText: 'Wireless Mouse' })

Yadda saxla: lokator birdən çox elementə uyğun gəlirsə, Playwright əməliyyatdan imtina edir — .first()-ə keçmək əvəzinə onu daralt.


Bəzi komponent kitabxanaları Shadow DOM — ev sahibi elementin içinə kapsullanmış ayrı bir DOM ağacı — istifadə edir. Standart CSS seçiciləri Shadow DOM sərhədlərini keçə bilmir.

Playwright bunu əksər hallar üçün pierce seçici mühərriki vasitəsilə avtomatik idarə edir:

// Playwright açıq shadow root-ları avtomatik keçir
page.locator('my-component >> button')
// Ya da pierce seçicisini açıq istifadə edin
page.locator('pierce/button')

>> — Playwright-in keçici kombinatorudur; ev sahibi (my-component) ilə içindəki element arasındakı shadow-DOM sərhədini keçir.

Qapalı shadow root-lar üçün (praktikada nadir hallarda) komponentin sənədləşdirilmiş API-si ilə ya da shadow ev sahibinə yerləşdirilmiş data-testid atributları ilə işləməli olacaqsınız.

Yadda saxla: Playwright-in getBy* lokatorları shadow DOM-u avtomatik keçir — xüsusi sintaksis lazım deyil.


Cədvəl sıraları — hamısını bir araya gətirmək

Bölmə: “Cədvəl sıraları — hamısını bir araya gətirmək”

Cədvəllər əksər tətbiqlərdə ən mürəkkəb lokator çətinliyidir. Zəncirvari lokatorları, filter-i və rol əsaslı lokatorları bir arada birləşdirirlər.

Admin sifarişlər cədvəlinin müştəri məlumatları, sifariş cəmləri, status badge-ları VƏ hər sırada <select> ilə <button> olan bir forması var. Tam şablon belədir:

// Sifarişlər cədvəlindəki bütün sıralar
const orderRows = page.locator('table tbody tr.table-row');
// Strategiya 1 — indeksə görə (sıra dəyişsə zərif)
const firstRow = orderRows.first();
// Strategiya 2 — mətn məzmununa görə (məlum məzmun üçün davamlı)
const janeRow = orderRows.filter({ hasText: 'Jane Smith' });
// Strategiya 3 — uşaq elementə görə (struktur uyğunluğu)
const pendingRow = orderRows.filter({
has: page.locator('.badge-warning')
});
// Strategiya 4 — tam zəncir: sıra tapın + form elementləri ilə əlaqə qurun
const janeSelect = orderRows
.filter({ hasText: 'Jane Smith' })
.locator('select[name="status"]');
const janeUpdateBtn = orderRows
.filter({ hasText: 'Jane Smith' })
.locator('button:has-text("Update")');
await janeSelect.selectOption('shipped');
await janeUpdateBtn.click();

Yadda saxla: cədvəllər bütün lokator bacarıqlarını bir araya gətirir — sıranı mətninə görə filter({ hasText }) ilə tap, sonra ehtiyacın olan xanaya, badge-ə ya da form elementinə zəncirlə.


Bu tapşırıqlar üçün öz kompüterinizdə kiçik bir e-ticarət tətbiqini — TestMarket Lab — işə salacaqsınız. Onu kursun qalan hissəsində də istifadə edəcəyiniz üçün indi bir dəfə quraşdırın (Node.js 20+ tələb olunur, Modul 0-da quraşdırılıb):

Terminal window
# Tətbiqi klonlayın (bir dəfə)
git clone https://github.com/TesterBaku/testmarket-lab.git
cd testmarket-lab
# Asılılıqları quraşdırın və serveri işə salın
npm install
npm start

Onu işlək vəziyyətdə saxlayın və brauzerdə http://localhost:3000 ünvanını açın. Tapşırıq sistemə daxil olmuş istifadəçi tələb etdikdə demo hesablardan istifadə edin:

  • Müştəricustomer@test.io / customer123
  • Adminadmin@test.io / admin123

Təmiz başlanğıc lazımdır? Tətbiqi yenidən başladın və ya ilkin məlumatları bərpa etmək üçün POST http://localhost:3000/api/reset göndərin.


Tapşırıq 1: Lokator növlərini müəyyən edin

Bölmə: “Tapşırıq 1: Lokator növlərini müəyyən edin”

Aşağıdakı HTML elementlərinin hər biri üçün ən yaxşı Playwright lokatorunu yazın:

HTML ElementiƏn Yaxşı Lokator
<label for="email">Email</label> ilə <input id="email">
<button type="submit">Log In</button>
<a href="/auth/register">Register</a>
<input placeholder="Search products...">
<div class="alert alert-success">Login successful!</div>
Həll
page.getByLabel('Email'); // label + input əlaqəsi
page.getByRole('button', { name: 'Log In' }); // rol + əlçatan ad
page.getByRole('link', { name: 'Register' }); // linklər də bir roldur
page.getByPlaceholder('Search products...'); // label yox, yalnız placeholder
page.getByText('Login successful!'); // müstəqil flash mətni
// (Flash üçün CSS də işləyir: page.locator('.alert.alert-success'))

Tapşırıq 2: Zərif lokatorları yenidən yazın

Bölmə: “Tapşırıq 2: Zərif lokatorları yenidən yazın”

Hər zərif lokatoru davamlı strategiya ilə yenidən yazın:

// 1. ❌ Zərif: CSS sinifi
page.locator('.btn.btn-primary')
// 2. ❌ Zərif: DOM yolu
page.locator('div > div > form > input').first()
// 3. ❌ Zərif: dinamik ID
page.locator('#product_abc123')
// 4. ❌ Zərif: nth-child
page.locator('table tbody tr:first-child td:nth-child(2)')
Həll
// 1. CSS sinifləri əvəzinə rol + əlçatan ad
page.getByRole('button', { name: 'Log In' });
// 2. Kövrək DOM yolu əvəzinə sahənin label-ı
page.getByLabel('Email');
// 3. Dinamik id əvəzinə sabit məzmun
page.locator('.product-card').filter({ hasText: 'Wireless Mouse' });
// 4. Mövqe əvəzinə sıranı məzmuna görə tap, sonra xananı
page.locator('tr.table-row')
.filter({ hasText: 'Wireless Mouse' })
.locator('td').nth(1); // nth(1) = ikinci <td> (0-dan başlayır)

Tapşırıq 3: Form sahəsi lokatorları

Bölmə: “Tapşırıq 3: Form sahəsi lokatorları”

TestMarket ödəniş forması sahələri üçün getByLabel lokatorları yazın: Shipping Name, Shipping Address, Shipping City, Shipping Zip Code və Place Order düyməsi.

Admin məhsul forması üçün getByLabel lokatorları yazın: Name, Price, Category, Stock və Create/Update Product düyməsi.

Həll
// Ödəniş forması (/checkout)
page.getByLabel('Shipping Name');
page.getByLabel('Shipping Address');
page.getByLabel('Shipping City');
page.getByLabel('Shipping Zip Code');
page.getByRole('button', { name: 'Place Order' });
// Admin məhsul forması (/admin/products/new və /admin/products/:id/edit)
page.getByLabel('Name');
page.getByLabel('Price');
page.getByLabel('Category');
page.getByLabel('Stock');
page.getByRole('button', { name: 'Create Product' }); // redaktədə 'Update Product'

Tapşırıq 4: Zəncirvari lokatorlar

Bölmə: “Tapşırıq 4: Zəncirvari lokatorlar”

views/admin/products.ejs-dən istifadə edərək:

  1. table-row sinfi olan bütün cədvəl sıralarını əldə edin
  2. Üçüncü sıranı (indeks 2) əldə edin
  3. Birinci sıradakı məhsul adını (ikinci <td>) əldə edin
  4. Son sıradakı Edit düyməsini kliklayın
  5. İkinci sıradakı Delete düyməsini kliklayın
Həll
// 1. Bütün sıralar
const rows = page.locator('tr.table-row');
// 2. Üçüncü sıra (0-dan başlayır)
const thirdRow = rows.nth(2);
// 3. Məhsul adı birinci sıranın ikinci xanasıdır
const firstName = rows.first().locator('td').nth(1);
// 4. Bu tətbiqdə Edit bir <a> linkdir
await rows.last().getByRole('link', { name: 'Edit' }).click();
// 5. Delete isə bir <button>-dur (kiçik formanın içində)
await rows.nth(1).getByRole('button', { name: 'Delete' }).click();

Admin dashboard stat kartlarından istifadə edərək:

  1. “Total Products” ehtiva edən kartı tapın və <p> sayını oxuyun
  2. “Total Users” ehtiva edən kartı tapın və <p> sayını oxuyun
  3. Mağaza səhifəsindəki “Gaming Keyboard” adlı məhsul üçün “Add to Cart” düyməsini kliklayın
Həll
// 1. "Total Products" kartının sayı
const products = await page.locator('.card')
.filter({ hasText: 'Total Products' })
.locator('p').textContent();
// 2. "Total Users" kartının sayı
const users = await page.locator('.card')
.filter({ hasText: 'Total Users' })
.locator('p').textContent();
// 3. Düzgün məhsulu səbətə əlavə et (mağaza /products səhifəsi)
await page.locator('.product-card')
.filter({ hasText: 'Gaming Keyboard' })
.getByRole('button', { name: 'Add to Cart' })
.click();

Tapşırıq 6: Admin cədvəl sırasının hədəflənməsi (çətin tapşırıq)

Bölmə: “Tapşırıq 6: Admin cədvəl sırasının hədəflənməsi (çətin tapşırıq)”

Aşağıdakıları yerinə yetirən bir test ardıcıllığı yazın:

  1. Admin sifarişlər səhifəsinə keçin
  2. “Jane Smith” müştərisi üçün sıranı tapın
  3. Status badge-ının “pending” dediğini iddia edin
  4. <select> və Update düyməsi vasitəsilə statusu “shipped”-ə dəyişin
  5. Flash mesajının yeniləməni təsdiqlədiğini iddia edin
Həll
await page.goto('http://localhost:3000/admin/orders');
// Sıranı müştərinin adına görə tap
const janeRow = page.locator('tr.table-row').filter({ hasText: 'Jane Smith' });
// Status badge-i "pending" oxuyur
await expect(janeRow.locator('.badge')).toHaveText('pending');
// Statusu sıranın <select>-i + Update düyməsi ilə dəyiş
await janeRow.locator('select[name="status"]').selectOption('shipped');
await janeRow.getByRole('button', { name: 'Update' }).click();
// Uğur flash-ı təsdiqləyir
await expect(page.locator('.alert.alert-success')).toBeVisible();

Tapşırıq 7: Strict mode düzəltməsi

Bölmə: “Tapşırıq 7: Strict mode düzəltməsi”

Hər strict mode pozuntusunu düzəldin:

// ❌ Bunları düzəldin:
await page.locator('button').click(); // 15 düymə
await page.locator('a').click(); // 50+ keçid
await page.locator('select').selectOption('electronics'); // 2 select
await page.locator('button:has-text("Remove")').click(); // 2+ Remove düymə
Həll
// Bir spesifik düymə / link rol + əlçatan adla
await page.getByRole('button', { name: 'Log In' }).click();
await page.getByRole('link', { name: 'Register' }).click();
// Bu tətbiqdə category <select>-in <label>-i yoxdur — test id-sini istifadə et
await page.getByTestId('category-filter').selectOption('electronics');
// "Remove"-u nəzərdə tutduğun səbət sırasına məhdudlaşdır
await page.locator('.cart-table .table-row')
.filter({ hasText: 'Wireless Mouse' })
.getByRole('button', { name: 'Remove' })
.click();

Tapşırıq 8: getByTestId miqrasiyası

Bölmə: “Tapşırıq 8: getByTestId miqrasiyası”

Məhsul kartı elementlərinə data-testid="product-card" əlavə edildiyi nəzərə alınaraq, mövcud sinif əsaslı lokator page.locator('.product-card')getByTestId istifadə etmək üçün miqrasiya edin. Sonra eyni şeyi table-rowcart-row üçün edin.

Həll
// product-card /products səhifəsində artıq real data-testid daşıyır
page.getByTestId('product-card');
// table-row və cart-row-da hələ data-testid yoxdur — əvvəlcə tətbiqdə əlavə
// edərsən (məs. data-testid="table-row"), sonra eyni şəkildə taparsan:
page.getByTestId('table-row');
page.getByTestId('cart-row');

Dərs: getByTestId yalnız atribut markup-da mövcud olduqda işləyir. product-card-da var; sıra nümunələri onu əlavə etdikdən sonra nə yazacağını göstərir.


Yazacağınız hər testə apardığınız üç qayda:

  1. Əvvəlcə getByRole-u üstün tutun — “Log In labeli olan düyməni tap” demək “bu CSS siniflərinə malik şeyi tap”-dan daha yaxşıdır
  2. Formlar üçün getByLabel, sabitlik üçün getByTestId — formlarda əlçatanlıq üçün labellər var; onlardan istifadə edin
  3. Dəqiqlik üçün zəncirləyin + filtrləyin — bir nəhəng seçici yazmayın; kiçik oxunaqlı lokatorları zəncirlə və gördüyünüz məzmunla filtrləyin

Lokator yaddaş vərəqi — hansına müraciət etməliyəm?

Vəziyyətİstifadə et
Düymə, link, başlıq, checkbox…getByRole(role, { name })
Görünən label-ı olan form sahəsigetByLabel
Label-ı olmayan, yalnız placeholder-li sahəgetByPlaceholder
Link və ya müstəqil mətngetByText
Əlçatan adı, mətni və ya sabit id-si olmayangetByTestId
Yalnız struktur / id iləCSS (page.locator('#id'))
Çox uyğunluqdan birizəncir + filter, son çarə kimi .first()/.nth()

Modul 3 fixture-ları və auth vəziyyətini əhatə edir — əvvəlcədən autentikasiya edilmiş brauzer kontekstlərindən istifadə edərək login formasını tamamilə atlayacaqsınız.