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şirpage.locator('.btn.btn-primary.mt-2').click();
// ❌ Zərif: bir sarıcı <div> əlavə edildikdə sınırpage.locator('div > div > form > button').click();
// ❌ Zərif: dəqiq mətn uyğunluğu boşluq dəyişikliklərindən sınırpage.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ğundurpage.getByRole('button', { name: 'Log In' }).click();
// ✅ Davamlı: <label> əlaqəsinə bağlıdır
// ✅ Davamlı: açıq-aşkar, yalnız siz dəyişdirəndə dəyişirpage.getByTestId('submit-btn').click();Playwright-in tövsiyə etdiyi lokator prioriteti:
getByRole— ilk olaraq bunu üstün tutungetByLabel— form sahələri üçüngetByPlaceholder— label olmadıqdagetByText— keçidlər və görünən mətn üçüngetByTestId— başqa heç nə sabit deyilsə- 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ğundurpage.getByRole('button', { name: 'Log In' })
// <a href="/register">Register</a>-a uyğundurpage.getByRole('link', { name: 'Register' })
// <h1>Welcome to TestMarket</h1>-a uyğundurpage.getByRole('heading', { name: 'Welcome to TestMarket' })
// "Category" labeli ilə əlaqəli <select>-ə uyğundurpage.getByRole('combobox', { name: 'Category' })Ümumi rol xəritəsi:
| HTML Elementi | ARIA Rolu |
|---|---|
<button> | button |
<a href> | link |
<input type="text"> | textbox |
<input type="checkbox"> | checkbox |
<select> | combobox |
<h1>–<h6> | heading |
<img alt="..."> | img |
Rol + əlçatan ad
Bölmə: “Rol + əlçatan ad”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-labelatributu:<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ırpage.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 olurpage.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ğundurpage.getByText('New Product')
// Dəqiq uyğunluq — oxşar mətnlər üçün qismən uyğunluğun qarşısını alırpage.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-labelledby və aria-label atributlarını hörmət edir.
// <label for="email">Email</label> ilə <input id="email">-a uyğundur
// <label for="password">Password</label> ilə <input id="password">-a uyğundurpage.getByLabel('Password').fill('customer123')
// "Shipping Name" labeli ilə <input id="shipping_name">-a uyğundurpage.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ğundurpage.getByPlaceholder('Search products...')
// <input placeholder="Enter your email address">-a uyğundurpage.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ğundurpage.getByTestId('submit-btn').click()
// <div data-testid="product-card">...</div>-a uyğundurpage.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ətlipage.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ərpage.locator('.alert.alert-success')
// Məhsul kartı grid elementipage.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, getByLabel və getByTestId-in əsaslandığı şeyləri.
2. npx playwright codegen — qoy lokatoru Playwright yazsın. İşlət:
npx playwright codegen https://senin-app.testBrauzer 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əncirvari lokatorlar
Bölmə: “Zəncirvari lokatorlar”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ınconst 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ünconst productName = secondRow.locator('td').nth(1);
// Son sıradakı Edit düyməsini kliklayınawait 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ünconst 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 }) və 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.
filter({ hasText })
Bölmə: “filter({ hasText })”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əyinconst totalProductsCard = page.locator('.card') .filter({ hasText: 'Total Products' });
// Həmin kartdan say dəyərini götürünconst count = await totalProductsCard.locator('p').textContent();
// "Gaming Keyboard" məhsul kartını tapınawait page.locator('.product-card') .filter({ hasText: 'Gaming Keyboard' }) .locator('button:has-text("Add to Cart")') .click();filter({ has })
Bölmə: “filter({ has })”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ınconst allStatCards = page.locator('.card') .filter({ has: page.locator('p') });
// "pending" status badge-ı olan bütün sifariş sıralarını tapınconst 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.
.first(), .last(), .nth()
Bölmə: “.first(), .last(), .nth()”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 xanaawait rows.last().locator('button:has-text("Delete")').click(); // son sıranı silawait rows.nth(2).locator('a:has-text("Edit")').click(); // üçüncü sıranı redaktə etMə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ırawait rows.nth(0).locator('button:has-text("Edit")').click();
// ✅ Davamlı — sıralama sırası nə olursa olsun düzgün sıranı tapırawait 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.
Strict mode pozuntuları
Bölmə: “Strict mode pozuntuları”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 elementsNiyə baş verir
Bölmə: “Niyə baş verir”// ❌ 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 varawait page.locator('select').selectOption('electronics');Üç düzəltmə yolu
Bölmə: “Üç düzəltmə yolu”Düzəltmə 1 — Rol + adla daha spesifik olun:
// ✅ Yalnız bir düymənin labeli "Log In"-dirawait page.getByRole('button', { name: 'Log In' }).click();
// ✅ Yalnız bir keçidin labeli "Register"-dirawait page.getByRole('link', { name: 'Register' }).click();
// ✅ Yalnız bir select-in labeli "Category"-dirawait page.getByRole('combobox', { name: 'Category' }).selectOption('electronics');Düzəltmə 2 — Konteynerə zəncirlə:
// ✅ Düyməni valideyn konteynerə məhdudlaşdırınawait page.locator('.login-form').getByRole('button', { name: 'Log In' }).click();
// ✅ Düzgün səbət sırasındakı Remove düyməsini tapınawait 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ərawait 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ırpage.locator('#product_abc123f')
// ✅ Sabit — sinif ya da rol dəyişməzpage.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.
Shadow DOM əsasları
Bölmə: “Shadow DOM əsasları”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çirpage.locator('my-component >> button')
// Ya da pierce seçicisini açıq istifadə edinpage.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ıralarconst 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ə qurunconst 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ə.
Tapşırıqlar
Bölmə: “Tapşırıqlar”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):
# Tətbiqi klonlayın (bir dəfə)git clone https://github.com/TesterBaku/testmarket-lab.gitcd testmarket-lab
# Asılılıqları quraşdırın və serveri işə salınnpm installnpm startOnu 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əri —
[email protected]/customer123 - Admin —
[email protected]/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/resetgö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əsipage.getByRole('button', { name: 'Log In' }); // rol + əlçatan adpage.getByRole('link', { name: 'Register' }); // linklər də bir roldurpage.getByPlaceholder('Search products...'); // label yox, yalnız placeholderpage.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 sinifipage.locator('.btn.btn-primary')
// 2. ❌ Zərif: DOM yolupage.locator('div > div > form > input').first()
// 3. ❌ Zərif: dinamik IDpage.locator('#product_abc123')
// 4. ❌ Zərif: nth-childpage.locator('table tbody tr:first-child td:nth-child(2)')Həll
// 1. CSS sinifləri əvəzinə rol + əlçatan adpage.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əzmunpage.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:
table-rowsinfi olan bütün cədvəl sıralarını əldə edin- Üçüncü sıranı (indeks 2) əldə edin
- Birinci sıradakı məhsul adını (ikinci
<td>) əldə edin - Son sıradakı Edit düyməsini kliklayın
- İkinci sıradakı Delete düyməsini kliklayın
Həll
// 1. Bütün sıralarconst 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ırconst firstName = rows.first().locator('td').nth(1);
// 4. Bu tətbiqdə Edit bir <a> linkdirawait 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();Tapşırıq 5: filter məşqi
Bölmə: “Tapşırıq 5: filter məşqi”Admin dashboard stat kartlarından istifadə edərək:
- “Total Products” ehtiva edən kartı tapın və
<p>sayını oxuyun - “Total Users” ehtiva edən kartı tapın və
<p>sayını oxuyun - 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:
- Admin sifarişlər səhifəsinə keçin
- “Jane Smith” müştərisi üçün sıranı tapın
- Status badge-ının “pending” dediğini iddia edin
<select>və Update düyməsi vasitəsilə statusu “shipped”-ə dəyişin- 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ə tapconst janeRow = page.locator('tr.table-row').filter({ hasText: 'Jane Smith' });
// Status badge-i "pending" oxuyurawait 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əyirawait 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çidawait page.locator('select').selectOption('electronics'); // 2 selectawait page.locator('button:has-text("Remove")').click(); // 2+ Remove düyməHəll
// Bir spesifik düymə / link rol + əlçatan adlaawait 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ə etawait page.getByTestId('category-filter').selectOption('electronics');
// "Remove"-u nəzərdə tutduğun səbət sırasına məhdudlaşdırawait 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-row və cart-row üçün edin.
Həll
// product-card /products səhifəsində artıq real data-testid daşıyırpage.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.
Əsas nəticələr
Bölmə: “Əsas nəticələr”Yazacağınız hər testə apardığınız üç qayda:
- Ə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 - Formlar üçün
getByLabel, sabitlik üçüngetByTestId— formlarda əlçatanlıq üçün labellər var; onlardan istifadə edin - 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əsi | getByLabel |
| Label-ı olmayan, yalnız placeholder-li sahə | getByPlaceholder |
| Link və ya müstəqil mətn | getByText |
| Əlçatan adı, mətni və ya sabit id-si olmayan | getByTestId |
| Yalnız struktur / id ilə | CSS (page.locator('#id')) |
| Çox uyğunluqdan biri | zə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.