Azimjon Abdurasulov

QA 17: QA uchun TypeScript — o'zgaruvchilar, funksiyalar, async/await, class'lar va npm

🧪 QA Engineer — Zero to Hero

QA 17: QA uchun TypeScript — o'zgaruvchilar, funksiyalar, async/await, class'lar va npm

Holat: 2026-yil oktabr. Bu darsda avtomatlashtirish uchun kerak bo'ladigan TypeScript bazasini QA misollarida o'rganamiz. Muhit: Node.js 24 LTS, TypeScript 7.0.2, tsx 4.23, @faker-js/faker 10.6.0. Darsdagi har bir kod namunasi shu versiyalarda ishga tushirib tekshirilgan.

Bu darsdan keyin siz: Playwright testlarini o'qib tushunadigan va o'zingiz yozadigan darajada TypeScript bilasiz.

  • TypeScript loyihasini noldan sozlaysiz va kodni tsx bilan ishga tushirasiz;
  • tiplar, interface, funksiya, massiv metodlari va class'lar bilan ishlaysiz;
  • async/await qanday ishlashini va unutilgan await nega beqaror testlarga olib kelishini tushunasiz;
  • fetch bilan haqiqiy API'ga so'rov yuborib, javobni tekshirasiz;
  • faker bilan test ma'lumotlari yaratasiz va kichik API smoke skript yozasiz.

Mundarija

  1. Nega QA'ga dasturlash va nega TypeScript
  2. Muhitni sozlash
  3. O'zgaruvchilar va tiplar
  4. Interface va type: DemoBozor modellari
  5. Funksiyalar
  6. Shartlar, switch va sikllar
  7. Massivlar va obyektlar
  8. Modullar va npm
  9. Class'lar — Page Object poydevori
  10. Asinxron kod: Promise va async/await
  11. fetch: API'ga birinchi so'rov
  12. Regex, sanalar va muhit o'zgaruvchilari
  13. faker bilan test ma'lumotlari
  14. Mini-loyiha: API smoke skripti
  15. Amaliy mashqlar
  16. O'zingizni tekshiring
  17. Xulosa
  18. Manbalar

1. Nega QA'ga dasturlash va nega TypeScript {#nega}

QA 16 da ko'rdik: avtotest — test case'ning kod ko'rinishi. Demak, avtomatlashtirish uchun dasturlash tilini bilish shart. Lekin QA'ga dasturchining hamma bilimi kerak emas: o'zgaruvchilar, funksiyalar, massivlar, class'lar va asinxron kod — 90% ish shular bilan bajariladi.

TypeScript (TS) — bu JavaScript + tiplar. Brauzer va Node.js oxir-oqibat JavaScript'ni bajaradi, TypeScript esa kodni ishga tushirishdan oldin xatolarni topib beradi:

function calculateDeliveryFee(subtotal: number): number {
  return subtotal >= 300_000 ? 0 : 20_000;
}

calculateDeliveryFee("250000");
// tsc: Argument of type 'string' is not assignable to parameter of type 'number'.

QA uchun TypeScript'ning foydasi:

  • Xatolar test ishlashidan oldin ko'rinadi — page.gotoo() kabi yozuv xatosi CI'da 10 daqiqadan keyin emas, muharrirda darhol qizaradi.
  • Avtomatik to'ldirish (autocomplete) — page. deb yozsangiz, VS Code barcha metodlarni ko'rsatadi.
  • Playwright hujjatlari TypeScript'da — misollarni to'g'ridan-to'g'ri ko'chirasiz.
  • Frontend jamoa bilan bitta til — React/Angular dasturchilari kodingizni review qila oladi.

🧠 Esda tuting: Playwright TypeScript'ni o'zi JavaScript'ga aylantirib ishga tushiradi, lekin tiplarni tekshirmaydi. Rasmiy tavsiya: tiplarni alohida npx tsc --noEmit bilan tekshirish (CI'da ham).


2. Muhitni sozlash {#setup}

1-qadam: Node.js va VS Code

  1. nodejs.org dan Node.js 24 LTS ni o'rnating.
  2. Terminalda tekshiring:
node -v   # v24.x.x
npm -v
  1. VS Code va kengaytmalar: Playwright Test for VS Code (Microsoft), ESLint, Prettier.

2-qadam: loyiha yaratish

mkdir qa-ts-basics && cd qa-ts-basics
npm init -y
npm pkg set type=module              # zamonaviy import/export (ES modules)
npm i -D typescript tsx @types/node@24
mkdir src
PaketNima uchun
typescripttsc — tiplarni tekshiruvchi kompilyator (hozir 7.0.2)
tsx.ts faylni kompilyatsiyasiz darhol ishga tushiradi
@types/node@24Node.js API tiplari (process, fetch...) — Node versiyangizga mos

3-qadam: tsconfig.json

npx tsc --init uzun konfiguratsiya yaratadi. Boshlash uchun quyidagi qisqasi yetarli — uni tsconfig.json nomi bilan saqlang:

{
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "moduleDetection": "force",
    "types": ["node"],
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}
SozlamaMa'nosi
strict: trueQat'iy tekshiruv — eng ko'p xatoni ushlaydi. Doim yoqing
noEmit: truetsc JS fayl yaratmaydi, faqat tekshiradi (ishga tushirishni tsx qiladi)
types: ["node"]Node.js tiplarini ulaydi. TypeScript 7 da standart qiymat [] bo'lib qoldi, shuning uchun yozish shart
moduleResolution: "Bundler"import { x } from './pricing' — kengaytmasiz import ishlaydi
moduleDetection: "force"Har bir fayl alohida modul — fayllar orasida nomlar to'qnashmaydi

4-qadam: birinchi fayl

src/hello.ts:

const appName = "DemoBozor";
let passedTests = 0;
passedTests = passedTests + 1;

console.log(`${appName}: ${passedTests} ta test o'tdi`);
npx tsx src/hello.ts   # natija: DemoBozor: 1 ta test o'tdi
npx tsc                # xato bo'lmasa — hech narsa chiqarmaydi

⚠️ Diqqat: TypeScript 7.0 2026-yil 8-iyulda chiqdi — kompilyator Go tilida qayta yozilgan (Microsoft uni "10 barobar tezroq native port" deb e'lon qilgan). Lekin 7.0 da dasturiy API yo'q, shuning uchun typescript-eslint kabi asboblar hozircha TypeScript 6.0 ga tayanadi (2026-yil oktabr holatida typescript-eslint 8.71 typescript <6.1.0 talab qiladi). Microsoft bunday holatlar uchun @typescript/typescript6 moslik paketini taklif qilgan. Oddiy o'rganish va Playwright testlari uchun bu sizga xalaqit bermaydi.


3. O'zgaruvchilar va tiplar {#tiplar}

const productName: string = "Elektr choynak";
const price: number = 185_000; // so'm; _ faqat o'qish uchun
const inStock: boolean = true;

const prices: number[] = [185_000, 95_000, 1_200_000];
const categories: string[] = ["oshxona", "kiyim", "elektronika"];

type OrderStatus =
  | "CREATED"
  | "PENDING_PAYMENT"
  | "PAID"
  | "PACKING"
  | "SHIPPED"
  | "DELIVERED"
  | "CANCELLED"
  | "RETURNED";

let status: OrderStatus = "CREATED";
status = "PAID";

let orderId: number | string = 42; // union: son YOKI matn
orderId = "ORD-42";
  • **const** — qayta tayinlanmaydi. Standart tanlov shu bo'lsin.
  • **let** — qiymat o'zgaradi (hisoblagich, holat). var ishlatmang.
  • Literal tip ('CREATED' | 'PAID' | ...) — faqat ruxsat etilgan qiymatlar. QA uchun oltin: status nomidagi xato darhol ushlanadi.
  • TypeScript ko'pincha tipni o'zi aniqlaydi: const price = 185_000 yozsangiz ham number bo'ladi.

TypeScript nimalarni ushlaydi (haqiqiy tsc xabarlari):

let status: OrderStatus = "CREATED";
status = "PAYED";
// error TS2322: Type '"PAYED"' is not assignable to type 'OrderStatus'.

let quantity: number = 5;
quantity = "5";
// error TS2322: Type 'string' is not assignable to type 'number'.

const data: unknown = JSON.parse('{"total": 300000}');
console.log(data.total);
// error TS18046: 'data' is of type 'unknown'.

any va unknown

  • **any** — "tekshirma". TypeScript ko'zini yumadi. Testlarda deyarli ishlatmang.
  • **unknown** — "nima ekanini bilmayman, avval tekshiraman". API javobi kabi tashqi ma'lumot uchun to'g'ri tanlov. Uni ishlatishdan oldin tipini aniqlash (narrowing) yoki validatsiya qilish kerak — buni QA 20 da Zod bilan chiroyli qilamiz.

⚠️ Diqqat: as Booking kabi "tip berish" (type assertion) TypeScript'ni ishontiradi, lekin ma'lumotni tekshirmaydi. Agar API boshqa shakldagi javob qaytarsa, as buni yashiradi. Shuning uchun API testlarida schema validatsiyasi kerak.


4. Interface va type: DemoBozor modellari {#interface}

Interface obyekt "shakli"ni tasvirlaydi. Test ma'lumotlari, API javoblari va page object'lar shular asosida quriladi.

interface Product {
  id: number;
  name: string;
  price: number; // so'm
  stock: number;
  category?: string; // ? — ixtiyoriy maydon
}

interface User {
  readonly id: number; // o'zgartirib bo'lmaydi
  phone: string;
  fullName: string;
  role: "buyer" | "seller" | "admin";
  isPremium: boolean;
}

const kettle: Product = {
  id: 1,
  name: "Elektr choynak",
  price: 185_000,
  stock: 12,
};
const buyer: User = {
  id: 7,
  phone: "+998901234567",
  fullName: "Aziz Karimov",
  role: "buyer",
  isPremium: false,
};

// buyer.id = 8; // ❌ Cannot assign to 'id' because it is a read-only property.
console.log(kettle.category ?? "kategoriya yo'q", buyer.role); // kategoriya yo'q buyer
interfacetype
Obyekt shakli uchun (User, Product)Union, literal va kombinatsiyalar uchun (OrderStatus)
extends bilan kengaytiriladi& bilan birlashtiriladi

Qoida sifatida: **obyektlar uchun interface, qolgani uchun type**.


5. Funksiyalar {#funksiyalar}

const FREE_DELIVERY_THRESHOLD = 300_000;
const DELIVERY_FEE = 20_000;

// 1) oddiy funksiya: parametr va natija tiplari bilan
function calculateDeliveryFee(subtotal: number): number {
  if (subtotal >= FREE_DELIVERY_THRESHOLD) {
    return 0;
  }
  return DELIVERY_FEE;
}

// 2) arrow funksiya
const isFreeDelivery = (subtotal: number): boolean =>
  calculateDeliveryFee(subtotal) === 0;

// 3) default va ixtiyoriy parametrlar
function formatPrice(
  amount: number,
  currency = "so'm",
  withSpaces?: boolean,
): string {
  const digits = withSpaces
    ? String(amount).replace(/\B(?=(\d{3})+(?!\d))/g, " ")
    : String(amount);
  return `${digits} ${currency}`;
}

console.log(calculateDeliveryFee(299_999)); // 20000
console.log(calculateDeliveryFee(300_000)); // 0
console.log(isFreeDelivery(450_000)); // true
console.log(formatPrice(1_250_000)); // 1250000 so'm
console.log(formatPrice(1_250_000, "so'm", true)); // 1 250 000 so'm

E'tibor bering: calculateDeliveryFee(299_999) va calculateDeliveryFee(300_000) — bu QA 5 dagi chegara qiymatlari tahlili (BVA). Agar dasturchi >= o'rniga > yozsa, aynan 300 000 so'mlik buyurtma uchun yetkazib berish pullik bo'lib qoladi — QA 2 dagi error → defect → failure misoli.

💡 Maslahat: "Sehrli sonlar" (300000, 20000) o'rniga nomlangan konstantalar (FREE_DELIVERY_THRESHOLD) ishlating. Qoida o'zgarsa — bitta joyni tuzatasiz, test nomidan esa nimani tekshirayotgani tushunarli bo'ladi.


6. Shartlar, switch va sikllar {#shartlar}

Statuslarni o'zbekcha yorliqqa aylantiramiz va DemoBozor qoidasini kodlaymiz: buyurtmani faqat CREATED, PENDING_PAYMENT, PAID va PACKING holatlarida bekor qilish mumkin (QA 6 dagi state transition).

// OrderStatus — 3-bo'limdagi tip
const STATUS_LABELS: Record<OrderStatus, string> = {
  CREATED: "Yaratildi",
  PENDING_PAYMENT: "To'lov kutilmoqda",
  PAID: "To'landi",
  PACKING: "Yig'ilmoqda",
  SHIPPED: "Yo'lda",
  DELIVERED: "Yetkazildi",
  CANCELLED: "Bekor qilindi",
  RETURNED: "Qaytarildi",
};

function statusLabel(status: OrderStatus): string {
  return STATUS_LABELS[status];
}

function canCancel(status: OrderStatus): boolean {
  switch (status) {
    case "CREATED":
    case "PENDING_PAYMENT":
    case "PAID":
    case "PACKING":
      return true;
    default:
      return false;
  }
}

const statuses: OrderStatus[] = ["CREATED", "PAID", "SHIPPED"];
for (const s of statuses) {
  console.log(
    `${s} -> ${statusLabel(s)} | bekor qilish mumkinmi: ${canCancel(s)}`,
  );
}
// CREATED -> Yaratildi | bekor qilish mumkinmi: true
// PAID -> To'landi | bekor qilish mumkinmi: true
// SHIPPED -> Yo'lda | bekor qilish mumkinmi: false

Record<OrderStatus, string> — "har bir status uchun yorliq bo'lishi shart" degani. Ertaga dasturchi REFUNDED statusini qo'shsa-yu, yorlig'ini yozishni unutsa, tsc darhol ogohlantiradi:

error TS2741: Property 'REFUNDED' is missing in type '{ ... }' but required in type 'Record<OrderStatus, string>'.

switchdagi bir nechta case ketma-ket yozilsa, ular bitta natijani bo'lishadi. for...of esa massivni elementma-element aylanib chiqadi — testlarda ma'lumotlar ro'yxati bo'yicha bir xil tekshiruvni takrorlash uchun eng ko'p ishlatiladigan sikl.

Ikki klassik tuzoq

1. Truthy/falsy. JavaScript'da 0, '', null, undefined, NaN — "yolg'on" (falsy):

const quantity = 0;
if (quantity) {
  console.log("miqdor bor");
} else {
  console.log("miqdor yo'q (0 falsy!)"); // shu chiqadi
}

Agar "miqdor kiritilganmi?" degan tekshiruv if (quantity) bilan yozilsa, 0 qiymati "kiritilmagan" deb hisoblanadi — bu haqiqiy bug manbai. To'g'risi: if (quantity !== undefined).

**2. == va ===.** JavaScript'da '0' == 0 → true (tipni o'zgartirib solishtiradi). Har doim === ishlating. TypeScript bunday solishtirishni o'zi ham to'xtatadi: This comparison appears to be unintentional because the types 'string' and 'number' have no overlap.


7. Massivlar va obyektlar {#massivlar}

Test kodining katta qismi — ro'yxatlar bilan ishlash: UI'dan o'qilgan narxlar, API qaytargan mahsulotlar, savatcha qatorlari.

interface Product {
  id: number;
  name: string;
  price: number;
  stock: number;
}

const products: Product[] = [
  { id: 1, name: "Elektr choynak", price: 185_000, stock: 12 },
  { id: 2, name: "Paxta ko'ylak", price: 95_000, stock: 0 },
  { id: 3, name: "Simsiz quloqchin", price: 420_000, stock: 5 },
  { id: 4, name: "Gilam 2x3 m", price: 1_200_000, stock: 2 },
  { id: 5, name: "Telefon g'ilofi", price: 45_000, stock: 40 },
];

const names = products.map((p) => p.name); // har biridan nom
const outOfStock = products.filter((p) => p.stock === 0); // shartga mos kelganlar
const headphones = products.find((p) => p.name.includes("quloqchin")); // birinchi mos kelgan
const hasExpensive = products.some((p) => p.price > 1_000_000); // kamida bittasi?
const allHavePrice = products.every((p) => p.price > 0); // hammasi?

console.log(
  names.length,
  outOfStock.map((p) => p.name),
  headphones?.price,
  hasExpensive,
  allHavePrice,
);
// 5 [ "Paxta ko'ylak" ] 420000 true true
MetodQaytaradiQA'dagi odatiy ishlatilishi
mapYangi massivUI matnlarini songa aylantirish: '$29.99' → 29.99
filterMos kelganlar massiviTugagan mahsulotlar, xato statusdagi buyurtmalar
findBirinchi element yoki undefinedID bo'yicha mahsulot topish
some / everyboolean"Hech bo'lmasa bittasi..." / "hammasi..." tekshiruvi
reduceBitta qiymatSavatcha jami summasi

Savatcha summasi va saralash tekshiruvi

const cart = [
  { productId: 1, quantity: 1 },
  { productId: 5, quantity: 3 },
];
const subtotal = cart.reduce((sum, item) => {
  const product = products.find((p) => p.id === item.productId);
  return sum + (product ? product.price * item.quantity : 0);
}, 0);
console.log("subtotal:", subtotal, "| bepul yetkazish:", subtotal >= 300_000);
// subtotal: 320000 | bepul yetkazish: true

// UI'da "arzonidan qimmatiga" saralash to'g'rimi?
const uiPrices = [45_000, 95_000, 185_000, 420_000, 1_200_000];
const isSortedAsc = uiPrices.every(
  (price, i) => i === 0 || uiPrices[i - 1] <= price,
);
console.log("o'sish tartibida:", isSortedAsc); // true

// Takrorlanuvchi ID'lar (dublikat buyurtmalar bug'i)
const orderIds = [101, 102, 103, 102, 104, 101];
const duplicates = orderIds.filter((id, i) => orderIds.indexOf(id) !== i);
console.log("dublikatlar:", [...new Set(duplicates)]); // [ 102, 101 ]

Obyektlar: spread, destructuring, ?. va ??

interface TestUser {
  phone: string;
  fullName: string;
  isPremium: boolean;
  address?: { city: string; street?: string };
}

const baseUser: TestUser = {
  phone: "+998901234567",
  fullName: "Aziz Karimov",
  isPremium: false,
};

// spread: asosiy ma'lumotdan test varianti yasash
const premiumUser = { ...baseUser, isPremium: true };
const longNameUser = { ...baseUser, fullName: "A".repeat(51) }; // 51 belgi — chegaradan oshgan

// destructuring
const { phone, fullName } = premiumUser;

// optional chaining va nullish coalescing
console.log(baseUser.address?.city ?? "manzil kiritilmagan"); // manzil kiritilmagan

// || va ?? farqi: 0 — haqiqiy qiymat!
const deliveryFeeFromApi = 0;
console.log("|| bilan:", deliveryFeeFromApi || 20_000); // 20000 — BUG
console.log("?? bilan:", deliveryFeeFromApi ?? 20_000); // 0 — to'g'ri

⚠️ Diqqat: || chap tomondagi har qanday falsy qiymatni (shu jumladan 0 va '') "yo'q" deb hisoblaydi. API deliveryFee: 0 qaytarganda || uni 20 000 ga almashtirib yuboradi. Standart qiymat berishda **??** ishlating — u faqat null va undefined ni almashtiradi.


8. Modullar va npm {#modullar}

Kod bitta faylda qolib ketmasligi uchun modullarga bo'linadi. export — tashqariga beramiz, import — olib kelamiz.

src/pricing.ts:

export const FREE_DELIVERY_THRESHOLD = 300_000;
export const DELIVERY_FEE = 20_000;

export function calculateDeliveryFee(subtotal: number): number {
  return subtotal >= FREE_DELIVERY_THRESHOLD ? 0 : DELIVERY_FEE;
}

src/main.ts:

import { calculateDeliveryFee, FREE_DELIVERY_THRESHOLD } from "./pricing";

console.log(
  `Chegara: ${FREE_DELIVERY_THRESHOLD}, 150000 uchun: ${calculateDeliveryFee(150_000)}`,
);
// Chegara: 300000, 150000 uchun: 20000

💡 Maslahat: Named export (export function ...) ishlating, export default emas. Nomi bilan import qilinadi, qayta nomlash xavfsizroq, IDE uni oson topadi.

npm asoslari

Buyruq / faylVazifasi
package.jsonLoyiha pasporti: nomi, script'lar, bog'liqliklar
npm i -D <paket>Paketni devDependenciesga qo'shadi (testlar uchun asboblar odatda shu yerda)
npx <buyruq>Lokal o'rnatilgan asbobni ishga tushiradi (npx tsc, npx playwright test)
package-lock.jsonAniq versiyalarni "muzlatadi". Git'ga commit qiling
npm ciLock fayldan aynan bir xil o'rnatish — CI uchun
node_modules/O'rnatilgan paketlar. Git'ga qo'shmang (.gitignore)
"^1.64.0"1.64.0 va undan yuqori, lekin 2.0 dan past versiyalar

package.jsondagi script'lar uzun buyruqlarga qisqa nom beradi:

{
  "scripts": {
    "typecheck": "tsc",
    "smoke": "tsx src/project/smoke.ts"
  }
}

Endi npm run typecheck va npm run smoke yetarli.


9. Class'lar — Page Object poydevori {#classlar}

Class — ma'lumot (property) va xatti-harakat (method)ni bir joyga yig'adigan "qolip". QA 19 dagi Page Object Model aynan class'lar ustiga quriladi.

interface CartLine {
  productId: number;
  price: number;
  quantity: number;
}

class Cart {
  static readonly MAX_QUANTITY = 99;
  static readonly MAX_DISTINCT_PRODUCTS = 50;

  private readonly lines = new Map<number, CartLine>();

  constructor(private readonly ownerPhone: string) {}

  addItem(productId: number, price: number, quantity = 1): void {
    if (quantity < 1 || quantity > Cart.MAX_QUANTITY) {
      throw new Error(
        `Miqdor 1–${Cart.MAX_QUANTITY} oralig'ida bo'lishi kerak, keldi: ${quantity}`,
      );
    }
    if (
      !this.lines.has(productId) &&
      this.lines.size >= Cart.MAX_DISTINCT_PRODUCTS
    ) {
      throw new Error(
        `Savatchada ko'pi bilan ${Cart.MAX_DISTINCT_PRODUCTS} xil mahsulot bo'ladi`,
      );
    }
    this.lines.set(productId, { productId, price, quantity });
  }

  get subtotal(): number {
    let sum = 0;
    for (const line of this.lines.values()) {
      sum += line.price * line.quantity;
    }
    return sum;
  }

  get deliveryFee(): number {
    return this.subtotal >= 300_000 ? 0 : 20_000;
  }

  get owner(): string {
    return this.ownerPhone;
  }
}

const cart = new Cart("+998901234567");
cart.addItem(1, 185_000);
cart.addItem(5, 45_000, 3);
console.log(cart.owner, cart.subtotal, cart.deliveryFee); // +998901234567 320000 0

try {
  cart.addItem(2, 95_000, 100);
} catch (error) {
  console.log("Kutilgan xato:", (error as Error).message);
  // Kutilgan xato: Miqdor 1–99 oralig'ida bo'lishi kerak, keldi: 100
}
Kalit so'zMa'nosi
constructor(private readonly ownerPhone: string)Parametr avtomatik ravishda maxfiy, o'zgarmas property bo'ladi
privateFaqat class ichidan ko'rinadi
readonlyBir marta beriladi, keyin o'zgarmaydi
staticObyektga emas, class'ning o'ziga tegishli (Cart.MAX_QUANTITY)
get subtotal()Property kabi o'qiladi: cart.subtotal

Aynan shu g'oya — ma'lumot va harakatni bitta class'ga yig'ish — QA 19 dagi Page Object'larning asosi: u yerda LoginPage, InventoryPage kabi class'lar locator'lar va sahifa action'larini saqlaydi.

💡 Maslahat: Page object'lar uchun meros (inheritance) o'rniga kompozitsiyani afzal ko'ring: BasePage → CatalogPage → ProductPage → ... kabi uzun zanjir tez chalkashadi. Umumiy qismni (masalan, header) alohida class qilib, sahifaga property sifatida bering.


10. Asinxron kod: Promise va async/await {#async}

Brauzer, tarmoq va fayl bilan ishlash vaqt oladi. JavaScript bu vaqtda "qotib qolmaydi" — natijani keyinroq beradigan Promise (va'da) qaytaradi. await esa "natija kelguncha shu yerda kutib tur" degani.

Playwright'dagi deyarli hamma narsa asinxron: page.goto(), click(), fill(), expect(...).toBeVisible(). Shuning uchun async/awaitni tushunmasdan avtotest yozib bo'lmaydi.

const sleep = (ms: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, ms));

async function checkService(name: string, ms: number): Promise<string> {
  await sleep(ms); // tarmoq so'rovini taqlid qilamiz
  return `${name}: OK`;
}

// 1) ketma-ket — har biri oldingisini kutadi
await checkService("catalog", 300);
await checkService("cart", 300);
await checkService("orders", 300); // jami ~900 ms

// 2) parallel — uchalasi bir vaqtda
const results = await Promise.all([
  checkService("catalog", 300),
  checkService("cart", 300),
  checkService("orders", 300),
]); // jami ~300 ms
console.log(results); // [ 'catalog: OK', 'cart: OK', 'orders: OK' ]

🧠 Esda tuting: Bu yerda sleep faqat sekin tarmoqni taqlid qilish uchun. Haqiqiy testlarda "N soniya kutish" yozilmaydi — Playwright kerakli holatni o'zi kutadi (QA 18).

Unutilgan await — beqaror testlarning klassik sababi

await unutilganda va qo'yilganda bajarilish tartibi

async function saveOrder(): Promise<void> {
  await sleep(200);
  console.log("  buyurtma bazaga saqlandi");
}

async function wrongOrder(): Promise<void> {
  saveOrder(); // ❌ await unutildi
  console.log("  tekshiruv: buyurtma bormi?");
}

async function rightOrder(): Promise<void> {
  await saveOrder(); // ✅
  console.log("  tekshiruv: buyurtma bormi?");
}

Haqiqiy natija:

await yo'q:
  tekshiruv: buyurtma bormi?      ← tekshiruv saqlashdan OLDIN ishladi
  buyurtma bazaga saqlandi
await bor:
  buyurtma bazaga saqlandi
  tekshiruv: buyurtma bormi?

Testda bu "ba'zan o'tadi, ba'zan yo'q" degan flaky test bo'ladi: tarmoq tez bo'lsa omad kulib boqadi, sekin bo'lsa — yo'q. Playwright 1.64 da awaitsiz yozilgan expect(...) test tugaguncha bajarilib ulgurmasa, test Error: Test ended. xatosi bilan yiqiladi (biz tekshirdik), lekin har qanday unutilgan awaitni bunday ushlab bo'lmaydi — masalan, awaitsiz click() shunchaki poyga holatini (race condition) keltirib chiqaradi. Playwright'ning rasmiy tavsiyasi — **@typescript-eslint/no-floating-promises** lint qoidasini yoqish: u qayta ishlanmagan Promise'ni ko'rsatib beradi.

Xatolarni ushlash: try / catch / finally

async function fetchOrder(id: number): Promise<{ id: number; status: string }> {
  await sleep(100);
  if (id <= 0) {
    throw new Error(`Noto'g'ri buyurtma ID: ${id}`);
  }
  return { id, status: "PAID" };
}

try {
  await fetchOrder(-1);
} catch (error) {
  console.log("ushlandi:", (error as Error).message); // ushlandi: Noto'g'ri buyurtma ID: -1
} finally {
  console.log("finally: test ma'lumotlarini tozalash"); // har doim ishlaydi
}

finally — test ma'lumotlarini tozalash uchun ideal joy: test yiqilsa ham ishlaydi.

🎯 Intervyuda: "Promise.all qachon kerak?" — Bir-biriga bog'liq bo'lmagan bir nechta asinxron ishni parallel bajarish uchun: masalan, testdan oldin API orqali 3 ta mahsulotni bir vaqtda yaratish. Ulardan biri xato bersa, Promise.all ham xato bilan tugaydi; hammasining natijasi kerak bo'lsa — Promise.allSettled.


11. fetch: API'ga birinchi so'rov {#fetch}

Node.js'da fetch o'rnatilgan — paket kerak emas. Mashq uchun API testlash uchun mo'ljallangan Restful-Booker xizmatidan foydalanamiz:

const BASE_URL = "https://restful-booker.herokuapp.com";

interface BookingDates {
  checkin: string; // YYYY-MM-DD
  checkout: string;
}

interface Booking {
  firstname: string;
  lastname: string;
  totalprice: number;
  depositpaid: boolean;
  bookingdates: BookingDates;
  additionalneeds?: string;
}

interface CreatedBooking {
  bookingid: number;
  booking: Booking;
}

async function createBooking(booking: Booking): Promise<CreatedBooking> {
  const response = await fetch(`${BASE_URL}/booking`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Accept: "application/json" },
    body: JSON.stringify(booking),
    signal: AbortSignal.timeout(10_000), // 10 soniyadan ko'p kutmaymiz
  });
  if (!response.ok) {
    throw new Error(`POST /booking: kutilmagan status ${response.status}`);
  }
  return (await response.json()) as CreatedBooking;
}

const newBooking: Booking = {
  firstname: "Aziz",
  lastname: "Karimov",
  totalprice: 150,
  depositpaid: true,
  bookingdates: { checkin: "2026-11-01", checkout: "2026-11-05" },
  additionalneeds: "Nonushta",
};

const created = await createBooking(newBooking);
console.log("yaratildi:", typeof created.bookingid === "number"); // yaratildi: true

const response = await fetch(`${BASE_URL}/booking/${created.bookingid}`, {
  headers: { Accept: "application/json" },
});
const saved = (await response.json()) as Booking;
console.log(
  response.status,
  saved.firstname === newBooking.firstname ? "ism mos" : "ism MOS EMAS",
);
// 200 ism mos

Muhim nuqtalar:

  • response.ok — status 200–299 oralig'ida bo'lsa true. Har doim tekshiring.
  • AbortSignal.timeout(10_000) — server javob bermasa, abadiy kutmaymiz.
  • await response.json() — tana (body) ham asinxron o'qiladi.

🎯 Intervyuda: "Restful-Booker'da noto'g'ri parol bilan POST /auth qanday status qaytaradi?" — Biz tekshirdik: 200 va {"reason":"Bad credentials"}. REST odatlariga ko'ra bu yerda 401 kutiladi. Yangi bron yaratish (POST /booking) esa 201 emas, 200 qaytaradi. Bunday "qiziq" javoblarni topish va muhokama qilish — QA'ning kundalik ishi (QA 10).


12. Regex, sanalar va muhit o'zgaruvchilari {#regex-sana-env}

Regex: formatlarni tekshirish

const UZ_PHONE = /^\+998\d{9}$/;
const PROMO_CODE = /^[A-Z0-9]{6,12}$/;
const OTP_CODE = /^\d{6}$/;

function isValidUzPhone(raw: string): boolean {
  const normalized = raw.replace(/[\s-]/g, ""); // probel va defislarni olib tashlaymiz
  return UZ_PHONE.test(normalized);
}

console.log(isValidUzPhone("+998 90 123 45 67")); // true
console.log(isValidUzPhone("998901234567")); // false — "+" yo'q
console.log(isValidUzPhone("+99890123456")); // false — 8 ta raqam
console.log(
  PROMO_CODE.test("BAHOR2026"),
  PROMO_CODE.test("bahor2026"),
  PROMO_CODE.test("A1B2C"),
);
// true false false
console.log(
  OTP_CODE.test("123456"),
  OTP_CODE.test("12345"),
  OTP_CODE.test("12a456"),
);
// true false false

// ^ va $ bo'lmasa regex matnning BIR QISMINI ham "to'g'ri" deb hisoblaydi
console.log(/\d{6}/.test("1234567"), /^\d{6}$/.test("1234567")); // true false

⚠️ Diqqat: Validatsiya regex'ida ^ (boshi) va $ (oxiri) unutilsa, 7 xonali SMS kod ham "6 xonali" deb qabul qilinadi. Dasturchi kodida shunday regex'ni ko'rsangiz — bu bug.

Sanalar: UTC va Toshkent vaqti

// Sana ichkarida UTC'da saqlanadi, foydalanuvchiga — Toshkent vaqti (UTC+5)
const orderTime = new Date("2026-10-09T20:30:00Z");
const tashkent = new Intl.DateTimeFormat("en-GB", {
  timeZone: "Asia/Tashkent",
  dateStyle: "short",
  timeStyle: "short",
});
console.log(orderTime.toISOString()); // 2026-10-09T20:30:00.000Z
console.log(tashkent.format(orderTime)); // 10/10/2026, 01:30 — Toshkentda allaqachon ertangi kun!

// Oylar 0 dan boshlanadi: 9 = oktabr
console.log(new Date(2026, 9, 9).getMonth()); // 9

UTC bo'yicha 9-oktabr kechqurun bo'lgan buyurtma Toshkentda 10-oktabr sanasiga tushadi. "Bugungi buyurtmalar" hisobotida buyurtma ko'rinmay qolsa — avval vaqt zonasini tekshiring. Kunning oxiri (19:00–24:00 UTC) atrofidagi holatlar — vaqt bilan bog'liq bug'larning sevimli joyi. Lokalizatsiya va sana formatlari haqida batafsil — QA 25.

Muhit o'zgaruvchilari va .env

Test kodida URL va parollar qattiq yozilmaydi — ular muhitdan olinadi:

npm i -D dotenv

.env (git'ga qo'shilmaydi!):

BASE_URL=https://www.saucedemo.com
API_BASE_URL=https://restful-booker.herokuapp.com
import "dotenv/config";

const baseUrl = process.env.BASE_URL ?? "https://www.saucedemo.com";
const apiBaseUrl = process.env.API_BASE_URL;

if (!apiBaseUrl) {
  throw new Error("API_BASE_URL .env faylida berilmagan");
}
console.log(baseUrl, apiBaseUrl);

Node.js'ning o'zida ham imkoniyat bor: npx tsx --env-file=.env src/app.ts yoki kod ichida process.loadEnvFile() — ikkalasi ham Node 24 da tekshirildi.

⚠️ Diqqat: .env faylni hech qachon git'ga commit qilmang. Uning o'rniga qiymatlarsiz .env.example saqlang, haqiqiy maxfiy ma'lumotlar esa CI'da secret sifatida beriladi (QA 21).


13. faker bilan test ma'lumotlari {#faker}

Har testda "Test Testov" yozish o'rniga, realistik va har safar yangi ma'lumot yaratamiz:

npm i -D @faker-js/faker
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";

faker.seed(2026); // bir xil seed — har safar bir xil ma'lumot

function randomUzPhone(): string {
  const operator = faker.helpers.arrayElement([
    "90",
    "91",
    "93",
    "94",
    "97",
    "99",
  ]);
  return `+998${operator}${faker.string.numeric(7)}`;
}

const users = Array.from({ length: 3 }, () => ({
  fullName: `${faker.person.firstName()} ${faker.person.lastName()}`,
  phone: randomUzPhone(),
  promoCode: faker.string.alphanumeric({ length: 8, casing: "upper" }),
}));

for (const user of users) {
  console.log(user);
}
// { fullName: 'Iroda Miranov', phone: '+998991957332', promoCode: 'W1GKDPX1' }
// ... (faker 10.6.0 da seed 2026 bilan)

Biz tekshirganda topgan uchta foydali fakt:

  1. Seed (faker.seed(2026)) bilan natija takrorlanadi — yiqilgan testni xuddi shu ma'lumot bilan qayta ishga tushirish mumkin. (Faker versiyasi o'zgarsa, natija ham o'zgarishi mumkin.)
  2. O'zbek lokali (fakerUZ_UZ_latin) ism-familiyalarni o'zbekcha beradi, lekin phone.number() o'zbek formatida emas (783.229.2490 x69259 kabi chiqdi). Shuning uchun +998 raqamni o'zimiz yasadik.
  3. faker.helpers.fromRegExp() murakkab regex'larni (masalan, (90|91) kabi guruhlarni) to'liq tushunmaydi — natijada \+998(90|91|...) matnining o'zi chiqib qoldi. Oddiy arrayElement + string.numeric ishonchliroq.

⚠️ Diqqat: Tasodifiy yaratilgan +998... raqam haqiqiy odamniki bo'lishi mumkin. Test muhitida real SMS yuborilmasligiga ishonch hosil qiling: SMS provayderning sandbox rejimi yoki dasturchilar bilan kelishilgan test raqamlar ishlatilsin.


14. Mini-loyiha: API smoke skripti {#mini-loyiha}

Endi o'rganganlarni birlashtiramiz: validatorlar + test ma'lumotlari + asinxron API tekshiruvlari. Natija — CI'da ishlay oladigan kichik smoke skript.

qa-ts-basics/
└── src/project/
    ├── validators.ts   # format tekshiruvlari
    ├── test-data.ts    # faker bilan ma'lumot
    └── smoke.ts        # API smoke tekshiruvlari

src/project/validators.ts:

const UZ_PHONE = /^\+998\d{9}$/;
const PROMO_CODE = /^[A-Z0-9]{6,12}$/;
const OTP_CODE = /^\d{6}$/;

export function isValidUzPhone(raw: string): boolean {
  return UZ_PHONE.test(raw.replace(/[\s-]/g, ""));
}

export function isValidPromoCode(code: string): boolean {
  return PROMO_CODE.test(code);
}

export function isValidOtp(code: string): boolean {
  return OTP_CODE.test(code);
}

src/project/test-data.ts:

import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";

export interface TestBooking {
  firstname: string;
  lastname: string;
  totalprice: number;
  depositpaid: boolean;
  bookingdates: { checkin: string; checkout: string };
  additionalneeds: string;
}

export function useSeed(seed: number): void {
  faker.seed(seed);
}

export function randomUzPhone(): string {
  const operator = faker.helpers.arrayElement([
    "90",
    "91",
    "93",
    "94",
    "97",
    "99",
  ]);
  return `+998${operator}${faker.string.numeric(7)}`;
}

export function createBookingData(
  overrides: Partial<TestBooking> = {},
): TestBooking {
  return {
    firstname: faker.person.firstName(),
    lastname: faker.person.lastName(),
    totalprice: faker.number.int({ min: 50, max: 500 }),
    depositpaid: faker.datatype.boolean(),
    bookingdates: { checkin: "2026-11-01", checkout: "2026-11-05" },
    additionalneeds: "Nonushta",
    ...overrides, // testga kerakli maydonlarni almashtirish
  };
}

src/project/smoke.ts:

import { createBookingData, randomUzPhone, useSeed } from "./test-data";
import { isValidUzPhone } from "./validators";

const BASE_URL =
  process.env.API_BASE_URL ?? "https://restful-booker.herokuapp.com";
const TIMEOUT_MS = 10_000;

type Check = () => Promise<string>;

async function run(name: string, check: Check): Promise<boolean> {
  try {
    const details = await check();
    console.log(`✅ ${name} — ${details}`);
    return true;
  } catch (error) {
    console.log(`❌ ${name} — ${(error as Error).message}`);
    return false;
  }
}

function expectStatus(response: Response, expected: number): void {
  if (response.status !== expected) {
    throw new Error(`kutilgan status ${expected}, keldi ${response.status}`);
  }
}

async function post(path: string, body: unknown): Promise<Response> {
  return fetch(`${BASE_URL}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Accept: "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(TIMEOUT_MS),
  });
}

useSeed(2026);
const booking = createBookingData();

const results = [
  await run("Test ma'lumoti: telefon formati", async () => {
    const phone = randomUzPhone();
    if (!isValidUzPhone(phone)) throw new Error(`yaroqsiz: ${phone}`);
    return phone;
  }),
  await run("GET /ping", async () => {
    const response = await fetch(`${BASE_URL}/ping`, {
      signal: AbortSignal.timeout(TIMEOUT_MS),
    });
    expectStatus(response, 201);
    return "servis ishlayapti";
  }),
  await run("POST /auth (to'g'ri login)", async () => {
    const response = await post("/auth", {
      username: "admin",
      password: "password123",
    });
    expectStatus(response, 200);
    const body = (await response.json()) as { token?: string };
    if (!body.token) throw new Error("token qaytmadi");
    return "token olindi";
  }),
  await run("POST /auth (noto'g'ri parol)", async () => {
    const response = await post("/auth", {
      username: "admin",
      password: "xato",
    });
    const body = (await response.json()) as { token?: string; reason?: string };
    if (body.token) throw new Error("noto'g'ri parol bilan token berildi!");
    return `token yo'q, status ${response.status}, sabab: ${body.reason}`;
  }),
  await run("POST /booking + GET /booking/:id", async () => {
    const created = await post("/booking", booking);
    expectStatus(created, 200);
    const { bookingid } = (await created.json()) as { bookingid: number };

    const response = await fetch(`${BASE_URL}/booking/${bookingid}`, {
      headers: { Accept: "application/json" },
      signal: AbortSignal.timeout(TIMEOUT_MS),
    });
    expectStatus(response, 200);
    const saved = (await response.json()) as typeof booking;
    if (saved.lastname !== booking.lastname)
      throw new Error("familiya mos emas");
    return `bron #${bookingid} saqlandi`;
  }),
];

const failed = results.filter((ok) => !ok).length;
console.log(`\nNatija: ${results.length - failed} o'tdi, ${failed} yiqildi`);
if (failed > 0) process.exitCode = 1; // CI uchun: xato bo'lsa nol bo'lmagan kod

Ishga tushiramiz:

npx tsx src/project/smoke.ts
✅ Test ma'lumoti: telefon formati — +998995733280
✅ GET /ping — servis ishlayapti
✅ POST /auth (to'g'ri login) — token olindi
✅ POST /auth (noto'g'ri parol) — token yo'q, status 200, sabab: Bad credentials
✅ POST /booking + GET /booking/:id — bron #1814 saqlandi

Natija: 5 o'tdi, 0 yiqildi

Bu skriptda keyingi darslarning urug'lari bor: tekshiruvlarni nomlash, xatolarni ushlash, test ma'lumotini yaratish, process.exitCode orqali CI'ga signal berish. QA 20 da xuddi shu ishni Playwright request va Zod bilan professional darajada qilamiz.


15. Amaliy mashqlar {#mashqlar}

Har bir javob TypeScript 7.0.2 + tsx da ishga tushirib tekshirilgan.

Mashq 1: Xatoni oldindan ko'ring

tsc quyidagi kod uchun qanday xatolar beradi?

interface OrderItem {
  productId: number;
  quantity: number;
  unitPrice: number;
}

const item: OrderItem = { productId: 3, quantity: "2", unitPrice: 420_000 };

function lineTotal(line: OrderItem): number {
  return line.quantity * line.unitPrice;
}

lineTotal({ productId: 1, quantity: 1 });

<details> <summary>💡 Javobni ko'rish</summary>

error TS2322: Type 'string' is not assignable to type 'number'.
error TS2741: Property 'unitPrice' is missing in type '{ productId: number; quantity: number; }' but required in type 'OrderItem'.
  1. quantity: '2' — matn, son kutilgan.
  2. lineTotal(...) ga unitPrice berilmagan — majburiy maydon.

Ikkala xato ham test ishga tushishidan oldin topildi — TypeScript'ning asosiy foydasi shu.

</details>

Mashq 2: Ism validatori

DemoBozor qoidasi: ism 2–50 belgi; faqat lotin yoki kirill harflari, probel, apostroflar (ʻ, ', ’) va defis. isValidName(name: string): boolean yozing va chegaraviy qiymatlar bilan tekshiring.

<details> <summary>💡 Javobni ko'rish</summary>

const NAME = /^[\p{Script=Latin}\p{Script=Cyrillic} 'ʻ’-]{2,50}$/u;

export function isValidName(name: string): boolean {
  return NAME.test(name);
}

const cases: Array<[string, boolean]> = [
  ["Aziz", true],
  ["Oʻgʻiloy", true],
  ["G'ulom", true],
  ["Анна-Мария", true],
  ["A", false], // 1 belgi — chegaradan past
  ["A".repeat(50), true], // 50 — chegarada
  ["A".repeat(51), false], // 51 — chegaradan oshdi
  ["Aziz2", false], // raqam
  ["Aziz!", false], // ruxsat etilmagan belgi
];

for (const [input, expected] of cases) {
  const actual = isValidName(input);
  console.log(
    actual === expected ? "✅" : "❌",
    `"${input.slice(0, 12)}" (uzunlik ${input.length}) -> ${actual}`,
  );
}

Barcha 9 holat ✅. E'tibor bering: u bayrog'i va \p{Script=Latin} Unicode harflarini to'g'ri taniydi, Oʻgʻiloy dagi ʻ (U+02BB) belgisi esa ro'yxatga alohida qo'shilgan.

</details>

Mashq 3: Buyurtma summasi

DemoBozor qoidalari bo'yicha calculateOrder() yozing: yangi mijozga 10%, promo-kod promoPercent foiz; chegirmalar qo'shilmaydi — kattasi olinadi; Premium a'zoga yetkazib berish har doim bepul; aks holda mahsulotlar summasi (chegirmagacha) ≥ 300 000 bo'lsa bepul, bo'lmasa 20 000 so'm. Natija: { discount, deliveryFee, total }.

Tekshiring: (250 000, yangi), (400 000, yangi, promo 15%), (200 000, promo 15%, premium), (300 000, oddiy).

<details> <summary>💡 Javobni ko'rish</summary>

interface OrderInput {
  subtotal: number; // mahsulotlar summasi, so'm
  isNewCustomer: boolean;
  promoPercent?: number; // masalan 15
  isPremium: boolean;
}

interface OrderSummary {
  discount: number;
  deliveryFee: number;
  total: number;
}

export function calculateOrder(input: OrderInput): OrderSummary {
  const newCustomerPercent = input.isNewCustomer ? 10 : 0;
  const percent = Math.max(newCustomerPercent, input.promoPercent ?? 0); // chegirmalar qo'shilmaydi
  const discount = Math.round((input.subtotal * percent) / 100);
  const deliveryFee = input.isPremium || input.subtotal >= 300_000 ? 0 : 20_000;
  return {
    discount,
    deliveryFee,
    total: input.subtotal - discount + deliveryFee,
  };
}

console.log(
  calculateOrder({ subtotal: 250_000, isNewCustomer: true, isPremium: false }),
);
// { discount: 25000, deliveryFee: 20000, total: 245000 }
console.log(
  calculateOrder({
    subtotal: 400_000,
    isNewCustomer: true,
    promoPercent: 15,
    isPremium: false,
  }),
);
// { discount: 60000, deliveryFee: 0, total: 340000 }
console.log(
  calculateOrder({
    subtotal: 200_000,
    isNewCustomer: false,
    promoPercent: 15,
    isPremium: true,
  }),
);
// { discount: 30000, deliveryFee: 0, total: 170000 }
console.log(
  calculateOrder({ subtotal: 300_000, isNewCustomer: false, isPremium: false }),
);
// { discount: 0, deliveryFee: 0, total: 300000 }

QA savoli: talabda "bepul yetkazib berish chegarasi chegirmagacha yoki chegirmadan keyingi summaga qo'llanadimi?" deb aniq yozilmagan bo'lsa — bu talabdagi noaniqlik, uni kod yozishdan oldin aniqlashtirish kerak (QA 3).

</details>

Mashq 4: Massiv metodlari

7-bo'limdagi products ro'yxatidan toping: (a) tugagan mahsulotlar nomlari, (b) ombordagi jami qiymat (price × stock yig'indisi), (c) eng qimmat mahsulot nomi.

<details> <summary>💡 Javobni ko'rish</summary>

const outOfStockNames = products
  .filter((p) => p.stock === 0)
  .map((p) => p.name);
const inventoryValue = products.reduce((sum, p) => sum + p.price * p.stock, 0);
const mostExpensive = products.reduce((max, p) =>
  p.price > max.price ? p : max,
);

console.log(outOfStockNames); // [ "Paxta ko'ylak" ]
console.log(inventoryValue); // 8520000
console.log(mostExpensive.name); // Gilam 2x3 m

Hisob: 185 000×12 + 95 000×0 + 420 000×5 + 1 200 000×2 + 45 000×40 = 2 220 000 + 0 + 2 100 000 + 2 400 000 + 1 800 000 = 8 520 000.

</details>

Mashq 5: Saralash tekshiruvi

Saucedemo'da "Price (low to high)" tanlanganda narxlar [7.99, 9.99, 15.99, 15.99, 29.99, 49.99] chiqadi, standart tartibda esa [29.99, 9.99, 15.99, 49.99, 7.99, 15.99]. isSorted(values, order) funksiyasini yozing (order: 'asc' yoki 'desc', standart 'asc'). Teng qiymatlar (15.99, 15.99) xato hisoblanmasin.

<details> <summary>💡 Javobni ko'rish</summary>

export function isSorted(
  values: number[],
  order: "asc" | "desc" = "asc",
): boolean {
  return values.every((value, i) => {
    if (i === 0) return true;
    const previous = values[i - 1];
    return order === "asc" ? previous <= value : previous >= value;
  });
}

const lowToHigh = [7.99, 9.99, 15.99, 15.99, 29.99, 49.99];
const defaultOrder = [29.99, 9.99, 15.99, 49.99, 7.99, 15.99];

console.log(isSorted(lowToHigh)); // true
console.log(isSorted(defaultOrder)); // false
console.log(isSorted([...lowToHigh].reverse(), "desc")); // true

<= (qat'iy emas) — teng narxlar ham to'g'ri tartib. [...lowToHigh].reverse() — asl massivni buzmaslik uchun nusxa olamiz.

</details>

Mashq 6: Asinxron API funksiya

getBookingIds(firstname) yozing: Restful-Booker'dan GET /booking?firstname=... bilan ID'lar ro'yxatini qaytarsin; status 2xx bo'lmasa xato tashlasin. Keyin Promise.all bilan ikki ism uchun parallel chaqiring va xatoni ushlang.

<details> <summary>💡 Javobni ko'rish</summary>

const BASE_URL = "https://restful-booker.herokuapp.com";

async function getBookingIds(firstname: string): Promise<number[]> {
  const url = `${BASE_URL}/booking?firstname=${encodeURIComponent(firstname)}`;
  const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
  if (!response.ok) {
    throw new Error(`GET /booking: status ${response.status}`);
  }
  const body = (await response.json()) as Array<{ bookingid: number }>;
  return body.map((item) => item.bookingid);
}

try {
  const [first, second] = await Promise.all([
    getBookingIds("Aziz"),
    getBookingIds("Jim"),
  ]);
  console.log("Aziz:", Array.isArray(first), "| Jim:", Array.isArray(second));
} catch (error) {
  console.error("Smoke yiqildi:", (error as Error).message);
  process.exitCode = 1;
}

encodeURIComponent — ismda probel yoki ' bo'lsa ham URL buzilmaydi. ID'lar soni har safar boshqacha bo'lishi mumkin (servisdagi ma'lumotlar o'zgarib turadi), shuning uchun aniq son emas, shakl (massivligi) tekshiriladi.

</details>

Mashq 7: Tartibni bashorat qiling

Quyidagi kod nima chiqaradi? Keyin uni tuzating.

const sleep = (ms: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, ms));

async function login(): Promise<void> {
  await sleep(100);
  console.log("A: login tugadi");
}

async function runTest(): Promise<void> {
  console.log("B: test boshlandi");
  login();
  console.log("C: savatchani tekshirish");
  await sleep(200);
  console.log("D: test tugadi");
}

await runTest();

<details> <summary>💡 Javobni ko'rish</summary>

B: test boshlandi
C: savatchani tekshirish
A: login tugadi
D: test tugadi

Savatcha login tugashidan oldin tekshirildi. Tuzatish — await login();:

B: test boshlandi
A: login tugadi
C: savatchani tekshirish
D: test tugadi

Bu kodda await sleep(200) tasodifan "yordam berib" turibdi: login 100 ms da tugaydi. Agar login 300 ms olsa, A umuman Ddan keyin chiqardi. Mana shuning uchun unutilgan await testlarni "ba'zan o'tadigan" qiladi.

</details>

Mashq 8: SMS kod qo'riqchisi (class + vaqt)

DemoBozor qoidasi: 3 marta noto'g'ri kod → 15 daqiqa blok. OtpGuard class'ini yozing: verify(code) metodi 'OK' | 'WRONG' | 'LOCKED' qaytarsin. Testda 15 daqiqa kutmaslik uchun vaqtni konstruktor orqali bering.

<details> <summary>💡 Javobni ko'rish</summary>

type OtpResult = "OK" | "WRONG" | "LOCKED";

export class OtpGuard {
  static readonly MAX_ATTEMPTS = 3;
  static readonly LOCK_MS = 15 * 60 * 1000;

  private failedAttempts = 0;
  private lockedUntil = 0;

  // vaqtni tashqaridan beramiz — testda "soatni" boshqarish oson bo'ladi
  constructor(
    private readonly correctCode: string,
    private readonly now: () => number = Date.now,
  ) {}

  verify(code: string): OtpResult {
    if (this.now() < this.lockedUntil) {
      return "LOCKED";
    }
    if (code === this.correctCode) {
      this.failedAttempts = 0;
      return "OK";
    }
    this.failedAttempts += 1;
    if (this.failedAttempts >= OtpGuard.MAX_ATTEMPTS) {
      this.lockedUntil = this.now() + OtpGuard.LOCK_MS;
      this.failedAttempts = 0;
      return "LOCKED";
    }
    return "WRONG";
  }
}

// "soxta soat" bilan tekshiramiz
let fakeNow = Date.parse("2026-10-09T10:00:00Z");
const guard = new OtpGuard("123456", () => fakeNow);

console.log(guard.verify("000000")); // WRONG
console.log(guard.verify("111111")); // WRONG
console.log(guard.verify("222222")); // LOCKED (3-xato)
console.log(guard.verify("123456")); // LOCKED (to'g'ri kod ham o'tmaydi)

fakeNow += 15 * 60 * 1000 - 1; // 14:59.999
console.log(guard.verify("123456")); // LOCKED
fakeNow += 1; // roppa-rosa 15 daqiqa
console.log(guard.verify("123456")); // OK

Bu yerda ikki muhim QA g'oyasi bor: (1) chegara qiymati vaqtda ham ishlaydi — 14:59.999 va 15:00.000; (2) testlanuvchanlik — vaqtni tashqaridan berish orqali 15 daqiqalik qoidani millisekundlarda tekshiramiz. Haqiqiy loyihada dasturchilardan aynan shunday "boshqariladigan soat" so'rang.

</details>


16. O'zingizni tekshiring {#checklist}

  • [ ] Node 24 + TypeScript + tsx loyihasini noldan sozlay olaman
  • [ ] const/let, union va literal tiplar, unknown va any farqini bilaman
  • [ ] interface bilan test ma'lumoti modelini yoza olaman
  • [ ] map, filter, find, some, every, reduce bilan ishlay olaman
  • [ ] ?? va || farqini va truthy/falsy tuzog'ini tushuntira olaman
  • [ ] Class, private, readonly, getter'lardan foydalana olaman
  • [ ] async/await, Promise.all, try/catch/finallyni bilaman va unutilgan await xavfini tushunaman
  • [ ] fetch bilan API'ga so'rov yuborib, statusni tekshira olaman
  • [ ] .env va faker bilan test ma'lumotlarini boshqara olaman

Xulosa {#xulosa}

  • TypeScript = JavaScript + tiplar: xatolar test ishga tushishidan oldin ko'rinadi. Playwright TS'ni o'zi ishga tushiradi, tiplarni esa tsc --noEmit tekshiradi.
  • Muhit: Node 24 LTS, typescript (7.0.2), tsx, @types/node@24; tsconfig.jsonda strict: true va TypeScript 7 uchun types: ["node"].
  • Literal tiplar (OrderStatus) va interfacelar test ma'lumotlarini xavfsiz qiladi; never bilan to'liqlik tekshiruvi yangi statuslarni unutishga yo'l qo'ymaydi.
  • Massiv metodlari — UI va API natijalarini tekshirishning asosiy quroli; standart qiymat uchun ??, || emas.
  • Class'lar Page Object Model'ning poydevori: private readonly, getter, validatsiya va "boshqariladigan soat".
  • Asinxron kodda awaitni unutish — flaky testning klassik sababi; no-floating-promises lint qoidasini yoqing.
  • fetch + AbortSignal.timeout + response.ok — API smoke'ning minimal to'plami; tashqi ma'lumotni as emas, validatsiya bilan tekshirish kerak (QA 20).
  • Keyingi dars: QA 18: Playwright (1) — o'rnatish, locator'lar, action'lar, assertion'lar va auto-waiting

Manbalar {#manbalar}

  1. TypeScript — rasmiy hujjatlar
  2. TypeScript Handbook — The Basics
  3. Microsoft DevBlogs — Announcing TypeScript 7.0
  4. Playwright — TypeScript
  5. Playwright — Best Practices (lint va tsc)
  6. typescript-eslint — no-floating-promises
  7. Node.js — Download
  8. Node.js — Globals (fetch va boshqalar)
  9. MDN — Promise
  10. MDN — async function
  11. MDN — Fetch API
  12. tsx — GitHub
  13. Faker — Guide va Localization
  14. npm — npm ci
  15. Restful-Booker API hujjati
  16. Playwright Test for VS Code

Markdown manba

iOS Lessons