こんにちは。 テストオートメーショングループのおすしです。
レコーディング機能があるテスト自動化ツールを利用していると、以下のような課題に遭遇したことはありませんか?
「このボタン操作、レコーディングしてくれない…」
「どうしてそこをクリックする!そのすぐとなりのボタンでレコーディングしたでしょ!」
「このテスト、動かすたびに違うデータクリックするの…CSSセレクタを指定しないと(修正対象は数百個)」
ツールの紹介では「クリック操作で簡単にレコーディングできます」と言われていても、レコーディング機能だけで作成するのは難しい場合があります。
上記の課題を解決する機能として、一部のテスト自動化ツールはJavaScriptでコードを書くことができます。この機能によって、レコーディング機能ではうまくいかなかった操作ができるようになり、自動テストを安定して動作させたり、メンテナンスをしやすくしたりすることができます。
今回はチェックボックスのクリック操作について、JavaScriptで実装する際に起こりうる課題とその解決方法についてお話します。課題は以下の三つです。
- 【課題1】指定したチェックボックスをクリックできない
- 【課題2】意図しない操作をしてしまう
- 【課題3】通信が遅い場合に操作できない
上から順番に解説していきます。
実装シナリオ
以下のようなチェックボックスがあるWebアプリを例に説明します。
テスト対象の画面仕様
・チェックボックスは最大3個までチェックできる。
・チェックしたデータは「本日のトレーニング」欄に表示される
・3個チェックされると、他の未チェックのボックスは操作不可能になる。
・チェックボックスの並び順は一定ではない(直近で実行した筋トレメニューは下に移動する)。
テスト内容
操作:
以下のデータに該当する、3個のチェックボックスをクリックする
「アームカール」「ベンチプレス」「スクワット」
確認内容:
チェックを入れたデータが「本日のトレーニング」欄に表示されていること
レコーディングしたテストの問題点
レコーディングでは「アームカール」「ベンチプレス」「スクワット」を選択しましたが、データの順番が変わった状態でテストを実行すると、別のトレーニングが選択されてしまいました。どうやら「データの上から3つを選択する」とレコーディングされてしまうようです。
特定のトレーニング名に対応するチェックボックスをクリックする必要があります。
JavaScriptのコード
【課題1】指定したチェックボックスをクリックできない
ページ内のDOM構造を見ると、チェックボックスには「<input type=”checkbox”>」以外の設定がありません。(「こんなページがあるか!」と突っ込まれそうですが、実際に要素を区別できるものがない場合はよくあります。)
そのため、クリック対象をJavaScriptで指定する場合は、「トレーニング名の要素」→「その親要素(.parentNode)」→「その子要素の1番目(.children[0])」→「その子要素(.firstChild)」と順を追って取得する必要があります。
{トレーニング名の要素}.parentNode.children[0].firstChild
トレーニング名の要素のテキストが指定したテキストと一致する場合に、横にあるチェックボックスをクリックするJavaScriptを作成しました。
/**
* 指定したトレーニング名のチェックボックスをクリックします。
* @param {string} check_target:チェックを入れたいデータのトレーニング名(カンマ「,」区切りで複数指定可能)。空欄にすると何もチェックしません。
* @author osushi
*/
function click_specified_check_box(check_target){
const check_list = check_target.split(',');
// すべてのトレーニング名の要素を取得
let items = document.querySelectorAll(".training_menu");
// チェック処理
let training_name,target;
for (let i = 0; i < check_list.length; i++){
for (let j = 0; j < items.length; j++){
// トレーニング名の要素から空欄を取り除いたテキストを取得する
training_name = items[j].textContent.trim();
if (training_name == check_list[i]){
target = items[j].parentNode.children[0].firstChild;
// クリック対象の要素をログ出力で確認
console.log(target);
target.click();
console.log(training_name + " をチェックしました");
break;
}
}
}
}
指定したデータ名のチェックボックスをクリックできるようになりました。
【課題2】意図しない操作をしてしまう
ところが、以下の問題が発生しました。テスト実装者が間違って引数に4個以上のデータを渡すと、上限の3個よりも多い数をチェックできてしまいました。
どうやらチェックごとにチェック数の確認をし、3個になった時点でチェックボックスを非活性にする処理が入る画面のようです。手動操作では上記処理が間に合うため発生しませんが、JavaScriptの操作速度では意図しない挙動が発生してしまいました。
実装ミスを防止しておいたほうが良さそうです。そこで、引数に4個以上のデータが渡された場合は処理を停止するようにしました。
/**
* 指定したトレーニング名のチェックボックスをクリックします。
* 注意:アプリの仕様に合わせて、チェック数の上限は3個です。4個以上の引数を渡した場合はエラーで停止します。
* @param {string} check_target:チェックを入れたいデータのトレーニング名(カンマ「,」区切りで複数指定可能)。空欄にすると何もチェックしません。
* @author osushi
*/
function click_specified_check_box(check_target){
const check_list = check_target.split(',');
// 引数のチェック
// 引数に渡されたトレーニング名が4個以上の場合は処理を停止する
if (check_list.length > 3) {
console.log(check_list.length);
throw new Error('チェック対象のトレーニング名が4個以上指定されています。3個に修正してください。');
}
// すべてのトレーニング名の要素を取得
let items = document.querySelectorAll(".training_menu");
// チェック処理
let training_name,target;
for (let i = 0; i < check_list.length; i++){
for (let j = 0; j < items.length; j++){
// トレーニング名の要素から空欄を取り除いたテキストを取得する
training_name = items[j].textContent.trim();
if (training_name == check_list[i]){
target = items[j].parentNode.children[0].firstChild;
// クリック対象の要素をログ出力で確認
console.log(target);
target.click();
console.log(training_name + " をチェックしました");
break;
}
}
}
}
以下のエラーが表示されるようになり、実装者がミスをしてもすぐに気づくことができるようになりました。
複数人で作業をする場合、JavaScriptのコードを作成した人以外が利用することがあります。わかりやすい説明を書いたり、エラー処理を入れることで意図しない使用方法を防止することが大切です。
【課題3】通信が遅い場合に操作できない
何回か実行していると別の問題が発生しました。
チェックボックスがクリックされるたびにサーバーと何らかの通信をしているようで、その通信が完了するまでは画面操作を受け付けない仕組みのようです。そのため、通信速度が遅い場合にチェックボックスを連続してクリックできない現象が発生しました。
自動テスト実行時は、何らかの原因により処理が遅くなることがよくあります。不具合として検出すべきでない程度の遅延であれば、テストが失敗しないようにしておく必要があります。
対応策として、チェックボックスをクリックしたあとに2秒の待機を入れるようにしました。
/**
* 指定したトレーニング名のチェックボックスを2秒ごとにクリックします。
* 注意:アプリの仕様に合わせて、チェック数の上限は3個です。4個以上の引数を渡した場合はエラーで停止します。
* @param {string} check_target:チェックを入れたいデータのトレーニング名(カンマ「,」区切りで複数指定可能)。空欄にすると何もチェックしません。
* @author osushi
*/
function click_specified_check_box(check_target){
const check_list = check_target.split(',');
// 待機用の関数
const sleep = ms => new Promise(r => setTimeout(r, ms));
// 引数のチェック
// 引数に渡されたトレーニング名が4個以上の場合は処理を停止する
if (check_list.length > 3) {
console.log(check_list.length);
throw new Error('チェック対象のトレーニング名が4個以上指定されています。3個に修正してください。');
}
// すべてのトレーニング名の要素を取得
let items = document.querySelectorAll(".training_menu")
// チェック処理
let training_name,target;
const click_check_box = async () => {
for (let i = 0; i < check_list.length; i++){
for (let j = 0; j < items.length; j++){
// トレーニング名の要素から空欄を取り除いたテキストを取得する
training_name = items[j].textContent.trim();
if (training_name == check_list[i]){
target = items[j].parentNode.childNodes[1].firstChild;
// クリック対象の要素をログ出力で確認
console.log(target);
target.click();
console.log(training_name + " をチェックしました");
// 待機処理
console.log("2秒待機");
await sleep(2000);
console.log("2秒待機終了");
break;
}
}
}
}
return click_check_box();
}
安定して操作ができるようになりました。
おわりに
今回は以下の課題と解決案を解説しました。
【課題1】指定したチェックボックスをクリックできない
→ 解決策:特定のテキスト要素の横にあるチェックボックスをクリックする
【課題2】意図しない操作をしてしまう
→ 解決策:引数に上限を設定して、実装ミスを防止する
【課題3】通信が遅い場合に操作できない
→ 解決策:チェック操作毎に待機を入れる
JavaScriptを使えば、レコーディング機能やその他の機能ではできないテストが実装できたり、手順を節約したりすることもできます。(レコーディングでは10個の手順が必要な場合でも、JavaScriptを使用すると1個にできることがありました。)
コードでテストを実装する、というのは難しいイメージがあるかもしれません。
JavaScriptが使用できる自動化ツールは、ツールを提供している会社が公式のスニペット(JavaScriptの便利なコード)を公開していることがあります。
まずはこちらからはじめてみることをお勧めします。