Maestro
英語から自動翻訳されています。
英語で読むスクリプトを保存する
Section titled “スクリプトを保存する”Maestro はアプリを外部から操作するため、1 つのスクリプトでネイティブの iOS、Android、React Native、Flutter アプリに対応できます。フローと同じ場所に
proxymail.jsとして保存します。proxymail.js // Proxy Mail QA for Maestro flows: works for native iOS and Android, React// Native and Flutter apps. Guide: https://proxymail.qa/docs/maestro//// - runScript: { file: proxymail.js, env: { ACTION: create } }// output.address, output.password, output.since. Add ENVIRONMENT// (staging), AGE (adult, under18…) or COUNTRY (GB) to set them.// - runScript: { file: proxymail.js, env: { ACTION: code } } (or link)// output.code (or output.link), for mail received since output.since.// Add APP_LINK: true for the link that opens the app (myapp://…) when// the email also has a web one, UNUSED: true to skip a used code, and// TYPE: coupon for a coupon or promo code (any for either), or for a// link, TYPE: unsubscribe for the email's unsubscribe link.// - runScript: { file: proxymail.js, env: { ACTION: count } }// output.count, the emails received since output.since// - runScript: { file: proxymail.js, env: { ACTION: check, EXPECT_HOST: links.example.com } }// fails the flow if the newest email fails a check (SPF, DKIM, DMARC,// local or http links, where its link goes…); output.failed lists them// - runScript: { file: proxymail.js, env: { ACTION: none } }// fails the flow if an email arrives within 30 seconds (TIMEOUT);// CATEGORY: marketing watches only for offers and newsletters// - runScript: { file: proxymail.js, env: { ACTION: signin, TAGS: verified } }// output.address and output.password of a saved test account; needs a// key allowed to read saved passwords// - runScript: { file: proxymail.js, env: { ACTION: checkout, TAGS: verified } }// borrows an account no other test has: output.address, output.password,// output.token. ACTION: return gives it back (ADD_TAGS, REMOVE_TAGS).// - runScript: { file: proxymail.js, env: { ACTION: coupon_result, RESULT: accepted } }// after applying the coupon at checkout (the store's sandbox, never a// real card): RESULT is accepted, rejected or error; add MESSAGE (what// the app said), CODE and ONE_TIME: true. output.verdict; fails the// flow if the coupon_result check fails.// - runScript: { file: proxymail.js, env: { ACTION: consent, MARKETING: declined } }// records what the account agreed to at sign-up: MARKETING opted_in or// declined; add PRECHECKED: true when the box started ticked and// DOUBLE_OPT_IN: pending or confirmed.//// The API key comes from the run: maestro test -e PROXYMAIL_QA_API_KEY=... flow.yaml// Add -e PROXYMAIL_QA_RUN=$PROXYMAIL_QA_RUN to add the calls to a run (the// GitHub Action sets it), and TEST: a name, in env, to name the step.const API = 'https://api.proxymail.qa/v1';const headers = { Authorization: 'Bearer ' + PROXYMAIL_QA_API_KEY, 'Content-Type': 'application/json' };if (typeof PROXYMAIL_QA_RUN !== 'undefined' && PROXYMAIL_QA_RUN) {headers['Proxy-Mail-QA-Run'] = PROXYMAIL_QA_RUN;// Percent-encoded: a test's name can hold characters headers can't.if (typeof TEST !== 'undefined' && TEST) headers['Proxy-Mail-QA-Test'] = encodeURIComponent(TEST);}function call(method, path, body, extra) {const options = { headers: extra || headers };if (body) options.body = JSON.stringify(body);const response = method === 'POST' ? http.post(API + path, options) : method === 'PUT' ? http.put(API + path, options) : http.get(API + path, options);return { ok: response.ok, status: response.status, body: response.body ? json(response.body) : {} };}function fail(response) {// When nothing arrives, the hint says why (an email provider in sandbox mode, say).throw new Error('Proxy Mail QA: ' + (response.body.message || response.status) + ' ' + (response.body.hint || ''));}function tags(value) {return String(value).split(',').map(function (tag) { return tag.trim(); }).filter(function (tag) { return tag; });}function account(body) {output.address = body.address;output.password = body.password;output.since = new Date().toISOString();}const at = typeof output.address === 'undefined' ? '' : '/addresses/' + encodeURIComponent(output.address) + '/';const since = '?since=' + encodeURIComponent(output.since);if (ACTION === 'create') {const body = { password: true, persona: true };if (typeof ENVIRONMENT !== 'undefined') body.environment = ENVIRONMENT;if (typeof AGE !== 'undefined') body.age = AGE;if (typeof COUNTRY !== 'undefined') body.country = COUNTRY;const created = call('POST', '/addresses', body);if (!created.ok) fail(created);account(created.body);} else if (ACTION === 'count') {const counted = call('GET', at + 'count' + since);if (!counted.ok) fail(counted);output.count = counted.body.count;} else if (ACTION === 'check') {const host = typeof EXPECT_HOST !== 'undefined' ? '&expect_host=' + encodeURIComponent(EXPECT_HOST) : '';const checked = call('GET', at + 'checks' + since + host);if (!checked.ok) fail(checked);output.failed = checked.body.failed.join(', ');const failures = checked.body.checks.filter(function (c) { return c.status === 'fail'; }).map(function (c) { return c.check + ': ' + c.detail; });if (failures.length) throw new Error('Proxy Mail QA: the email failed ' + failures.join(' '));} else if (ACTION === 'none') {const timeout = typeof TIMEOUT !== 'undefined' ? TIMEOUT : 30;const category = typeof CATEGORY !== 'undefined' ? '&category=' + encodeURIComponent(CATEGORY) : '';const watched = call('GET', at + 'none' + since + '&timeout=' + timeout + category);if (!watched.ok) fail(watched);} else if (ACTION === 'signin') {const body = { tags: typeof TAGS !== 'undefined' ? tags(TAGS) : [] };if (typeof ENVIRONMENT !== 'undefined') body.environment = ENVIRONMENT;const found = call('POST', '/addresses/sign-in', body);if (!found.ok) fail(found);account(found.body);} else if (ACTION === 'checkout') {const held = call('POST', '/addresses/checkout', { tags: tags(TAGS) });if (!held.ok) fail(held);// Its saved password, when the key may read it.const secret = call('GET', '/addresses/' + encodeURIComponent(held.body.address) + '/password');account({ address: held.body.address, password: secret.ok ? secret.body.secret : undefined });output.token = held.body.checkout.token;} else if (ACTION === 'consent') {const body = { marketing: MARKETING };if (typeof PRECHECKED !== 'undefined') body.prechecked = String(PRECHECKED) === 'true';if (typeof DOUBLE_OPT_IN !== 'undefined') body.double_opt_in = DOUBLE_OPT_IN;const recorded = call('PUT', at + 'consent', body);if (!recorded.ok) fail(recorded);} else if (ACTION === 'coupon_result') {const body = { result: RESULT, one_time: typeof ONE_TIME !== 'undefined' && String(ONE_TIME) === 'true' };if (typeof CODE !== 'undefined') body.code = CODE;if (typeof MESSAGE !== 'undefined') body.message = MESSAGE;const recorded = call('POST', at + 'coupon-result', body);if (!recorded.ok) fail(recorded);const check = recorded.body.check || { status: 'skip', detail: '' };output.verdict = check.status;if (check.status === 'fail') throw new Error('Proxy Mail QA: ' + check.detail);} else if (ACTION === 'return') {const body = { token: output.token };if (typeof ADD_TAGS !== 'undefined') body.add_tags = tags(ADD_TAGS);if (typeof REMOVE_TAGS !== 'undefined') body.remove_tags = tags(REMOVE_TAGS);const returned = call('POST', at + 'return', body);if (!returned.ok) fail(returned);} else {// Each request waits up to 10 seconds; four tries cover the email taking up to 40.const kind = ACTION === 'link' ? 'link' : 'code';const app = kind === 'link' && typeof APP_LINK !== 'undefined' && String(APP_LINK) === 'true' ? '&app=true' : '';const unused = typeof UNUSED !== 'undefined' && String(UNUSED) === 'true' ? '&unused=true' : '';const type = typeof TYPE !== 'undefined' && TYPE ? '&type=' + encodeURIComponent(TYPE) : '';let found = null;for (let i = 0; i < 4 && !found; i++) {// Only the last try's "nothing arrived" counts in a run's report.const retry = JSON.parse(JSON.stringify(headers));if (i < 3) retry['Proxy-Mail-QA-Run-Retry'] = 'true';found = call('GET', at + kind + since + '&timeout=10' + app + unused + type, null, retry);if (!found.ok && i < 3) found = null;}if (!found.ok) throw new Error('Proxy Mail QA: no ' + kind + ' arrived. ' + (found.body.hint || ''));output[kind] = found.body.value;}フローで使う
Section titled “フローで使う”次にフローで使います。アドレスを作成し、それでサインアップして、コードを入力します。
signup.yaml appId: com.example.app---- launchApp- runScript:file: proxymail.jsenv:ACTION: create- tapOn: "Email"- inputText: ${output.address}- tapOn: "Password"- inputText: ${output.password}- tapOn: "Sign up"- runScript:file: proxymail.jsenv:ACTION: code- inputText: ${output.code}キーを指定して実行する
Section titled “キーを指定して実行する”API キー を指定して実行します:
コードではなくリンクの場合は、
ACTION: linkを使い、次に- openLink: ${output.link}でアプリで開きます。メールに Web リンクとアプリを開くリンク(myapp://…)の両方がある場合は、スクリプトのenvにAPP_LINK: trueを追加してアプリのリンクを取得します。maestro test -e PROXYMAIL_QA_API_KEY=$PROXYMAIL_QA_API_KEY signup.yaml
コードだけでなくメールもチェックする
Section titled “コードだけでなくメールもチェックする”- runScript: file: proxymail.js env: ACTION: count- assertTrue: ${output.count == 1}- runScript: file: proxymail.js env: ACTION: check EXPECT_HOST: links.example.com最新のメールがチェックに失敗すると、ACTION: check は失敗した項目を示してフローを失敗させます。SPF、DKIM、DMARC、localhost や平文の http へのリンク、リンクの行き先(EXPECT_HOST:アプリのリンクが使うドメイン)、記載された有効期限、サイズなどです(各チェックの意味)。ACTION: none は、「配信停止したユーザーには何も届かない」ことを確認するため、30 秒以内にメールが届くとフローを失敗させます。CATEGORY: marketing を追加するとマーケティングだけを監視するため、領収書は届いても問題ありません。再送後は、ACTION: code に UNUSED: true を付けると、使用済みのコードを飛ばします。オファーに登録した後は、ACTION: code に TYPE: coupon を付けると、代わりにクーポンやプロモーションコードを待ちます。チェックアウトで適用した後(ストアのサンドボックスで行い、実際のカードは使わないでください)、RESULT: accepted(または rejected、error)、MESSAGE、ONE_TIME: true を指定した ACTION: coupon_result がアプリの動作を記録し、output.verdict を設定して、coupon_result のチェックが失敗するとフローを失敗させます。MARKETING: declined(および PRECHECKED、DOUBLE_OPT_IN)を指定した ACTION: consent はアカウントが同意した内容を記録し、ACTION: check がペルソナの居住地のルールでマーケティングを判定します。
ACTION: create では、ENVIRONMENT: staging がアカウントの対象環境を記録し、AGE: under18 または COUNTRY: GB が、それらを求めるフォーム向けにペルソナの生年月日や国を設定します。
保存したアカウントでサインインする、または借りる
Section titled “保存したアカウントでサインインする、または借りる”サインインフローでは、TAGS: verified を指定した ACTION: signin が、output.address と output.password を保存済みテスト用アカウントの値に設定します。フローを並列実行する場合は、TAGS: verified を指定した ACTION: checkout が、ほかのフローが使っていないアカウントをパスワードとともに貸し出し、ACTION: return で返却します(ADD_TAGS と REMOVE_TAGS はフローで変更した内容を記録します)。
- runScript: file: proxymail.js env: ACTION: checkout TAGS: verified# …sign in with ${output.address} and ${output.password}…- runScript: file: proxymail.js env: ACTION: returnどちらも保存済みのパスワードを読むため、キーには Can read saved passwords 権限が必要です(Project settings → API keys)。アプリでの状態をアカウントにタグ付けするとプールを作れます。アカウントプール を参照してください。
プルリクエストへのレポート
Section titled “プルリクエストへのレポート”ジョブで GitHub Action を使う場合は、開始される実行をキーと同じように渡し、各スクリプトの env に TEST を追加してステップに名前を付けます。
maestro test -e PROXYMAIL_QA_API_KEY=$PROXYMAIL_QA_API_KEY -e PROXYMAIL_QA_RUN=$PROXYMAIL_QA_RUN signup.yamlこのページについて質問がありますか?
質問はどの言語でもかまいません。ドキュメントからすぐに回答を得ることも、Proxy Mail QA チームに質問を送ることもできます。チームからの返信は英語です。