Jika ujian e-mel anda berjalan dengan sempurna pada komputer riba anda tetapi gagal sebaik sahaja ia sampai ke CI, anda tidak bersendirian. Tindakan biasa adalah dengan menyelitkan panggilan sleep dalam kod ujian atau menambah bilangan cubaan semula (retry) sehingga binaan (build) berjaya. Ia mungkin dapat mengurangkan gangguan buat seketika, tetapi ia tidak membaiki pepijat tersebut. Ia hanya menyembunyikannya.

Isu sebenarnya adalah bagaimana ujian anda mengenal pasti e-mel mana yang perlu dibuka.

Masalah Peti Masuk Kongsi

Pada mesin tempatan anda, anda menjalankan satu ujian pada satu masa. Satu e-mel tiba. Anda ambil. Mudah.

CI adalah persekitaran yang sangat berbeza. Satu permintaan tarik (pull request) mungkin mencetuskan empat, lapan, atau enam belas tugasan selari (parallel jobs). Jika semuanya berkongsi satu peti masuk ujian—sama ada ia pelayan Mailosaur, peti masuk Mailtrap, atau akaun sebenar pada domain staging—semuanya menulis ke dalam bekas (bucket) yang sama pada masa yang sama. Tugasan A menghantar tetapan semula kata laluan. Tugasan B menghantar jemputan. Tugasan C mencuba semula aliran aluan yang gagal. Sementara itu, pekerja latar belakang (background workers) dan barisan penghantaran (delivery queues) menambah gangguan (jitter) yang tidak dapat anda kawal.

Apabila setiap tugasan mencari ke dalam peti masuk kongsi tersebut dan meminta mesej terbaru dengan subjek "Reset your password," ia menjadi satu perlumbaan. Ujian yang menang akan mendapat e-mel yang betul. Ujian yang kalah akan mengklik pautan yang dimaksudkan untuk tugasan lain, membuat pengesahan (assertion) terhadap kandungan yang salah, dan gagal dengan ralat yang kelihatan seperti masalah masa (timing problem). Ia bukan masalah masa. Ia adalah masalah identiti.

Mengapa "Mesej Terbaru" Gagal

Corak yang rapuh ini mudah diikuti kerana ia terasa intuitif:

  1. Cetuskan aliran pengguna.
  2. Semak (poll) peti masuk setiap beberapa saat.
  3. Buka mesej terkini yang sepadan dengan baris subjek.
  4. Klik pautan pertama dan jalankan pengesahan (assertions).

Ini akan gagal kerana beberapa sebab selain daripada selari (parallelism) yang mudah. Cubaan semula daripada larian sebelumnya yang gagal boleh tiba lewat, dan tiba-tiba menjadi mesej terbaru tepat ketika ujian semasa anda menyemak peti masuk. Pekerja latar belakang di dalam aplikasi anda mungkin menyusun dua e-mel dalam barisan dan menghantar yang kedua sebelum yang pertama. Baris subjek sahaja adalah pengenal pasti yang lemah; aplikasi staging anda mungkin menghantar e-mel yang serupa daripada laluan yang berbeza. Menyusun mengikut cap masa (timestamp) adalah lebih buruk daripada yang disangka kerana ralat penyimpangan jam (clock skew) antara pelari CI dan penyedia e-mel adalah benar, dan API e-mel sering menyimpan cache atau memproses indeks mereka secara berkelompok (batch).

Cap masa menjadi tidak menentu dalam persekitaran yang sibuk. Anda memerlukan sesuatu yang terus (direct).

Apakah Sebenarnya Run Token Itu

Run token hanyalah satu rentetan (string) unik yang dijana pada permulaan ujian anda dan dimasukkan ke dalam e-mel yang dihantar oleh aplikasi anda. Ia tidak perlu dilihat oleh pengguna, dan tidak perlu kelihatan elegan. Ia hanya perlu menjamin bahawa anda boleh membuktikan mesej khusus ini milik pelaksanaan ujian khusus ini.

Contoh konkrit adalah yang terbaik. Sebelum ujian bermula, jana token seperti:

  • 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

Jika anda mengawal kod backend, masukkan token ke dalam konteks e-mel dan paparkannya di mana-mana bahagian badan e-mel. Jika anda menguji aplikasi kotak hitam (black-box), lihat jika aplikasi tersebut sudah menerima medan rujukan yang boleh anda gunakan. Jika tidak, anda kadangkala boleh menyematkan token dalam bahagian tempatan (local-part) penerima menggunakan pengalamatan tambah (plus addressing)—testuser+a4f9c2d1@example.com—walaupun itu hanya berfungsi jika aplikasi anda mengekalkan dan memaparkannya semula dalam e-mel.

Matlamatnya adalah untuk berhenti memadankan berdasarkan metadata yang sudah dimiliki oleh sistem e-mel. Padankan berdasarkan data yang dimiliki oleh ujian anda.

Corak yang Boleh Dipercayai

Gantikan algoritma "mesej terbaru" dengan carian sempit yang dipacu oleh token:

  1. Jana run token sebelum anda mencetuskan sebarang aliran.
  2. Mulakan tindakan pengguna, pastikan aplikasi akan menyertakan token dalam e-mel keluar.
  3. Semak (poll) penyedia e-mel dengan penapis yang dihadkan kepada token tersebut. Jika API menyokong carian badan e-mel, gunakannya. Jika tidak, ambil mesej calon dan lakukan grep pada badannya di bahagian klien.
  4. Buat pengesahan (assert) bahawa token wujud dalam badan mesej sebelum anda menyentuh sebarang pautan, butang, atau kod pengesahan.
  5. Hanya selepas itu, ekstrak URL atau kod pengesahan dan teruskan.

Urutan ini penting. Jika anda mengekstrak pautan terlebih dahulu dan menyemak token kemudian, anda sudah pun mengklik e-mel yang salah. Pengesahan (assertion) adalah penjaga pintu anda.

Practically, your helper should look for Subject:"Welcome to AppName" AND Body:"a4f9c2d1" rather than Subject:"Welcome to AppName" sort:-received. Many mail testing services expose search APIs that accept body content filters. Use them. If you are working against a simpler provider, keep your polling logic in one place so you can add client-side filtering consistently across every test.

Three Rules to Keep the System Honest

A run token fixes selection, but you still need discipline around how you poll and what you do when things go wrong.

Log the inbox state on failure. When a test fails, output the inbox identifier, the subject line you queried, the exact timestamp window, and how many messages matched your criteria. This turns a vague "email not found" error into a concrete story. If job 7823 picked up a retry message from job 7821 because it arrived three seconds later, your logs should make that obvious. Without this context, you will blame timing and add another sleep.

Keep all email polling in one helper file. Do not scatter setTimeout and cy.task calls across twenty test files. Centralize the logic that waits for messages, retries the API call, and applies backoff. If every test uses the same helper, your filtering rules stay consistent, and when you improve the search logic, every test benefits. It also makes it easier to enforce the token check; if the helper requires a token argument, no one can accidentally fall back to the "latest message" crutch.

Watch your retries. Test retries are common in CI, but each retry creates another email in the inbox. If your test passes on attempt three, you might celebrate and move on. What you miss is that attempts one and two exposed a real bug—a race condition, a duplicate send, or a missing index—that extra messages masked. If you must use retries, check whether the inbox contains unexpected duplicates after a failure. Better yet, consider cleaning the inbox or using a unique address per job if your provider supports dynamic inboxes. Retries should not become a strategy for absorbing unreliable selection logic.

The Real Takeaway

Sorting an inbox by date and grabbing the top result is not testing. It is guessing dressed up in code. A run token costs almost nothing—one string variable, one extra filter parameter, maybe a small template change—and it gives your test deterministic identity. It proves the message in front of you belongs to the run you are executing right now.

Stop adding sleeps and hoping the network behaves. Generate a token, put it in the email, and search for it directly. Your CI runs will be faster, your logs will be readable, and you will finally trust what the email suite is telling you.