Məzmuna keç

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() 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əsi
await page.getByRole('button', { name: 'Log In' }).click();
// Keçidə click edin
await page.getByRole('link', { name: 'Register' }).click();
// Məhsul kartına click edin
await page.locator('.product-card').filter({ hasText: 'Wireless Mouse' }).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 edin
await page.locator('.editable-cell').dblclick();

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 edin
await page.locator('.product-card').click({ button: 'right' });

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ır
await 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.


Bu, həm ümumi müsahibə sualı, həm də gündəlik dizayn qərarıdır.

  • Sahəni əvvəlcə təmizləyir (üçlü click etməyə ehtiyac yoxdur)
  • Tək input hadisə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 edir
await page.locator('#email').fill('customer@test.io');
await page.locator('#password').fill('customer123');
// Mətn qəbul edən istənilən elementdə işləyir
await 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');
  • Hər simvol üçün keydown, keypress, keyup işə 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
await page.locator('#email').pressSequentially('customer@test.io', { delay: 30 });
// Ümumi istifadə halı: autocomplete / debounce edilmiş sahələr
await page.locator('#search').pressSequentially('Wire', { delay: 50 });
// Hər klaviatura zərbəsi autocomplete komponentinin dinlediyi hadisələri işə salır

pressSequentially() 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ə.


.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ın
await page.locator('#shipping_name').press('Tab');
// Modal ya da dropdown-u bağlamaq üçün Escape
await page.keyboard.press('Escape');
// Simvolları silmək üçün Backspace
await page.keyboard.press('Backspace');
// Naviqasiya üçün ox düymələri
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');

Modifier düymələri başqaları ilə birləşdirmək üçün + istifadə edin:

// Fokuslanmış elementdəki bütün mətni seçin
await page.keyboard.press('Control+A');
// Seçilmiş mətni kopyalayın
await page.keyboard.press('Control+C');
// Yapışdırın
await page.keyboard.press('Control+V');
// Seçilmiş mətni silin
await 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 edin
await 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().uncheck() istifadə edin.

// Checkboxu işarələyin
await page.getByLabel('Remember me').check();
// Checkboxu işarəsiz edin
await page.getByLabel('Subscribe to newsletter').uncheck();
// Cari vəziyyətdən asılı olmayaraq checkbox-ı müəyyən vəziyyətə qoyun
await 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 qrupları eyni name atributunu paylaşır. Seçmək istədiyiniz seçenekdə .check() istifadə edin.

// Radio düyməsini seçin
await page.getByLabel('Express Shipping').check();
await page.getByLabel('Standard Shipping').check();
// Hansı radio-nun hazırda seçildiyini doğrulayın
await 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ın
await expect(page.getByLabel('Remember me')).toBeChecked();
// Checkboxun işarəsiz olduğunu doğrulayın
await 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çin
await 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 verin
await page.locator('#tags').selectOption(['javascript', 'testing', 'playwright']);

TestMarket nümunəsi — məhsulları filtrləyin:

await page.goto('/products');
// Kateqoriyaya görə filtrləyin
await page.locator('#category').selectOption('electronics');
// Qiymətə görə azalan sıralama
await page.locator('#sort').selectOption('price_desc');
// Filtrləri tətbiq edin
await 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).


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 edin
await 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 edin
await page.locator('.dropdown-option:has-text("Option Name")').click();
// Addım 4 (isteğe bağlı): Menyunun bağlandığını doğrulayın
await 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 sabit waitForTimeout yox (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() mausu elementin üzərinə gətirir, mouseentermousemove 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 edin
await page.locator('.product-card').filter({ hasText: 'Wireless Mouse' }).hover();
// Hover etdikdən sonra tooltip / göstərilən element görünür olur
await expect(page.locator('#hover-tooltip')).toBeVisible();
await expect(page.locator('#hover-tooltip')).toContainText('Tooltip content');
// Uzaqlaşın — tooltip gizlənir
await 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 edin
await 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əyin
await 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.


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əyir
await page.locator('#start-date').fill('2025-06-15');
await page.locator('#end-date').fill('2025-07-31');

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çın
await 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 edin
await page.locator('.calendar-day:has-text("15")').click();
// Addım 4: Seçimi doğrulayın
await 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ç.


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əyin
await 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əyin
await 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ç.


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əyin
await page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf');
// Çoxlu fayl yükləyin
await page.locator('input[type="file"]').setInputFiles([
'/path/to/file1.txt',
'/path/to/file2.txt',
]);
// Buffer-dən yükləyin — fiziki fayl lazım deyil
await 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 edin
await 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.


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çirin
page.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ır
await 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 edin
page.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 verin
page.on('dialog', async (dialog) => {
await dialog.accept('yazılmış mətn burada');
});
// Müxtəlif dialoq növlərini seçici şəkildə idarə edin
page.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-ında
class 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.


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ə edin
const frame = page.frameLocator('#payment-iframe');
// iframe içindəki bütün normal lokator metodlarını istifadə edin
await 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 iframe
const frame = page.frameLocator('#my-iframe');
// id-siz iframe — src ya da başqa atribut istifadə edin
const 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 üçün
const 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()).


İ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 dropdownklik → 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 dialoqutetikdən əvvəl page.on('dialog', …)
Iframe içindəpage.frameLocator(sel)


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('#email').fill('customer@test.io');
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('#email').pressSequentially('customer@test.io', { delay: 30 });
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('#email').fill('customer@test.io');
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 --headed ilə 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('#email').fill('customer@test.io');
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 — value atributları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').fill('customer@test.io');
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');
expect(await page.locator('#password').inputValue()).toBe('customer@test.io');
});
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, confirm vs prompt vs alert gö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şa page-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.

  1. fill() ilə pressSequentially() arasındakı fundamental fərq nədir?
  2. selectOption() istifadə etmək ilə click → gözlə → click şablonu arasında nə vaxt seçim edərdiniz?
  3. setInputFiles() OS fayl dialoqundan nə ilə fərqlənir?
  4. page.on('dialog', handler) dialoqu işə salan düyməni click etmədən əvvəl qeydiyyatdan keçirilməlidir?
  5. iframe içindəki elementlərlə qarşılıqlı əlaqə üçün hansı metodu istifadə edirsiniz?
  6. selectOption('nonexistent_value') çağırıldıqda nə baş verir?
  7. Playwright-də Ctrl+A (hamısını seç) necə simulyasiya edilir?
  8. .check() ilə .setChecked(true) arasındakı fərq nədir?

Hər testə apardığınız dörd qayda:

  1. Formlar üçün fill(), xüsusi hallar üçün pressSequentially()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
  2. Native <select> = selectOption(), custom dropdown = click → gözlə → click — qərar vermədən əvvəl həmişə DOM-u yoxlayın
  3. setInputFiles() OS fayl dialoqlarını tamamilə əvəz edir — fayl path-ləri, massivlər və Buffer obyektləri ilə işləyir
  4. 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.