Məzmuna keç

Modul 1: QA üçün JavaScript və TypeScript

Bu modul Playwright testlərini yazmaq üçün lazım olan TypeScript və JavaScript biliklərini verir — nə çox, nə az. Əgər daha əvvəl kod yazmamısınızsa, problem deyil. Əgər daha əvvəl JS yazmısınızsa, bu modul gündəlik istifadə edəcəyiniz şablonları möhkəmləndirəcək.

Kurs boyu TypeScript istifadə edirik — Modul 0-da npm init playwright@latest bunu sizin üçün qurub. TypeScript ixtiyari tiplərlə JavaScript-dir, ona görə də aşağıdakı hər bir JavaScript nümunəsi eyni zamanda etibarlı TypeScript-dir.

🎬 Video tezliklə əlavə olunacaq


constletvar heç vaxt istifadə etməyin

Bölmə: “const və let — var heç vaxt istifadə etməyin”

Hər test faylında iki dəyişən açar sözü görəcəksiniz: constlet.

const baseURL = 'http://localhost:3000'; // yenidən mənimsədilməyəcək
let retryCount = 0; // sonra dəyəri dəyişəcək

Qayda: standart olaraq const istifadə edin. Dəyişənə yeni dəyər mənimsətməli olduqda let-ə keçin.

// ✅ Düzgün
const TIMEOUT = 30000;
let attempts = 0;
attempts = attempts + 1; // yaxşıdır — let yenidən mənimsətməyə icazə verir
// ❌ Xəta verir
const MAX = 5;
MAX = 10; // TypeError: Assignment to constant variable

Vacib bir məqam: const dəyişməzlik demək deyil. Obyekt xüsusiyyətlərini dəyişdirmək mümkündür:

const config = { retries: 1 };
config.retries = 2; // ✅ buna icazə verilir
config = { retries: 3 }; // ❌ buna icazə verilmir — referansı yenidən mənimsədə bilməzsiniz

var haqqında: mövcuddur, amma çaşdırıcı scope xətaları yaradır. Bütün müasir linter-lər bunu işarələyir. Playwright layihəsində heç vaxt var-a ehtiyac olmayacaq.

constlet blok-scoped-dır — yalnız elan edildikləri { } blokunun daxilindən əlçatandır. Aşağıdakı nümunədə message yalnız if bloku daxilindədir; blokundan kənarda ReferenceError atılır:

if (true) {
let message = 'Salam!';
}
console.log(message); // ❌ ReferenceError: message is not defined

{ } içindəki let-i yalnız o otaqda oxuya biləcəyin qeyd kimi düşün — mötərizədən kənara çıxdın, getdi.

Tapşırıq 1-də məhz bu xətanı düzəldəcəksiniz.


Tapşırıqları Necə İşlətməli Slaydlar — yeni pəncərədə açılır

Bütün tapşırıqlar sadə Node.js ilə işləyir — brauzerlərə ehtiyac yoxdur.

  1. Modul 0-da qurduğunuz Playwright layihəsini VS Code-da açın.
  2. Layihənin kökündə exercises/mod1/ qovluğunu yaradın (Explorer panelində sağ klik → New Folder).
  3. Hər tapşırıq üçün ayrı fayl yaradın: exercise-1.ts, exercise-2.tsexercise-6.ts.
  4. İnteqrasiya olunmuş terminalı açın (Ctrl + `) — yolun layihə kökü olduğundan əmin olun.
  5. Faylı işlədin: npx tsx exercises/mod1/exercise-1.ts

tsx tapılmırsa: npm install -D tsx


Tapşırıq 1: Dəyişənlər — xətaları tapın

Bölmə: “Tapşırıq 1: Dəyişənlər — xətaları tapın”

Aşağıdakı kodda bir neçə problem var. Onları düzəldin və faylı işlədin.

// ❌ Bu kodda problemlər var. Düzəldin.
var baseURL = 'http://localhost:3000';
const TIMEOUT = 30000;
TIMEOUT = 50000; // Bu icazəlidirmi?
const user = { email: 'test@test.io', role: 'admin' };
user = { email: 'admin@test.io', role: 'admin' }; // Bu işləyəcəkmi?
if (true) {
let message = 'Salam!';
}
console.log(message); // Niyə xəta var?
console.log('Tapşırıq 1 tamamlandı');

Addımlar:

  • var baseURLconst ilə əvəzləyin
  • TIMEOUT yenidən mənimsətməni düzəldin — yeni dəyişən istifadə edin (məs. TIMEOUT_EXTENDED)
  • user = {...}const dəyişəninə mənimsətmə xətasıdır; let user ilə elan edin
  • message scope xətasını düzəldin — console.log-u blokun içinə köçürün
  • Düzəldilmiş faylı işlədin və xəta olmadığını yoxlayın

Gözlənilən nəticə:

Salam!
Tapşırıq 1 tamamlandı
Həll
const baseURL = 'http://localhost:3000';
const TIMEOUT = 30000;
const TIMEOUT_EXTENDED = 50000; // TIMEOUT-u yenidən mənimsətmək əvəzinə yeni dəyişən
let user = { email: 'test@test.io', role: 'admin' };
user = { email: 'admin@test.io', role: 'admin' }; // `let` yenidən mənimsətməyə icazə verir
if (true) {
let message = 'Salam!';
console.log(message); // scope-da olsun deyə blokun içinə köçürüldü
}
console.log('Tapşırıq 1 tamamlandı');

Yadda saxla: standart olaraq const, yenidən mənimsətmək lazımdırsa let, heç vaxt var.


Bu üç primitiv tip test fayllarında istifadə edəcəklərinizin böyük hissəsini əhatə edir.

const url = 'http://localhost:3000/login';
const selector = '#email-input';
// Template literal (backtick) — dəyər yerləşdirəndə bunları istifadə edin
const message = `Keçildi: ${url}`;
const fullSelector = `[data-testid="${selector}"]`;

Template literal-lar (backtick-li string-lər) Playwright-də çox istifadə olunur — konfiqurasiya fayllarında, page object-lərdə və iddia ifadələrində.

“Dəyər yerləşdirmək” nə deməkdir?

“Dəyər yerləşdirmək” — dəyişənin içindəkini birbaşa string-in içinə salmaq deməkdir. Template literal-ın içində ${ ... } yer tutucudur: JavaScript mötərizələrin arasındakını hesablayır və nəticəni mətnə əlavə edir.

const url = 'http://localhost:3000';
const message = `Keçildi: ${url}`;
// indi message belədir: 'Keçildi: http://localhost:3000'

Template literal olmadan hissələri əllə + ilə birləşdirməli olardın:

const message = 'Keçildi: ' + url; // eyni nəticə, çox dəyər olanda oxumaq çətindir

Təkcə dəyişəni yox, istənilən ifadəni də yerləşdirə bilərsən — `Cəmi: ${price * qty}` da işləyir.

İstifadə edəcəyin string metodları. Səhifə mətni qarışıq olur — çox vaxt artıq boşluq və ya fərqli registrlə gəlir, ona görə string-ləri təmizləmək və yoxlamaq gündəlik QA işidir:

const path = 'http://localhost:3000/login';
console.log(path.includes('/login')); // → true (bu mətni ehtiva edir?)
console.log(path.startsWith('http')); // → true
console.log(path.endsWith('.com')); // → false
console.log(' Submit '.trim()); // → 'Submit' (ətrafdakı boşluqları silir)
console.log('LOGIN'.toLowerCase()); // → 'login' (müqayisədən əvvəl registri normallaşdır)

Həmçinin .replace() (məs. Number()-dən əvvəl $ və vergülləri sil) və .split() (ayrılmış mətni array-ə böl) istifadə edəcəksən.

const timeout = 5000; // millisaniyə
const retries = 3;
const price = 19.99;

Tip çevrilmələrinə diqqət edin (aşağıdakı nümunələr xalis JavaScript davranışını göstərir — TypeScript tipləri yoxdur):

'5' - 3 // → 2 (JS string-i rəqəmə çevirir)
'5' + 3 // → '53' (JS 3-ü string-ə çevirir — tələ!)

Praktikada açıq çevirmədən istifadə edin: Number('5') və ya parseInt('5', 10).

”Açıq çevirmə” nədir?

JavaScript bəzən tipləri sənin əvəzinə avtomatik çevirir — yuxarıdakı tələ də budur, orada '5' + 3 səssizcə '53' olur. Açıq çevirmə o deməkdir ki, çevirməni sən qəsdən edirsən, beləliklə nəticə heç vaxt gözlənilməz olmur:

Number('5') // → 5 (string-dən rəqəmə)
parseInt('5', 10) // → 5 (string-dən tam ədədə; 10 onluq say sistemi deməkdir)
String(5) // → '5' (rəqəmdən string-ə)
Boolean('') // → false (boş string-dən boolean-a)

Buna dəyər string kimi gəldikdə, amma sənə əsl rəqəm və ya boolean lazım olduqda müraciət edəcəksən — məsələn, process.env dəyişənlərini və ya səhifədən götürülən mətni oxuyanda (hər ikisi bu modulda sonra var).

const isLoggedIn = true;
const hasErrors = false;

Boolean-ları && (VƏ) və || (VƏ YA) ilə birləşdirirsən — if ifadələrində və iddialarda daim istifadə olunur (! birini tərsinə çevirir):

console.log(isLoggedIn && !hasErrors); // → true (hər iki tərəf true olmalıdır)
console.log(isLoggedIn || hasErrors); // → true (ən azı bir tərəf true-dur)

Konfiqurasiya və env fayllarında rast gələcəyin daha iki operator:

const port = process.env.PORT ?? '3000'; // ?? — sağ tərəfi yalnız sol null/undefined olduqda işlədir
const isCI = !!process.env.CI; // !! — istənilən dəyəri əsl true/false-a çevirir (! tərsinə çevirir, !! iki dəfə)
// Həmişə === (ciddi bərabərlik), heç vaxt ==
'5' === 5 // → false (fərqli tiplər)
'5' == 5 // → true (tip çevrilməsi — bundan qaçın)

Playwright-də boolean-lar iddia ifadələrində (expect(isVisible).toBe(true)) və konfiqurasiyada (headless: true) görünür.

Yadda saxla: template literal ${ } ilə dəyər yerləşdirir; şərtləri &&/|| ilə birləşdir; qarışıq mətni .trim() / .toLowerCase() / .includes() ilə təmizlə.


const browsers = ['chromium', 'firefox', 'webkit'];
const prices = [9.99, 14.99, 29.99];
// Ümumi əməliyyatlar
console.log(browsers.length); // 3
console.log(browsers[0]); // 'chromium'
browsers.push('electron'); // array-i dəyişir: sona əlavə edir
console.log(browsers); // ['chromium', 'firefox', 'webkit', 'electron']
// filter və map orijinalı DƏYİŞMİR — YENİ array qaytarır,
// ona görə nəticəni yeni dəyişənə yaz
const withoutWebkit = browsers.filter(b => b !== 'webkit');
console.log(withoutWebkit); // ['chromium', 'firefox', 'electron']
const upperCased = browsers.map(b => b.toUpperCase());
console.log(upperCased); // ['CHROMIUM', 'FIREFOX', 'ELECTRON']

Bunu sənəd kimi düşün: push/sort/splice orijinalı yerində dəyişir, filter/map isə onu fotosurət çıxarıb sənə nüsxəni verir — orijinal toxunulmaz qalır.

Bəzən istifadə edəcəyin daha çox array metodu

Bunlar Playwright testlərində push, filtermap qədər tez-tez rast gəlmir, amma tanımağa dəyər. Hansının orijinal array-i dəyişdiyinə diqqət et:

const nums = [3, 1, 2];
console.log(nums.pop()); // SONUNCU elementi silib qaytarır → 2; nums indi [3, 1]
console.log(nums.shift()); // BİRİNCİ elementi silib qaytarır → 3; nums indi [1]
nums.unshift(9); // ƏVVƏLƏ əlavə edir → nums indi [9, 1]
nums.splice(1, 1); // 1-ci indeksdən başlayaraq 1 element silir → nums indi [9]
const letters = ['c', 'a', 'b'];
letters.sort(); // dəyişir → ['a', 'b', 'c']
letters.reverse(); // dəyişir → ['c', 'b', 'a']

pop, shift, unshift, splice, sortreverse hamısı orijinal array-i dəyişir (filter/map-dən fərqli olaraq — onlar yeni array qaytarır). Orijinalı qorumaq üçün əvvəlcə spread operatoru ilə kopyala: const sorted = [...letters].sort();.

const user = {
email: 'customer@test.io',
password: 'customer123',
role: 'admin',
};
// Xüsusiyyətlərə giriş
console.log(user.email); // 'customer@test.io'
console.log(user['role']); // 'admin' — dinamik açarlar üçün mötərizə notasiyası

Destructuring, obyekt və array-lərdən dəyərləri təmiz şəkildə çıxarmağa imkan verir:

const { email, password } = user;
// eyni məna daşıyır: const email = user.email; const password = user.password;
console.log(email, password); // 'customer@test.io' 'customer123'
const [first, second] = browsers;
// eyni məna daşıyır: const first = browsers[0]; const second = browsers[1];
console.log(first, second); // 'chromium' 'firefox'

Playwright-də bunu daim görəcəksiniz — məsələn, freymvorkdan testexpect-i çıxararkən:

import { test, expect } from '@playwright/test';

Spread operatoru (...) obyekt və array-ləri kopyalayır və birləşdirir:

const defaultOptions = { headless: true, slowMo: 0 };
const debugOptions = { ...defaultOptions, headless: false, slowMo: 500 };
console.log(debugOptions); // { headless: false, slowMo: 500 }

Test konfiqurasiyalarını birləşdirmək və dəyişdirilmiş test data obyektləri qurmaq üçün geniş istifadə olunur.


Tapşırıq 2: Test data qur və istifadə et

Bölmə: “Tapşırıq 2: Test data qur və istifadə et”

Obyektlər, array-lər, spread operatoru və destructuring istifadə edərək kiçik test data dəsti qur — hələ funksiyalar yoxdur (onlar növbətidir).

const validCustomer = {
email: 'customer@test.io',
password: 'customer123',
role: 'customer',
};
const products = [
{ name: 'Siçan', price: 9.99, category: 'aksesuarlar' },
{ name: 'Klaviatura', price: 29.99, category: 'aksesuarlar' },
{ name: 'Monitor', price: 199.99, category: 'displeylər' },
];
// 1. validAdmin-i validCustomer-dən spread ilə qur — yalnız email və role-u dəyiş.
// 2. accessoryNames — bütün 'aksesuarlar' məhsullarının adları (filter + map).
// 3. validAdmin-dən email və role-u destructure et.
console.log('Admin:', validAdmin);
console.log('Aksesuarlar:', accessoryNames);
console.log('Destructure:', email, role);

Gözlənilən nəticə:

Admin: { email: 'admin@test.io', password: 'customer123', role: 'admin' }
Aksesuarlar: [ 'Siçan', 'Klaviatura' ]
Destructure: admin@test.io admin
Həll
const validAdmin = { ...validCustomer, email: 'admin@test.io', role: 'admin' };
const accessoryNames = products.filter(p => p.category === 'aksesuarlar').map(p => p.name);
const { email, role } = validAdmin;

Yadda saxla: filter/map yeni array qaytarır; push/sort/splice orijinalı dəyişir. Dəyərləri destructuring ilə çıxar, spread ... ilə kopyala/birləşdir.


Funksiyalar və arrow funksiyalar

Bölmə: “Funksiyalar və arrow funksiyalar”
// Parametrsiz — sadəcə dəyər qaytarır
function getTimestamp(): number {
return Date.now();
}
// Parametrli
function formatPrice(price: number): string {
return `$${price.toFixed(2)}`;
}

price-dakı : number annotasiyası və mötərizələrdən sonrakı : string TypeScript tipləridir — onlar redaktora nəyin daxil ola biləcəyini və nəyin çıxdığını bildirir. getTimestamp-dakı boş () heç bir arqument qəbul etmədiyini bildirir. Hər ikisini hər yerdə görəcəksiniz.

Arrow funksiyalar daha qısa, müasir sintaksisdir — Playwright-də hər yerdə görünür:

const formatPrice = (price: number): string => `$${price.toFixed(2)}`;
// Çox sətirli arrow funksiya { } və açıq return tələb edir
const generateUser = (role: string) => {
const timestamp = Date.now();
return { email: `${role}_${timestamp}@test.io`, role };
};

Hər Playwright testi arrow funksiyadan istifadə edir:

test('istifadəçi daxil ola bilər', async ({ page }) => {
await page.goto('/login');
// ...
});

Nəyi nə vaxt istifadə etməli:

  • Arrow funksiyalar: callback-lər, test gövdələri, qısa köməkçi funksiyalar
  • Adi function: class metodları (məsələn, this istifadə edən Page Object metodları)

Tək ifadəli arrow funksiyalar dəyəri gizli qaytarır — return açar sözü lazım deyil:

const double = x => x * 2; // gizli return — x * 2 qaytarır
const triple = x => { x * 3 }; // ❌ return yoxdur — undefined qaytarır
const triple = x => { return x * 3 }; // ✅ açıq return

Tək ifadəli arrow-dan obyekt qaytarmaq bu tələnin klassik formasıdır — { funksiya gövdəsi kimi görünür, ona görə obyekti mötərizəyə almalısan:

const makeUser = role => { email: role }; // ❌ `{ }` gövdə kimi oxunur — undefined qaytarır
const makeUser = role => ({ email: role }); // ✅ mötərizə → obyekti qaytarır

Tapşırıq 3: Köməkçi funksiyalar yazın

Bölmə: “Tapşırıq 3: Köməkçi funksiyalar yazın”

Bu beş arrow funksiyanı exercise-3.ts-də yaz. 5-ci tapşırıqda bu kataloqu istifadə edəcəksən:

const catalog = [
{ name: 'Siçan', price: 9.99, category: 'aksesuarlar' },
{ name: 'Monitor', price: 199.99, category: 'displeylər' },
];
// 1. formatPrice(price) — 19.99-dan "$19.99" (tək ifadə → gizli return)
// 2. isEmail(email) — string '@' və '.' ehtiva edirsə true
// 3. makeUser(role) — { email: `${role}_${Date.now()}@test.io`, role } (çox sətirli → açıq return)
// 4. makeProduct(name, price) — { name, price, category: 'aksesuarlar' } (iki parametr)
// 5. filterByCategory(products, category) — yalnız kateqoriyası uyğun gələn elementlər

Sonra onları çağır ki, nəticə uyğun gəlsin:

console.log(formatPrice(19.99)); // $19.99
console.log(isEmail('customer@test.io')); // true
console.log(isEmail('e-poçt-deyil')); // false
console.log(makeUser('admin')); // { email: 'admin_1712345678901@test.io', role: 'admin' }
console.log(makeProduct('Klaviatura', 29.99)); // { name: 'Klaviatura', price: 29.99, category: 'aksesuarlar' }
console.log(filterByCategory(catalog, 'aksesuarlar')); // [ { name: 'Siçan', price: 9.99, category: 'aksesuarlar' } ]

İpuclar: template literal, .toFixed(2), .includes(), Date.now(), .filter(), və hər parametrə tip annotasiyası. Qeyd: #1 return olmadan birbaşa qaytarır; #3 və #4 çox sətirlidir, ona görə açıq return lazımdır.

Həll
const formatPrice = (price: number): string => `$${price.toFixed(2)}`;
const isEmail = (email: string): boolean => email.includes('@') && email.includes('.');
// Tək ifadəli arrow-dan obyekt qaytarmaq üçün mötərizə lazımdır:
const makeUser = (role: string) => ({ email: `${role}_${Date.now()}@test.io`, role });
const makeProduct = (name: string, price: number) => ({ name, price, category: 'aksesuarlar' });
const filterByCategory = (
products: { name: string; price: number; category: string }[],
category: string,
) => products.filter(p => p.category === category);

Yadda saxla: tək ifadəli arrow gizli qaytarır; çox sətirli { }return tələb edir; obyekti => ({ ... }) ilə qaytar.


Playwright ES modulları (ESM) istifadə edir — import / export sintaksisi. Yazacağınız hər .ts faylı ən azı bir import ilə başlayacaq.

// Adlandırılmış import-lar { } istifadə edir — adlar faylın adlandırılmış export-ları ilə uyğun gəlməlidir
import { test, expect } from '@playwright/test';
import { chromium, firefox } from '@playwright/test'; // bir neçəsini birdən import et
// ÖZ LoginPage faylınızı import etmək — necə export olunduğuna görə BİRİNİ seç:
import LoginPage from './pages/LoginPage'; // LoginPage.ts `export default` istifadə edirsə
// import { LoginPage } from './pages/LoginPage'; // adlandırılmış `export` istifadə edirsə

Qeyd: öz faylınızı import edəndə yola .ts uzantısı əlavə etmirsiniz.

// LoginPage.ts — Variant A: adlandırılmış export (bir faylda bir neçə ola bilər)
import { Page } from '@playwright/test';
export class LoginPage {
constructor(public page: Page) {}
}
// LoginPage.ts — Variant B: default export (fayl başına yalnız bir dənə)
import { Page } from '@playwright/test';
class LoginPage {
constructor(public page: Page) {}
}
export default LoginPage;
Adlandırılmış vs default export — hansı import hansına uyğundur?
  • Adlandırılmış export (export class LoginPage, export const x) → onu mötərizə ilə import et: import { LoginPage }. Bir faylda çoxlu adlandırılmış export ola bilər və import adı export ilə uyğun gəlməlidir (lazım olsa import { LoginPage as Login } ilə adını dəyiş).
  • Default export (export default LoginPage) → onu mötərizəsiz import et: import LoginPage. Bir faylda yalnız bir default ola bilər və import edərkən onu istədiyin kimi adlandıra bilərsən.

Yadda saxla: mötərizə ⇔ adlandırılmış, mötərizəsiz ⇔ default. Playwright freymvorku sənə adlandırılmış export-lar verir (test, expect); Page Object fayllarınız çox vaxt default export olur.

export açar sözünü unutsanız, import undefined qaytaracaq və TypeError: LoginPage is not a constructor kimi xətalar görəcəksiniz.

CommonJS-i hələ də görə bilərsiniz

Bölmə: “CommonJS-i hələ də görə bilərsiniz”

Köhnə Node.js və Playwright nümunələri CommonJS sintaksisindən istifadə edir — const { test } = require('@playwright/test')module.exports = LoginPage. Hər iki üsul Node.js-də işləyir, amma rəsmi Playwright generatoru və bu kurs hər yerdə ESM import / export istifadə edir. Bir təlimatda ya köhnə repo-da require() görsəniz, eyni ideya, fərqli sintaksis kimi qəbul edin.

Yadda saxla: mötərizə = adlandırılmış, mötərizəsiz = default; fayl başına bir default.


async/await — Niyə Vacibdir? Slaydlar — yeni pəncərədə açılır

Bu bölmə flaky testlərdən qaçmaq üçün ən vacibdir. await unutmaq test uğursuzluqlarının 1 nömrəli səbəbidir.

Asinxron əməliyyatları anlamaq üçün qəhvə sifariş etməyi təsəvvür edin: kassaya pul verirsiniz və çek alırsınız — bu sizin Promise-inizdir. await isə piştaxtanın yanında dayanıb qəhvə hazır olana qədər gözləməkdir. Çek (Promise) dərhal əlinizdədir, nəticə isə bir az sonra gəlir.

Promise hələ mövcud olmayan bir dəyəri təmsil edir — asinxron əməliyyatın nəticəsidir. Playwright demək olar ki, hər əməliyyatdan Promise qaytarır:

// page.goto() Promise qaytarır
const promise = page.goto('/login'); // naviqasiyanı başladır amma gözləmir
async function login() {
await page.goto('/login'); // naviqasiyanın tamamlanmasını gözləyir
await page.fill('#email', '...'); // doldurmağın tamamlanmasını gözləyir
await page.click('button'); // klikləmənin tamamlanmasını gözləyir
}

await olmadan test əməliyyat bitmədən növbəti sətirə keçir:

// ❌ await yoxdur — test dərhal davam edir
page.goto('/login');
page.fill('#email', 'user@test.io'); // səhifə hələ yüklənməyib ola bilər!
expect(page).toHaveURL('/dashboard'); // login olmadan işləyir!

Sıranın necə dəyişdiyini özün görə bilərsən — bu xalis Node-da, brauzersiz işləyir:

const log = (label: string) =>
new Promise<void>(r => setTimeout(() => { console.log(label); r(); }, 50));
async function demo() {
log('first'); // ❌ await edilmir
console.log('second'); // "first" bitmədən işləyir!
await log('third'); // ✅ await edilir
console.log('fourth'); // "third"-ü gözləyir
}
demo();
// Çap edir: second, first, third, fourth ← "second" "first"-i qabaqlayır, çünki "first" await edilmədi

await istifadə edən hər funksiya async ilə elan edilməlidir:

// ✅ Düzgün
async function runTest() {
await page.goto('/');
}
// Arrow funksiya kimi də işləyir
const runTest = async () => {
await page.goto('/');
};

async funksiya həmişə Promise qaytarır. Əgər async funksiyadan 'salam' qaytarsanız, çağıran tərəf Promise<string> alır — onlar da await etməlidir.

Promise.all — paralel əməliyyatlar

Bölmə: “Promise.all — paralel əməliyyatlar”

Eyni anda bir neçə şeyi gözləmək lazım olduqda:

// Naviqasiya VƏ başqa bir şeyi eyni anda gözləyin
const [response] = await Promise.all([
page.waitForResponse('/api/login'),
page.click('button[type="submit"]'),
]);

Bu, klik etdikdə şəbəkə sorğusunu gözləmək lazım olan naviqasiya şablonları üçün istifadə olunur.


Tapşırıq 4: Xətalı asinxron kodu düzəldin

Bölmə: “Tapşırıq 4: Xətalı asinxron kodu düzəldin”

Aşağıdakı kodda 6 əksik await açar sözü var. Onları tapın və düzəldin.

const fakePage = {
goto: (url: string) => new Promise<void>(r =>
setTimeout(() => { console.log(`Keçildi: ${url}`); r(); }, 50)),
fill: (selector: string, value: string) => new Promise<void>(r =>
setTimeout(() => { console.log(`${selector} dolduruldu: ${value}`); r(); }, 50)),
click: (selector: string) => new Promise<void>(r =>
setTimeout(() => { console.log(`Klik edildi: ${selector}`); r(); }, 50)),
waitForSelector: (selector: string) => new Promise<void>(r =>
setTimeout(() => { console.log(`Gözlənildi: ${selector}`); r(); }, 50)),
screenshot: () => new Promise<void>(r =>
setTimeout(() => { console.log('Ekran görüntüsü alındı'); r(); }, 50)),
};
async function runTest() {
console.log('Test başlayır...');
fakePage.goto('/auth/login');
console.log('Login səhifəsinə keçildi');
fakePage.waitForSelector('#email');
fakePage.fill('#email', 'customer@test.io');
fakePage.fill('#password', 'customer123');
fakePage.click('button[type="submit"]');
console.log('Login formu göndərildi');
fakePage.screenshot();
console.log('Test tamamlandı!');
}
runTest();

İşlətməzdən əvvəl çıxış sırasını proqnozlaşdır və qeyd et. Sonra xətalı versiyanı işlət — sıranın yanlış olduğunu gör — await əlavə et və müqayisə üçün yenidən işlət.

Həll

Altı fakePage çağırışının hər birindən əvvəl await əlavə et:

async function runTest() {
console.log('Test başlayır...');
await fakePage.goto('/auth/login');
console.log('Login səhifəsinə keçildi');
await fakePage.waitForSelector('#email');
await fakePage.fill('#email', 'customer@test.io');
await fakePage.fill('#password', 'customer123');
await fakePage.click('button[type="submit"]');
console.log('Login formu göndərildi');
await fakePage.screenshot();
console.log('Test tamamlandı!');
}

Yadda saxla: hər Playwright əməliyyatından əvvəl await — əksik await flaky testlərin #1 səbəbidir.


Bu modul boyu artıq TypeScript yazırsınız — hər annotasiya edilmiş parametr (price: number, role: string) iş başında olan TypeScript-dir. Bu bölmə sadəcə “tip annotasiyaları olan JS” hissələri bir araya gətirmir: interface-lər, istəyə bağlı xüsusiyyətlər və JSDoc.

Tiplər kodu işlətməzdən əvvəl xətaları aşkar edir:

// JavaScript — xəta yalnız işləmə zamanı baş verir
function formatPrice(price) {
return `$${price.toFixed(2)}`;
}
formatPrice('rəqəm-deyil'); // işləmə zamanı çöküş
// TypeScript — xəta dərhal redaktorda görünür
function formatPrice(price: number): string {
return `$${price.toFixed(2)}`;
}
formatPrice('rəqəm-deyil'); // ❌ TypeScript xətası: Argument of type 'string' is not assignable to parameter of type 'number'

VS Code-dakı qırmızı dalğalı xətt — siqnaldır. İşə salmadan öncə xətanı düzəldirsiniz.

Qırmızı dalğalı xətt — tiplər səhvi necə tutur Slaydlar — yeni pəncərədə açılır

Interface-lər obyektin formasını təsvir edir. Test data üçün istifadə edin:

interface User {
email: string;
password: string;
role: 'admin' | 'customer'; // union type — yalnız bu iki dəyər
}
interface Product {
name: string;
price: number;
category: string;
}
const user: User = {
email: 'customer@test.io',
password: 'customer123',
role: 'customer',
};

İstəyə bağlı xüsusiyyətlər

Bölmə: “İstəyə bağlı xüsusiyyətlər”

Xüsusiyyəti istəyə bağlı etmək üçün ? əlavə edin:

interface ShippingDetails {
name: string;
address: string;
city: string;
zip: string;
country?: string; // istəyə bağlı — undefined ola bilər
}

JSDoc — .ts faylları olmadan TypeScript faydaları

Bölmə: “JSDoc — .ts faylları olmadan TypeScript faydaları”

Sadə .js faylında tip yoxlaması lazım olarsa, JSDoc annotasiyalarından istifadə edin. VS Code onları oxuyur və sizə avtomatik tamamlama və tip xətaları göstərir — TypeScript kimi, amma heç bir build addımı olmadan.

/**
* @param {number} price
* @returns {string}
*/
const formatPrice = (price) => `$${price.toFixed(2)}`;
class LoginPage {
/** @param {import('@playwright/test').Page} page */
constructor(page) {
this.page = page;
}
}

Tez-tez ehtiyacınız olmayacaq — kurs .ts faylları istifadə edir — amma köhnə kod bazalarında görəcəksiniz.

Yadda saxla: tiplər xətaları işə salmazdan əvvəl redaktorda tutur; interfeyslər obyektin formasını təsvir edir.


Xətaları və stack trace-ləri oxumaq

Bölmə: “Xətaları və stack trace-ləri oxumaq”

Test uğursuz olduqda, xəta mesajı nə baş verdiyini dəqiq deyir — əgər oxumağı bilsəniz.

Playwright xətasının anatomiyası

Bölmə: “Playwright xətasının anatomiyası”
1) [chromium] › login.spec.ts:10:3 › admin can login to dashboard ───────────
TypeError: Cannot read properties of undefined (reading 'click')
16 | async login(email: string, password: string) {
17 | await this.emailInput.fill(email);
> 18 | await this.loginButton.click();
| ^
19 | }
at LoginPage.login (tests/pages/LoginPage.ts:18:28)
at tests/login.spec.ts:15:12

Yuxarıdan aşağı oxuyun:

  1. Test başlığı[chromium] › login.spec.ts:10:3 › admin can login to dashboard brauzer proyektini, testin başladığı fayl və sətri, və test adını göstərir.
  2. Xəta tipi və mesajıTypeError: Cannot read properties of undefined (reading 'click'). Bir şey undefined-dir və siz onun üzərində .click() çağırmağa cəhd etdiniz.
  3. Kod çərçivəsi — xətanın ətrafındakı sətirlər. 18-ci sətirdəki > oxu uğursuz sətri işarələyir; ^ işarəsi tam olaraq pozulan ifadəni göstərir — burada this.loginButton undefined-dir.
  4. Stack sətirləri — hər çağırışın öz fayllarınızda haradan gəldiyi. Stack-in başı — xətanın baş verdiyi yer; aşağıda — onu çağıran kod.

node_modules stack sətirlərinə məhəl qoymayın — bunlar Playwright-in daxili işləridir. Öz fayllarınıza diqqət edin.

XətaMənaHəll
Cannot read properties of null (reading 'click')Bir dəyişən və ya handle null-dur — gözlənilən obyekt tapılmadıDəyişənin düzgün başladıldığını və selector-un düzgün yazıldığını yoxlayın
Cannot read properties of undefinedDəyişən mənimsədilməyibImport-ları, dəyişən scope-unu yoxlayın
ReferenceError: X is not definedDəyişən scope-da elan edilməyibYazılışı, elan yerini yoxlayın
TypeError: X is not a functionYanlış şey import edilib ya export unudulubconsole.log(typeof X) yoxlayın
Nəticə sırası yanlışdırawait yoxdurAsinxron çağırışlardan əvvəl await əlavə edin

Tapşırıq 5: Stack trace-i oxuyun

Bölmə: “Tapşırıq 5: Stack trace-i oxuyun”

Bu xətanı oxuyun və aşağıdakı sualları cavablayın:

1) [chromium] › login.spec.ts:10:3 › admin can login to dashboard ───────────
TypeError: Cannot read properties of undefined (reading 'click')
16 | async login(email: string, password: string) {
17 | await this.emailInput.fill(email);
> 18 | await this.loginButton.click();
| ^
19 | }
at LoginPage.login (tests/pages/LoginPage.ts:18:28)
at tests/login.spec.ts:15:12

Suallar:

  1. Xəta faktiki olaraq harada baş verdi? (fayl, sətir, sütun)
  2. Uğursuz olan testin adı nədir?
  3. “Cannot read properties of undefined” bu kontekstdə nə deməkdir — nə undefined-dir?
  4. Bunu düzəltmək üçün ilk nəyi yoxlayardınız?

Cavablarınızı exercise-5.ts faylında şərh kimi yazın.

Həll
  1. Harada: tests/pages/LoginPage.ts, sətir 18, sütun 28> 18 | sətri, ^ işarəsi this.loginButton-un altındadır.
  2. Uğursuz test: “admin can login to dashboard” (login.spec.ts:10:3-dən).
  3. Nə undefined-dir: this.loginButton — sən onun üzərində .click() çağırdın, amma o heç vaxt mənimsədilməyib, ona görə undefined-dir.
  4. İlk yoxla: loginButton-un LoginPage class-ında necə qurulduğunu — çox güman ki, konstruktorda əksik və ya səhv yazılmış lokator mənimsətməsi (ya da selektoru heç nə tapmayıb).

Yadda saxla: trace-i yuxarıdan aşağı oxu — xəta tipi → > kod çərçivəsi → öz fayllarına işarə edən stack (node_modules-u nəzərə alma).


URL-ləri, parolları və ya tokenləri test fayllarına heç vaxt birbaşa yazmayın. Bunun əvəzinə mühit dəyişənlərindən istifadə edin.

const baseURL = process.env.BASE_URL;
const adminEmail = process.env.ADMIN_EMAIL;

Standart dəyərlər və məcburi dəyişənlər

Bölmə: “Standart dəyərlər və məcburi dəyişənlər”
function getConfig() {
const baseUrl = process.env.BASE_URL ?? 'http://localhost:3000'; // standart dəyər
const adminEmail = process.env.ADMIN_EMAIL;
if (!adminEmail) {
throw new Error('ADMIN_EMAIL mühit dəyişəni tələb olunur');
}
const isCI = process.env.CI === 'true'; // string-i boolean-a çevir
return { baseUrl, adminEmail, isCI };
}

Layihə kökündə .env faylı hazırlayın:

BASE_URL=http://localhost:3000
ADMIN_EMAIL=admin@test.io
ADMIN_PASSWORD=admin123
CI=false

Sonra konfiqurasiyanın başında yükləyin:

import 'dotenv/config';
// İndi process.env.BASE_URL, process.env.ADMIN_EMAIL və s. mövcuddur

.env-i həmişə .gitignore-a əlavə edin — sirrləri heç vaxt version control-a göndərməyin.

Bu şablonu playwright.config.ts-də görəcəksiniz:

playwright.config.ts
import { defineConfig } from '@playwright/test';
import 'dotenv/config';
export default defineConfig({
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
},
forbidOnly: !!process.env.CI, // !! string-i boolean-a çevirir
});

Tapşırıq 6: Mühit dəyişənləri

Bölmə: “Tapşırıq 6: Mühit dəyişənləri”

process.env-dən oxuyan getConfig() funksiyasını tətbiq edin:

// dotenv-i simulyasiya edirik
process.env.BASE_URL = 'http://localhost:3000';
process.env.ADMIN_EMAIL = 'admin@test.io';
process.env.ADMIN_PASSWORD = 'admin123';
process.env.CI = 'true';
function getConfig() {
// Qaytarın:
// - baseUrl (BASE_URL-dən, standart 'http://localhost:3000')
// - adminEmail (ADMIN_EMAIL-dən, yoxdursa Error at)
// - adminPassword (ADMIN_PASSWORD-dən, yoxdursa Error at)
// - isCI (CI-dən, 'true' string-ini boolean true-ya çevirin)
}
console.log(getConfig());

Bonus: process.env.ADMIN_EMAIL-i silin və xətanı try/catch ilə idarə edin.

Həll
function getConfig() {
const baseUrl = process.env.BASE_URL ?? 'http://localhost:3000';
const adminEmail = process.env.ADMIN_EMAIL;
if (!adminEmail) throw new Error('ADMIN_EMAIL is required');
const adminPassword = process.env.ADMIN_PASSWORD;
if (!adminPassword) throw new Error('ADMIN_PASSWORD is required');
const isCI = process.env.CI === 'true';
return { baseUrl, adminEmail, adminPassword, isCI };
}

Yadda saxla: heç vaxt sirləri kodda saxlama; onları process.env-dən (?? standartı ilə) oxu və .env-i gitignore-da saxla.


Yapışqan kağıza yazacağınız dörd qayda:

  1. Standart olaraq const, yenidən mənimsətmək üçün let, heç vaxt var
  2. Hər Playwright əməliyyatını await edinawait unudulması = flaky test
  3. Stack trace-ləri yuxarıdan aşağı oxuyun — başlıq, xəta mesajı, kod çərçivəsi, sonra öz fayllarınıza işarə edən stack sətirləri
  4. Sirrləri heç vaxt kodda yazmayınprocess.env.env fayllarından istifadə edin

Modul 2-də bu modulun hər bir anlayışı real Page Object class-larında istifadə olunacaq.