Modul 3: Əməliyyatlar və istifadəçi qarşılıqlı əlaqəsi
Lokatorları mənimsədiniz. İndi isə faktiki olaraq səhifə ilə qarşılıqlı əlaqəyə girməyin vaxtıdır. Bu modul real bir Playwright testindəki hər istifadəçi əməliyyatını əhatə edir — sadə bir klikdən tutmuş brauzer dialoqunu idarə etməyə və ya iframe içindəki faylı yükləməyə qədər.
🎬 Video tezliklə əlavə olunacaq
Click əməliyyatları
Bölmə: “Click əməliyyatları”.click() Playwright-dəki ən yaygın əməliyyatdır. Klikdən əvvəl Playwright avtomatik olaraq elementin görünür, aktiv, sabit olduğunu və başqa bir element tərəfindən örtülmədiyini gözləyir. Əksər kliklərdən əvvəl manual waitFor() yazmağa ehtiyac yoxdur.
// Sadə click — hər testin əsas hissəsiawait page.getByRole('button', { name: 'Log In' }).click();
// Keçidə click edinawait page.getByRole('link', { name: 'Register' }).click();
// Məhsul kartına click edinawait page.locator('.product-card').filter({ hasText: 'Wireless Mouse' }).click();Double-click
Bölmə: “Double-click”Sətirdəki redaktə interfeyslərini və tək klikdən fərqlənən double-click-i ayırd edən elementlər üçün istifadə olunur.
// Redaktə edilə bilən cədvəl xanasına double-click edinawait page.locator('.editable-cell').dblclick();Sağ click
Bölmə: “Sağ click”Sağ click brauzer kontekst menyusunu açır və ya tətbiqdəki contextmenu hadisə dinləyicilərini işə salır.
// Kontekst menyusunu işə salmaq üçün sağ click edinawait page.locator('.product-card').click({ button: 'right' });Force click
Bölmə: “Force click”Playwright-in actionability yoxlamalarını atlayır. Ehtiyatla istifadə edin — yalnız elementin orada olduğunu bildiyiniz amma Playwright-in yoxlamaları düzgün bir qarşılıqlı əlaqəni blokladığı zaman istifadə edin.
// Force click — görünürlük / actionability yoxlamalarını atlayırawait page.locator('#hidden-trigger').click({ force: true });Playwright-in avtomatik gözləməsi o deməkdir ki: real testlərin 95%-ində sadə .click() kifayətdir. Parametrləri kənar hallar üçün saxlayın.
Yadda saxla: sadə .click() avtomatik gözləyir (görünən, aktiv, sabit); { force: true } / { button: 'right' }-ə yalnız kənar hallarda müraciət et.
fill() vs pressSequentially()
Bölmə: “fill() vs pressSequentially()”Bu, həm ümumi müsahibə sualı, həm də gündəlik dizayn qərarıdır.
fill()
Bölmə: “fill()”- Sahəni əvvəlcə təmizləyir (üçlü click etməyə ehtiyac yoxdur)
- Tək
inputhadisəsi göndərir — sürətlidir - Form sahələrinin 95%-i üçün üstün tutulur
// fill() — bir əməliyyatda sahəni təmizləyir və dəyər təyin edirawait page.locator('#password').fill('customer123');
// Mətn qəbul edən istənilən elementdə işləyirawait page.getByLabel('Shipping Name').fill('Jane Doe');await page.getByLabel('Shipping Address').fill('123 Main Street');await page.getByLabel('Shipping City').fill('Portland');await page.getByLabel('Shipping Zip Code').fill('97201');pressSequentially()
Bölmə: “pressSequentially()”- Hər simvol üçün
keydown,keypress,keyupişə salaraq simvol-simvol yazır - Sahəni əvvəlcə təmizləmir — mövcud mətnin sonuna əlavə edir
- Daha yavaşdır — yalnız
fill()düzgün davranışı işə salmadıqda istifadə edin
// pressSequentially() — real klaviatura zərbələrini simulyasiya edir, simvol-simvol
// Ümumi istifadə halı: autocomplete / debounce edilmiş sahələrawait page.locator('#search').pressSequentially('Wire', { delay: 50 });// Hər klaviatura zərbəsi autocomplete komponentinin dinlediyi hadisələri işə salırpressSequentially() nə vaxt lazımdır: React-Select, typeahead komponentlər, zəngin mətn redaktorları və ya yekun dəyər əvəzinə hər bir ayrı klaviatura zərbəsinə reaksiya verən istənilən daxiletmə.
Tuz: Sahədə artıq mətn varsa, pressSequentially() ona əlavə edir. fill() isə həmişə əvvəlcə təmizləyir.
// Sahə "old value" ilə başlayır — pressSequentially() əlavə edir:await page.locator('#name').pressSequentially(' new suffix'); // nəticə: "old value new suffix"
// fill() əvvəlki dəyəri nəzərə almır:await page.locator('#name').fill('fresh value'); // nəticə: "fresh value"Yadda saxla: sahələrin ~95%-i üçün fill() (təmizləyir + bir hadisə); pressSequentially() yalnız hər klaviş vuruşu işə düşməlidirsə.
Klaviatura press()
Bölmə: “Klaviatura press()”.press() fokuslanmış elementə tək bir düymə zərbəsi göndərir. page.keyboard.press() isə hansı elementin fokusda olduğundan asılı olmayaraq düyməni qlobal olaraq göndərir.
// Formu göndərmək üçün Enter düyməsini sıxın (lokator əhatəsindədir)await page.locator('#password').press('Enter');
// Növbəti fokuslanabilən sahəyə keçmək üçün Tab düyməsini sıxınawait page.locator('#shipping_name').press('Tab');
// Modal ya da dropdown-u bağlamaq üçün Escapeawait page.keyboard.press('Escape');
// Simvolları silmək üçün Backspaceawait page.keyboard.press('Backspace');
// Naviqasiya üçün ox düymələriawait page.keyboard.press('ArrowDown');await page.keyboard.press('ArrowUp');Düymə kombinasiyaları
Bölmə: “Düymə kombinasiyaları”Modifier düymələri başqaları ilə birləşdirmək üçün + istifadə edin:
// Fokuslanmış elementdəki bütün mətni seçinawait page.keyboard.press('Control+A');
// Seçilmiş mətni kopyalayınawait page.keyboard.press('Control+C');
// Yapışdırınawait page.keyboard.press('Control+V');
// Seçilmiş mətni silinawait page.keyboard.press('Control+A');await page.keyboard.press('Delete');Düymə adlarının hərfi böyüklüyü önəmlidir: Düzgün adlar 'Control', 'Shift', 'Alt', 'Meta'-dır — 'ctrl' ya da 'shift' deyil.
Shift+Click və saxlama şablonları
Bölmə: “Shift+Click və saxlama şablonları”// Shift saxlayaraq bir neçə elementi çox seçim üçün click edinawait page.keyboard.down('Shift');await page.locator('.list-item').nth(3).click();await page.keyboard.up('Shift');Əlçatanlıq ipucu: Tab naviqasiyası klaviatura əlçatanlığını yoxlamaq üçün güclü bir vasitədir. Tab sırasının məntiqi olduğunu yoxlamaq üçün formdan mouse-a toxunmadan keçin.
Yadda saxla: .press('Enter') lokatora bağlıdır, page.keyboard.press(...) qlobaldır; klaviş adları böyük hərflə (Control, ctrl yox).
Checkboxlar və radio düymələri
Bölmə: “Checkboxlar və radio düymələri”Intent-i özü-özünə sənədləşdirdiyindən .click() əvəzinə .check() və .uncheck() istifadə edin.
// Checkboxu işarələyinawait page.getByLabel('Remember me').check();
// Checkboxu işarəsiz edinawait page.getByLabel('Subscribe to newsletter').uncheck();
// Cari vəziyyətdən asılı olmayaraq checkbox-ı müəyyən vəziyyətə qoyunawait page.getByLabel('Terms and conditions').setChecked(true);await page.getByLabel('Marketing emails').setChecked(false);Fərq: .check() checkbox artıq işarəlidirsə xəta atır (öz-özünü doğrulayır). .click() vəziyyətdən asılı olmayaraq dəyişdirir. .setChecked(true/false) xəta atmadan müəyyən vəziyyəti təyin edir.
Radio düymələri
Bölmə: “Radio düymələri”Radio qrupları eyni name atributunu paylaşır. Seçmək istədiyiniz seçenekdə .check() istifadə edin.
// Radio düyməsini seçinawait page.getByLabel('Express Shipping').check();await page.getByLabel('Standard Shipping').check();
// Hansı radio-nun hazırda seçildiyini doğrulayınawait expect(page.getByLabel('Express Shipping')).toBeChecked();Checkboxlar və radiolar üçün assertion-lar
Bölmə: “Checkboxlar və radiolar üçün assertion-lar”// Checkboxun işarəli olduğunu doğrulayınawait expect(page.getByLabel('Remember me')).toBeChecked();
// Checkboxun işarəsiz olduğunu doğrulayınawait expect(page.getByLabel('Newsletter')).not.toBeChecked();Yadda saxla: .click() əvəzinə .check()/.uncheck() üstün tut (özünü yoxlayır); .setChecked(bool) konkret vəziyyəti məcbur edir.
Native <select> dropdown-ları
Bölmə: “Native <select> dropdown-ları”selectOption() native HTML <select> elementləri üçündür. Brauzer və framework-lərin gözlediyi düzgün dəyişiklik hadisələrini göndərir.
// Dəyərlə seçin (option-un `value` atributu)await page.locator('#category').selectOption('electronics');
// Görünən etiket mətni ilə seçinawait page.locator('#category').selectOption({ label: 'Electronics' });
// İndeksə görə seçin (0-dan başlayır)await page.locator('#sort').selectOption({ index: 2 });
// Çoxlu seçim — massiv verinawait page.locator('#tags').selectOption(['javascript', 'testing', 'playwright']);TestMarket nümunəsi — məhsulları filtrləyin:
await page.goto('/products');
// Kateqoriyaya görə filtrləyinawait page.locator('#category').selectOption('electronics');
// Qiymətə görə azalan sıralamaawait page.locator('#sort').selectOption('price_desc');
// Filtrləri tətbiq edinawait page.getByRole('button', { name: 'Apply Filters' }).click();Hazırkı seçimi doğrulayın:
await expect(page.locator('#category')).toHaveValue('electronics');await expect(page.locator('#sort')).toHaveValue('price_desc');Ümumi səhv: value atributu 'electronics' (kiçik hərflə) olan zaman selectOption('Electronics') böyük E ilə istifadə etmək. Dəyərlər hərfə həssasdır.
Yadda saxla: selectOption() yalnız real <select> elementləri üçündür; görünən mətnə yox, value-ya uyğunlaşdır (hərf həssas).
Custom dropdown-lar
Bölmə: “Custom dropdown-lar”Bütün dropdown-lar native <select> elementi deyil. Müasir UI framework-ləri (React-Select, Headless UI, Material UI) özlərinkini qururlar. Bu cür elementlərdə selectOption() istifadə edə bilməzsiniz.
Fərqi necə anlamaq: DevTools → Elements tabını açın. <option> uşaqları olan <select> görürsünüzsə → selectOption() istifadə edin. <li> ilə <div>, <button>, <ul> görürsünüzsə → custom dropdown-dır.
Custom dropdown-lar üçün şablon həmişə eynidir:
// Addım 1: Dropdown-u açmaq üçün click edinawait page.locator('.dropdown-trigger').click();
// Addım 2: Menyunun görünməsini gözləyin (animasiya ola bilər)await page.locator('.dropdown-menu').waitFor({ state: 'visible' });
// Addım 3: İstədiyiniz seçeneği click edinawait page.locator('.dropdown-option:has-text("Option Name")').click();
// Addım 4 (isteğe bağlı): Menyunun bağlandığını doğrulayınawait expect(page.locator('.dropdown-menu')).toBeHidden();Custom dropdown-ları debug etmək:
- Menyu animasiya ilə açılır? Onu düzgün gözlə:
await page.locator('.dropdown-menu').waitFor({ state: 'visible' })— heç vaxt sabitwaitForTimeoutyox (yavaş və qeyri-sabitdir). - Seçim viewport xaricindədir? Click-dən əvvəl scroll edin.
- Click etmədən əvvəl menyu bağlanır? Adətən vaxt problemidir — əvvəlcə seçimin görünməsini gözlə;
{ force: true }Playwright-in təhlükəsizlik yoxlamalarını keçən son çarədir.
// Seçimi gözlə, sonra click et — animasiyaya qarşı dayanıqlıconst option = page.locator('.dropdown-option:has-text("Option Name")');await option.waitFor({ state: 'visible' });await option.click();Yadda saxla: klik → waitFor({ state: 'visible' }) → seçimə klik — heç vaxt sabit waitForTimeout.
Hover və tooltip-lər
Bölmə: “Hover və tooltip-lər”.hover() mausu elementin üzərinə gətirir, mouseenter və mousemove hadisələrini işə salır. Hover-da göstərilən tooltip-lər, dropdown menyular və məlumat popup-ları test edilə bilər hala gəlir.
// "Quick View" düyməsini göstərmək üçün məhsul kartının üzərinə hover edinawait page.locator('.product-card').filter({ hasText: 'Wireless Mouse' }).hover();
// Hover etdikdən sonra tooltip / göstərilən element görünür olurawait expect(page.locator('#hover-tooltip')).toBeVisible();await expect(page.locator('#hover-tooltip')).toContainText('Tooltip content');
// Uzaqlaşın — tooltip gizlənirawait page.locator('h1').hover();await expect(page.locator('#hover-tooltip')).toBeHidden();Koordinatlı hover — element içindəki konkret pikseldə hover edin (qrafiklər ya da şəkil xəritələri üçün faydalıdır):
// Elementin içindəki dəqiq piksel ofsetdə hover edinawait page.locator('.chart-area').hover({ position: { x: 100, y: 50 } });Hover etdikdən sonra göstərilən elementi gözləyin:
await page.locator('#info-icon').hover();
// Tooltip-in CSS keçidi var — görünür olmasını gözləyinawait page.locator('.tooltip').waitFor({ state: 'visible' });await expect(page.locator('.tooltip')).toContainText('Additional info');Yadda saxla: .hover(), sonra açılan elementin görünən olduğunu waitFor/assert et, yalnız bundan sonra ona toxun.
Date picker-lər
Bölmə: “Date picker-lər”Native <input type="date">
Bölmə: “Native <input type="date">”ISO format string-i ilə doldurun (YYYY-MM-DD). Bu ən etibarlı yanaşmadır.
// Native tarix daxiletməsi — ISO formatı, həmişə işləyirawait page.locator('#start-date').fill('2025-06-15');await page.locator('#end-date').fill('2025-07-31');Custom date picker vidjetlər
Bölmə: “Custom date picker vidjetlər”React DatePicker, flatpickr, Pikaday və oxşarları üçün — vidjetin interfeysi ilə qarşılıqlı əlaqəyə girməlisiniz: tetikleyicini click edin, ayları keçin, gün xanasını click edin.
// Addım 1: Təqvimi açınawait page.locator('[data-testid="date-trigger"]').click();
// Addım 2: Düzgün aya keçin// Hədəf: İyun 2025. Cari ay fərqli ola bilər.await page.locator('button[aria-label="Next month"]').click();
// Addım 3: Gün xanasını click edinawait page.locator('.calendar-day:has-text("15")').click();
// Addım 4: Seçimi doğrulayınawait expect(page.locator('#start-date')).toHaveValue('2025-06-15');Bir neçə ay proqramatik şəkildə keçmək üçün:
// Hədəf ay/il başlığına çatana qədər irəli keçin.// Səhv hədəf heç vaxt sonsuz dövr etməsin deyə dövrü məhdudlaşdır,// və textContent()-i null-dan qoru (null qaytara bilər).const target = 'June 2025';for (let i = 0; i < 24; i++) { const heading = await page.locator('.calendar-header').textContent(); if (heading?.includes(target)) break; await page.locator('button[aria-label="Next month"]').click();}await page.locator('.calendar-day:has-text("15")').click();Yadda saxla: native <input type="date"> → fill('YYYY-MM-DD'); custom widget-lər → təqvim UI-dən klik-klik keç.
Drag & drop
Bölmə: “Drag & drop”Playwright üç yanaşma təklif edir. dragTo() ilə başlayın — framework düzgün cavab vermirsə manual ardıcıllığa keçin.
Metod 1: dragTo() (ən sadəsi)
Bölmə: “Metod 1: dragTo() (ən sadəsi)”// Elementi hədəfə sürükləyinawait page.locator('#drag-item').dragTo(page.locator('#drop-zone'));Metod 2: Manual mouse ardıcıllığı (ən etibarlısı)
Bölmə: “Metod 2: Manual mouse ardıcıllığı (ən etibarlısı)”Bəzi drag-and-drop kitabxanaları (react-beautiful-dnd, SortableJS, Angular CDK) dragTo()-un düzgün göndərmədiyi custom hadisə işləmə mexanizmindən istifadə edir. Manual ardıcıllıq bu kitabxanaların dinlediyi mousedown, mousemove, mouseup hadisələrini işə salır.
const source = page.locator('.drag-handle').nth(0);const target = page.locator('.drop-target');
const sourceBox = await source.boundingBox();const targetBox = await target.boundingBox();
await page.mouse.move( sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2);await page.mouse.down();await page.mouse.move( targetBox.x + targetBox.width / 2, targetBox.y + targetBox.height / 2, { steps: 10 } // bəzi kitabxanalar üçün lazım olan hamar hərəkət);await page.mouse.up();Metod 3: Koordinatlarla dragTo()
Bölmə: “Metod 3: Koordinatlarla dragTo()”// Hədəf daxilindəki müəyyən mövqeyə sürükləyinawait page.locator('#drag-item').dragTo(page.locator('#drop-zone'), { targetPosition: { x: 10, y: 10 },});dragTo() uğursuz olduqda: Brauzer konsolunu xətalara görə yoxlayın. DnD kitabxanası sintetik hadisələr istifadə edirsə, Metod 2-yə keçin.
Yadda saxla: əvvəl dragTo() cəhd et; custom drag-and-drop kitabxanaları üçün əl ilə mouse.down/move/up ardıcıllığına keç.
Fayl yükləmə
Bölmə: “Fayl yükləmə”setInputFiles() OS fayl dialoqlarını tamamilə əvəz edir. AutoIT, Robot class, OS səviyyəli avtomatlaşdırma lazım deyil.
// Tək faylı path ilə yükləyinawait page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf');
// Çoxlu fayl yükləyinawait page.locator('input[type="file"]').setInputFiles([ '/path/to/file1.txt', '/path/to/file2.txt',]);
// Buffer-dən yükləyin — fiziki fayl lazım deyilawait page.locator('input[type="file"]').setInputFiles({ name: 'data.csv', mimeType: 'text/csv', buffer: Buffer.from('id,name,price\n1,Widget,9.99\n2,Gadget,19.99'),});
// Fayl seçimini ləğv edinawait page.locator('input[type="file"]').setInputFiles([]);Portativ path-lər — istənilən əməliyyat sistemdə işləməsi üçün path.join istifadə edin:
import path from 'node:path';
await page.locator('input[type="file"]').setInputFiles( path.join(import.meta.dirname, '..', 'fixtures', 'test-upload.txt'));Gizli fayl daxiletməsi: <input type="file"> display: none ya da opacity: 0 ilə stilləndirilibsə, { force: true } istifadə edin:
await page.locator('input[type="file"]').setInputFiles('file.pdf', { force: true });Faylın seçildiyini doğrulayın:
const files = await page.locator('input[type="file"]').evaluate(el => Array.from(el.files).map(f => f.name));expect(files).toContain('test-upload.txt');Yadda saxla: setInputFiles() OS dialoqunu əvəz edir — yol, massiv və ya { name, mimeType, buffer } ötür.
Dialoqlar və popup-lar
Bölmə: “Dialoqlar və popup-lar”page.on('dialog') bir hadisə dinləyicisidir. Dialoqu işə salan əməliyyatdan əvvəl qeydiyyatdan keçirilməlidir. Sil düyməsini click etdikdən sonra qeydiyyatdan keçirsəniz, dialoq artıq işə salınmış və avtomatik olaraq rədd edilmişdir.
// ✅ DÜZGÜN — tetikləyici əməliyyatdan əvvəl qeydiyyatdan keçirinpage.on('dialog', async (dialog) => { await dialog.accept(); // qəbul et / təsdiqləyin});await page.locator('button:has-text("Delete")').click();
// ❌ YANLIŞ — dialoq bu handler qeydiyyatdan keçirilmədən əvvəl işə salınırawait page.locator('button:has-text("Delete")').click();page.on('dialog', async (dialog) => { await dialog.accept(); // çox gec});Dialoq növləri və idarə edilməsi
Bölmə: “Dialoq növləri və idarə edilməsi”// Confirm dialoqunu qəbul edinpage.on('dialog', async (dialog) => { expect(dialog.message()).toContain('Are you sure'); await dialog.accept();});
// Confirm dialoqunu rədd edin (ləğv et)page.on('dialog', async (dialog) => { await dialog.dismiss();});
// Prompt dialoquna mətn daxil edərək cavab verinpage.on('dialog', async (dialog) => { await dialog.accept('yazılmış mətn burada');});
// Müxtəlif dialoq növlərini seçici şəkildə idarə edinpage.on('dialog', async (dialog) => { if (dialog.type() === 'confirm') { await dialog.accept(); } else if (dialog.type() === 'prompt') { await dialog.accept('test məlumatı'); } else { await dialog.dismiss(); // alert }});Ən yaxşı praktika: Dialoq handler-ini ayrı-ayrı test funksiyalarında deyil, beforeEach içində ya da page object constructor-ında qeydiyyatdan keçirin. Bu handler-in istənilən dialoq tetikləyən əməliyyatdan əvvəl hazır olmasını təmin edir.
// Page object constructor-ındaclass AdminProductsPage { constructor(page) { this.page = page; // Bütün silmə təsdiqnamələrini avtomatik qəbul edin page.on('dialog', async (dialog) => { await dialog.accept(); }); }}Yadda saxla: dialoqu tetikləyən klikdən əvvəl page.on('dialog', …) qeydiyyatdan keçir.
Iframelər
Bölmə: “Iframelər”page.frameLocator() bir FrameLocator qaytarır — bütün Playwright lokator metodları onun üzərində mövcuddur. Onu lokatorlar üçün page istifadə etdiyiniz kimi istifadə edin.
// iframe üçün FrameLocator əldə edinconst frame = page.frameLocator('#payment-iframe');
// iframe içindəki bütün normal lokator metodlarını istifadə edinawait frame.getByLabel('Card number').fill('4111 1111 1111 1111');await frame.getByLabel('Expiry date').fill('12/28');await frame.getByLabel('CVV').fill('123');await frame.getByRole('button', { name: 'Pay Now' }).click();iframe seçicisini tapmaq:
// id-li iframeconst frame = page.frameLocator('#my-iframe');
// id-siz iframe — src ya da başqa atribut istifadə edinconst frame = page.frameLocator('iframe[src="/payment"]');const frame = page.frameLocator('iframe[title="Payment form"]');İç-içə iframelər:
// iframe içindəki iframelər üçün frameLocator-ları zəncirləconst innerFrame = page.frameLocator('#outer-frame').frameLocator('#inner-frame');await innerFrame.locator('#card-number').fill('4111...');frameLocator-da mövcud olmayan şeylər: goto(), evaluate() — o, bir Page obyekti deyil. Bu əməliyyatlar üçün page.frame() istifadə edin:
// page.frame() adı ya da URL ilə — qabaqcıl ssenarilər üçünconst frame = page.frame({ url: /payment/ });await frame.evaluate(() => document.title);Cross-origin iframelər: Playwright bunları şəffaf şəkildə idarə edir. frameLocator() iframe-in origin-indən asılı olmayaraq işləyir.
Yadda saxla: iframe içindəki elementlər üçün frameLocator() istifadə et; onun bütün lokator metodları var, amma goto()/evaluate() yoxdur (onlar üçün page.frame()).
Əməliyyat yaddaş vərəqi
Bölmə: “Əməliyyat yaddaş vərəqi”| İstəyirəm ki… | İstifadə et |
|---|---|
| Klik / cüt / sağ / force | .click() · .dblclick() · .click({ button: 'right' }) · .click({ force: true }) |
| Sahəni təmizlə + doldur | .fill(value) |
| Klaviş-klaviş yaz (autocomplete) | .pressSequentially(value) |
| Klaviş / kombinasiya bas | .press('Enter') · page.keyboard.press('Control+A') |
| Checkbox işarələ / sil / təyin et | .check() · .uncheck() · .setChecked(bool) |
Native <select> | .selectOption(value / label / index) |
| Custom dropdown | klik → waitFor({ state: 'visible' }) → seçimə klik |
| Hover | .hover() |
| Drag & drop | .dragTo(target) (ehtiyat: əl ilə mouse.*) |
| Fayl yüklə | .setInputFiles(path / massiv / { name, buffer }) |
| Brauzer dialoqu | tetikdən əvvəl page.on('dialog', …) |
| Iframe içində | page.frameLocator(sel) |
Tapşırıqlar
Bölmə: “Tapşırıqlar”Bu tapşırıqlar http://localhost:3000-də yerli olaraq işləyən TestMarket Lab istifadə edir.
Tapşırıq 1: Login forması — fill() vs pressSequentially()
Bölmə: “Tapşırıq 1: Login forması — fill() vs pressSequentially()”tests/starter/tests/actions/login-actions.spec.js yaradın:
import { test, expect } from '@playwright/test';
test.describe('Login form actions', () => {
test('fill() — sürətli, tək hadisə', async ({ page }) => { await page.goto('/auth/login'); await page.locator('#password').fill('customer123'); await page.getByRole('button', { name: 'Log In' }).click(); await expect(page.locator('.alert.alert-success')).toBeVisible(); });
test('pressSequentially() — simvol-simvol', async ({ page }) => { await page.goto('/auth/login'); await page.locator('#password').pressSequentially('customer123', { delay: 30 }); await page.getByRole('button', { name: 'Log In' }).click(); await expect(page.locator('.alert.alert-success')).toBeVisible(); });
test('göndərmək üçün Enter-a sıxın', async ({ page }) => { await page.goto('/auth/login'); await page.locator('#password').fill('customer123'); await page.locator('#password').press('Enter'); await expect(page.locator('.alert.alert-success')).toBeVisible(); });});Tapşırıqlar:
npx playwright test --headedilə işlədin və yazma sürətini müşahidə edin:fill()anlıqdır,pressSequentially()simvol-simvol yazır{ delay: 30 }şərhə alın və yenidən işlədin — nə dəyişir?- Əvvəlcədən doldurulmuş sahədə əvvəlcə təmizləmədən
pressSequentially()istifadə edərək əlavə etmə davranışını görün
Tapşırıq 2: Checkout forması — doldur və göndər
Bölmə: “Tapşırıq 2: Checkout forması — doldur və göndər”tests/starter/tests/actions/checkout-actions.spec.js yaradın. beforeEach daxil olur, səbətə məhsul əlavə edir və checkout səhifəsini açır:
import { test, expect } from '@playwright/test';
test.describe('Checkout əməliyyatları', () => { test.beforeEach(async ({ page }) => { await page.goto('/auth/login'); await page.locator('#password').fill('customer123'); await page.locator('#login-btn').click();
await page.goto('/products'); await page.locator('[data-testid="add-to-cart"]').first().click(); await page.goto('/checkout'); });
test('bütün çatdırılma sahələrini doldurun və sifariş verin', async ({ page }) => { await page.locator('#shipping_name').fill('Jane Doe'); await page.locator('#shipping_address').fill('123 Main Street'); await page.locator('#shipping_city').fill('Portland'); await page.locator('#shipping_zip').fill('97201'); await page.locator('#place-order').click(); await expect(page).toHaveURL(/\/orders\/\d+/); await expect(page.locator('h1')).toContainText('Order'); });
test('sahələri Tab-la keçin, hər dəyəri yazın', async ({ page }) => { await page.locator('#shipping_name').pressSequentially('John Smith', { delay: 20 }); await page.locator('#shipping_name').press('Tab'); await page.locator('#shipping_address').pressSequentially('456 Oak Ave', { delay: 20 }); await page.locator('#shipping_address').press('Tab'); await page.locator('#shipping_city').pressSequentially('Seattle', { delay: 20 }); await page.locator('#shipping_city').press('Tab'); await page.locator('#shipping_zip').pressSequentially('98101', { delay: 20 }); await page.locator('#place-order').click(); await expect(page).toHaveURL(/\/orders\/\d+/); });
test('boş formu göndərin — HTML5 validasiyası bloklayır', async ({ page }) => { await page.locator('#place-order').click(); await expect(page).toHaveURL('/checkout'); // tələb olunan sahələr göndərməni bloklayır });});Tapşırıq 3: Məhsul filtri — selectOption()
Bölmə: “Tapşırıq 3: Məhsul filtri — selectOption()”tests/starter/tests/actions/product-filter-actions.spec.js yaradın:
test('selectOption ilə kateqoriyaya görə filtrləyin', async ({ page }) => { await page.goto('/products'); await page.locator('#category').selectOption('electronics'); await page.getByRole('button', { name: 'Apply Filters' }).click(); expect(await page.locator('.product-card').count()).toBeGreaterThan(0);});
test('görünən etiket mətni ilə seçin', async ({ page }) => { await page.goto('/products'); await page.locator('#category').selectOption({ label: 'Electronics' }); await page.getByRole('button', { name: 'Apply Filters' }).click(); expect(await page.locator('.product-card').count()).toBeGreaterThan(0);});
test('axtarış + filtr + sıralama kombinasiyası', async ({ page }) => { await page.goto('/products'); await page.locator('#search').fill('Wireless'); await page.locator('#category').selectOption('electronics'); await page.locator('#sort').selectOption('price_asc'); await page.getByRole('button', { name: 'Apply Filters' }).click(); await expect(page.locator('.product-card, p:has-text("No products found")')) .toBeVisible();});Tapşırıqlar:
- DevTools → Elements açın,
<select id="category">-ni tapın,<option>nodlarını genişləndirin —valueatributlarına diqqət edin selectOption('nonexistent')cəhd edin — Playwright hansı xəta atır?- Böyük E ilə
selectOption('Electronics')cəhd edin — uyğun gəlir?
Tapşırıq 4: Klaviatura qısayolları — seç, kopyala, sil
Bölmə: “Tapşırıq 4: Klaviatura qısayolları — seç, kopyala, sil”tests/starter/tests/actions/keyboard-actions.spec.js yaradın:
test('klaviatura ilə hamısını seç və sil', async ({ page }) => { await page.goto('/auth/register'); await page.locator('#name').fill('Some text to clear'); await page.locator('#name').click(); await page.keyboard.press('Control+A'); await page.keyboard.press('Delete'); expect(await page.locator('#name').inputValue()).toBe('');});
test('klaviatura ilə seç, kopyala, yapışdır', async ({ page }) => { await page.goto('/auth/login'); await page.locator('#email').click(); await page.keyboard.press('Control+A'); await page.keyboard.press('Control+C'); await page.locator('#email').press('Tab'); await page.keyboard.press('Control+V');});
test('Backspace simvolları silir', async ({ page }) => { await page.goto('/auth/register'); await page.locator('#name').fill('Product Launch Event'); for (let i = 0; i < 6; i++) { await page.keyboard.press('Backspace'); } expect(await page.locator('#name').inputValue()).toBe('Product Launch');});Tapşırıq 5: Fayl yükləmə — setInputFiles()
Bölmə: “Tapşırıq 5: Fayl yükləmə — setInputFiles()”tests/starter/tests/actions/file-upload-actions.spec.js yaradın:
import { test, expect } from '@playwright/test';import path from 'node:path';import fs from 'node:fs';
test('path vasitəsilə fayl yükləyin', async ({ page }) => { await page.goto('/auth/register');
const tmpDir = path.join(import.meta.dirname, '..', '..', 'tmp'); if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true }); const testFile = path.join(tmpDir, 'test-upload.txt'); fs.writeFileSync(testFile, 'Playwright-dən salam!');
// Fayl daxiletməsini inject edin (TestMarket register səhifəsindəki yoxdur) await page.evaluate(() => { const input = document.createElement('input'); input.type = 'file'; input.id = 'test-file-upload'; document.body.appendChild(input); });
await page.locator('#test-file-upload').setInputFiles(testFile);
const files = await page.locator('#test-file-upload').evaluate(el => Array.from(el.files).map(f => f.name) ); expect(files).toContain('test-upload.txt');
fs.unlinkSync(testFile);});
test('Buffer-dən yükləyin — diskdə fayl lazım deyil', async ({ page }) => { await page.goto('/auth/register');
await page.evaluate(() => { const input = document.createElement('input'); input.type = 'file'; input.id = 'test-buffer-upload'; document.body.appendChild(input); });
await page.locator('#test-buffer-upload').setInputFiles({ name: 'data.csv', mimeType: 'text/csv', buffer: Buffer.from('id,name,price\n1,Widget,9.99\n2,Gadget,19.99'), });
const fileName = await page.locator('#test-buffer-upload').evaluate(el => el.files[0].name ); expect(fileName).toBe('data.csv');});Tapşırıqlar:
setInputFiles()-ın heç vaxt OS fayl dialoqu açmadığını müşahidə edin- Mövcud olmayan fayl path-i yükləməyə cəhd edin — Playwright hansı xəta atır?
- İki path-lik bir massiv verin: çoxlu fayl yükləmə variantını sınayın
Tapşırıq 6: Brauzer dialoqları — page.on('dialog')
Bölmə: “Tapşırıq 6: Brauzer dialoqları — page.on('dialog')”tests/starter/tests/actions/dialog-actions.spec.js yaradın. Səhifəyə native confirm() / prompt() dialoqlarını tetikləyən düymə inject olunur, ona görə tapşırıq istənilən səhifədə işləyir:
import { test, expect } from '@playwright/test';
test.describe('Dialoq idarəetməsi', () => {
test('confirm dialoqunu qəbul et', async ({ page }) => { await page.goto('/products');
// İstifadəçinin təsdiqləyib-təsdiqləmədiyini qeyd edən "Delete" düyməsi inject edin await page.evaluate(() => { const btn = document.createElement('button'); btn.id = 'confirm-btn'; btn.textContent = 'Delete'; btn.addEventListener('click', () => { const ok = confirm('Are you sure you want to delete this item?'); btn.dataset.result = ok ? 'accepted' : 'dismissed'; }); document.body.appendChild(btn); });
// Handler-i dialoqu tetikləyən klikdən ƏVVƏL qeydiyyatdan keçirin page.on('dialog', async (dialog) => { expect(dialog.type()).toBe('confirm'); expect(dialog.message()).toContain('Are you sure'); await dialog.accept(); });
await page.locator('#confirm-btn').click(); await expect(page.locator('#confirm-btn')).toHaveAttribute('data-result', 'accepted'); });
test('confirm dialoqunu rədd et', async ({ page }) => { await page.goto('/products'); await page.evaluate(() => { const btn = document.createElement('button'); btn.id = 'confirm-btn'; btn.addEventListener('click', () => { const ok = confirm('Delete this item?'); btn.dataset.result = ok ? 'accepted' : 'dismissed'; }); document.body.appendChild(btn); });
page.on('dialog', (dialog) => dialog.dismiss());
await page.locator('#confirm-btn').click(); await expect(page.locator('#confirm-btn')).toHaveAttribute('data-result', 'dismissed'); });
test('prompt dialoquna mətnlə cavab ver', async ({ page }) => { await page.goto('/products'); await page.evaluate(() => { const out = document.createElement('p'); out.id = 'prompt-output'; document.body.appendChild(out); const btn = document.createElement('button'); btn.id = 'prompt-btn'; btn.addEventListener('click', () => { out.textContent = prompt('Enter a coupon code:') || ''; }); document.body.appendChild(btn); });
page.on('dialog', (dialog) => dialog.accept('SAVE10'));
await page.locator('#prompt-btn').click(); await expect(page.locator('#prompt-output')).toHaveText('SAVE10'); });});Tapşırıqlar:
page.on('dialog', …)sətrini klikdən sonraya keçirin — dialoq avtomatik rədd olunur və assertion uğursuz olur. Bu, dialoqda 1 nömrəli səhvdir.- Handler içinə
console.log(dialog.type())əlavə edin ki,confirmvspromptvsalertgörəsiniz.
Tapşırıq 7: Iframelər — frameLocator()
Bölmə: “Tapşırıq 7: Iframelər — frameLocator()”tests/starter/tests/actions/iframe-actions.spec.js yaradın. beforeEach bir iframe inject edir (real ödəniş iframe-inin əvəzedicisi), beləcə frameLocator()-i istənilən səhifədə məşq edə bilərsiniz:
import { test, expect } from '@playwright/test';
test.describe('Iframe qarşılıqlı təsirləri', () => { test.beforeEach(async ({ page }) => { await page.goto('/products'); await page.evaluate(() => { const iframe = document.createElement('iframe'); iframe.id = 'payment-iframe'; iframe.srcdoc = '<label>Card number <input id="card" /></label>' + '<label>CVV <input id="cvv" /></label>' + '<button id="pay">Pay Now</button>'; document.body.appendChild(iframe); }); });
test('iframe içindəki sahələri doldur', async ({ page }) => { const frame = page.frameLocator('#payment-iframe'); await frame.locator('#card').fill('4111 1111 1111 1111'); await frame.locator('#cvv').fill('123'); await expect(frame.locator('#card')).toHaveValue('4111 1111 1111 1111'); await frame.getByRole('button', { name: 'Pay Now' }).click(); });
test('səhifə səviyyəli lokatorlar frame-in içini görə bilmir', async ({ page }) => { // #card iframe içindədir — page lokatoru heç nə tapmır await expect(page.locator('#card')).toHaveCount(0); // …amma frameLocator vasitəsilə eyni selektor onu tapır await expect(page.frameLocator('#payment-iframe').locator('#card')).toHaveCount(1); });});Tapşırıqlar:
#card-ı birbaşapage-dən (frameLocator olmadan) sorğulayın və timeout-a düşməsinə baxın — iframe məzmunu page səviyyəli lokatorlara görünmür.frameLocator()-in nəticəsində.goto()çağırmağa cəhd edin —FrameLocator-in naviqasiya metodları olmadığını qeyd edin.
Özünü yoxlama sualları
Bölmə: “Özünü yoxlama sualları”fill()iləpressSequentially()arasındakı fundamental fərq nədir?selectOption()istifadə etmək ilə click → gözlə → click şablonu arasında nə vaxt seçim edərdiniz?setInputFiles()OS fayl dialoqundan nə ilə fərqlənir?page.on('dialog', handler)dialoqu işə salan düyməni click etmədən əvvəl qeydiyyatdan keçirilməlidir?- iframe içindəki elementlərlə qarşılıqlı əlaqə üçün hansı metodu istifadə edirsiniz?
selectOption('nonexistent_value')çağırıldıqda nə baş verir?- Playwright-də Ctrl+A (hamısını seç) necə simulyasiya edilir?
.check()ilə.setChecked(true)arasındakı fərq nədir?
Əsas nəticələr
Bölmə: “Əsas nəticələr”Hər testə apardığınız dörd qayda:
- Formlar üçün
fill(), xüsusi hallar üçünpressSequentially()—fill()daha sürətlidir, həmişə əvvəlcə təmizləyir və daxiletmələrin 95%-i üçün düzgün hadisələri işə salır - Native
<select>=selectOption(), custom dropdown = click → gözlə → click — qərar vermədən əvvəl həmişə DOM-u yoxlayın setInputFiles()OS fayl dialoqlarını tamamilə əvəz edir — fayl path-ləri, massivlər və Buffer obyektləri ilə işləyir- Dialoqlar tetikleyicilərdən əvvəl dinləyici tələb edir; iframelər
frameLocator()istifadə edir — dialoq handler-ini onu açan əməliyyatdan əvvəl qeydiyyatdan keçirin
Modul 4 assertion-ları əhatə edir — tam expect() API-ni öyrənəcək və həm dəqiq, həm də zamanlama baxımından davamlı assertion-lar yazmağı öyrənəcəksiniz.