Zum Inhalt springen

Dieselben Tests, die auf deinem Rechner bestehen, laufen in CI mit einer Änderung: Der API-Schlüssel kommt aus den Secrets des CI-Systems. Diese Seite behandelt das, wie du Adressen nutzt, damit parallele Läufe sich nicht in die Quere kommen, einen Bericht zu den E-Mails jedes Laufs im Pull Request und wie du Cloud-Coding-Agents Zugriff gibst.

Erstelle in der App unter Project settings → API keys einen Key (siehe API-Schlüssel) und speichere ihn als Secret namens PROXYMAIL_QA_API_KEY. In GitHub Actions:

.github/workflows/e2e.yml
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
PROXYMAIL_QA_API_KEY: ${{ secrets.PROXYMAIL_QA_API_KEY }}

Andere CI-Systeme funktionieren genauso: eine maskierte CI/CD-Variable in GitLab, eine Context- oder Projektvariable in CircleCI, eine Umgebungsvariable in den Jenkins-Credentials. Halte den Key aus dem Repository und aus Logs heraus.

Ein Key funktioniert innerhalb eines Projekts, gib CI also ein eigenes Projekt (oder zumindest einen eigenen Key). Seine Adressen bleiben dann getrennt von denen, die du von Hand nutzt, und das Widerrufen des CI-Keys betrifft niemanden.

Ein Free-Konto hat insgesamt 500 Adressen, bis zu 50 pro Tag, die eine ausgelastete Pipeline aufbrauchen kann; der Team-Plan hat kein Limit. Du brauchst nicht für jeden Test eine neue Adresse: Mail an name+anything@ kommt bei name@ an, und das Warten auf die getaggte Adresse findet nur die Mail, die an diesen Tag gesendet wurde.

Erstelle also eine Adresse für den Lauf und gib jedem Test einen eigenen Tag:

One address, a tag per test
// Once per run (Playwright's globalSetup, say): POST /v1/addresses
const base = 'k7f2x9ab12cd@mailclub.dev';
// In each test: sign up with a tagged version of it…
const address = base.replace('@', `+${process.env.GITHUB_RUN_ID}-${testInfo.workerIndex}-${Date.now()}@`);
// …then wait on that same tagged address.
// GET /v1/addresses/{address}/code only finds mail sent to that tag.

Mit dem Node.js-Client ist das einmal qa.createAddress() und qa.waitForCode(tagged, { since }) pro Test.

Manche Apps lehnen ein + in Registrierungsformularen ab; erstelle für diese stattdessen eine Adresse pro Test. Ungewöhnliche E-Mail-Adressen listet, welche Zeichen ein Tag enthalten kann.

Übergib since mit dem Zeitpunkt, zu dem der Test das Formular abgeschickt hat, damit ein Warten nie eine E-Mail aus einem früheren Schritt oder Lauf aufgreift:

const submitted = new Date();
await page.getByRole('button', { name: 'Sign up' }).click();
// GET /v1/addresses/{address}/code?since=<submitted, ISO 8601>

Jeder Key kann im Free-Plan 60 Anfragen pro Minute stellen und im Team-Plan 600 (Limits). Ein Warten ist eine Anfrage, egal wie lange es dauert (bis zu 30 Sekunden), daher sind ein paar parallele Worker im Free-Plan kein Problem. Für große Suites nutze den Team-Plan oder einen Key pro Pipeline.

Lösche Adressen am Ende des Laufs mit DELETE /v1/addresses/{address}, zum Beispiel in globalTeardown von Playwright. Alles, was übrig bleibt, wird gelöscht, wenn es abläuft. Das Löschen einer Basisadresse entfernt auch die Mail, die an ihre Tags gesendet wurde.

Die Proxy Mail QA-Action fasst die E-Mail-Tests eines Jobs zu einem Lauf zusammen und postet im Pull Request, was sie gesehen hat: welche E-Mails angekommen sind und wie lange nach dem Absenden durch den Test, die Codes, wohin jeder Link führt, die E-Mail-Prüfungen, die du ausgeführt hast (mit der Zahl der E-Mails, die wahrscheinlich im Spam landen würden, wenn Spamprüfungen in Labs aktiviert sind), Prüfungen zu Abmeldung, Angeboten und Gutscheinen, die fehlgeschlagenen Tests und jede E-Mail, die nie kam. Derselbe Bericht steht in der Job-Zusammenfassung, und alle im Projekt können den Lauf in der App öffnen, wo QA report for this run die Adressen und E-Mails des Laufs als QA-Bericht zum Drucken oder Speichern als PDF liefert.

.github/workflows/e2e.yml
jobs:
e2e:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v7
- uses: aaquib90/proxymail-qa-action@v1
with:
api-key: ${{ secrets.PROXYMAIL_QA_API_KEY }}
- run: npm ci
- run: npx playwright test
env:
PROXYMAIL_QA_API_KEY: ${{ secrets.PROXYMAIL_QA_API_KEY }}

Die Action setzt PROXYMAIL_QA_RUN für die Schritte danach. Der Node.js-Client (0.5.0 oder neuer), der Python-Helper und der Detox- und Appium-Helper lesen es, sodass die Aufrufe, die deine Tests schon machen, in den Lauf eingehen. Die anderen Mobile-Helper nehmen es so wie den Key: eine Umgebungsvariable für XCUITest, ein Instrumentation-Argument für Espresso, ein --dart-define für Flutter und -e für Maestro. Aus anderen Tools sendest du die ID in einem Header: Proxy-Mail-QA-Run: $PROXYMAIL_QA_RUN.

Nach dem Job, egal ob er bestanden hat oder nicht, schließt die Action den Lauf ab und postet den Bericht, wobei sie ihren Kommentar bei jedem Push aktualisiert. Ein Lauf schlägt fehl, wenn eine E-Mail, auf die die Tests gewartet haben, nie kam, eine kam, obwohl keine kommen sollte, eine E-Mail-Prüfung fehlschlug, ein Test fehlschlug, nach der Abmeldung Marketing kam, eine Person ein Angebot zweimal bekam, ein Gutschein akzeptiert wurde, obwohl er es nicht hätte werden sollen, oder eine Notiz das festhielt. Damit der Job dann ebenfalls fehlschlägt, auch wenn die Tests bestanden haben, setze fail-job: true. Läufe mit demselben Namen teilen sich einen Kommentar, gib Matrix-Jobs also eigene Namen: name: e2e (${{ matrix.browser }}).

Der Bericht vergleicht den Lauf außerdem mit den letzten 10 abgeschlossenen Läufen mit demselben Namen und listet auf, was sich geändert hat: E-Mails, die viel länger als üblich zum Ankommen gebraucht haben, ein Test, der jetzt zwei E-Mails statt einer bekommt, Tests, die zum ersten Mal oder ab und zu fehlschlagen, E-Mail-Prüfungen, die neu fehlschlagen, Schritte (Wartevorgänge, Zählungen, Abmeldung, Angebots- und Gutscheinprüfungen), die beim letzten Mal bestanden haben und jetzt nicht liefen oder fehlschlagen, sowie ein neuer Absender oder eine neue Link-Website. Damit das funktioniert, muss der Name von Lauf zu Lauf gleich bleiben.

Pull Requests aus Forks bekommen keine Secrets, deshalb gibt die Action dort eine Warnung aus und startet keinen Lauf.

@v1 folgt jedem 1.x-Release, so erreichen dich Fehlerbehebungen ohne Änderung. Wenn du Änderungen nur übernehmen willst, wann du es willst, pinne stattdessen den Commit eines Releases, mit seiner Version in einem Kommentar: uses: aaquib90/proxymail-qa-action@<commit> # v1.0.1. Die Commits findest du auf der Tags-Seite, und Dependabot hält einen gepinnten Commit aktuell.

Ein Lauf speichert die Adressen, die Codes (mit dem Rabatt eines Gutscheins und der Antwort des Checkouts), Zeiten, Prüfergebnisse, Namen und Fehler der fehlgeschlagenen Tests sowie Website und Pfad jedes Links, wobei alles im Pfad, das wie ein Token aussieht, verborgen wird. Er speichert nie den Text oder Betreff einer E-Mail und nie die Query eines Links, wo Anmelde-Tokens meist stehen. Läufe werden 90 Tage aufbewahrt.

Anderswo (ein anderes CI-System oder die Sitzung eines Agents) startest du einen Lauf mit POST /v1/runs, sendest seine ID im Header Proxy-Mail-QA-Run, beendest ihn mit POST /v1/runs/{id}/finish und holst den Bericht mit GET /v1/runs/{id}?format=markdown. Mit dem Node.js-Client sind das qa.startRun(), qa.finishRun() und qa.runReport(). Siehe die REST API.

Der Bericht kann angeben, zu welchem Test jede E-Mail gehört, und die fehlgeschlagenen Tests auflisten, mit dem, was ihre E-Mails getan haben.

Playwright: Importiere test aus proxymail-qa/playwright und nutze seine Fixture qa, einen Client, der seinen Test bei jedem Aufruf nennt, und füge den Reporter hinzu, der fehlgeschlagene und instabile Tests sowie die Summen erfasst. Die Zeile eines fehlgeschlagenen Tests nennt den Schritt oder die Browser-Aktion, die fehlgeschlagen ist (etwa At “Enter the code › locator.fill(…)”):

playwright.config.ts
export default defineConfig({
reporter: [['list'], ['proxymail-qa/playwright/reporter']],
});
signup.spec.ts
import { test, expect } from 'proxymail-qa/playwright';
test('sign-up sends one code', async ({ page, qa }) => {
const { address } = await qa.createAddress();
// …sign up, wait for the code, check the email…
});

pytest: Importiere die Hooks des Python-Helpers in conftest.py:

conftest.py
from proxymail_qa import pytest_runtest_setup, pytest_runtest_teardown, pytest_runtest_logreport, pytest_sessionfinish

Mobile Tests: Jeder mobile Helper nimmt den Namen des Tests: ProxyMailQA(test:) in Swift, Kotlin und Dart, TEST im env eines Maestro-Skripts und den aktuellen Jest-Test (oder setTest) für Detox und Appium.

Andere Tools senden den Namen des Tests prozentkodiert im Header Proxy-Mail-QA-Test und die Ergebnisse an POST /v1/runs/{id}/tests (REST API). Beides tut außerhalb eines Laufs nichts und lässt die Tests selbst nie fehlschlagen.

Wenn ein Wartevorgang leer zurückkommt, sagt die Antwort, warum, soweit Proxy Mail QA das erkennen kann (hint und refused für E-Mails, die wir abgewiesen haben). Die übliche Ursache in CI ist der E-Mail-Anbieter der Staging-Umgebung: Im Sandbox- oder Testmodus sendet er nur an verifizierte Adressen, also verlässt die E-Mail ihn nie. GET /v1/addresses/{address}/delivery prüft eine Adresse, ohne zu warten. Mehr unter Test-E-Mail kommt nicht an?

Wenn alle Läufe auf einmal fehlschlagen oder die API mit 5xx-Fehlern antwortet, schau auf unsere Statusseite, bevor du deine Tests änderst.

Die Anmeldung am MCP server über den Browser braucht einen Menschen, deshalb nutzen Agents, die in der Cloud laufen, stattdessen einen API-Schlüssel, gesendet als Bearer-Token. Gib ihnen einen Schlüssel für ein Projekt, das eigens dafür angelegt ist.

Schreib die MCP-Konfiguration in einem Schritt vor der Action, mit dem Schlüssel aus einem Secret, und erlaube die Tools, die der Agent braucht:

.github/workflows/claude.yml
- name: Proxy Mail QA for Claude
run: |
cat > /tmp/mcp.json << 'EOF'
{
"mcpServers": {
"proxymail-qa": {
"type": "http",
"url": "https://api.proxymail.qa/mcp",
"headers": { "Authorization": "Bearer ${{ secrets.PROXYMAIL_QA_API_KEY }}" }
}
}
}
EOF
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--mcp-config /tmp/mcp.json
--allowedTools mcp__proxymail-qa__get_project,mcp__proxymail-qa__create_address,mcp__proxymail-qa__wait_for_code,mcp__proxymail-qa__wait_for_link,mcp__proxymail-qa__check_delivery,mcp__proxymail-qa__delete_address

Füge in Settings → Copilot → MCP servers des Repositorys hinzu:

{
"mcpServers": {
"proxymail-qa": {
"type": "http",
"url": "https://api.proxymail.qa/mcp",
"headers": { "Authorization": "Bearer $COPILOT_MCP_PROXYMAIL_QA_API_KEY" },
"tools": ["get_project", "create_address", "wait_for_code", "wait_for_link", "check_delivery", "delete_address"]
}
}
}

Füge dann den Schlüssel als Agents-Secret namens COPILOT_MCP_PROXYMAIL_QA_API_KEY in der Umgebung copilot des Repositorys hinzu: Copilot gibt nur Secrets, deren Namen mit COPILOT_MCP_ beginnen, an MCP-Server weiter. Der Coding-Agent von Copilot kann sich nicht über den Browser anmelden, deshalb braucht er den Schlüssel.

Für die Codex-CLI in einem CI-Job (codex exec) fügst du den Server zu ~/.codex/config.toml hinzu und setzt PROXYMAIL_QA_API_KEY aus dem Secret CI:

~/.codex/config.toml
[mcp_servers.proxymail-qa]
url = "https://api.proxymail.qa/mcp"
bearer_token_env_var = "PROXYMAIL_QA_API_KEY"

Die Dokumentation von Codex sagt nicht, ob seine Cloud-Aufgaben entfernte MCP-Server erreichen können. Wenn deine das nicht können, lass Codex stattdessen die REST API aufrufen, mit dem Schlüssel aus den Einstellungen der Umgebung.

Eine Frage zu dieser Seite?

Frag in jeder Sprache. Die Doku kann dir sofort antworten oder deine Frage an das Team von Proxy Mail QA schicken, das auf Englisch antwortet.

Doku fragen