শুরুটা হয় একটি Slack মেসেজের মাধ্যমে। বিল্ডটি লাল (red) দেখাচ্ছে। আপনি ব্যর্থতাগুলো স্ক্রল করে দেখলেন, ভ্রু কুঁচকালেন এবং আপনার ল্যাপটপে একই টেস্ট আবার চালালেন। এবার সবুজ (green)। আপনি CI জবটি আবার চেষ্টা করলেন। হয়তো এটি সাময়িক কোনো সমস্যা ছিল। কিন্তু ব্যর্থতাটি আবারও ফিরে এল—সার্ভারে এটি জেদি এবং বারবার ঘটছে, কিন্তু আপনার কাছে এটি অদৃশ্য।

একটি ব্রাউজার টেস্ট যা CI-তে ব্যর্থ হয় কিন্তু লোকালি পাস করে, তা কেবল বিরক্তির কারণ নয়। এটি অবিশ্বাসের জন্ম দেয়। টিমগুলো সময়ের (timing) ওপর দোষারোপ করতে শুরু করে। তারা এমন কিছু সাময়িক সমাধান (temporary fixes) প্রয়োগ করে যা কখনোই সরানো হয় না। এখানে একটি setTimeout, সেখানে একটি .wait(5000)। এতে টেস্ট স্যুট ধীর হয়ে যায়। ব্যর্থতাগুলো বারবার ফিরে আসে। সেই ফ্ল্যাকি (flaky) টেস্টগুলো স্থায়ী সমস্যা হয়ে দাঁড়ায় এবং শেষ পর্যন্ত সবাই লাল পাইপলাইনকে (red pipeline) সাধারণ ব্যাকগ্রাউন্ড নয়েজ হিসেবে গণ্য করতে শুরু করে।

এটি বিপজ্জনক। আপনি এমন একটি টেস্ট স্যুট চান না যা বারবার মিথ্যা সতর্কতা দেয় (cries wolf)।

CI ভেঙে পড়েনি; এটি কেবল ভিন্ন

CI এনভায়রনমেন্টগুলো এলোমেলো নয়। এগুলো ডিটারমিনিস্টিক (deterministic)। সমস্যা হলো, এগুলো এমন একটি সিস্টেমের ব্যাপারে ডিটারমিনিস্টিক যা আপনার MacBook বা আপনার Linux ওয়ার্কস্টেশন নয়। আপনার লোকাল সেটআপ সেই পার্থক্যগুলো লুকিয়ে রাখে যা একটি ক্লিন CI রানার তাৎক্ষণিকভাবে প্রকাশ করে দেয়।

ভেবে দেখুন কতগুলো পরিবর্তনশীল অংশ একে অপরের থেকে আলাদা হয়ে যায়। আপনার লোকাল মেশিন হয়তো হট মডিউল রিলোডিং (hot module reloading) সহ একটি ডেভেলপমেন্ট সার্ভার চালাতে পারে, যেখানে CI হয়তো ট্রি শেকিং (tree shaking) এবং মিনিফিকেশন (minification) সহ একটি প্রোডাকশন আর্টিফ্যাক্ট তৈরি করে। শুধুমাত্র এটিই কোড পাথগুলো সরিয়ে ফেলতে পারে বা এক্সিকিউশন অর্ডার পরিবর্তন করতে পারে। ডিপেন্ডেন্সি ট্রি (Dependency trees) পরিবর্তিত হয়। একটি লকফাইল (lockfile) দেখতে একই রকম মনে হলেও প্যাকেজ ম্যানেজার ভার্সন যদি সামান্যতম ভিন্ন হয়, তবে তা ভিন্নভাবে রেজলভ হতে পারে। নেটওয়ার্ক সিকোয়েন্স পরিবর্তিত হয়। আপনার অফিসের Wi-Fi হয়তো একটি স্টেজিং API এক ধাপে (one hop) রেজলভ করতে পারে; কিন্তু CI রানার লোড ব্যালেন্সারের পেছনে থাকা অন্য একটি ক্লাস্টারে হিট করতে পারে, যা এমন ল্যাটেন্সি (latency) তৈরি করে যা আপনি কখনোই দেখেন না।

ব্রাউজারগুলোও বিভিন্ন এনভায়রনমেন্টে ভিন্নভাবে আচরণ করে। আপনার লোকাল Chrome-এ এক্সটেনশন, ক্যাশ করা ক্রেডেনশিয়াল (cached credentials), পারসিস্টেন্ট লোকাল স্টোরেজ এবং হার্ডওয়্যার অ্যাক্সিলারেশনসহ একটি GPU থাকে। CI প্রতিবার রান করার সময় একটি ব্ল্যাঙ্ক প্রোফাইল থেকে শুরু হয়। ব্রাউজার লাইফসাইকেল ভিন্ন হয়। রেন্ডারিং পাথ ভিন্ন হয়। আপনার সিস্টেমে থাকা ফন্টগুলো CI-তে প্রতিস্থাপিত (substituted) হতে পারে। ভিউপোর্ট সাইজিং (Viewport sizing) এবং ডিভাইস পিক্সেল রেশিও ভিন্ন হয়, যা রেসপনসিভ ব্রেকপয়েন্টগুলোকে উল্টে দিতে পারে বা লেজি-লোডিং (lazy-loading) আচরণ পরিবর্তন করতে পারে।

এই ব্যবধানগুলো বাস্তব। এগুলো যান্ত্রিক। এগুলোকে এলোমেলো বলে ভান করলে সেগুলো দূর হয়ে যাবে না।

প্রিভিউ এনভায়রনমেন্ট মিথ্যা বলে

প্রিভিউ এনভায়রনমেন্টগুলো সমস্যাটিকে আরও জটিল করে তোলে। এগুলো মানুষের পর্যালোচনার (human review) জন্য উপযোগী, কিন্তু এগুলো প্রোডাকশন নয়। এগুলো প্রায়শই আসল API হোস্টের পরিবর্তে api-staging-কে নির্দেশ করে। ফিচার ফ্ল্যাগগুলো (Feature flags) প্রতিটি এক্সপেরিমেন্টের জন্য 'true' হিসেবে কাজ করে, যা প্রোডাকশনে কার্যকর হওয়া কন্ডিশনাল লজিকগুলোকে লুকিয়ে রাখে। অথেন্টিকেশন হয়তো একটি ধাপ বাদ দিয়ে দেয় বা একটি মক টোকেন (mock token) ইনজেক্ট করে। কুকিজ হয়তো শিথিল পলিসি ব্যবহার করতে পারে। ডেটাসেটটি হয়তো খুব ছোট হতে পারে—দশ হাজার রো-এর পরিবর্তে মাত্র দশটি রো, যার মানে হলো পেজিনেশন (pagination), সার্চ র‍্যাঙ্কিং বা ভার্চুয়ালাইজেশন লজিক কখনোই পরীক্ষা করা হয় না।

যদি আপনার টেস্ট একটি প্রিভিউ URL-এ পাস করে কিন্তু প্রোডাকশনে ব্যর্থ হয়, অথবা এর উল্টোটা ঘটে, তবে টেস্টটি বাগ নয়। সমস্যাটি হলো এনভায়রনমেন্ট।

অনুমানের আগে লগ (Log) করুন

যখন প্রথমবার কোনো ব্যর্থতা দেখা দেয়, তখন টেস্টটি সামান্য পরিবর্তন করে সফল হওয়ার আশায় থাকার প্রবণতা রোধ করুন। অনুমান করা বন্ধ করুন। আপনাকে কনটেক্সটটি ফ্রিজ (freeze) করতে হবে যাতে আপনি একটি সফল রান এবং একটি ব্যর্থ রানের মধ্যে তুলনা করতে পারেন।

স্পষ্ট সন্দেহভাজন বিষয়গুলো লগ করুন। ব্যর্থতার মুহূর্তে পেজ URL, বিল্ড ID এবং কমিট SHA রেকর্ড করুন। সক্রিয় ফিচার ফ্ল্যাগগুলো নোট করুন। API হোস্ট, সঠিক ব্রাউজার ভার্সন এবং ভিউপোর্ট সাইজ ক্যাপচার করুন। এই বিবরণগুলো একটি রহস্যময় ব্যর্থতাকে একটি পুনরুৎপাদনযোগ্য (reproducible) অবস্থায় পরিণত করে।

শুধুমাত্র স্ক্রিনশটের ওপর নির্ভর করবেন না। দুটি পেজ দেখতে একদম হুবহু এক হতে পারে কিন্তু তারা সম্পূর্ণ ভিন্ন জাভাস্ক্রিপ্ট চালাতে পারে। একটি স্ক্রিনশট আপনাকে এটি বলবে না যে CI বান্ডলে একটি অতিরিক্ত পলিফিল (polyfill) অন্তর্ভুক্ত ছিল অথবা লোকাল বান্ডলটি একটি চাঙ্ক (chunk) বাদ দিয়ে গেছে কারণ সেটি আপনার ব্রাউজার ক্যাশে ছিল।

আরও মনে রাখবেন যে DevTools ওপেন করলে টাইমিং পরিবর্তিত হয়। DevTools গারবেজ কালেকশন (garbage collection) বিলম্বিত করতে পারে, নেটওয়ার্ক প্রায়োরিটি পরিবর্তন করতে পারে এবং কিছু রেন্ডারিং অপ্টিমাইজেশন নিষ্ক্রিয় করতে পারে। আপনি যখন DOM পরিদর্শন করছেন তখন একটি টেস্ট পাস করতে পারে, কিন্তু প্যানেলটি বন্ধ করে হেডলেস (headless) মোডে রান করার সাথে সাথেই সেটি ব্যর্থ হতে পারে। ডিবাগার একটি দরকারী টুল, কিন্তু এটি কোনো নিরপেক্ষ পর্যবেক্ষক নয়।

অপরাধের দৃশ্যটি পুনরায় তৈরি করুন

আপনি যদি নির্ভুলভাবে ব্যর্থতাটি পুনরুৎপাদন করতে চান, তবে আপনি কেবল আপনার লোকাল ডেভেলপমেন্ট সার্ভার চালিয়ে ভালো কিছুর আশা করতে পারেন না। আপনাকে CI-এর হুবহু পরিস্থিতি বা কন্ডিশনগুলো রেপ্লিকেট (replicate) করতে হবে।

CI ঠিক যা তৈরি করেছে, হুবহু সেই আর্টিফ্যাক্টটি তৈরি করুন। প্রয়োজনে সেটি ডাউনলোড করে নিন। Vite বা Webpack ডেভ মিডলওয়্যার ব্যবহার না করে একটি সাধারণ স্ট্যাটিক ফাইল সার্ভারের মাধ্যমে সেই আর্টিফ্যাক্টটি লোকালি সার্ভ করুন। CI যে এনভায়রনমেন্ট ভেরিয়েবলগুলো ইনজেক্ট করেছিল, ঠিক সেগুলোই ব্যবহার করুন। ব্রাউজারের ভার্সনটিও হুবহু মেলান। এটি একই মোডে (headed বা headless) চালান, কারণ ফোকাস ইভেন্ট, মিডিয়া কোয়েরি এবং অটো-প্লে পলিসির ক্ষেত্রে এই দুটির মধ্যে সূক্ষ্ম পার্থক্য থাকতে পারে। যদি আপনার CI একটি ডকার কন্টেইনার ব্যবহার করে, তবে লোকালিও একই ইমেজ চালান। আপনার ব্যক্তিগত ব্রাউজার প্রোফাইলটি সম্পূর্ণভাবে সরিয়ে ফেলুন।

যখন লোকাল রিপ্রোডাকশনটি অবশেষে ব্যর্থ হবে, তখনই আপনি একটি প্রকৃত ডিবাগিং সেশন শুরু করতে পারবেন। ততক্ষণ পর্যন্ত, আপনি কেবল ছায়ার পেছনে ছুটছেন।

ঘুমানো বন্ধ করুন, অপেক্ষা করা শুরু করুন

একটি ফ্ল্যাকি (flaky) ব্রাউজার টেস্টের ক্ষেত্রে সবচেয়ে সাধারণ প্রতিক্রিয়া হলো ডিলে (delay) বা বিলম্ব যোগ করা। পাঁচ সেকেন্ড অপেক্ষা করুন। দশ সেকেন্ড অপেক্ষা করুন। এটি কোনো সমাধান নয়। এটি একটি আত্মসমর্পণ। যথেচ্ছ বিলম্ব আপনার টেস্ট স্যুটকে ধীর করে দেয়, একটি মিথ্যা আত্মবিশ্বাস তৈরি করে এবং নেটওয়ার্কের সমস্যা হলে লোডের চাপে আবারও ব্যর্থ হয়।

এর পরিবর্তে, অবস্থার (state) প্রমাণের জন্য অপেক্ষা করুন। যদি একটি ফর্ম সাবমিট করার পর কোনো নোটিফিকেশন আসার কথা থাকে, তবে সময়ের জন্য অপেক্ষা করবেন না। DOM-এ একটি নির্দিষ্ট নোটিফিকেশন ID আছে কি না তার জন্য অপেক্ষা করুন। যদি কোনো কাউন্টার বৃদ্ধি পাওয়ার কথা থাকে, তবে টেক্সট বা মানের পরিবর্তনের জন্য অপেক্ষা করুন। যদি একটি লোডিং স্টেট ইন্টারঅ্যাকশন ব্লক করে রাখে, তবে লোডিং মার্কারটি অদৃশ্য হওয়ার জন্য অপেক্ষা করুন। আপনি যদি WebSocket বা server-sent events নিয়ে কাজ করেন, তবে নেটওয়ার্ক স্ট্রিম থেকে একটি নির্দিষ্ট ইভেন্ট তৈরি হওয়া পর্যন্ত অপেক্ষা করুন।

এক্সপ্লিসিট ওয়েট (Explicit waits) আপনার টেস্টকে একটি অনুমানের খেলা থেকে একটি চুক্তিতে পরিণত করে। টেস্টটি বলে: "অ্যাপ্লিকেশনটি প্রস্তুত বলে নিশ্চিত করার পরেই আমি এগোব।" এটি "পর্যাপ্ত সেকেন্ড পার হওয়ার পর আমি এগোব" বলার চেয়ে অনেক বেশি শক্তিশালী।

হাইড্রেশন এবং অদৃশ্য হয়ে যাওয়া বাটন

আধুনিক React অ্যাপ্লিকেশনগুলোতে, হাইড্রেশন (hydration) এমন এক ধরনের ব্যর্থতা ঘটায় যা লোকাল ডেভ সার্ভারগুলো প্রায়ই ঢেকে রাখে। সার্ভার HTML পাঠায়। ব্রাউজারে React চালু হয় এবং ইভেন্ট লিসেনারগুলো যুক্ত করে। সেই সময়ের মধ্যে, আপনার টেস্ট একটি বাটনে ক্লিক করতে পারে। এরপর হাইড্রেশনের সময় React সেই DOM নোডটিকে প্রতিস্থাপন বা পুনর্গঠন করে। আপনার টেস্ট ফ্রেমওয়ার্ক যে এলিমেন্ট হ্যান্ডেলটি ধরে রেখেছিল, সেটি এখন একটি বিচ্ছিন্ন (detached) নোডকে নির্দেশ করে, এবং আপনি একটি রিমুভ করা এলিমেন্টের সাথে ইন্টারঅ্যাক্ট করার ত্রুটি বা এরর পান।

এর সমাধান কম্পোনেন্ট ট্রিতে আরও গভীরে যাওয়ার জন্য কোনো জটিল সিলেক্টর লেখা নয়। এর সমাধান হলো প্রস্তুতির সংকেত (readiness signals) খোঁজা। একটি রুট এলিমেন্ট যতক্ষণ না একটি হাইড্রেশন অ্যাট্রিবিউট বা পরিচিত কোনো ডেটা প্রপার্টি অর্জন করছে, ততক্ষণ অপেক্ষা করুন। একটি স্কেলিটন লোডার অদৃশ্য হওয়া পর্যন্ত অপেক্ষা করুন। একটি ক্লায়েন্ট-সাইড ইভেন্ট হ্যান্ডলার সক্রিয় হওয়া পর্যন্ত অপেক্ষা করুন। ক্লিক করার আগে অ্যাপ্লিকেশনটিকে স্থিতিশীল হওয়ার ঘোষণা দিতে দিন।

লুকানো অপরাধী: ডিপেন্ডেন্সি এবং থার্ড-পার্টি স্ক্রিপ্ট

কখনও কখনও অ্যাপ্লিকেশন কোড পরিবর্তন না হলেও এনভায়রনমেন্ট পরিবর্তিত হয়ে যায়। আপনার node_modules-এর তিন স্তর গভীরে থাকা একটি ছোট ইউটিলিটি লাইব্রেরির ট্রানজিটিভ আপডেট (transitive update) ব্রাউজারের আচরণ বদলে দিতে পারে। এটি প্রমিজ (promises) কীভাবে রেজলভ হয়, স্টাইল কীভাবে ইনজেক্ট হয়, বা মক (mocks) কীভাবে রিকোয়েস্ট ইন্টারসেপ্ট করে তা পরিবর্তন করতে পারে। যখন রুটিন ডিপেন্ডেন্সি আপডেটের পরে টেস্টগুলো ব্যর্থ হতে শুরু করে, তখন আপনার প্যাকেজ ম্যানেজার ভার্সন এবং লকফাইল চেকসাম (lockfile checksum) রেকর্ড করুন। আপনাকে জানতে হবে যে আপনি গত সপ্তাহের মতো একই ট্রি (tree) দেখছেন কি না।

থার্ড-পার্টি স্ক্রিপ্ট হলো আরেকটি ঘন ঘন ঘটে যাওয়া সমস্যা সৃষ্টিকারী। অ্যানালিটিক্স ট্র্যাকার, পেমেন্ট SDK এবং চ্যাট উইজেটগুলো অ্যাসিনক্রোনাসলি লোড হয়। তারা এমন মুহূর্তে আইফ্রেম (iframe) ইনজেক্ট করে, লেআউট পরিবর্তন করে বা ফোকাস চুরি করে যা আপনার টেস্ট প্রত্যাশা করে না। CI-তে এই স্ক্রিপ্টগুলো আরও ধীরগতিতে লোড হতে পারে, অথবা নেটওয়ার্ক সীমাবদ্ধতার কারণে পুরোপুরি লোড নাও হতে পারে, যার ফলে আপনার অ্যাপ্লিকেশনটি ভিন্ন কোনো এরর-হ্যান্ডলিং পাথ অনুসরণ করে। কোন কোন থার্ড-পার্টি রিসোর্স লোড হয়েছে এবং তাদের HTTP স্ট্যাটাস কী ছিল তা লগ করুন। যদি একটি পেমেন্ট আইফ্রেম CI-তে মাউন্ট হতে তিন সেকেন্ড সময় নেয় কিন্তু আপনার দ্রুত লোকাল কানেকশনে তাৎক্ষণিকভাবে লোড হয়, তবে আপনার "element not clickable" এররটির একটি স্পষ্ট কারণ হঠাৎ সামনে চলে আসবে।

এবং "element not clickable" কখনোই কোনো রোগ নির্ণয় নয়। এটি একটি লক্ষণ মাত্র। মূল কারণটি সমাধান করুন।

একটি এভিডেন্স কিট (Evidence Kit) তৈরি করুন

প্রতিটি CI ব্যর্থতা যেন কার্যকর (actionable) হয়। শুধুমাত্র একটি স্ট্যাক ট্রেস (stack trace) যথেষ্ট নয়। আপনার একটি এভিডেন্স কিট প্রয়োজন যা অন্য কোনো ইঞ্জিনিয়ার বা আগামী মাসে আপনি নিজে কী ঘটেছিল তা পুনর্গঠন করতে পারবেন।

ব্যর্থ রান থেকে স্ক্রিনশট এবং ভিডিও রেকর্ডিং রাখুন। সম্পূর্ণ ব্রাউজার কনসোল আউটপুট ক্যাপচার করুন, শুধু এরর নয় বরং ওয়ার্নিংগুলোও। নেটওয়ার্ক ব্যর্থতা লগ করুন, যার মধ্যে 404, CORS রিজেকশন এবং ড্রপ করা কানেকশন অন্তর্ভুক্ত। সক্রিয় থাকা বিল্ড আইডি এবং ফিচার ফ্ল্যাগগুলো সংরক্ষণ করুন। অ্যাসারশন (assertion) ব্যর্থ হওয়ার ঠিক সেই মুহূর্তে একটি DOM স্ন্যাপশট নিন। একটি স্ন্যাপশট আপনাকে ঘটনার পরে HTML কাঠামোটি পরীক্ষা করতে সাহায্য করে, বরং