वेट्स और सिंक्रोनाइज़ेशन
Synchronization का मतलब है automated test को उस application के साथ कदम मिलाकर चलाना जिसे वह चला रहा है: हर step तभी action या check करे जब application उस step के लिए ज़रूरी state तक पहुँच चुका हो। एक test "Pay now" पर click करता है और तुरंत payment status पढ़ता है; page अब भी "Processing" दिखा रहा है क्योंकि server ने अभी जवाब नहीं दिया, और payment ठीक काम करने के बावजूद test fail हो जाता है।
Wait वह तरीका है जिससे test synchronize होता है। अच्छा wait एक condition बताता है, जैसे "status Paid दिखाए", उसे बार-बार जाँचता है और जैसे ही वह सच हो, आगे बढ़ जाता है, एक timeout की सीमा के अंदर। Selenium implicit और explicit waits देता है; Playwright और Cypress actions से पहले और assertions में अपने-आप इंतज़ार करते हैं। एक तरह का wait हमेशा गलत है: fixed sleep।
यह क्यों ज़रूरी है
Timing flaky UI tests की एक आम वजह है: test तेज़ laptop पर pass होता है और व्यस्त CI runner पर fail, या बीस में से एक run में fail होता है। हर झूठा failure किसी की जाँच का समय लेता है, और जो team ऐसे बहुत failures देखती है वह red builds पढ़ने की बजाय उन्हें दोबारा चलाने लगती है। इसी तरह असली defect बिना ध्यान दिए निकल जाता है।
खराब waits हर run में समय भी खाते हैं, और धीमा suite कम बार चलाया जाता है। अच्छे waits suite को एक साथ तेज़ और भरोसेमंद बनाते हैं: वह application की रफ़्तार से चलता है, और fail होने पर message बताता है कि कौन-सी state कभी नहीं आई।
Test की timing और application की timing
Test और application दो अलग घड़ियों पर चलते हैं। Click जैसा command browser के उसे पूरा करते ही लौट आता है, और test अपनी अगली line पर चला जाता है। Click ने जो शुरू किया वह बाद में, application के अपने समय पर पूरा होता है: जैसा "टेस्टर्स के लिए JavaScript बेसिक्स" lesson में देखा, fetch() एक pending promise लौटाता है और page तभी बदलता है जब response आता है।
Synchronize करने के लिए test को एक ऐसी condition चाहिए जिसे वह जाँच सके: page पर, URL में या network पर कुछ ऐसा जो तभी सच हो जब application अगले step के लिए तैयार हो। आम देरी जिनके लिए plan करना होता है:
- Network requests: loading, saving और searching तब पूरे होते हैं जब server जवाब देता है।
- Rendering: कोई list या total अपना data आने के बाद दिखता है, कभी-कभी दो चरणों में (पहले placeholder, फिर values)।
- Animations: slide होकर आता dialog, या layout के सेट होते समय खिसकता button।
- Timers: search box जो आख़िरी keystroke के 300 ms बाद search करता है (debounce), या message जो 5 seconds बाद ग़ायब हो जाता है।
- Navigation: sign-in के बाद redirect, नया tab, या download।
Fixed sleep कभी इलाज क्यों नहीं है
जल्दबाज़ test को pass कराने का सबसे आसान तरीका एक pause है: Thread.sleep(3000), time.sleep(3), cy.wait(3000) या page.waitForTimeout(3000)। Sleep को पता नहीं होता कि वह किसका इंतज़ार कर रहा है; वह तय समय रुकता है और फिर आगे बढ़ जाता है, app तैयार हो या न हो। Playwright documentation समय का इंतज़ार करने वाले tests को स्वभाव से flaky कहता है, और Cypress best practices मनमाने समय तक इंतज़ार को anti-pattern मानती हैं।
धीमे endpoint के साथ आप यह समस्या खुद देख सकते हैं: API स्टेटस कोड सिम्युलेटर आपकी चुनी देरी (अधिकतम 10 seconds) के बाद जवाब देता है। Sleep तीन तरह से गलत होता है:
- धीमे run पर बहुत छोटा: व्यस्त CI runner पर payment response 3.4 seconds बाद आता है, test 3.0 पर पढ़ता है और fail होता है, पर सिर्फ़ कुछ runs में, इसलिए test flaky बन जाता है।
- तेज़ run पर बहुत लंबा: response 0.3 seconds में आ जाए तब भी test 3 seconds रुकता है। ऐसे एक-एक sleep वाले दो सौ tests हर run में कुल मिलाकर 10 मिनट कुछ किए बिना बिताते हैं।
- कोई इरादा नहीं:
sleep(3000)यह नहीं बताता कि लिखने वाला किसका इंतज़ार कर रहा था, इसलिए न अगला पढ़ने वाला और न failure message बता पाता है कि कौन-सी condition मायने रखती थी।
Fixed sleep:
waitForTimeout(3000)- टेस्ट
- पेज ब्राउज़र में application
- सर्वर
- 1 टेस्ट से पेज तक: "Pay now" पर click करता है Click होते ही command लौट आता है
- 2 पेज से सर्वर तक: Payment request भेजता है
POST /api/payments - 3 टेस्ट आंतरिक रूप से: 3 seconds sleep करता है कोई condition नहीं: page को देखता ही नहीं
- 4 टेस्ट से पेज तक: 3.0 s पर status एक बार पढ़ता है
- 5 पेज से टेस्ट तक: अब भी "Processing" जवाब फ़ेल
- 6 सर्वर से पेज तक:
201 Createdजवाब व्यस्त CI runner पर 3.4 s पर; page "Paid" तब दिखाता है जब test fail हो चुका
परिणाम: Payment ठीक होने पर भी fail होता है; तेज़ run पर pass होता है लेकिन फिर भी पूरे 3 seconds रुकता है। फ़ेल
Condition-based wait: status "Paid" हो
- टेस्ट
- पेज ब्राउज़र में application
- सर्वर
- 1 टेस्ट से पेज तक: "Pay now" पर click करता है Click होते ही command लौट आता है
- 2 पेज से सर्वर तक: Payment request भेजता है
POST /api/payments - 3 टेस्ट से पेज तक: जाँचता है: क्या status "Paid" है? बार-बार, जब तक ऐसा न हो या timeout ख़त्म न हो
- 4 पेज से टेस्ट तक: अभी नहीं: "Processing" जवाब
- 5 सर्वर से पेज तक:
201 Createdजवाब 3.4 s पर; page "Paid" दिखाता है - 6 पेज से टेस्ट तक: "Paid": wait 3.4 s पर ख़त्म जवाब पास
परिणाम: Page तैयार होते ही आगे बढ़ता है, तेज़ run पर 0.3 s पर; fail सिर्फ़ तब होता है जब timeout से पहले "Paid" कभी न दिखे। पास
Implicit और explicit waits
हर condition-based wait के तीन हिस्से होते हैं: condition, एक timeout जो wait को error के साथ ख़त्म करता है, और polling interval, यानी condition कितनी बार जाँची जाती है। Selenium WebDriver दो तरह के waits देता है।
Implicit wait पूरे session की एक setting है: हर element lookup तब तक retry करता है जब तक element DOM में मौजूद न हो जाए या timeout ख़त्म न हो (default 0 है, यानी तुरंत fail)। यह सिर्फ़ मौजूदगी का इंतज़ार करता है, और element मौजूद होकर भी hidden, disabled या किसी और element से ढका हो सकता है। यह उन lookups को भी धीमा करता है जिन्हें कुछ नहीं मिलना चाहिए: findElements से यह जाँचना कि error message मौजूद नहीं है, पूरा implicit timeout खा जाता है।
Explicit wait उसी step पर लिखा जाता है जिसे उसकी ज़रूरत है: timeout और condition के साथ WebDriverWait। ExpectedConditions (Python में expected_conditions) आम conditions देता है, जैसे visible, clickable, invisible, text present और URL contains, और आप अपना function भी दे सकते हैं: Java में d -> … या Python में lambda d: …, यह वही idea है जो JavaScript के arrow function में है, और d driver है। Default रूप से WebDriverWait हर 500 milliseconds पर जाँचता है और condition सच होते ही लौट आता है। Selenium documentation implicit और explicit waits मिलाने से मना करता है: दोनों timeouts आपस में उलझते हैं और कोई step दोनों values से ज़्यादा देर बाद fail हो सकता है।
// A) Implicit wait: one session-wide setting, used by every findElement call.
// It waits until the element is present in the DOM, not visible or clickable.
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
// B) Explicit wait: a condition at the step that needs it. Use B instead of A,
// never both in the same session.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.textToBePresentInElementLocated(
By.cssSelector("[data-testid=payment-status]"), "Paid"));
wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("[data-testid=download-receipt]"))).click();
// Your own condition: a function the wait calls until it returns true
wait.until(d -> d.findElements(By.cssSelector("[data-testid=result-row]")).size() == 1);Playwright और Cypress में auto-waiting
Playwright हर action से पहले actionability checks चलाता है। click() के लिए element visible, stable (हिल न रहा हो), events receive करने लायक (किसी दूसरे element से ढका न हो) और enabled होना चाहिए; fill() के लिए visible, enabled और editable। ये checks तब तक दोहराए जाते हैं जब तक pass न हों या timeout ख़त्म न हो। Assertions वाले lesson के retry करने वाले assertions expected state का इंतज़ार इसी तरह करते हैं। Locator हर इस्तेमाल पर फिर से resolve होता है, इसलिए दोबारा render हुआ element test के हाथ में पुराना reference नहीं छोड़ता, यानी वह StaleElementReferenceException जिसे Selenium users जानते हैं।
Cypress cy.get() और cy.contains() जैसी queries को उनसे जुड़े assertion के साथ retry करता है, default रूप से 4 seconds तक, और .click() या .type() से पहले element के actionable होने का इंतज़ार करता है। Server का इंतज़ार करने के लिए cy.intercept() से request को alias दें और उस alias का इंतज़ार करें, किसी number का कभी नहीं।
Auto-waiting यह जाँचता है कि element इस्तेमाल हो सकता है, यह नहीं कि application तैयार है। "Pay now" button order total load होने से पहले ही enabled हो सकता है, और Playwright उस पर click कर देगा। जब step किसी data पर निर्भर हो, तो पहले उस data को assert करें। Response का इंतज़ार करना हो तो request भेजने वाले action से पहले सुनना शुरू करें, वरना तेज़ response wait शुरू होने से पहले ही आ सकता है। Playwright documentation tests में networkidle state का इंतज़ार करने को भी discourage करता है।
// Playwright: click() waits until the button is visible, stable, receives
// events and is enabled; toHaveText() retries until it matches (5 s default).
await page.getByRole('button', { name: 'Pay now' }).click();
await expect(page.getByTestId('payment-status')).toHaveText('Paid');
// Playwright: to wait for the server, start listening before the click.
const response = page.waitForResponse('**/api/payments');
await page.getByRole('button', { name: 'Pay now' }).click();
expect((await response).status()).toBe(201);
// Cypress: queries retry with their assertion (4 s default);
// an intercept alias waits for the request instead of a number.
cy.intercept('POST', '/api/payments').as('pay');
cy.contains('button', 'Pay now').click();
cy.wait('@pay');
cy.get('[data-testid=payment-status]').should('have.text', 'Paid');Worked example: flaky payment test ठीक करना
Payment page का एक Playwright test लगभग दस में से एक pipeline run में Expected: "Paid", Received: "Processing" के साथ fail होता है; लिखने वाले के laptop पर वह हमेशा pass होता है। Trace दिखाता है कि status click के 3.0 seconds बाद पढ़ा गया और payment response 3.4 पर आया। Page order total भी page के बाद load करता है, और पहला sleep इसी को ढक रहा था।
Fix हर sleep को उस condition से बदलता है जिसका वह अंदाज़ा लगा रहा था। Team ने shared staging environment पर payment step नापा: आम तौर पर एक second से कम, सबसे बुरी हालत में लगभग 8 seconds। यही measurement अकेले लंबे timeout को तय करता है। किसी public endpoint के लिए रिस्पॉन्स टाइम एनालाइज़र इसी तरह का data देता है: कई samples पर median, maximum और p95 (वह समय जिसके अंदर 95% samples रहे)।
- [1] पहला sleep दिखने वाले total, 172.50 SAR, पर assertion बन जाता है। यह बताता है कि "load हो गया" का मतलब क्या है, और गलत total भी पकड़ता है।
- [2] और [3] एक retry करने वाला assertion बन जाते हैं। सामान्य run में यह click के एक second के अंदर आगे बढ़ जाता है; सिर्फ़ इसी step को 15 seconds तक की छूट है, और comment उसकी वजह दर्ज करता है।
- आख़िरी click को अलग wait नहीं चाहिए: Playwright "Download receipt" के enabled होने तक इंतज़ार करता है।
- अगर बाद में payment को 15 seconds से ज़्यादा लगने लगे, तो timeout फिर बढ़ाने की बजाय timings के साथ इसे performance problem के रूप में report करें।
// BEFORE: always passes locally, fails in about one CI run in ten
await page.goto('/orders/1042/pay');
await page.waitForTimeout(2000); // [1] "let the total load"
await page.getByRole('button', { name: 'Pay now' }).click();
await page.waitForTimeout(3000); // [2] "let the payment finish"
const status = await page.getByTestId('payment-status').textContent();
expect(status).toBe('Paid'); // [3] reads once
await page.getByRole('button', { name: 'Download receipt' }).click();
// AFTER: each sleep replaced by the condition it was guessing at
await page.goto('/orders/1042/pay');
await expect(page.getByTestId('order-total')).toHaveText('172.50 SAR'); // [1]
await page.getByRole('button', { name: 'Pay now' }).click();
// [2] + [3]: payment sandbox measured at up to 8 s on shared staging
await expect(page.getByTestId('payment-status'))
.toHaveText('Paid', { timeout: 15_000 });
await page.getByRole('button', { name: 'Download receipt' }).click(); // auto-waitsआम गलतियाँ
- Flaky failure को sleep जोड़कर या मौजूदा sleep लंबा करके ठीक करना।
- एक ही Selenium session में implicit wait set करना और explicit waits भी इस्तेमाल करना।
- End state की बजाय spinner के ग़ायब होने का इंतज़ार करना: अगर spinner अभी दिखा ही नहीं, तो wait तुरंत pass हो जाता है।
- Action के ठीक बाद value एक बार पढ़ना (
textContent(),.text), retry करने वाले assertion या explicit wait की बजाय। - Request भेजने वाले click के बाद network response का इंतज़ार शुरू करना; तेज़ response छूट जाता है और wait time out हो जाता है।
- एक step धीमा होने पर पूरे suite का timeout बढ़ा देना, जो performance regressions छिपाता है और हर असली failure की report धीमी कर देता है।
QA का नज़रिया
Team में synchronization की समस्याएँ pipeline में flaky failures के रूप में आती हैं। किसी एक को triage करते समय trace या video खोलें और दो timelines मिलाएँ: test ने कब action किया या पढ़ा, और application कब बदला। अगर test जल्दी था, तो wait ठीक करें। अगर application तय limit से धीमा था, या गलत state में पहुँचा, तो यह timings के साथ report करने वाला defect है। जल्दबाज़ी में किए दूसरे click के बाद दो बार submit होना product की race condition है, test का noise नहीं।
Code review में माँगें: committed tests में कोई sleep नहीं, framework के default timeouts, और लंबा timeout सिर्फ़ एक step पर, वजह बताने वाले comment के साथ। Developers से ऐसे signals माँगें जिनका tests इंतज़ार कर सकें: status text, saving के दौरान disabled button, load होते समय किसी region पर aria-busy="true" (एक accessibility attribute जो screen readers को बताता है कि content update हो रहा है), result पर test id। साफ़ signals assistive technology इस्तेमाल करने वालों की भी मदद करते हैं।
अभ्यास
Flaky Selenium test से sleeps हटाएँ
नीचे का Python test sign in करता है, एक order search करता है और उसका status जाँचता है। यह CI पर हफ़्ते में लगभग दो बार fail होता है, हर बार अलग line पर। Sign-in के बाद app orders page पर redirect करता है, जो पहले customer के सारे orders दिखाता है; search box में typing 300 ms के pause के बाद उन्हें filter करती है, और एक spinner, जो page में हमेशा मौजूद पर hidden रहता है, filtered results load होते समय दिखता है।
हर synchronization problem ढूँढें, बताएँ कि हर एक कैसे fail हो सकता है, और test को सिर्फ़ explicit waits के साथ दोबारा लिखें।
driver.implicitly_wait(10)
wait = WebDriverWait(driver, 10)
driver.get(BASE_URL + "/login")
driver.find_element(By.ID, "email").send_keys("layla.h@example.com")
driver.find_element(By.ID, "password").send_keys(PASSWORD)
driver.find_element(By.ID, "sign-in").click()
time.sleep(5)
driver.find_element(By.ID, "search").send_keys("ORD-1042")
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".spinner")))
time.sleep(1)
results = driver.find_elements(By.CSS_SELECTOR, "[data-testid=result-row]")
assert len(results) == 1
assert "Paid" in results[0].text- हर fixed pause चिह्नित करें और लिखें कि वह असल में किस condition का इंतज़ार कर रहा है।
- वह wait ढूँढें जो उस चीज़ के शुरू होने से पहले ही pass हो सकता है जिसका वह इंतज़ार करता है।
- हर वह line चिह्नित करें जो page को एक बार पढ़ती है, और बताएँ कि वह क्या जल्दी पढ़ सकती है।
- Timeout settings में वह combination ढूँढें जिससे Selenium documentation मना करता है।
- Test को एक
WebDriverWaitऔर हर step के लिए एक condition के साथ दोबारा लिखें।
मॉडल उत्तर दिखाएँ
time.sleep(5) अंदाज़ा लगाता है कि sign-in और redirect में कितना समय लगेगा। इसकी जगह अगले page के अपने signal का इंतज़ार करें: wait.until(EC.visibility_of_element_located((By.ID, "search"))), फिर उसी लौटाए गए element में type करें।
Spinner वाला wait typing के ठीक बाद चलता है, जब spinner अभी hidden ही है क्योंकि 300 ms का pause ख़त्म नहीं हुआ, इसलिए invisibility_of_element_located तुरंत pass हो जाता है और कुछ साबित नहीं करता। फिर time.sleep(1) pause और search request दोनों के समय का अंदाज़ा लगाता है।
find_elements वाली line list को एक बार पढ़ती है: धीमे run में उसमें अब भी बिना filter वाले orders होते हैं और len(results) == 1 fail होता है; results[0].text भी एक ही बार पढ़ा जाता है। Spinner wait, sleep और दोनों reads को दो conditions से बदलें: wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, "[data-testid=result-row]")) == 1), फिर wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, "[data-testid=result-row]"), "Paid"))।
implicitly_wait(10) के साथ WebDriverWait इस्तेमाल करना implicit और explicit waits को मिलाना है, जिसके बारे में Selenium documentation चेतावनी देता है कि इससे wait का समय अनुमान से बाहर हो जाता है; इससे wait के अंदर हर वह find_elements call, जिसे कोई row नहीं मिलती, 10 seconds तक अटक भी सकता है। इसे हटाएँ और wait = WebDriverWait(driver, 10) एक बार बनाएँ। अगर form login page के HTML का हिस्सा है, तो login वाली lines इसके बिना भी सुरक्षित हैं, क्योंकि driver.get() page load होने का इंतज़ार करता है; अगर कोई script load के बाद form बनाती है, तो पहले email field के visible होने का इंतज़ार करें।
दोबारा लिखे गए test में कोई sleep नहीं है और तीन explicit conditions हैं: search box visible, ठीक एक result row, और उस row में "Paid"। तेज़ run 6 fixed seconds की बजाय application जितना समय लेता है, और failure पर message वह condition बताता है जो कभी सच नहीं हुई।
अक्सर पूछे जाने वाले सवाल
क्या implicit wait कभी सही choice होता है?
यह ऐसे simple pages पर काम कर सकता है जहाँ elements देर से आते हैं लेकिन मौजूद होते ही इस्तेमाल लायक होते हैं। इसकी सीमाएँ (सिर्फ़ मौजूदगी, हर lookup पर लागू होना, उन पर भी जिन्हें कुछ नहीं मिलना चाहिए, और explicit waits के साथ अनुमान से बाहर व्यवहार) कई teams को इसे 0 पर छोड़कर सिर्फ़ explicit waits इस्तेमाल करने की ओर ले जाती हैं।
Playwright के auto-waiting के साथ क्या मुझे अब भी waits लिखने पड़ते हैं?
अलग lines के रूप में कम ही। Actions actionability का और retry करने वाले assertions states का इंतज़ार करते हैं, इसलिए ज़्यादातर synchronization अगले step से पहले एक assertion होता है। फिर भी आप तय करते हैं कि किसका इंतज़ार करना है, और response, download या नए page के लिए page.waitForResponse() या page.waitForEvent() इस्तेमाल करते हैं। Committed test में waitForTimeout() की कभी ज़रूरत नहीं होती।
Timeout कितना लंबा होना चाहिए?
इतना कि आपके सबसे धीमे environment पर सबसे धीमा जायज़ run पूरा हो सके, measurements के आधार पर, और उससे ज़्यादा नहीं। Timeout एक सीमा है, देरी नहीं: pass होने वाले runs उसे कभी नहीं चुकाते। Framework के defaults रखें (Playwright Test में हर assertion के लिए 5 seconds और हर test के लिए 30 seconds, Cypress में हर command के लिए 4 seconds) और सिर्फ़ उसी step का timeout बढ़ाएँ जो आप जानते हैं कि धीमा है।
लंबे समय बाद होने वाली चीज़, जैसे 15 मिनट बाद inactivity warning, कैसे टेस्ट करूँ?
15 मिनट sleep न करें। अगर timer page में चलता है, तो clock को control करें: Playwright में page.clock.install() के साथ page.clock.fastForward(), या Cypress में cy.clock() के साथ cy.tick(), page के timers को तुरंत आगे बढ़ा देते हैं। अगर समय server लागू करता है, तो छोटी limit वाली test configuration माँगें।
संदर्भ
- Waiting Strategies — Seleniumब्राउज़र टेस्ट पेज से race क्यों करते हैं, और implicit व explicit waits WebDriver स्क्रिप्ट को ऐप के साथ कैसे सिंक करते हैं।
- Auto-waiting — Playwrightहर एक्शन से पहले Playwright की actionability जाँचें — visible, stable, enabled, events प्राप्त करने योग्य — जो ज़्यादातर हाथ से लिखे wait की ज़रूरत हटाती हैं।
- Best Practices — Cypressसमर्पित एट्रिब्यूट से एलिमेंट चुनने, फ़िक्स्ड wait से बचने और टेस्ट को स्वतंत्र रखने पर Cypress की गाइडेंस।
- WebDriver (W3C)वह W3C प्रोटोकॉल जिसे ब्राउज़र Selenium जैसे ऑटोमेशन टूल से रिमोट कंट्रोल के लिए लागू करते हैं (Level 2 संस्करण Working Draft है)।
Mutqan tools से अभ्यास करें
ऐसा सार्वजनिक endpoint पाएँ जो कोई भी HTTP स्टेटस कोड लौटाए, वैकल्पिक देरी और कस्टम JSON बॉडी के साथ, ताकि क्लाइंट की error हैंडलिंग टेस्ट हो सके।
किसी endpoint का रिस्पॉन्स टाइम कई सैंपल पर मापें: न्यूनतम, अधिकतम, औसत, माध्यिका और p95।
और गहराई से
इस topic में आपकी प्रगति
प्रगति ट्रैक करने के लिए साइन इन करें
Topics पूर्ण चिह्नित करें, जहाँ छोड़ा था वहीं से जारी रखें और specialisation परीक्षाएँ खोलें।