今回はパスワードリセットのテストです。リセットリンクをクリックしてメールアドレスを入力、MailHogに届いたメールの確認、パスワードリセット実行、という一連のユーザーの操作をCypress13.xで実装します。

Cypressのセットアップや画面表示テストなど、以前の記事は以下からご覧いただけます。

Cypressセットアップ・実行方法
Cypress画面表示のテスト
Cypressログイン・ログアウトのテスト

Cypressでパスワード再設定のテスト

引き続きLaravel Breezeのログイン画面を使ってテストします。テストの流れは、少し項目が多いですが以下のような形で進めようと思います。

  1. ログイン画面で「パスワードを忘れた?」というリンクをクリック
  2. パスワードリセットのリクエスト画面でメールアドレスを入力・送信
  3. 受信したメールのリンクからパスワードリセット画面へ遷移
  4. パスワードリセットを実行
  5. 旧パスワードでログインが失敗することを確認
  6. 新しいパスワードでログインが成功することを確認

受信メールの確認にはMailHogを使用しますので、もしインストールがまだの場合は環境に合わせてインストールくださいね。

Laravelの.envは以下のように設定しています。

...
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="local@example.com"
MAIL_FROM_NAME=メールテスト
...

事前準備1: cypress-mailhog

CypressからMailHogのデータを取得するための便利なコマンドが提供されているパッケージ、cypress-mailhogを使用します。

以下のコマンドでインストールできます。執筆時点で最新の2.4.0がインストールされました。

$ npm install --save-dev cypress-mailhog

また、以下の設定が必要です。まずはcypress/support/commands.jsファイルにインポート。

import 'cypress-mailhog';
...

そして、cypress.config.jsファイルのenvにMailHogのURLを追記します。私の環境ではhttp://localhost:8025にてMailHogのUIが確認できます。

import { defineConfig } from "cypress";

export default defineConfig({
  env: {
    mailHogUrl: "http://localhost:8025", // MailHogのURLをここに追記
  },
  e2e: {
    baseUrl: 'http://127.0.0.1:8000',
  },
});

これでCypressのテスト内でcypress-mailhogのコマンドが使用できるようになりました。

具体的には以下のように、cy.に続けてCypressコマンドのように使用できます。mhGetAllMails()はメールボックスのメールを最大50件取得するコマンドで、mhFirst()は、直前のコマンドで取得したメールから最初の1件を抽出するコマンドです。

cy.mhGetAllMails()            //メールを最大50件取得
cy.mhGetAllMails().mhFirst()  //メールの最初の1件を取得

事前準備2: mailparser

次に、mailparserをインストールします。今回は届いたメールの本文を確認する必要があるのですが、先ほどのcypress-mailhogで取得したメールの本文を見てみると、以下のようにテキストとして読めない状態です。

そのためmailparserを使用して、人間が読めるように解析する必要があります。

以下のコマンドでインストール。

$ npm install mailparser --save-dev

こちらも2ファイルに設定が必要です。まずcypress/support/commands.jsにカスタムコマンドを追加します。先ほどインストールしたcypress-mailhogのimport文も含めると、以下のようになります。

import 'cypress-mailhog'; // cypress-mailhog 用の記述
...
Cypress.Commands.add('mhParseMail', { prevSubject: true }, (mail) => {
  return cy.task('parse-mail', { mail: mail.Raw.Data })
})
...

カスタムコマンド名は任意ですが、mhParseMailとしました。

そして、cypress.config.jsにmailparserのimporttaskを追加します。taskとは、テストコード内からブラウザ外で必要な作業を呼び出すための機能です。taskのドキュメントはこちら

先ほどcypress-mailhogで同ファイルに書き込んだものと合わせると、最終的には以下のようになります。

import { defineConfig } from "cypress";
import { simpleParser as parser } from 'mailparser'; // ここでmailparserをインポート

export default defineConfig({
  env: {
    mailHogUrl: "http://localhost:8025"
  },
  e2e: {
    baseUrl: 'http://127.0.0.1:8000',
    setupNodeEvents(on, config) {                 // タスク用の追記ここから
      on('task', {
        'parse-mail': ({ mail }) => parser(mail), // タスクを定義
      })
    },                                            // タスク用の追記ここまで
  },
});

on('task', {...})がタスクを定義している箇所です。task名は任意ですが、ここではparse-mailとしました。カスタムコマンドmhParseMail()を実行するとparse-mailというtaskが呼ばれ、mailparserによりメール解析が実行される、という流れです。

ここまで設定できたらテストコードからの呼び出しは簡単で、cypress-mailhogで提供されているmhGetAllMails().mhFirst()に繋いで以下のように使用します。

cy.mhGetAllMails().mhFirst().mhParseMail().then((mail) => {
 mail.subject               // 件名を取得
 mail.from.value[0].address // Fromを取得
 mail.text                  // 本文を取得
}

取得したメールの1通目のデータをmhParseMail()で解析してからthen()に渡します。受け取ったmailオブジェクトはmailparserの形式になっているため、上の例のようにmail.subjectで件名を、mail.from.value[0].addressでメールのfromを、mail.textで本文を取得できます。

他にもプロパティが色々ありますのでmailparserのドキュメントをご確認ください。

wrap

ここまでで事前準備は完了ですが、最後に今回新しく使用するコマンドwrap()をご紹介します。

cypress-mailhogやmailparserから受け取ったメールデータはJavaScriptのオブジェクトです。このデータをCypressのアサーションコマンドで扱うために、以下のようにwrap()コマンドでラップします。

cy.mhGetAllMails().mhFirst().mhParseMail().then((mail) => {
 cy.wrap(mail.subject).should('eq', 'パスワードのリセット方法について');
}

これで、Cypressコマンドを繋いでメールの件名が期待した内容がどうかを検証できるようになります。

Cypressでパスワードリセットのテスト

以下がパスワードリセットのテストコード全文になります。

it('パスワードリセットのテスト', () => {

  cy.visit('/login');
  cy.contains('a', 'パスワードを忘れた').click();

  // パスワードリセットページに遷移したことを確認
  cy.url().should('include', '/forgot-password');

  // メールアドレス入力
  cy.get('input[name="email"]').type(Cypress.env('email'));

  // パスワードリセットメール送信ボタンをクリック
  cy.contains('button', 'パスワードリセットメールを送信').click();

  // 取得した一覧のうち、1件目(最新)のメール本文を取得
  cy.mhGetAllMails().mhFirst().mhParseMail().then((mail) => {

    cy.wrap(mail.subject).should('eq', 'パスワードのリセット方法について');
    cy.wrap(mail.from.value[0].address).should('eq', 'from@example.com');

    // メールの本文からパスワードリセットのためのリンクを抽出
    const hrefRegex = /https?:\/\/[^\s/"']+\/reset-password\/[^\s/"']+/;
    let match = hrefRegex.exec(mail.text)

    // パスワードリセットの画面へ遷移
    cy.visit(match[0])

    cy.get('input[name="email"]').type(Cypress.env('email')); // メールアドレス入力
    cy.get('input[name="password"]').type('new-password');    // 新しいパスワード
    cy.get('input[name="password_confirmation"]').type('new-password'); // パスワードの再入力

    // パスワードリセットメール送信ボタンをクリック
    cy.contains('button', 'パスワードのリセット').click();

    // ログイン画面へ遷移したことを確認
    cy.url().should('eq', Cypress.config().baseUrl + '/login');

    // 旧パスワードでログインすると失敗する
    cy.get('input[name="email"]').type(Cypress.env('email'));
    cy.get('input[name="password"]').type(Cypress.env('password'));
    cy.get('button[type="submit"]').click();

    cy.get('input[name="email"]')
      .next('ul')
      .should('contain', 'ログイン情報が存在しません。');

    // 新しいパスワードで再度ログイン試行(メールアドレスの入力値は残っているので不要)
    cy.get('input[name="password"]').type('new-password'); // 新しいパスワード
    cy.get('button[type="submit"]').click();

    // ダッシュボードに遷移したことを確認
    cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
  })
})

ではテストを実行してみます。npx cypress openでCypressを立ち上げて、対象ファイルをクリックします。

成功しました!ですが、このテストではDBのパスワードを変更してしまうため、どこかでパスワードをデフォルトに戻すなどの処理を行う必要があります。

CypressでDBをリセットするには、テスト対象のプロジェクトにDBリセット用のAPIを用意する、DBリセット用のシェルスクリプトを作成してテストから呼び出す・・・などの方法が考えられますが、今回はLaravelのプロジェクトがテスト対象ということもあり、ユニットテストのようにrefreshDatabase()が実行できれば便利ですよね。

ということで、次回はLaravelプロジェクトをCypressでテストするときに便利なパッケージをご紹介します。

参照

Cypressセットアップ・実行方法
Cypress画面表示のテスト
Cypressログイン・ログアウトのテスト

メルマガ購読の申し込みはこちらから。

By hmatsu