コンテンツにスキップ

proxymail-qa は、JavaScript と TypeScript 向けに REST API をラップします。Playwright、Cypress のタスク、Vitest、Jest、Detox、または普通のスクリプトで使えます。依存関係はなく、型定義を同梱しており、Node 18 以降(または fetch がある環境)で動作します。ソースは github.com/aaquib90/proxymail-qa-js にあり、各リリースは npm の来歴付きで GitHub Actions から公開されます。

  1. パッケージをインストールする

    Section titled “パッケージをインストールする”
    npm install --save-dev proxymail-qa
  2. API キー を作成して PROXYMAIL_QA_API_KEY として設定するか、引数で渡してください。

  3. アドレスを作成し、アプリでそのアドレスを使ってサインアップしてから、コードやリンクを待ちます。

    待機では、コードやリンクそのものが、送信したサービスと有効期限とともに返されます。メールの本文は返されません。1回の待機は最大30秒で、それより長い timeout では繰り返し待機します。

    import { ProxyMailQA } from 'proxymail-qa';
    const qa = new ProxyMailQA(); // reads PROXYMAIL_QA_API_KEY
    const { address, persona, password } = await qa.createAddress({ persona: true, password: true });
    const submitted = new Date();
    // …sign up in your app with address, persona and password…
    const { value: code } = await qa.waitForCode(address, { since: submitted });
    // or: const { value: link } = await qa.waitForLink(address, { type: 'verify', since: submitted });
    await qa.deleteAddress(address);

完全な例:Playwright と Cypress。

メソッド 機能
new ProxyMailQA({ apiKey?, baseUrl?, fetch?, run? }) クライアントです。apiKey の既定値は PROXYMAIL_QA_API_KEY、run の既定値は PROXYMAIL_QA_RUN です
createAddress(options?) 新しいテスト用アドレスです。オプション:label、name(@ の前のカスタム名。test のような一般的な名前だけのものは拒否されます)、persona、password(1回だけ返され、password: true を渡すと string として型付けされます)、lifetimeDays、domain、age または dateOfBirth、country、および使用場所:site と environment(0.9.0 で追加)
waitForCode(address, options?) 最新の認証コードまたはログインコードです。オプション:since、service、timeout(秒、既定値30)、unused(使用済みのコードをスキップします。以前の待機で返されたもの、またはアプリや Proxy Mail QA for Chrome でコピーや入力されたもの。0.5.1 で追加)、クーポンやプロモーションコード用の type: 'coupon' とその discount、valid_until、after_seconds(0.10.0 で追加)
waitForLink(address, options?) リンク用の同様のメソッドです。さらに type(verify、magic_signin、invite、reset、any、またはそのリスト)と app(モバイルアプリを開くリンクには true)があります
messages(address, options?) コードとリンク付きのメールです。includeText と includeHtml はテンプレートのテスト用に内容を追加します。limit
countEmails(address, options?) 届いたメールの数です。各メールは内容なしで要約されます。オプション:since、service、type(code または link)、subtype、および 0.10.0 からの category(auth、transactional、marketing)
expectNoEmail(address, options?) timeout 秒以内(既定値30。分単位も可能で、再度問い合わせます)に一致するメールが届かなければ解決します。届いた時点で ProxyMailQAError(409、email_arrived)をスローします。フィルターは同じで、since の既定値は現在時刻です
markUnsubscribed(address, options?) Labs. テストがブラウザで配信停止を行った後、そのアドレスが送信者(emailId または service)から配信停止したことを記録します(過去の日時にするには at、送信中のメールには grace: '1d')。その送信者からの以降のマーケティングメールは after_unsubscribe で不合格になります。0.10.0 で追加
markResubscribed(address, options?) Labs. アドレスが再度購読しました。監視を終了します。0.10.0 で追加
recordConsent(address, consent) Labs. 国ごとのマーケティングルールのため、アカウントがサインアップ時に同意した内容(marketing、prechecked、doubleOptIn)を記録します。consent(address) がこれを読み取ります。0.10.0 で追加
consent(address) Labs. 記録された、アカウントが同意した内容です。0.10.0 で追加
recordCouponResult(address, result, options?) Labs. チェックアウト(ステージング、テスト決済)でクーポンを適用した後、アプリの message(code、oneTime)とともに accepted、rejected、error を記録します。check は coupon_result の判定です。0.10.0 で追加
checkSequence(address, steps, options?) Labs. since の後、ウェルカムシリーズやドリップキャンペーンが順番どおり、時間どおりに届いたかどうかです。各ステップには subject、category、type、after(‘1d’)、within を指定できます。0.10.0 で追加
offers(address) Labs. 送信者ごとのクーポンと、それぞれがどの表記のアドレス(そのまま、大文字、+tag)に送られたかです。1人がオファーを2回受け取った場合、passed は false になります。0.10.0 で追加
checkUnsubscribe(address) Labs. 各配信停止と、その後に届いたマーケティングメールです。1通でも届いた場合、passed は false になります。0.10.0 で追加
forTest(name), recordTest(result), recordTestTotals(totals) すべての呼び出しでテスト名を付けるクライアントと、実行のレポート用のテスト結果です。proxymail-qa/playwright とそのレポーターが使います。0.8.0 で追加(詳細)
formValues({ fields, address?, age?, country?, … }) サインアップフォームの各入力欄に入力または選択する値です。そのために作成された新しいアドレスから生成されます。0.7.0 で追加(詳細)
checkEmail(address, options?) 一致する最新のメール(または emailId)を確認します:SPF、DKIM、DMARC、ローカルや通常の http のリンク、リンク先(expectHost)、記載された有効期限(0.10.0 からはクーポンの終了日も)、プレーンテキストのパート、サイズ、画像のみのメール。0.7.0 からは含むべきフレーズ(expectText)と言語(expectLanguage)、0.10.0 からは since からどれだけ早く届いたか(expectWithin: '5m')と添付ファイル(expectAttachment: ['.pdf'])も確認します。マーケティングメールでは list_unsubscribe、one_click_unsubscribe、unsubscribe_link、preheader も、すべてのメールで harmful_attachment と spam_risk(迷惑メールに振り分けられるかどうかの推定)も確認します。返すのは判定のみで、内容は返しません。0.4.0 で追加
checkDelivery(address) 待たずに、アドレスに届いたものと拒否されたものを返します
variants(address) メールが届く珍しい表記のアドレスと、無効な表記のアドレス(詳細)
nameVariants({ field?, maxLength? }) 名前の入力欄向けの扱いにくい名前です。受け入れるべきもの(アポストロフィ、アクセント、他の文字体系)、拒否すべきもの、テキストとして保存すべき HTML や SQL、アプリによって扱いが異なるものがあります。0.11.0 で追加(詳細)
passwordVariants({ minLength?, maxLength?, symbols? }) 入力欄のルール内の扱いにくいパスワードです。最小と最大、各記号の種類、スペース、非 ASCII、アプリによって扱いが異なるものがあり、呼び出しごとに新しく生成されます。0.11.0 で追加(詳細)
getAddress(address), listAddresses({ limit?, before?, tags?, country?, minAge?, maxAge?, hasPassword?, search?, site?, environment? }) アドレスを読み取るか、以前に作成したアドレスを探します。指定したすべてのフィルターに一致する必要があります
updateAddress(address, patch) label、notes、tags、first_name、last_name、username、date_of_birth、country を変更します。null で1つをクリアします
setPassword(address, { password?, label? }) アプリがパスワードを変更またはリセットした後、テスト用アカウントが現在使っているパスワードを保存します。指定しない場合は、強力なパスワードが生成され、1回だけ返されます
savedPassword(address, label?) 保存済みのパスワードを読み取ります。「Can read saved passwords」が付いたキーが必要です。すべての読み取りは記録されます
signInAccount({ site?, environment?, tags? }) サインインに使う保存済みテストアカウントとそのパスワードを 1 回の呼び出しで返します。このサイトで最初に使われたものが選ばれ、合うものがなければ null を返します。「保存済みパスワードを読み取れる」権限のあるキーが必要です。0.9.0 で追加されました(詳細)
markDevHost(host) localhost:3000 などのホストをプロジェクトの開発環境として登録し、そこへのリンクが checkEmail で合格するようにします。0.9.0 で追加されました
deleteAddress(address) アドレスとそのメールを削除します
project() キーのプロジェクト:メールドメイン、メールのプレフィックス、上限
updateProject({ name?, website?, environments?, emailPrefix?, mailDomain? }) プロジェクト設定と同じようにプロジェクトを変更します。null は website または emailPrefix をクリアします。プロジェクトのリーダー、または組織のオーナーか管理者である必要があります
checkOutAccount({ tags?, country?, minAge?, maxAge?, hasPassword?, search?, site?, environment?, minutes? }), extendCheckout(account, minutes?), returnAccount(account, { addTags?, removeTags? }), withAccount(options, fn) アカウントプール:状態がわかっていて、ほかのテストが使っていないアカウントを借りて、返します。0.6.0 で追加されました(詳細)。0.7.0 以降、withAccount は create、check、reset のフックを受け取ります:状態がわかっているアカウント
startRun({ name?, source?, meta? }), finishRun({ failed? }), runReport(), addRunNote(text, { failed? }), getRun(), listRuns() 実行:このクライアントの呼び出しが確認した内容を追加し、プルリクエスト用のレポートにします。0.5.0 で追加されました

どのメソッドも、通常のアドレスかそのバリエーションを受け取ります。+tag のアドレスで待機すると、そのタグ宛てのメールだけが見つかるため、1 つのアドレスでテスト実行全体をまかなえます。CI での実行を参照してください。

Labs の付いたメソッドは、Labs 機能であるマーケティング・オファーメールの一部です。キーの作成者がアプリの Account settings → Labs でオンにします。それまでは、その旨を伝える 403 をスローします。checkEmail はオフになっている Labs 機能のチェックを含めません。

formValues は、フォーム用に作った新しいアドレスをもとに、各フィールドに入力または選択する値を返します。フィールドのルールに合うユーザー名とパスワード、フィールドの形式に合った生年月日、ドロップダウンにある国などです。Playwright では、ページからフィールドを読み取り、それぞれに入力します:

const form = page.locator('form#sign-up');
const fields = await form.locator('input, select').evaluateAll((elements) =>
elements.map((el, i) => {
const input = el as HTMLInputElement & HTMLSelectElement;
input.dataset.field = String(i);
return {
id: String(i),
type: input.tagName === 'SELECT' ? 'select' : input.type,
name: input.name,
label: input.labels?.[0]?.textContent ?? input.getAttribute('aria-label') ?? '',
placeholder: input.placeholder,
autocomplete: input.getAttribute('autocomplete') ?? '',
pattern: input.pattern || undefined,
minLength: input.minLength > 0 ? input.minLength : undefined,
maxLength: input.maxLength > 0 ? input.maxLength : undefined,
options: input.tagName === 'SELECT' ? Array.from(input.options, (o) => ({ value: o.value, label: o.text })) : undefined,
};
}),
);
const { address, values } = await qa.formValues({ fields });
for (const { id, kind, value } of values) {
if (value === null) continue;
const field = form.locator(`[data-field="${id}"]`);
if (await field.evaluate((el) => el.tagName === 'SELECT')) await field.selectOption(value);
else await field.fill(value);
}

以前作ったアカウントを使うには address を、新しいアカウントには age または country を渡します。詳しくは REST API ガイドを参照してください。

メールが届かないこと、または 1 通だけ届くことの確認

Section titled “メールが届かないこと、または 1 通だけ届くことの確認”
// An unsubscribed user gets no newsletter.
const sent = new Date();
await sendNewsletter();
await qa.expectNoEmail(unsubscribed.address, { since: sent, timeout: 20 });
// A sign-up sends one verification email, not two.
await qa.waitForLink(address, { since: submitted });
await new Promise((resolve) => setTimeout(resolve, 10_000)); // time for a duplicate
expect((await qa.countEmails(address, { since: submitted, type: 'link' })).count).toBe(1);

どちらもメールの内容は返さず、各メールが届いた時刻、送信者、コードやリンクの種類だけを返します。詳しくは REST API ガイドを参照してください。

await qa.waitForLink(address, { since: submitted });
const result = await qa.checkEmail(address, { since: submitted, type: 'link', expectHost: 'staging.example.com' });
expect(result.failed).toEqual([]); // e.g. ['local_links'] when the link goes to localhost
// A localised email: the right language, with the user's name in it.
const german = await qa.checkEmail(address, { since: submitted, expectLanguage: 'de', expectText: [persona.first_name] });
expect(german.failed).toEqual([]);

各チェックは、合格、不合格、警告、スキップのいずれかになり、1 行の理由が付きます。それぞれの意味は REST API ガイドにあります。自分のマシンで動くアプリをテストしていますか?await qa.markDevHost('localhost:3000') を 1 回実行すると、そこへのリンクは local_links で合格します。ほかの localhost へのリンクは引き続き不合格になります。

並列テスト用のアカウントプール

Section titled “並列テスト用のアカウントプール”

アカウントに状態のタグを付け、各テストに 1 つずつ借りさせます。withAccount はアカウントを 1 つ借り出し、関数を実行し、必ず返却します。テストで変更した内容を記録するには { addTags, removeTags } を返します:

await qa.withAccount({ tags: ['verified', 'premium'] }, async (account) => {
const { secret } = await qa.savedPassword(account.address);
await signIn(page, account.address, secret);
await cancelSubscription(page);
return { addTags: ['cancelled'], removeTags: ['premium'] };
});

借り出し中のアカウントは、ほかのテストには渡りません(minutes を渡さない限り 10 分間)。返却されなかったアカウントは、時間が切れると自動で解放されます。

状態がわかっているアカウント

Section titled “状態がわかっているアカウント”

アプリ内で特定の状態(確認済み、プレミアムプラン、注文なしなど)のアカウントがテストに必要なときは、withAccount に 3 つのフックを渡します。フックはテストコードが自身のアプリへのアクセス権で実行します。Proxy Mail QA がアプリにサインインすることはありません。ここで app は、アプリとやり取りする独自のコードを表します。管理用 API、データベースのシード、UI などです。

await qa.withAccount(
{
tags: ['verified', 'premium'],
// When none is free: sign up with the new address, verify it, upgrade it.
create: async (account) => {
const submitted = new Date();
await app.signUp(account.address, account.password);
const { value: link } = await qa.waitForLink(account.address, { type: 'verify', since: submitted });
await app.open(link);
await app.admin.setPlan(account.address, 'premium');
},
// Is it still in that state? Return false or throw if not.
check: async (account) => (await app.admin.user(account.address)).plan === 'premium',
// After the test: put it back.
reset: async (account) => app.admin.resetUser(account.address, { plan: 'premium', orders: [] }),
},
async (account) => {
// …the test…
},
);
  • check は、テストがアカウントを受け取る前に実行されます。失敗すると reset が実行され、もう一度チェックされます。それでも失敗すると、次のアカウントが試されます(最大 attempts、3 回)。失敗したアカウントはプールのタグを外され、check-failed のタグが付きます。
  • create は、一致するアカウントがないとき、または一致するものがすべて借り出し中のときに実行されます。新しいアドレスは最初からこのテスト用に確保され、create と check が成功するとタグが付きます。年齢、ラベル、ドメインを指定するには newAccount を渡します。国はフィルターの国になります。
  • reset は、テストの合否にかかわらず、テストの後に実行されます。reset が失敗すると、アカウントはプールのタグを外され、needs-reset のタグが付きます。これで、状態が不明なアカウントがほかのテストに渡ることはありません。

実行中は、新しいアカウント、失敗した check、失敗した reset がそれぞれレポートに 1 行追加されます。

実行が開いている間の呼び出しは、確認した内容を実行に追加します。アドレス、届いたメールと since からの経過時間、コード、各リンクの行き先、チェック、配信停止・オファー・クーポンのチェック、失敗したテスト、届かなかったメールです。GitHub Actions では、Proxy Mail QA アクションが実行を開始・終了し、レポートをプルリクエストに投稿します。クライアントは PROXYMAIL_QA_RUN から実行を取得するので、テストを変更する必要はありません。それ以外の環境では、自分で実行します:

const run = await qa.startRun({ name: 'Sign-up tests', meta: { branch: process.env.BRANCH } });
// …the tests, using qa…
await qa.finishRun({ failed: testsFailed });
console.log(await qa.runReport(run.id)); // Markdown, for a pull request comment

ほかのプロセスは、実行の ID を PROXYMAIL_QA_RUN として渡すと実行に参加します。

各メールがどのテストから来たかを示すには、テストごとにクライアントを使います。qa.forTest('sign-up › sends one code') はすべての呼び出しでテスト名を伝えます。Playwright では、proxymail-qa/playwright がすべてのテストにこれを行う qa フィクスチャを提供し、proxymail-qa/playwright/reporter が失敗したテスト、不安定なテスト、合計を実行に記録します。CI での実行を参照してください。ほかのテストランナーは、qa.recordTest({ name, status, durationMs, error }) と qa.recordTestTotals({ passed, failed, skipped, flaky }) で結果を自分で送信できます。0.8.0 で追加されました。

失敗した呼び出しは、HTTP status、code、読みやすいメッセージとともに ProxyMailQAError をスローします。待機で何も見つからないときは、Proxy Mail QA がわかる範囲でその理由もメッセージに含めます(error.hint)。たとえば、アプリのメールプロバイダーが確認済みアドレスにしか送信しない、などです。error.refused は、大きすぎる、上限を超えた、または Proxy Mail QA が対応していないサイトから来た、という理由で拒否されたメールを一覧にします(refused[].site がそのサイト名を示します):

import { ProxyMailQAError } from 'proxymail-qa';
try {
await qa.waitForCode(address, { since: submitted });
} catch (error) {
if (error instanceof ProxyMailQAError && error.status === 404) console.log(error.hint);
throw error;
}

アプリが任意のアドレスに送信できるようになるまで、再試行しても解決しません。テストメールが届かない場合を参照してください。

テストアカウントをもう一度見つける

Section titled “テストアカウントをもう一度見つける”

テストやエージェントは、新しく作る代わりに、以前作ったアカウントを再利用できます。指定したフィルターはすべて一致する必要があります。年齢はペルソナの生年月日から計算した今日時点の満年齢で、search はアドレス、ラベル、メモ、名前、ユーザー名を検索します。

// An under-18 in the UK, made for the age-gate tests, with a saved password.
const [teen] = await qa.listAddresses({ tags: ['age-gate'], country: 'GB', maxAge: 17, hasPassword: true });
// Record where an account is used, then sign in with it again later: the
// account used on this site first, with its password (needs a key with
// "Can read saved passwords").
await qa.createAddress({ password: true, site: 'staging.example.com', environment: 'staging' });
const account = await qa.signInAccount({ site: 'staging.example.com', tags: ['verified'] });

このページについて質問がありますか?

質問はどの言語でもかまいません。ドキュメントからすぐに回答を得ることも、Proxy Mail QA チームに質問を送ることもできます。チームからの返信は英語です。