टेस्ट ऑटोमेशन के लिए प्रोग्रामिंग
Automated tests छोटे programs हैं। एक test file runner और page object import करती है, test data पर loop चलाती है, एक object बनाती है, browser actions का इंतज़ार करती है और नतीजा जाँचती है। "टेस्टर्स के लिए JavaScript बेसिक्स" पाठ में values, functions, promises और stack trace पढ़ना शामिल था; यह पाठ वे पाँच language features जोड़ता है जिनसे लगभग हर test file बनती है: control flow, modules, classes, error handling के साथ asynchronous code, और basic types।
उदाहरण TypeScript में हैं: यानी JavaScript और साथ में email: string जैसी type annotations, जिन्हें TypeScript compiler (tsc) और आपका editor जाँचते हैं और जो code चलने से पहले हटा दी जाती हैं। Playwright TypeScript test files को बिना अतिरिक्त setup के चलाता है। लक्ष्य है कि आप अपनी टीम की कोई भी spec file खोलें, बता सकें कि हर line क्या करती है, और उसी shape में नया test लिख सकें।
यह क्यों ज़रूरी है
जो टेस्टर test code नहीं पढ़ सकता, वह यह नहीं बता सकता कि fail हुए test ने product में defect पकड़ा है या उसमें खुद defect है, और वह साथी के change की review भी नहीं कर सकता। इससे बुरा यह कि कुछ test defects test को pass करा देते हैं जबकि product टूटा है: भूला हुआ await assertion को page बदलने से पहले चला देता है, और log करके आगे बढ़ जाने वाला try/catch fail हुई जाँच को runner से छिपा देता है। ऐसे tests से भरी suite green दिखाती है और कुछ नहीं पकड़ती।
Test code हर change पर चलता है, इसलिए उसकी structure तय करती है कि user interface में छोटे बदलाव की कीमत क्या होगी: एक file में एक edit, या हर test में वही edit।
Control flow: if, for...of और arrow functions
Test code में भी वही decisions और loops होते हैं जो किसी भी program में, पर कम। if/else एक रास्ता चुनता है, जैसे cookie banner तभी बंद करना जब वह दिखे। for...of एक array के हर item के लिए वही steps एक बार चलाता है, और test code में array में test-data objects होते हैं: हर case के लिए एक object, जिसमें inputs और expected result हों। Loop हर object के लिए एक named test बनाता है, इसलिए failure बताती है कि कौन-सा case fail हुआ।
Arrow function, (a, b) => a + b, value के रूप में लिखा गया function है। Runners functions को arguments के रूप में लेते हैं, इसलिए हर test की body एक function है जो callback के रूप में pass होता है, आमतौर पर arrow function: test('name', async () => { ... })। Runner इस callback को रखता है और बाद में call करता है; file load होते समय इसकी body नहीं चलती। नीचे के async ({ page }) => { ... } में runner callback को एक object देता है, और { page } उसकी page property निकाल लेता है (इसे destructuring कहते हैं): यह इस test का browser page है।
- Expected message को data object में लिखें; उसे test के अंदर
ifसे न निकालें, वरना test bug से सहमत हो सकता है। for...ofvalues पर चलता है,for...inobject की keys पर; test code में लगभग हमेशाfor...ofचाहिए।
const cases = [
{ email: 'sara@example.com', password: 'Correct-1', message: 'Welcome, Sara' },
{ email: 'sara@example.com', password: 'wrong', message: 'Invalid login' },
];
for (const c of cases) {
test('login: ' + c.message, async ({ page }) => { // one test per data object
// the steps use c.email, c.password and c.message
});
}Modules: export और import
Module एक file है जो बताती है कि वह क्या share करती है। export class LoginPage नाम को उपलब्ध कराता है; import { LoginPage } from './pages/login-page' उसे दूसरी file में लाता है। कुछ भी संयोग से share नहीं होता: बिना export वाला नाम अपनी file में private रहता है, और जिस file को कुछ चाहिए उसे import करना पड़ता है।
इसीलिए spec file दो तरह के import से शुरू होती है: runner package से test और expect, यानी वे functions जो test register करते और नतीजा जाँचते हैं, और आपकी अपनी files से page objects और data। तब spec एक scenario की तरह पढ़ी जाती है और locators (किसी element को ढूँढने वाले expressions, जैसे getByLabel('Email')) एक जगह रहते हैं: login form बदलने पर आप page object बदलते हैं और उसे import करने वाली हर spec ठीक हो जाती है।
- Relative paths
./या../से शुरू होते हैं;@playwright/testजैसे package names नहीं। import type { Page }सिर्फ़ type import करता है; compiler उसे output से हटा देता है।
// pages/login-page.ts
export class LoginPage { /* ... */ }
// login.spec.ts
import { test, expect } from '@playwright/test'; // the runner
import { LoginPage } from './pages/login-page'; // your page objectClasses और instances: page object की shape
Class ऐसे objects का template है जो fields (data) और methods (functions) share करते हैं। new LoginPage(page) एक instance बनाता है: यह constructor चलाता है, जो browser page को एक field में रखता है, और ऐसा object लौटाता है जिसके methods this के ज़रिए उस field तक पहुँचते हैं। हर test अपना instance बनाता है, इसलिए कोई दो tests एक page object share नहीं करते।
Page object ठीक यही है: page के लिए एक field, उसे लेने वाला constructor, और screen पर यूज़र जो-जो कर सकता है उसके लिए एक-एक method, जैसे open() और signIn(email, password)। Methods locators और clicks छिपाते हैं; test उन्हें नाम से call करता है। Selenium documentation एक नियम जोड़ता है जो हर language में लागू है: page objects में assertions न हों, क्योंकि क्या सही है यह test तय करता है; page object के अंदर वह सिर्फ़ एक जाँच की अनुमति देता है, कि page सही से load हुआ है।
private readonly pageका मतलब है कि field को सिर्फ़ class के अंदर का code इस्तेमाल करता है और constructor के बाद कोई उसे दोबारा assign नहीं करता; जो testlogin.pageतक पहुँचे उसे compiler error मिलता है।
import type { Page } from '@playwright/test';
export class LoginPage {
private readonly page: Page; // a field
constructor(page: Page) { // runs once, on new
this.page = page; // this = the instance being built
}
async open(): Promise<void> { // a method
await this.page.goto('/login'); // relative to baseURL in the config
}
async signIn(email: string, password: string): Promise<void> {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Sign in' }).click();
}
}Async code और errors: हर चीज़ await करें, कुछ भी निगलें नहीं
हर browser action (goto, fill, click) और हर retry करने वाला assertion एक promise लौटाता है, क्योंकि काम browser में होता है और समय लेता है। जो function इंतज़ार करता है वह async घोषित होता है; उसके अंदर await promise settle होने तक रुकता है, फिर value के साथ आगे बढ़ता है या error throw करता है। नियम: promise लौटाने वाले हर action और हर assertion को await करें।
JavaScript test flaky हो तो सबसे पहले ढूँढी जाने वाली चीज़ों में missing-await bug है। बिना await के login.open() navigation शुरू करके तुरंत अगली line चला देता है, तो page को एक बार पढ़ने वाली जाँच पुराना page पढ़ सकती है; और अगर बिना await वाली call आख़िरी action हो, तो test function उसके पूरा होने से पहले ख़त्म हो सकता है, और test में कुछ भी उसका नतीजा नहीं जाँचता। Compiler ऐसे promise पर कुछ नहीं कहता जिसका कोई इंतज़ार नहीं करता; linter (code में जोखिम भरे patterns जाँचने वाला tool, जैसे ESLint) @typescript-eslint/no-floating-promises जैसे rule से इसकी चेतावनी देता है, और type भी उसे पकड़ सकता है, जैसा worked example में दिखेगा।
Throw हुआ error ही test के fail होने का तरीका है: fail हुआ expect throw करता है, और runner उस error को result के रूप में दर्ज करता है। try/catch/finally को इसका सम्मान करना चाहिए। finally उस cleanup के लिए रखें जो दोनों हालात में चलना चाहिए, और catch सिर्फ़ तब जब error ही वह चीज़ हो जिसे आप test कर रहे हैं, फिर उस पर assert करें। Log करके आगे बढ़ जाने वाला catch fail हुई जाँच को pass बना देता है। असली cleanup आमतौर पर वह data हटाता है जो test ने बनाया; नीचे की clearCookies call सिर्फ़ यह दिखाती है कि cleanup कहाँ जाता है, क्योंकि Playwright हर test को पहले से fresh cookies के साथ शुरू करता है।
// Wrong: the navigation is started but not awaited
login.open();
expect(await page.title()).toBe('Sign in'); // may read the previous page
// Wrong: the error is swallowed, so the runner records a pass
try {
await expect(page.getByTestId('message')).toHaveText('Welcome, Sara');
} catch (error) {
console.log('check failed', error); // the test still passes
}
// Right: await every action, let a failed check throw, clean up in finally
await login.open();
try {
await login.signIn('sara@example.com', 'Correct-1');
await expect(page.getByTestId('message')).toHaveText('Welcome, Sara');
} finally {
await page.context().clearCookies(); // runs on pass and on fail
}Worked example: arrange-act-assert रूप में एक test file
नीचे login.spec.ts है, जो classes वाले section की LoginPage class इस्तेमाल करती है और इस पाठ की हर feature को label करती है। हर test arrange-act-assert में चलता है: state arrange करें (page object बनाएँ, page खोलें), act करें (sign in), assert करें (message जाँचें)। हर test में एक act और एक focused assertion report को पढ़ने लायक रखता है।
email: string, qty: number और Promise<void> जैसी annotations TypeScript types हैं; Promise<T> को ऐसे पढ़ें: एक promise जो T type की value देगा, और void का मतलब है कोई value नहीं। Run time पर ये कुछ नहीं बदलतीं। Compiler इन्हें जाँचता है और मेल न खाने वाली हर call की रिपोर्ट करता है: signIn(c.email, 123) क्योंकि number string नहीं है, login.signin(...) क्योंकि यह method है ही नहीं, और const title = page.title(); title.includes('x') क्योंकि title एक Promise<string> है जिसमें includes नहीं होता: type ने missing await पकड़ लिया। Playwright types को बिना जाँचे हटा देता है और tests फिर भी चला देता है, इसलिए ये errors सिर्फ़ आपके editor में और npx tsc --noEmit (सिर्फ़ जाँच, कोई file नहीं लिखी जाती) चलाने पर दिखती हैं, जिसे टीमें tests से पहले एक step के रूप में जोड़ती हैं।
यह file चलती कैसे है? Test runner एक program है जिसे आप command line से शुरू करते हैं (अगला पाठ)। यह *.spec.ts जैसे pattern से मेल खाती files ढूँढता है और हर एक को load करता है, जिससे top-level code चलता है: loop test(...) call करता है और runner callbacks रख लेता है। फिर यह हर callback को नया page देकर call करता है, लौटे promise का इंतज़ार करता है, और fulfil होने पर pass, और reject होने पर error व stack trace के साथ fail दर्ज करता है।
- Selenium users के लिए:
importJava काimportया Python काfrom ... importहै; fields और constructor वाली class दोनों में है (Python में__init__औरself, Java में class के नाम वाला constructor औरthis); Java और Python bindings synchronous हैं, इसलिए वहाँawaitनहीं: हर call driver के जवाब तक रुकती है, और वहाँ का जोखिम missing explicit wait है।
login.spec.ts: टेस्ट फ़ाइल
भाग 1:import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/login-page'; भाग 2:const cases = [ { email: 'sara@example.com', password: 'Correct-1', message: 'Welcome, Sara' }, { email: 'sara@example.com', password: 'wrong', message: 'Invalid login' }, ]; भाग 3:for (const c of cases) { भाग 4:test('login: ' + c.message, async ({ page }) => { const login = भाग 5:new LoginPage(page); भाग 6:await login.open(); await login.signIn(c.email, c.password); भाग 7:await expect(page.getByTestId('message')).toHaveText(c.message); }); }
pages/login-page.ts: page object की फ़ाइल
import type { Page } from '@playwright/test'; भाग 8:export class LoginPage { भाग 9:private readonly page: Page; constructor(page: Page) { this.page = page; } async open(): Promise<void> { await this.page.goto('/login'); } भाग 10:async signIn(email: string, password: string): Promise<void> { await this.page.getByLabel('Email').fill(email); await this.page.getByLabel('Password').fill(password); await this.page.getByRole('button', { name: 'Sign in' }).click(); } }
- 1Imports: दो modulesRunner package
testऔरexpectदेता है; आपकी अपनी file page object देती है। कुछ भी global नहीं। - 2Test data: objects का arrayहर case के लिए एक object, जिसमें inputs और expected message हैं।
- 3
for...of: हर object के लिए एक testFile load होते समय चलता है और दो tests register करता है, हर एक का नाम उसके data से। - 4Callback के रूप में async arrow functionRunner इसे रखता है और बाद में नए
pageके साथ call करता है;asyncसे bodyawaitकर सकती है। - 5
new: class का एक instanceConstructor चलाता है;pagefield में रखा जाता है। हर test को अपना instance मिलता है। - 6हर browser action पर
awaitइसके बिना अगली line page बदलने से पहले चल जाती है: missing-await bug। - 7AssertionFail हुआ
expectthrow करता है, और वह error ही test का result है। नतीजे पर assert test करता है, page object नहीं। - 8
export class: page objectवह नाम जो spec file import करती है; आम चलन है हर file में एक class, और file का नाम उसी class पर। - 9Field और constructor
this.pagenewपर एक बार set होता है और हर method उसे पढ़ता है;private readonlyकिसी और को उसे बदलने से रोकता है। - 10Types वाला async methodदो
stringparameters औरPromise<void>result: compiler यहाँ number को reject करता है, और promise caller को बताता है कि call कोawaitकरे।
पाठ संस्करण
// login.spec.ts
import { test, expect } from '@playwright/test'; // module: the runner
import { LoginPage } from './pages/login-page'; // module: our page object
const cases = [ // test data: one object per case
{ email: 'sara@example.com', password: 'Correct-1', message: 'Welcome, Sara' },
{ email: 'sara@example.com', password: 'wrong', message: 'Invalid login' },
];
for (const c of cases) { // control flow: one test per object
test('login: ' + c.message, async ({ page }) => { // async arrow function as callback
// Arrange
const login = new LoginPage(page); // class -> instance
await login.open(); // await: a browser action
// Act
await login.signIn(c.email, c.password); // typed: (string, string)
// Assert
await expect(page.getByTestId('message')).toHaveText(c.message);
});
}आज़माएँ: JavaScript फ़ॉर्मेटर आज़माएँ: REST रिक्वेस्ट जनरेटर
आम गलतियाँ
- Browser action को बिना
awaitcall करना और फिर ऐसे page पर assert करना जो अभी बदला नहीं; test flaky हो जाता है या गलत वजह से pass होता है। expectको ऐसेtry/catchमें लपेटना जो error log करके आगे बढ़ जाए; runner pass रिपोर्ट करता है जबकि product टूटा है।- Assertions को page object methods के अंदर रखना, जिससे page object pass या fail तय करता है और test यह नहीं बताता कि वह क्या जाँचता है।
- एक page object import करने की जगह हर spec file में locators copy करना; तब login form का एक बदलाव बीस edits बन जाता है।
anyया// @ts-ignoreसे compiler को चुप कराना, जो ठीक उन्हीं checks को बंद करता है जो गलत arguments और typos पकड़ते हैं।
QA का नज़रिया
टीम में test code की review product code की तरह होती है। कुछ भी लिखने से पहले मौजूदा suite पढ़ें: spec files, page objects और test data कहाँ रहते हैं, कौन-सा runner import होता है, और naming, async methods और cleanup के लिए टीम के conventions क्या हैं। पूछें कि un-awaited promises के लिए lint rule चालू है या नहीं; न हो तो उसे propose करें, क्योंकि यह flaky tests की पूरी एक category हटा देता है।
जब कोई test fail हो, पहले तय करें कि क्या fail हुआ: product, test या environment। Runner का stack trace एक line की ओर इशारा करता है; अगर वह page object या spec में है, तो इस पाठ के सवालों के साथ आसपास का code पढ़ें: क्या हर action await हुआ, क्या किसी catch ने कुछ छिपाया, क्या data row वही कहती थी जो test ने expect किया? जवाब defect report में या test ठीक करने वाले change में दर्ज करें।
अभ्यास
Registration test में defects खोजें
नीचे की spec file दो users register करती है और message जाँचती है। यह type check (npx tsc --noEmit) एक fix के बाद ही pass करती है, और इसमें दो और defects हैं जो इसे unreliable या भ्रामक बनाते हैं। तीनों खोजें, बताएँ कि हर एक result पर क्या असर डालता है, और कागज़ पर ठीक करें। फिर एक test के arrange, act और assert हिस्सों को label करें।
Page object RegisterPage का constructor page लेता है, और वह open(): Promise<void>, fillForm(name: string, age: string): Promise<void> और submit(): Promise<void> declare करता है।
import { test, expect } from '@playwright/test';
import { RegisterPage } from './pages/register-page';
const cases = [
{ name: 'Sara', age: 18, message: 'Account created' },
{ name: 'Omar', age: 17, message: 'You must be 18 or older' },
];
for (const c of cases) {
test('register: ' + c.message, async ({ page }) => {
const register = new RegisterPage(page);
register.open();
await register.fillForm(c.name, c.age);
await register.submit();
try {
await expect(page.getByTestId('message')).toHaveText(c.message);
} catch (error) {
console.log('message check failed', error);
}
});
}- हर line पढ़ें और पूछें: क्या यह action promise लौटाता है, और क्या वह await हुआ है?
- हर
try/catchदेखें: error का क्या होता है? - हर call की तुलना page object की method signature से करें।
- एक test की arrange, act और assert lines चिह्नित करें।
मॉडल उत्तर दिखाएँ
Compiler error: fillForm(c.name, c.age) वहाँ number भेजता है जहाँ age: string declare है। Fix: String(c.age), या बेहतर, page object को age: number लेने और अंदर convert करने के लिए बदलें।
Missing await: register.open() navigation शुरू करता है और test तुरंत आगे बढ़ जाता है, तो fillForm page load से होड़ करता है: result timing पर निर्भर हो जाता है, और open() का error उस line पर throw नहीं होता। Fix: await register.open()।
निगला हुआ error: catch fail हुए assertion को log करता है और test बिना error के ख़त्म हो जाता है, इसलिए age rule टूटा होने पर भी runner Omar के लिए pass रिपोर्ट करता है। Fix: try/catch हटाएँ; cleanup चाहिए तो बिना catch के finally रखें।
Arrange: new RegisterPage(page) और open()। Act: fillForm और submit। Assert: expect वाली line। Loop और data array किसी एक test का हिस्सा नहीं हैं; वे दोनों tests generate करते हैं।
अक्सर पूछे जाने वाले सवाल
क्या मुझे TypeScript चाहिए, या JavaScript काफ़ी है?
Tests चलाने के लिए JavaScript काफ़ी है; runner दोनों लेता है। TypeScript type annotations जोड़ती है जिन्हें compiler जाँचता है, जिससे गलत arguments, गलत spelling वाले methods और कुछ missing awaits कुछ भी चलने से पहले पकड़े जाते हैं। Playwright TypeScript test files को बिना अतिरिक्त setup के चलाता है पर उनके types नहीं जाँचता, इसलिए ये errors अपने editor में देखें या npx tsc --noEmit चलाएँ। इस पाठ की बाकी features JavaScript में भी वैसे ही काम करती हैं।
मैं await भूल गया, फिर भी test pass क्यों हुआ?
बिना await वाली call ने ऐसा promise लौटाया जिसका किसी ने इंतज़ार नहीं किया, इसलिए test function बिना error के अपने अंत तक पहुँच गया और runner ने pass दर्ज कर लिया। उसके बाद action ने जो भी किया, test ने उसे कभी नहीं जाँचा। सिर्फ़ await किया हुआ promise ही अपना error उसी line पर रिपोर्ट करता है जिसने उसे पैदा किया, इसलिए await जोड़ें ताकि test सही वजह से fail या pass हो।
क्या हर test को page object चाहिए?
नहीं। जिस page को आप एक ही test में छूते हैं उसे सीधे चलाया जा सकता है। Page object तब बनाएँ जब दो tests एक ही interaction दोहराएँ, या कोई screen बार-बार बदले, ताकि locators एक file में रहें। इस module में आगे का "पेज ऑब्जेक्ट मॉडल" पाठ इस pattern को गहराई से बताता है।
मुझे Java या Python आती है। क्या TypeScript भी सीखूँ?
Concepts transfer होते हैं: modules, classes, constructors और page objects Java और Python में वैसे ही दिखते हैं, और Selenium के पास दोनों के official bindings हैं। वह language सीखें जो आपकी टीम का framework इस्तेमाल करता है; समझने लायक सबसे अहम फ़र्क़ यह है कि Playwright और Selenium की JavaScript binding में browser actions asynchronous हैं और उन्हें await चाहिए। Cypress भी JavaScript में है, पर वह अपनी commands को queue में रखता है और उसके साथ await इस्तेमाल नहीं होता।
संदर्भ
- ECMAScript® Language Specification (ECMA-262)JavaScript भाषा का मानक, जिसमें RegExp का व्याकरण और अर्थ, JSON.parse/JSON.stringify और Date ऑब्जेक्ट शामिल हैं।
- JavaScript — MDNMDN की JavaScript गाइड और reference: भाषा का सिंटैक्स, types, objects और built-ins, जैसे ब्राउज़र उन्हें लागू करते हैं।
- Playwright DocumentationPlaywright का आधिकारिक दस्तावेज़ — Chromium, WebKit और Firefox चलाने वाला end-to-end टेस्ट फ़्रेमवर्क।
- Page object models — SeleniumPage object पैटर्न: हर पेज या कंपोनेंट के लिए एक क्लास जो locator छिपाती है और टेस्ट को चाहिए वे एक्शन देती है, जिससे दोहराव घटता है।
Mutqan tools से अभ्यास करें
JavaScript और TypeScript कोड को एकसमान इंडेंटेशन और स्पेसिंग के साथ सुंदर बनाएँ।
टेस्ट अकाउंट के लिए फ़ेक यूज़र रिकॉर्ड (नाम, username, ईमेल, पासवर्ड, avatar seed, role) जनरेट करें।
CSV या TSV डेटा को JSON ऑब्जेक्ट के ऐरे में बदलें।
cURL, JavaScript fetch, axios, Python requests, Java, C#, Go और PHP में चलाने लायक़ REST API रिक्वेस्ट कोड बनाएँ।
और गहराई से
इस topic में आपकी प्रगति
प्रगति ट्रैक करने के लिए साइन इन करें
Topics पूर्ण चिह्नित करें, जहाँ छोड़ा था वहीं से जारी रखें और specialisation परीक्षाएँ खोलें।