0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

mablでOTP入力フォームをテストする3つのアプローチ

0
Posted at

はじめに

アカウントを登録する際に多要素認証の登録はしましたか? QRコードをGoogle Authenticatorなどで読み取ると、6桁の数字が30秒周期で切り替わるあれです。mablではテスト対象のアプリケーションがTOTP認証を要求する場合でも、ログインアカウントをクレデンシャルに登録しておくことで対応可能です。

ログイン時にこの6桁の数字を入力するフォームは、E2Eテストで意外と詰まりやすいUIです。

  • 入力ボックスが6つに分かれていて、idnameがついていない
  • 1桁入力すると自動で次のボックスにフォーカスが移る
  • 6桁をペーストすると全ボックスに一括で入力される

「mablで普通にクリック&入力しようとしたら、全部の数字が意図通り入力されなかった」という経験をお持ちの方もいるかもしれません。

本記事では、mablのJavaScriptスニペットを活用してOTP入力フォームを安定テストする方法を紹介します。

OTP入力UIの2つのパターン

実装上よく見る2パターンを今回のテスト対象ページで確認します。

パターン1: 6分割ボックス型

6分割ボックス型

<input class="otp-input" type="text" maxlength="1" inputmode="numeric" />
<input class="otp-input" type="text" maxlength="1" inputmode="numeric" />
<!-- ... 6つ並ぶ -->

各ボックスはmaxlength="1"で1桁のみ受け付けます。このような場合、JavaScriptで次の3つの動作を実装しています。

// ① 1桁入力 → 次のボックスへ自動フォーカス
input.addEventListener('input', (e) => {
    if (e.target.value.length === 1 && index < inputs.length - 1) {
        inputs[index + 1].focus();
    }
});
// ② 空ボックスでBackspace → 前のボックスへ戻る
input.addEventListener('keydown', (e) => {
    if (e.key === 'Backspace' && !input.value && index > 0) {
        inputs[index - 1].focus();
    }
});
// ③ 6桁をペースト → 全ボックスに分配
input.addEventListener('paste', (e) => {
    const pasteData = e.clipboardData.getData('text');
    if (/^\d{6}$/.test(pasteData)) {
        pasteData.split('').forEach((digit, i) => {
            if (inputs[i]) inputs[i].value = digit;
        });
        inputs[5].focus();
        e.preventDefault();
    }
});

パターン2: 単一フィールド型

単一フィールド型

<input class="otp-input2" type="text" minlength="6" maxlength="6" inputmode="numeric" />

1つのフィールドに最大6桁を入力するシンプルな実装です。こちらはmablの変数値入力機能で問題なくテストできます。

本記事では、難易度の高い パターン1 (6分割ボックス型) に焦点を当てます。

なぜmablの入力機能だけでは詰まるのか

6分割ボックス型をmablトレーナーでテスト作成しようとすると、フォームの実装によりうまくいく場合とうまくいかない場合があります。

問題1: 要素の特定があいまい

6つのボックスがすべて同じクラス(.otp-input)で、idnameもないことがあります。mablが「何番目のinput」としてテストステップを記録した場合、DOM構造が変わると壊れやすくなります。

問題2: 自動フォーカス移動との競合

1桁目を入力した瞬間にフォーカスが2番目のボックスに飛びます。mablが「2番目のボックスをクリック → 入力」しようとしたとき、フォーカスがすでに3番目に移っている、という競合が起きることがあります。

問題3: OTPコードの変更に弱い

1桁ずつ入力する方式では、(テスト用の固定コードを入力する使用の場合)OTPコードを変えるたびに6ステップすべてを編集することになります。

アプローチ1: 値を直接セットする (推奨)

JavaScriptで各ボックスに直接値を入力し、inputイベントを発火させます。フォーカス移動ロジックも正しくトリガーされ、最も安定します。

function mablJavaScriptStep(mablInputs, callback) {
    const otp = '123456'; // テスト用OTPコード

    const inputs = document.querySelectorAll('.otp-input');

    if (inputs.length !== 6) {
        console.error(`OTPボックスが6つ見つかりません: ${inputs.length}個`);
        callback(false);
        return;
    }

    otp.split('').forEach((digit, i) => {
        inputs[i].value = digit;
        // input イベントを発火してフォーカス自動移動ロジックをトリガー
        inputs[i].dispatchEvent(new Event('input', { bubbles: true }));
    });

    console.log(`OTP入力完了: ${otp}`);
    callback(true);
}

new Event('input', { bubbles: true })bubbles: true は重要です。これがないと親要素にイベントが伝播せず、フレームワーク(React/Vueなど)が値の変化を検知できないことがあります。

アプローチ2: ペーストイベントをシミュレートする

アプリがペースト処理(OTP一括入力)をサポートしているなら、その動作自体をテストの対象にできます。ペーストロジックが壊れていれば、このスニペットで検知できます。

function mablJavaScriptStep(mablInputs, callback) {
    const otp = '654321';
    const inputs = document.querySelectorAll('.otp-input');

    if (inputs.length === 0) {
        callback(false);
        return;
    }

    // DataTransfer でクリップボードデータを模倣
    const dt = new DataTransfer();
    dt.setData('text', otp);

    const pasteEvent = new ClipboardEvent('paste', {
        clipboardData: dt,
        bubbles: true,
        cancelable: true
    });

    inputs[0].focus();
    inputs[0].dispatchEvent(pasteEvent);

    // ペースト後に全ボックスの値を即時検証
    const result = Array.from(inputs).map(el => el.value).join('');
    if (result === otp) {
        console.log(`ペーストOTP確認: ${result}`);
        callback(true);
    } else {
        console.error(`ペースト失敗: 期待値=${otp}, 実際=${result}`);
        callback(false);
    }
}

このスニペットはペーストの発火と検証を1ステップで行うため、「ペーストで全桁入力できること」というアクセプタンス基準を直接テストできます。

OTP入力値の検証スニペット

入力後に全ボックスの値が期待通りかを確認するスニペットです。入力ステップとは別のJavaScriptステップとして追加します。

function mablJavaScriptStep(mablInputs, callback) {
    const expectedOtp = '123456';
    const inputs = document.querySelectorAll('.otp-input');

    const actualOtp = Array.from(inputs).map(el => el.value).join('');

    if (actualOtp === expectedOtp) {
        console.log(`OTP検証OK: ${actualOtp}`);
        callback(true);
    } else {
        console.error(`OTP不一致: 期待値=${expectedOtp}, 実際=${actualOtp}`);
        callback(false);
    }
}

単一フィールド型はシンプルに

パターン2(.otp-input2)は、mablの「変数 > 変数を使用する > 入力値」で対応可能です。追加で確認しておきたいポイントは、次の2点です。

  • 6桁ちょうどで入力完了できること
  • maxlength="6"により7桁目が入力できないこと(入力後に文字数アサーションで確認)

まとめ

テスト内容 アプローチ 安定性
6分割ボックスへの入力 JavaScriptスニペット: 値の直接セット
ペースト一括入力の動作確認 JavaScriptスニペット: ペーストイベント発火
全ボックス入力値音検証 JavaScriptスニペット: 値の読み取りと比較
単一フィールドへの入力 mablの 変数 > 変数を使用する > 入力値 で入力
1桁ずつネイティブ入力(6分割) mablの 変数 > 変数を使用する > 入力値 で入力

idnameのない複数同一クラス要素・自動フォーカス移動・ペーストの特殊処理と、OTP入力フォームはE2Eテストの「詰まりポイント」が凝縮されたUIです。mablのJavaScriptスニペットを使えば、いずれも安定したテストに変換できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?