Bir runner'da ne değişir
Testin kendisi değişmez. Değişen, onun etrafındaki her şeydir ve bunların her birinin omuz silkmek yerine belirli bir yanıtı vardır:
- Bağlanacak bir secret yoktur
- Genel bir posta kutusunu okumak anahtar, hesap ya da başlık gerektirmez, bu yüzden posta kutusu tarafı
secrets'a hiçbir şey eklemez. Job'daki tek kimlik bilgisi, uygulamanızın zaten posta göndermek için ihtiyaç duyduğu şeydir — SendGrid, Postmark, SES, hangisiyse — ve bu, teste değil uygulamanıza aittir. - Runner'ın internete ulaşması gerekir
grabmail.io'e giden HTTPS ve göndereninizin kullandığı her şeye giden trafik. GitHub'ın barındırdığı runner'lar varsayılan olarak ikisine de izin verir; bir giden trafik filtresinin arkasındaki kendi barındırdığınız bir runner'ın bir kural eklemesi gerekir.- Çalıştırmalar çakışır
- İki pull request, dört shard, kararsız bir job'ın yeniden denemesi — aynı testin birkaç kopyası aynı anda posta okur. Paylaşılan bir adres, birbirinin kodlarını okumalarına izin verirdi; test başına taze bir adres ise bu tür sorunların tamamını imkansız kılar.
- Zaman sayaçla ölçülür
- Posta için altmış saniye bekleyen bir test sorun değildir. Kırk testin her birinde altmış saniye bekleyen bir job ise faturalanan kırk dakikalık runner demektir. Beklemelerin sınırlı olması gerekir ve paket büyüdüğünde shard'lara ayrılması gerekir.
Bu workflow'ların çalıştırdığı test kodu, Playwright rehberinden ya da Python rehberinden gelen koddur: taze bir adres, bir son tarihli bekleme, şablonunuza sabitlenmiş bir extractor. İçinde CI'a özgü hiçbir şey yoktur ve asıl mesele de budur — runner'a özgü kısımların tamamı workflow dosyasında yaşar.
Workflow, Playwright için
Tek bir job. Uygulamanızı gerçek bir giden posta göndericisiyle başlatır, yanıt vermesini bekler, paketi çalıştırır ve raporu yalnızca bir şey başarısız olduğunda saklar.
name: e2e
on:
push:
branches: [main]
pull_request:
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 20 # the whole job, comfortably above every wait inside it
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
# Your application, started the way it runs in staging: a REAL outbound
# mailer. Its credentials are YOUR secret; the mailbox side needs none.
- name: Start the application
run: npm run start:test &
env:
MAILER_API_KEY: ${{ secrets.MAILER_API_KEY }}
APP_URL: http://localhost:3000
- name: Wait for the application
run: npx wait-on --timeout 60000 http://localhost:3000/health
- name: Run the suite
run: npx playwright test
env:
BASE_URL: http://localhost:3000
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7Üç satır ağırlığı taşır. timeout-minutes: 20, geri kalan her şeyin içine yerleştiği dış sınırdır. Uygulama gerçek posta gönderici kimlik bilgileriyle başlatılır, çünkü gerçek postayı okuyan bir test, gerçek postanın gönderilmesine ihtiyaç duyar. Ve rapor yalnızca başarısızlık durumunda, kısa bir saklama süresiyle yüklenir — geçen bir çalıştırmanın saklamaya değer hiçbir şeyi yoktur.
Workflow, pytest için
Python araç zinciriyle aynı şekil: uygulamayı başlatın, health endpoint'ini bekleyin, paketi posta son tarihinin üzerinde test başına bir zaman aşımıyla çalıştırın, başarısızlıkta JUnit raporunu saklayın.
name: e2e
on:
push:
branches: [main]
pull_request:
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- run: pip install -r requirements.txt -r requirements-test.txt
- name: Start the application
run: python -m app.server &
env:
MAILER_API_KEY: ${{ secrets.MAILER_API_KEY }}
APP_URL: http://localhost:8000
- name: Wait for the application
run: |
for i in $(seq 1 60); do
curl -sf http://localhost:8000/health && exit 0
sleep 1
done
echo "application did not come up" >&2; exit 1
- name: Run the suite
run: pytest tests/e2e -q --timeout=120 --junitxml=report.xml
env:
BASE_URL: http://localhost:8000
- uses: actions/upload-artifact@v4
if: failure()
with:
name: pytest-report
path: report.xml--timeout=120, pytest-timeout eklentisinden gelir ve test başına üst sınırdır; yardımcı fonksiyonun içindeki posta son tarihi altmış saniyedir, bu yüzden bir mesajı bekleyip ardından biraz tarayıcı işi yapan bir test yine de sığar. Health-check döngüsü bir action olarak içeri çekilmek yerine doğrudan yazılmıştır, çünkü sekiz satırdır ve içlerinde yanlış gidebilecek hiçbir şey yoktur.
Tek ağ kuralı
Bir posta kutusunu okumak, runner'dan grabmail.io'e giden bir HTTPS isteğidir. Ağ ayak izinin tamamı budur:
- Gelen trafik yok. Runner'a hiçbir şey bağlanmaz. Alınacak bir webhook, çalıştırılacak bir SMTP sunucusu, dışa açılacak bir port yoktur.
- Runner'dan SMTP yok. Posta, uygulamanız tarafından sağlayıcısı üzerinden, o sağlayıcının API'si ya da SMTP endpoint'i aracılığıyla gönderilir — tıpkı production'da yaptığı gibi. Runner'ın kendisi asla SMTP konuşmaz.
- Bir giden trafik izin listesi olan bir runner'da eklenecek tek şey
grabmail.io:443'tür — artı job'ın zaten ihtiyaç duyduğu posta sağlayıcınız ve paket kayıt defteriniz.
- uses: step-security/harden-runner@v2
with:
egress-policy: block
allowed-endpoints: >
grabmail.io:443
api.your-mail-provider.example:443
registry.npmjs.org:443Paket yerelde geçiyor ama CI'da yardımcı fonksiyondan bir bağlantı hatasıyla başarısız oluyorsa, kontrol edilecek ilk şey bu kuraldır ve neredeyse her zaman yanıtın tamamı budur. Kurumsal bir ağdaki kendi barındırılan bir runner'da, HTTPS giden trafiği genellikle ana bilgisayar adına göre filtrelenir; izin verilecek ana bilgisayar adı API'ninkidir ve istek, 443 üzerinde düz bir HTTPS'tir.
Shard'lar, matrix job'lar ve yeniden denemeler
Paket shard'lara ayrılacak kadar yavaşladığında, ayırın. Playwright, --shard ile bir çalıştırmayı job'lara böler ve her test kendi posta kutusunu açtığı için, shard'ların birbirinden hiçbir şeye ihtiyacı yoktur:
e2e:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
# ... the same steps as above, then:
- run: npx playwright test --shard=${{ matrix.shard }}/4Aynı özellik, bir testin aynı anda iki kez çalışmasının diğer iki yolunu da kapsar:
- Aynı anda iki pull request
- İki job, iki rastgele adres kümesi, çakışma yok. API'deki istemci başına üst sınır dakikada 1200 istektir; bu da saniyede bir kez yoklanan yirmi posta kutusu demektir — bir seferde bir posta kutusunu yoklayan bir job buna hiç yaklaşmaz.
- Yeniden denenen bir job
- Bir yeniden deneme, test gövdesini yeniden çalıştırır, bu da yeni bir adresi yeniden uydurur. Eski posta kutusu, eski mesajı hâlâ 5 gün boyunca tutar ve onu hiçbir şey okumaz — yeniden deneme onu asla görmez.
- Playwright'ın kendi
retries'ı - Aynı şey, bir seviye aşağıda: her deneme fixture'ı yeniden çalıştırır. Zamandan tasarruf etmek için adresi bir
beforeAll'a taşımayın; bu, tam olarak bir denemenin önceki denemenin kodunu okumasına izin veren paylaşımdır.
Job'ın içine sığan zaman aşımları
Dört saat vardır ve iç içe geçmeleri gerekir, en içteki en kısa olacak şekilde. Böyle olmadığında, başarısızlık yanlış olan tarafından bildirilir ve yanlış nedene işaret eder.
| Saat | Nerede ayarlanır | Makul bir değer |
|---|---|---|
| Posta son tarihi | Yardımcı fonksiyonun içinde (timeoutMs, timeout=) | 60 sn. İşlemsel posta saniyeler içinde düşer; bir dakika, yavaş bir sağlayıcı kuyruğunu kapsar. |
| Test zaman aşımı | playwright.config.ts / --timeout | 120 sn. Son tarihin, artı etrafındaki tarayıcı işinin üzerinde. |
| Adım zaman aşımı | Adım üzerinde timeout-minutes, varsa | Genellikle ayarlanmaz; job sınırı yeterlidir. |
| Job zaman aşımı | Job üzerinde timeout-minutes | 20 dk. Kurulum, başlatma, paket ve yükleme için yeterli; takılı kalan bir uygulamanın bir saat faturalandırmaması için yeterince düşük. |
Bozuk bir iç içe geçmenin belirtisi bellidir: Playwright'ın varsayılan otuz saniyelik test zaman aşımı içinde altmış saniye bekleyen bir test, her seferinde test hakkında bir mesajla otuz saniyede ölür ve posta hakkında hiçbir şey söylemez. Önce test zaman aşımını ayarlayın, sonra dışa doğru her şeyi.
Bir başarısızlığı okumak
Başarısız bir çalıştırma, hiçbir şeyi yeniden çalıştırmadan üç şeyden hangisinin olduğunu size söylemelidir — posta hiç gelmedi, yanlış posta geldi ya da içindeki kod yanlıştı. Dört alışkanlık bunu doğru kılar:
- Adresi loglayın. Yardımcı fonksiyonun başarısızlık mesajı, beklediği posta kutusunu belirtir. Doğrulama başka bir yerde olsa bile job günlüğünde bulunsun diye testin en başında bir kez daha yazdırın.
- Posta kutusunu elle açın. Mesajlar 5 gün boyunca kalır, bu yüzden bu sitedeki
/inbox/<address>, haftanın geri kalanında runner'ın tam olarak ne gördüğünü — ya da görmediğini — gösterir. Gerçek bir posta kutusunun mocklanmış birine karşı en yararlı tek yönü budur. - Başarısızlıkta raporu saklayın. Playwright'ın trace'i, postayı göndermesi gereken tıklamayı gösterir; JUnit dosyası ise hangi testin ne kadar beklediğini gösterir.
- Testi suçlamadan önce hizmet durumunu kontrol edin. Durum sayfası, dışarıdan her iki dakikada bir yoklanır; çalıştırma sırasında gelen posta çökmüşse, başarısızlık gerçekti ve sizden kaynaklanmıyordu.
Temizlik yapmak, isteğe bağlı olarak
Kimse silmese de her şeyin süresi 5 gün sonra doluyor, bu yüzden temizliği atlayan bir çalıştırmanın hiçbir bedeli yoktur. Çalıştırmanın okuduğunu silmek yine de bir adıma değer, çünkü bir sonraki başarısızlık o zaman gerçekten boş bir posta kutusuna karşı okunur. İdempotenttir — iki kez silmek yine de 200 yanıtı verir — bu yüzden tek başına bir build'i asla başarısız kılamaz:
- name: Delete what the run read
if: always()
run: |
for addr in $(cat .e2e-addresses 2>/dev/null); do
curl -sG https://grabmail.io/api/v1/mailbox --data-urlencode "address=$addr" \
| jq -r '.messages[].id' \
| xargs -r -I{} curl -sX DELETE -G "https://grabmail.io/api/v1/message/{}" --data-urlencode "mailbox=$addr" -o /dev/null
doneOnu if: always() yapın ve asla zorunlu kılmayın: başarısız olan bir temizlik, kırmızı bir build değil, günlükte bir uyarı olmalıdır.
Bitti demeden önce
secrets'ta posta kutusu kimlik bilgisi yok; yalnızca uygulamanızın kendi posta gönderici anahtarı.grabmail.io'e giden HTTPS'e izin verilmiş, gelen hiçbir şey yok.- Test gövdesinde uydurulan, test başına taze bir adres — shard'lar ve yeniden denemeler altında güvenli.
- Dört zaman aşımı iç içe: son tarih < test < adım < job.
- Başarısızlıkta yüklenen, günlükte adresi olan rapor.
- Her zaman çalışan, asla zorunlu olmayan bir adım olarak temizlik.
Runner'ın eklediği her şey bu kadar. Altındaki test disiplini — son tarih, taze adres, sabitlenmiş desen — bir doğrulama akışını uçtan uca test etmek rehberinde, ve tek başına ayıklama kuralları da otomatik testlerde OTP kodları rehberinde.
Sorular
Depoya bir GrabMail secret'ı eklemem gerekir mi?
Hayır. Genel alan adları anahtar, hesap ya da başlık gerektirmez, bu yüzden secrets'a eklenecek hiçbir şey yoktur. Workflow'daki tek kimlik bilgisi, uygulamanızın posta göndermek için kullandığı ve zaten çalışması için ihtiyaç duyacağı bilgidir.
Bu, özel bir depoda ya da kendi barındırdığınız bir runner'da çalışır mı?
Evet. Runner, grabmail.io'e giden HTTPS istekleri yapar ve başka hiçbir şey yapmaz; runner'ı nerede barındırdığınız önemsizdir. Bir giden trafik filtresinin arkasındaki kendi barındırdığınız bir runner'da, o ana bilgisayar adına 443 portunda izin verin.
Eşzamanlı job'lar hız sınırına takılır mı?
Pratikte hayır. Adres başına sınır, yardımcı fonksiyonun uyduğu saniyede bir okumadır ve istemci başına üst sınır dakikada 1200 istektir — tek bir runner'dan, saniyede bir kez yoklanan yirmi posta kutusu. Birden fazla runner, birden fazla istemcidir. Bir 429, Retry-After ile yanıtlanır ve yardımcı fonksiyon başarısız olmak yerine bunu uyuyarak geçirir.
Bunu bir zamanlamayla, production kaydının sentetik bir kontrolü olarak çalıştırabilir miyim?
Evet, ve bu iyi bir kullanım şeklidir: her saat taze bir adresle kayıt olan ve kodu okuyan bir on: schedule workflow'u, sağlayıcı dahil tüm production posta yolunu kanıtlar. Kayıtların kendi tarafınızda kolayca temizlenebilmesi için adres önekini tanınabilir tutun.
Ya uygulamam tek kullanımlık alan adlarını reddederse?
Sahibi olduğunuz bir alan adını hizmete yönlendirin — tek bir MX kaydı, hesap yok — ve o alan adını fixture'da kullanın. Kurulum birkaç dakika sürer ve tek bir alan adında sınırsız test hesabı, bu deseni bir pakette gösterir.
Posta kutusundaki herhangi bir şey özel mi?
Hayır. Genel bir alan adında da kendi alan adınızda da, bir adresi bilen herkes onu okuyabilir. Tek bir kullan-at kod tutan rastgele bir adres için bu önemsizdir; gerçek müşteri verisi gönderen bir staging ortamı için ise bu diskalifiye edici bir durumdur — böyle birini buraya yönlendirmeyin.


