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
tsxbilan ishga tushirasiz; - tiplar, interface, funksiya, massiv metodlari va class'lar bilan ishlaysiz;
async/awaitqanday ishlashini va unutilganawaitnega beqaror testlarga olib kelishini tushunasiz;fetchbilan haqiqiy API'ga so'rov yuborib, javobni tekshirasiz;- faker bilan test ma'lumotlari yaratasiz va kichik API smoke skript yozasiz.
Mundarija
- Nega QA'ga dasturlash va nega TypeScript
- Muhitni sozlash
- O'zgaruvchilar va tiplar
- Interface va type: DemoBozor modellari
- Funksiyalar
- Shartlar, switch va sikllar
- Massivlar va obyektlar
- Modullar va npm
- Class'lar — Page Object poydevori
- Asinxron kod: Promise va async/await
- fetch: API'ga birinchi so'rov
- Regex, sanalar va muhit o'zgaruvchilari
- faker bilan test ma'lumotlari
- Mini-loyiha: API smoke skripti
- Amaliy mashqlar
- O'zingizni tekshiring
- Xulosa
- 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 --noEmitbilan tekshirish (CI'da ham).
2. Muhitni sozlash {#setup}
1-qadam: Node.js va VS Code
- nodejs.org dan Node.js 24 LTS ni o'rnating.
- Terminalda tekshiring:
node -v # v24.x.x
npm -v
- 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
| Paket | Nima uchun |
|---|---|
typescript | tsc — tiplarni tekshiruvchi kompilyator (hozir 7.0.2) |
tsx | .ts faylni kompilyatsiyasiz darhol ishga tushiradi |
@types/node@24 | Node.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"]
}
| Sozlama | Ma'nosi |
|---|---|
strict: true | Qat'iy tekshiruv — eng ko'p xatoni ushlaydi. Doim yoqing |
noEmit: true | tsc 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-eslintkabi asboblar hozircha TypeScript 6.0 ga tayanadi (2026-yil oktabr holatidatypescript-eslint8.71typescript <6.1.0talab qiladi). Microsoft bunday holatlar uchun@typescript/typescript6moslik 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).varishlatmang. - 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_000yozsangiz hamnumberbo'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 Bookingkabi "tip berish" (type assertion) TypeScript'ni ishontiradi, lekin ma'lumotni tekshirmaydi. Agar API boshqa shakldagi javob qaytarsa,asbuni 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
interface | type |
|---|---|
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
| Metod | Qaytaradi | QA'dagi odatiy ishlatilishi |
|---|---|---|
map | Yangi massiv | UI matnlarini songa aylantirish: '$29.99' → 29.99 |
filter | Mos kelganlar massivi | Tugagan mahsulotlar, xato statusdagi buyurtmalar |
find | Birinchi element yoki undefined | ID bo'yicha mahsulot topish |
some / every | boolean | "Hech bo'lmasa bittasi..." / "hammasi..." tekshiruvi |
reduce | Bitta qiymat | Savatcha 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 jumladan0va'') "yo'q" deb hisoblaydi. APIdeliveryFee: 0qaytarganda||uni 20 000 ga almashtirib yuboradi. Standart qiymat berishda **??** ishlating — u faqatnullvaundefinedni 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 defaultemas. Nomi bilan import qilinadi, qayta nomlash xavfsizroq, IDE uni oson topadi.
npm asoslari
| Buyruq / fayl | Vazifasi |
|---|---|
package.json | Loyiha 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.json | Aniq versiyalarni "muzlatadi". Git'ga commit qiling |
npm ci | Lock 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'z | Ma'nosi |
|---|---|
constructor(private readonly ownerPhone: string) | Parametr avtomatik ravishda maxfiy, o'zgarmas property bo'ladi |
private | Faqat class ichidan ko'rinadi |
readonly | Bir marta beriladi, keyin o'zgarmaydi |
static | Obyektga 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
sleepfaqat 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.allqachon 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.allham 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'lsatrue. 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 /authqanday 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:
.envfaylni hech qachon git'ga commit qilmang. Uning o'rniga qiymatlarsiz.env.examplesaqlang, 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:
- 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.) - O'zbek lokali (
fakerUZ_UZ_latin) ism-familiyalarni o'zbekcha beradi, lekinphone.number()o'zbek formatida emas (783.229.2490 x69259kabi chiqdi). Shuning uchun+998raqamni o'zimiz yasadik. faker.helpers.fromRegExp()murakkab regex'larni (masalan,(90|91)kabi guruhlarni) to'liq tushunmaydi — natijada\+998(90|91|...)matnining o'zi chiqib qoldi. OddiyarrayElement+string.numericishonchliroq.
⚠️ 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'.
quantity: '2'— matn, son kutilgan.lineTotal(...)gaunitPriceberilmagan — 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,unknownvaanyfarqini bilaman - [ ]
interfacebilan test ma'lumoti modelini yoza olaman - [ ]
map,filter,find,some,every,reducebilan 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 unutilganawaitxavfini tushunaman - [ ]
fetchbilan API'ga so'rov yuborib, statusni tekshira olaman - [ ]
.envva 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 --noEmittekshiradi. - Muhit: Node 24 LTS,
typescript(7.0.2),tsx,@types/node@24;tsconfig.jsondastrict: trueva TypeScript 7 uchuntypes: ["node"]. - Literal tiplar (
OrderStatus) vainterfacelar test ma'lumotlarini xavfsiz qiladi;neverbilan 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-promiseslint qoidasini yoqing. fetch+AbortSignal.timeout+response.ok— API smoke'ning minimal to'plami; tashqi ma'lumotniasemas, 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}
- TypeScript — rasmiy hujjatlar
- TypeScript Handbook — The Basics
- Microsoft DevBlogs — Announcing TypeScript 7.0
- Playwright — TypeScript
- Playwright — Best Practices (lint va tsc)
- typescript-eslint — no-floating-promises
- Node.js — Download
- Node.js — Globals (fetch va boshqalar)
- MDN — Promise
- MDN — async function
- MDN — Fetch API
- tsx — GitHub
- Faker — Guide va Localization
- npm — npm ci
- Restful-Booker API hujjati
- Playwright Test for VS Code