אם בדיקות האימייל שלכם רצות בצורה מושלמת על הלפטופ אבל קורסות ברגע שהן מגיעות ל-CI, אתם לא לבד. התגובה הרגילה היא לפזר קריאות sleep לאורך קוד הבדיקה או להעלות את מספר הניסיונות החוזרים (retry count) עד שה-build עובר. זה אולי ישתיק את הרעש למשך יום, אבל זה לא מתקן את הבאג. זה רק מסתיר אותו.

הבעיה האמיתית היא האופן שבו הבדיקה שלכם מזהה איזה אימייל עליה לפתוח.

בעיית תיבת הדואר המשותפת

במכונה המקומית שלכם, אתם מריצים בדיקה אחת בכל פעם. אימייל אחד מגיע. אתם תופסים אותו. פשוט.

CI הוא סביבה שונה לחלוטין. pull request בודד עשוי להפעיל ארבע, שמונה או שש עשרה עבודות (jobs) במקביל. אם כולן חולקות תיבת דואר לבדיקות — בין אם זה שרת Mailosaur, תיבת Mailtrap או חשבון אמיתי בדומיין staging — כולן כותבות לאותו "דלי" (bucket) באותו הזמן. Job A שולח איפוס סיסמה. Job B שולח הזמנה. Job C מנסה שוב תהליך קבלת פנים (welcome flow) שנכשל. בינתיים, עובדי רקע (background workers) ותורי משלוח מוסיפים jitter שאינכם יכולים לשלוט בו.

כשכל job שולח יד לתוך תיבת הדואר המשותפת הזו ומבקש את ההודעה החדשה ביותר עם הנושא "Reset your password", זה הופך למרוץ. הבדיקה שמנצחת מקבלת את האימייל הנכון. הבדיקה שמפסידה לוחצת על קישור שנועד ל-job אחר, מבצעת assertion על תוכן שגוי, ונכשלת עם שגיאה שנראית כמו בעיית תזמון (timing problem). זו לא בעיית תזמון. זו בעיית זהות.

למה השיטה של "ההודעה החדשה ביותר" נכשלת

קל ליפול לתבנית השבירה הזו כי היא מרגישה אינטואיטיבית:

  1. הפעלה של תהליך המשתמש (user flow).
  2. בדיקה תקופתית (polling) של תיבת הדואר בכל כמה שניות.
  3. פתיחת ההודעה האחרונה ביותר שמתאימה לשורת הנושא.
  4. לחיצה על הקישור הראשון והרצת assertions.

זה מתפרק מכמה סיבות מעבר למקביליות פשוטה. ניסיון חוזר (retry) מהרצה קודמת שנכשלה עלול להגיע באיחור, ולהפוך לפתע להודעה החדשה ביותר בדיוק כשהבדיקה הנוכחית שלכם מבצעת polling. עובדי רקע בתוך האפליקציה שלכם עשויים להכניס שני אימיילים לתור ולספק את השני לפני הראשון. שורות נושא לבדן הן מזהים חלשים; אפליקציית ה-staging שלכם עשויה לשלוח אימיילים דומים ממסלולים שונים. מיון לפי timestamp גרוע יותר ממה שהוא נראה, כי סטיות בשעון (clock skew) בין ה-CI runner לבין ספק המייל הן אמיתיות, ו-APIs של דואר נוטים לעיתים קרובות לשמור ב-cache או לבצע batching לאינדקסים שלהם.

חותמות זמן (timestamps) הופכות למעורפלות בסביבות עמוסות. אתם צריכים משהו ישיר.

מהו Run Token באמת

run token הוא לא יותר ממחרוזת ייחודית שנוצרת בתחילת הבדיקה שלכם ומוזרקת לאימייל שהאפליקציה שלכם שולחת. הוא לא חייב להיות גלוי למשתמש, והוא לא חייב להיראות אלגנטי. הוא רק צריך להבטיח שתוכלו להוכיח שההודעה הספציפית הזו שייכת להרצת הבדיקה הספציפית הזו.

דוגמאות קונקרטיות עובדות הכי טוב. לפני תחילת הבדיקה, צרו token כגון:

  • A UUID: 550e8400-e29b-41d4-a716-446655440001
  • A build-scoped request ID: req_ci_build_4821_a7f3
  • An invite slug או סיומת metadata: signup-token-8k2m9n
  • מחרוזת hex אקראית שנוצרת על ידי ה-test runner: test-run-a4f9c2d1

אם אתם שולטים בקוד ה-backend, העבירו את ה-token לתוך הקשר (context) האימייל ורנדרו אותו איפשהו בגוף ההודעה. אם אתם בודקים אפליקציית black-box, בדקו אם האפליקציה כבר מקבלת שדה רפרנס שתוכלו לנצל. אם לא, לעיתים ניתן להטמיע את ה-token בחלק המקומי (local-part) של הנמען באמצעות plus addressing — testuser+a4f9c2d1@example.com — אם כי זה עובד רק אם האפליקציה שלכם שומרת אותו ומחזירה אותו באימייל.

הנקודה היא להפסיק לבצע התאמה (matching) על מטא-דאטה שמערכת המייל כבר מחזיקה בו. בצעו התאמה על נתונים שהבדיקה שלכם מחזיקה בהם.

התבנית האמינה

החליפו את אלגוריתם ה-"הודעה החדשה ביותר" בחיפוש ממוקד מבוסס token:

  1. צרו את ה-run token לפני הפעלת כל תהליך.
  2. התחילו את פעולת המשתמש, תוך הבטחה שהאפליקציה תכלול את ה-token באימייל היוצא.
  3. בצעו polling לספק המייל עם פילטרים המוגבלים לאותו token. אם ה-API תומך בחיפוש בגוף ההודעה, השתמשו בו. אם לא, משכו הודעות מועמדות ובצעו grep לגופם בצד הלקוח (client-side).
  4. בצעו assertion לכך שה-token קיים בגוף ההודעה לפני שנוגעים בקישורים, כפתורים או קודי אימות.
  5. רק אז חלצו את ה-URL של האישור או את הקוד והמשיכו.

לסדר הפעולות הזה יש חשיבות. אם תחלצו קישור קודם ותבדקו את ה-token אחר כך, כבר לחצתם על האימייל הלא נכון. ה-assertion הוא השומר שלכם (gatekeeper).

מבחינה מעשית, ה-helper שלך צריך לחפש Subject:"Welcome to AppName" AND Body:"a4f9c2d1" במקום Subject:"Welcome to AppName" sort:-received. שירותי בדיקת אימייל רבים מספקים search APIs שמקבלים מסנני תוכן של גוף ההודעה (body). השתמש בהם. אם אתה עובד מול ספק פשוט יותר, שמור את לוגיקת ה-polling במקום אחד כדי שתוכל להוסיף סינון בצד הלקוח (client-side filtering) בצורה עקבית בכל בדיקה.

שלושה כללים לשמירה על אמינות המערכת

run token מקבע את הבחירה, אך עדיין נדרשת משמעת לגבי האופן שבו אתה מבצע polling ומה אתה עושה כשדברים משתבשים.

תעד את מצב תיבת הדואר (inbox) במקרה של כישלון. כאשר בדיקה נכשלת, פלוט את מזהה תיבת הדואר, שורת הנושא שחיפשת, חלון זמן (timestamp) מדויק, וכמה הודעות תאמו את הקריטריונים שלך. זה הופך שגיאת "email not found" מעורפלת לסיפור קונקרטי. אם job 7823 לקח הודעת retry מ-job 7821 כי היא הגיעה שלוש שניות מאוחר יותר, הלוגים שלך צריכים להפוך זאת לברור. ללא ההקשר הזה, תאשים את התזמון ותוסיף עוד sleep.

שמור את כל ה-email polling בקובץ helper אחד. אל תפזר קריאות ל-setTimeout ו-cy.task בין עשרות קבצי בדיקה. ריכוז הלוגיקה שממתינה להודעות, מבצעת retry לקריאת ה-API ומיישמת backoff. אם כל בדיקה משתמשת באותו helper, חוקי הסינון שלך יישארו עקביים, וכאשר תשפר את לוגיקת החיפוש, כל הבדיקות ירוויחו מכך. זה גם מקל על אכיפת בדיקת ה-token; אם ה-helper דורש ארגומנט של token, אף אחד לא יוכל בטעות להסתמך על "עזר" של "ההודעה האחרונה".

שים לב ל-retries שלך. ניסיונות חוזרים (retries) של בדיקות הם נפוצים ב-CI, אך כל retry יוצר אימייל נוסף בתיבת הדואר. אם הבדיקה שלך עוברת בניסיון השלישי, אולי תחגוג ותמשיך הלאה. מה שתפספס הוא שניסיונות אחד ושניים חשפו באג אמיתי — race condition, שליחה כפולה או אינדקס חסר — שההודעות הנוספות הסתירו. אם עליך להשתמש ב-retries, בדוק אם תיבת הדואר מכילה כפילויות לא צרוכות לאחר כישלון. עדיף מכך, שקול לנקות את תיבת הדואר או להשתמש בכתובת ייחודית לכל job אם הספק שלך תומך בתיבות דואר דינמיות. retries לא צריכים להפוך לאסטרטגיה לספיגת לוגיקת בחירה לא אמינה.

השורה התחתונה

מיון תיבת דואר לפי תאריך ולקיחת התוצאה הראשונה אינו בדיקה. זוהי ניחוש שמתחפש לקוד. run token כמעט לא עולה כלום — משתנה string אחד, פרמטר סינון נוסף אחד, אולי שינוי קטן בתבנית — והוא מעניק לבדיקה שלך זהות דטרמיניסטית. הוא מוכיח שההודעה שלפניך שייכת ל-run שאתה מריץ ממש עכשיו.

תפסיק להוסיף sleeps ולקוות שהרשת תתנהג כראוי. צור token, שים אותו באימייל, וחפש אותו ישירות. הרצות ה-CI שלך יהיו מהירות יותר, הלוגים שלך יהיו קריאים, ובסוף תוכל לסמוך על מה שחבילת בדיקות האימייל אומרת לך.