jQueryを用いたWebブラウザゲームの自動デバッグ

MoriKensukePosted by

こんにちは、サーバーサイドエンジニアの森です。
今回は弊社で開発しているWebブラウザゲームの新規イベント実装時にjQueryを用いて自動デバッグを行った話をします。

はじめに

ゲームの新規イベントのデバッグは大きな工数がかかります。
多くの場合はデバッガーの人が苦労してデバッグするのではないでしょうか。
ある時、弊社のあるプロジェクトの新規イベント開発途中に、テストするべきパターンが多すぎてとてもデバッグできないという事態が発生しました。
そこで「プログラムを使ってデバッグしよう!」という話になりました。

実装方法

みなさんはWebスクレイピングをご存知でしょうか?
Webサイトを解析して情報を抜き出し、欲しい情報を抽出する技術のことです。
ブラウザゲームはブラウザ上で動いているので、HTMLを解析することで現在の状態や次に飛ぶリンクを取得することができます。
これを利用して「ある状態Aの時はリンクBに飛ぶ」というパターンをプログラムに書くことで自動デバッグを行うことができます。

例えば、「仲間が助けを求めているよ!」というリンクがあったらそのリンクを踏む、といったプログラムはjQueryを使って以下のように書くことができます。

let target = $('a:contains("仲間が助けを求めているよ!")');
if (target.length > 0) {
  target[0].click();
}

Google ChromeのDeveloper Toolsを使うと簡単にプログラムを試すことができます。

Google ChromeのExtensionとして開発し、jQuery以外のライブラリは特に使わずに1000行足らずのプログラムを作り、これを社内の有志に配布してデバッグをしてもらいました。
使い方はChromeにインストールし、起動するだけです。

実装の詳細

よく使う操作

まず、実装する上でよく使う操作を関数にまとめました。
一部を紹介します。

ページ遷移

引数で指定したurlに遷移します。とてもよく使います。

/**
 * 1秒後に指定されたurlに遷移
 */
function jump(url) {
    if (url) {
        setTimeout(function () {
            location.href = url;
        }, 1000);
    }
}

// 使い方
jump('https://www.griphone.co.jp/'); // グリフォンのHPに遷移

ページ更新

現在見ているページをリロードします。
ページ上に何らかの変化が現れるのを待つときなどに使います。

/**
 * millisecondミリ秒後にページを更新
 */
function pageReload(millisecond) {
    return setTimeout(function () {
        location.reload();
    }, millisecond);
}

// 使い方
pageReload(5000); // 5秒後にページをリロード

要素の存在判定

文字列やリンク、画像などの存在判定に使います。

/**
 * cssセレクタにマッチするhtml要素が存在するか
 */
function isExistElement(element) {
    let element_object = $(element);
    return element_object.length > 0;
}

// 使い方
isExistElement('p:contains("今日はいい天気ですね")'); // 「今日はいい天気ですね」という文字列を含むp要素があるか

要素のクリック

リンクを踏む時に使います。
a要素やbutton要素が多いでしょう。

/**
 * cssセレクタにマッチする要素をクリック
 */
function clickElement(element) {
    let element_object = $(element);
    if (element_object.length <= 0) {
        return false;
    }

    element_object[0].click();
    return true;
}

// 使い方
clickElement('a#footer-1'); // idがfooter-1のa要素をクリック

実際のコード

続いて実際に使ったコードの一部を簡略化したものを紹介します。
urlやhtmlの要素は仮のものにしてあります。

while (true) {
    // トップページだったら
    if (url == 'http://mygame.jp/top') {
        // マイページに行く
        clickElement('.enter-mypage');
    }
    // イベントトップ
    else if (url == 'http://mygame.jp/event') {
        let is_enemy = isExistElement('p:contains("敵出現中!!")');
        // 敵がいなかったら
        if (!is_enemy) {
            // 敵が出るまで待つ
            pageReload(2000);
            continue;
        }

        // 対戦可能か?
        let is_exist = isExistElement('a[href="http://mygame.jp/battle"]');
        if (is_exist) {
            clickElement('a[href="http://mygame.jp/battle"]');
        }
    }
    // 結果画面だったら
    else if (url == 'http://mygame.jp/event/result') {
        // マイページに戻る
        jump('http://mygame.jp/mypage');
    }
    // 想定していないurlがきたらエラー
    else {
        throw new Error('不明なurlです ' + url);
    }
}

urlを見て処理を分岐し、urlにあった処理を書いていきます。
それぞれの処理の中では、html要素から状況を判断し、リンクを叩いたり更新することで次のアクションを行います。
基本的に上記を繰り返すことで、ユーザーがゲームを遊んでいるかのような状況を再現することができます。

実装が難しかった点

Webブラウザからは全てのDOM要素を取得できるわけではないので、一部対応できないページがありました。
例えば、要素を取得したい先がflashに含まれていると取得ができない場合があります。
この場合はDOM要素の取得を諦め、座標を決めうちしてクリックしたり、画像認識のような技術を使って色などで判別するなど別の方法をとる必要があります。

やってみてどうだったか

会社の空いているマシンを使い、1台あたり5つくらいchromeを立ち上げて3〜4台のPCで数日間回し続けました。
ゲームでバグが発生したらプログラムが止まるようにしておいたので、時々画面を見て止まっていたらサーバーのエラーログを見てバグを直し、また再開を繰り返しました。
結果いくつかのバグが見つかりましたが、そのうち一部は人力のデバッグだと発見が困難と思われるものだったので、やって正解でした。

最後に

以上のように、Webブラウザゲームの自動デバッグスクリプトを組むことでデバッグ工数を減らすことができました。
今回は新規イベントのデバッグでしたが、デバッグの手順が確立されているのであれば、既存箇所のデバッグにも使えます。
一般にUIのテストというのはサーバーサイドの単体テストに比べて難しいと思いますが、このような動作担保方法も有効ではないでしょうか。

最近はクライアントサイドでもツールによる人力を使わないデバッグの話を結構聞くようになりました。
エンジニアが改変せずとも自動で成長していくデバッグツールもあるそうです。
デバッグツールを使うことで、人間がデバッグしなくなる日も遠くないのかもしれません。