जर तुमचे ईमेल टेस्ट्स तुमच्या लॅपटॉपवर अगदी व्यवस्थित चालत असतील आणि CI मध्ये येताच फेल होत असतील, तर तुम्ही एकटे नाही आहात. सामान्यतः यावर उपाय म्हणून टेस्ट कोडमध्ये sleep कॉल्स टाकणे किंवा बिल्ड पास होईपर्यंत 'retry count' वाढवणे असे केले जाते. यामुळे काही दिवसांसाठी समस्या दबली जाऊ शकते, पण यामुळे बग (bug) सुटत नाही. तो फक्त लपवला जातो.
खरी समस्या ही आहे की तुमची टेस्ट कोणता ईमेल उघडायचा हे कसे ओळखते.
सामायिक इनबॉक्सची समस्या (The Shared Inbox Problem)
तुमच्या लोकल मशीनवर, तुम्ही एका वेळी एकच टेस्ट चालवता. एक ईमेल येतो. तुम्ही तो घेता. सोपे आहे.
CI हे पूर्णपणे वेगळे वातावरण आहे. एका सिंगल 'pull request' मुळे चार, आठ किंवा सोळा समांतर (parallel) जॉब्स सुरू होऊ शकतात. जर ते सर्व एकाच टेस्ट इनबॉक्सचा वापर करत असतील—मग तो Mailosaur सर्व्हर असो, Mailtrap इनबॉक्स असो किंवा स्टेजिंग डोमेनवरील एखादे खरे अकाउंट असो—ते सर्व एकाच वेळी एकाच ठिकाणी डेटा लिहित असतात. जॉब A पासवर्ड रिसेट पाठवतो. जॉब B आमंत्रण (invite) पाठवतो. जॉब C अयशस्वी झालेला वेलकम फ्लो पुन्हा प्रयत्न करतो. दरम्यान, बॅकग्राउंड वर्कर्स आणि डिलिव्हरी क्यूज (queues) अशा अनिश्चितता (jitter) निर्माण करतात ज्यावर तुमचे नियंत्रण नसते.
जेव्हा प्रत्येक जॉब त्या सामायिक इनबॉक्समध्ये जाऊन "Reset your password" या विषयाचा (subject) सर्वात नवीन मेसेज शोधतो, तेव्हा तिथे एक स्पर्धा (race) लागते. जो टेस्ट जिंकतो त्याला योग्य ईमेल मिळतो. जो टेस्ट हरतो तो दुसऱ्या जॉबसाठी असलेला लिंकवर क्लिक करतो, चुकीच्या कंटेंटवर अॅसर्ट (assert) करतो आणि टाइमिंगची समस्या असल्यासारख्या एररसह फेल होतो. ही टाइमिंगची समस्या नाही. ही ओळखीची (identity) समस्या आहे.
"Newest Message" का अपयशी ठरते
हा नाजूक पॅटर्न वापरणे सोपे वाटते कारण तो सहज वाटतो:
- युजर फ्लो ट्रिगर करा.
- दर काही सेकंदांनी इनबॉक्स तपासा (poll).
- विषयाशी (subject line) जुळणारा सर्वात अलीकडील मेसेज उघडा.
- पहिली लिंक क्लिक करा आणि अॅसर्शन (assertions) रन करा.
केवळ समांतरतेपलीकडे (parallelism) इतर अनेक कारणांमुळे ही पद्धत कोलमडते. मागील अयशस्वी रनचा 'retry' उशिरा येऊ शकतो आणि तुमची सध्याची टेस्ट इनबॉक्स तपासत असताना तो अचानक सर्वात नवीन मेसेज बनू शकतो. तुमच्या ॲप्लिकेशनमधील बॅकग्राउंड वर्कर्स दोन ईमेल रांगेत (queue) लावू शकतात आणि पहिला ईमेल येण्यापूर्वी दुसरा ईमेल पाठवू शकतात. फक्त 'subject lines' हे कमकुवत आयडेंटिफायर्स आहेत; तुमचे स्टेजिंग ॲप्लिकेशन वेगवेगळ्या मार्गांनी सारखेच ईमेल पाठवू शकते. टाइमस्टॅम्पनुसार सॉर्ट करणे हे दिसते त्यापेक्षाही जास्त धोकादायक आहे, कारण CI रनर आणि मेल प्रोव्हायडर यांच्यातील वेळेतील तफावत (clock skew) वास्तविक असते आणि मेल APIs अनेकदा त्यांचे इंडेक्स कॅश (cache) किंवा बॅच (batch) करतात.
व्यस्त वातावरणात टाइमस्टॅम्प अस्पष्ट होतात. तुम्हाला काहीतरी थेट हवे आहे.
रन टोकन (Run Token) म्हणजे नक्की काय?
रन टोकन म्हणजे तुमच्या टेस्टच्या सुरुवातीला तयार केलेली एक युनिक स्ट्रिंग (unique string) आहे, जी तुमचे ॲप्लिकेशन पाठवणाऱ्या ईमेलमध्ये समाविष्ट केली जाते. ते युजरला दिसण्याची किंवा दिसायला सुंदर असण्याची गरज नाही. फक्त हे सुनिश्चित करणे आवश्यक आहे की हा विशिष्ट मेसेज याच विशिष्ट टेस्ट एक्झिक्यूशनचा आहे, हे तुम्ही सिद्ध करू शकाल.
ठोस उदाहरणे सर्वोत्तम ठरतात. टेस्ट सुरू होण्यापूर्वी, खालीलप्रमाणे टोकन तयार करा:
- A UUID:
550e8400-e29b-41d4-a716-446655440001 - A build-scoped request ID:
req_ci_build_4821_a7f3 - An invite slug or metadata suffix:
signup-token-8k2m9n - A random hex string generated by the test runner:
test-run-a4f9c2d1
जर तुमच्याकडे बॅकएंड कोडचे नियंत्रण असेल, तर टोकन ईमेल कॉन्टेक्स्टमध्ये पास करा आणि बॉडीमध्ये कुठेही रेंडर करा. जर तुम्ही 'ब्लॅक-बॉक्स' ॲप्लिकेशनची टेस्ट करत असाल, तर ॲप्लिकेशनमध्ये आधीच एखादे 'रेफरन्स फील्ड' उपलब्ध आहे का ते तपासा ज्याचा तुम्ही वापर करू शकता. नसल्यास, तुम्ही कधीकधी 'प्लस ॲड्रेसिंग' (plus addressing) वापरून प्राप्तकर्त्याच्या लोकल-पार्टमध्ये टोकन एम्बेड करू शकता—testuser+a4f9c2d1@example.com—परंतु हे तेव्हाच काम करते जेव्हा तुमचे ॲप्लिकेशन ते जसेच्या तसे ईमेलमध्ये परत पाठवते.
मुद्दा असा आहे की मेल सिस्टमच्या मालकीच्या मेटाडेटावर मॅच करणे थांबवा. तुमच्या टेस्टच्या मालकीच्या डेटावर मॅच करा.
विश्वसनीय पॅटर्न (The Reliable Pattern)
"newest message" अल्गोरिदमच्या जागी टोकन-आधारित शोध (token-driven search) वापरा:
- कोणताही फ्लो ट्रिगर करण्यापूर्वी रन टोकन तयार करा.
- युजर ॲक्शन सुरू करा, आणि ॲप्लिकेशनने आउटबाउंड ईमेलमध्ये टोकन समाविष्ट केले जाईल याची खात्री करा.
- त्या टोकनवर मर्यादित असलेल्या फिल्टर्ससह मेल प्रोव्हायडरला पोल (poll) करा. जर API बॉडी सर्चला सपोर्ट करत असेल, तर त्याचा वापर करा. नसेल तर, संभाव्य मेसेजेस मिळवा आणि क्लायंट-साइडवर त्यांच्या बॉडीमध्ये टोकन शोधा (grep).
- कोणत्याही लिंक, बटण किंवा व्हेरिफिकेशन कोडला स्पर्श करण्यापूर्वी मेसेज बॉडीमध्ये टोकन अस्तित्वात आहे याची खात्री (assert) करा.
- त्यानंतरच कन्फर्मेशन URL किंवा कोड काढा आणि प्रक्रिया पुढे सुरू ठेवा.
ही क्रमवारी महत्त्वाची आहे. जर तुम्ही आधी लिंक काढली आणि नंतर टोकन तपासले, तर तुम्ही आधीच चुकीच्या ईमेलवर क्लिक केले आहे. 'अॅसर्शन' (Assertion) हा तुमचा गेटकीपर आहे.
व्यावहारिकदृष्ट्या, तुमच्या हेल्परने Subject:"Welcome to AppName" sort:-received ऐवजी Subject:"Welcome to AppName" AND Body:"a4f9c2d1" शोधले पाहिजे. अनेक मेल टेस्टिंग सेवा अशा सर्च APIs उपलब्ध करून देतात ज्या बॉडी कंटेंट फिल्टर्स स्वीकारतात. त्यांचा वापर करा. जर तुम्ही एखाद्या साध्या प्रोव्हायडरसोबत काम करत असाल, तर तुमचे पोलिंग लॉजिक एकाच ठिकाणी ठेवा जेणेकरून तुम्ही प्रत्येक टेस्टमध्ये सातत्याने क्लायंट-साइड फिल्टरिंग जोडू शकाल.
सिस्टमची अचूकता टिकवून ठेवण्यासाठी तीन नियम
रन टोकन (run token) निवडीला निश्चित करते, परंतु तुम्ही कसे पोलिंग करता आणि गोष्टी चुकल्यास काय करता, याबद्दल तुम्हाला शिस्त पाळणे आवश्यक आहे.
अपयशाच्या वेळी इनबॉक्सची स्थिती लॉग करा. जेव्हा एखादी टेस्ट फेल होते, तेव्हा इनबॉक्स आयडेंटिफायर, तुम्ही शोधलेली सब्जेक्ट लाईन, नेमकी टाइमस्टॅम्प विंडो आणि तुमच्या निकषांशी किती मेसेजेस जुळले हे आउटपुटमध्ये दाखवा. यामुळे "email not found" सारख्या अस्पष्ट त्रुटीचे रूपांतर एका ठोस माहितीमध्ये होते. जर जॉब 7823 ने जॉब 7821 चा रीट्राय मेसेज उचलला असेल कारण तो तीन सेकंद उशिरा आला होता, तर तुमच्या लॉग्समध्ये हे स्पष्टपणे दिसले पाहिजे. या संदर्भाशिवाय, तुम्ही वेळेच्या (timing) त्रुटीला दोष द्याल आणि आणखी एक sleep कमांड जोडून समस्या सोडवण्याचा प्रयत्न कराल.
सर्व ईमेल पोलिंग एकाच हेल्पर फाईलमध्ये ठेवा. setTimeout आणि cy.task कॉल्स वीस वेगवेगळ्या टेस्ट फाईल्समध्ये विखुरू नका. मेसेजेसची वाट पाहणारे, API कॉल पुन्हा करण्याचा प्रयत्न करणारे आणि बॅकऑफ (backoff) लागू करणारे लॉजिक एकत्रित करा. जर प्रत्येक टेस्ट एकच हेल्पर वापरत असेल, तर तुमचे फिल्टरिंग नियम सुसंगत राहतील आणि जेव्हा तुम्ही सर्च लॉजिकमध्ये सुधारणा कराल, तेव्हा त्याचा फायदा सर्व टेस्ट्सना होईल. यामुळे टोकन चेक लागू करणे देखील सोपे होते; जर हेल्परला टोकन आर्ग्युमेंटची आवश्यकता असेल, तर कोणीही चुकून "latest message" या सोयीचा आधार घेऊ शकणार नाही.
तुमच्या रीट्राइ (retries) वर लक्ष ठेवा. CI मध्ये टेस्ट रीट्राइ सामान्य आहेत, परंतु प्रत्येक रीट्रायमुळे इनबॉक्समध्ये आणखी एक ईमेल तयार होतो. जर तुमची टेस्ट तिसऱ्या प्रयत्नात पास झाली, तर तुम्ही आनंद साजरा करून पुढे जाऊ शकता. पण तुम्ही काय गमावता ते म्हणजे, पहिल्या आणि दुसऱ्या प्रयत्नात एक खरा बग समोर आला होता—उदा. रेस कंडिशन (race condition), डुप्लिकेट सेंड किंवा मिसिंग इंडेक्स—जो अतिरिक्त मेसेजेसमुळे झाकला गेला. जर तुम्हाला रीट्राइ वापरावेच लागत असतील, तर अपयशानंतर इनबॉक्समध्ये अनपेक्षित डुप्लिकेट्स आहेत का ते तपासा. त्यापेक्षा उत्तम म्हणजे, जर तुमचा प्रोव्हायडर डायनॅमिक इनबॉक्सला सपोर्ट करत असेल, तर इनबॉक्स साफ करणे किंवा प्रत्येक जॉबसाठी एक युनिक ॲड्रेस वापरण्याचा विचार करा. रीट्राइ हे अविश्वसनीय सिलेक्शन लॉजिक लपवण्याचे साधन बनू नये.
मुख्य निष्कर्ष
इनबॉक्स तारखेनुसार सॉर्ट करणे आणि पहिला रिझल्ट घेणे म्हणजे टेस्टिंग नाही. ते कोडमध्ये सजवलेले केवळ एक अनुमान आहे. रन टोकनसाठी जवळजवळ काहीही खर्च येत नाही—एक स्ट्रिंग व्हेरिएबल, एक अतिरिक्त फिल्टर पॅरामीटर, कदाचित टेम्प्लेटमध्ये छोटा बदल—आणि ते तुमच्या टेस्टला एक निश्चित ओळख (deterministic identity) देते. हे सिद्ध करते की तुमच्या समोर असलेला मेसेज तुम्ही सध्या रन करत असलेल्या रनचाच आहे.
'sleeps' जोडणे आणि नेटवर्क नीट चालेल अशी आशा करणे थांबवा. एक टोकन तयार करा, ते ईमेलमध्ये टाका आणि थेट त्यासाठी सर्च करा. तुमचे CI रन अधिक वेगवान होतील, तुमचे लॉग वाचनीय होतील आणि शेवटी ईमेल सूट तुम्हाला जे सांगत आहे त्यावर तुमचा विश्वास बसेल.
