開発者ブログ

3D画面のスクリーンショットを撮影する

Last modified by nakatani on 2016/11/07, 13:33

WebGLを利用した3D画面では、requestAnimationFrameを利用し、動的に画面が書き換わります。何かの操作を行ってもその反映はレンダリングのタイミングに依存し、いつ実行されるか分かりません。このような場合、Seleniumの非同期のスクリプトを利用してスクリーンショットを撮影することができます。

3D画面のテスト

非同期スクリプトを実行する

SeleniumのWebDriver経由でJavaのテストコードからJavaScriptのコードを実行するには二つの方法があり、1つはexecuteScriptを利用し同期メソッドやスクリプトを実行する方法、もう1つはexecuteAsyncScriptを利用し非同期のスクリプトを実行する方法です。executeAsyncScriptの利用はexecuteScriptと以下の点が異なります。

  • executeAsyncScriptは送信したJavaScriptコードが終了してもしばらく待機します。
  • executeAsyncScriptで実行されるJavaScriptコードのarguments変数の0番目は、この非同期関数を終了させるためのコールバック関数です。
  • arguments変数の0番目のコールバック引数に設定した値がexecuteAsyncScriptの戻り値となります。
executeAsyncScriptを使用した例
RemoteWebDriver driver = ...;

String script = "" +
   "var callback = arguments[0];" + // arguments[0]は非同期スクリプトを終了するためのコールバック
   "var arg0 = arguments[1];" + // argumentsの1番目以降がexecuteAsyncScriptの引数
   "var arg1 = arguments[2];" +
   "" +
   "setTimeout(function () {" +
   "  callback(arg0 + arg1);" +
   "}, 1000);";

Object result = driver.executeAsyncScript(script, "a", "b");

// resultは"ab"

このコードをexecuteScriptを使用して実行すると、setTimeoutが呼び出された時点で関数の実行が終了してしまいます。
それに対しexecuteAsyncScriptではsetTimeoutに渡した関数の実行を待機します。

Pitaliumの非同期スクリプトサポート

PitaliumではSeleniumの非同期スクリプトを利用するために以下の2つの機能をサポートしています。

  • EnvironmentConfig#scriptTimeoutは非同期スクリプトの最大実行時間です。  
    標準では30秒間、非同期スクリプトの実行を待機します。
    この値を変更するにはenvironmentConfig.jsonを変更します。
  • PtlWebDriver#executeAsyncJavaScriptはexecuteAsyncScriptをラップするメソッドです。
    JavaScriptから戻る値のキャストを不要にします。

テストを作ってみる

テストサイトについて

サンプル用に作成したテストサイトに対してPitaliumでスクリーンショットを撮影するコードを書いてみます。今回使用するサイトは three.js というWebGLライブラリを利用して3Dを描画しています。
テストサイトには床と3次元の軸と、空中に浮かぶ立方体が1つだけ存在しています。
またテストサイトの画面をマウスを使って動かすことができます。

テストコード

1.画面を開く

テストを実行するにはまずテストサイトを開きます。

driver.get("three_d");

2.初期状態のスクリーンショットを撮影する

画面を開いたばかりの状態のスクリーンショットを撮影します。

3DのレンダリングはrequestAnimationFrame(以下rAF)を利用して行われます。
しかしJavaのテストコードからはどのタイミングで呼び出されるのか判断することは出来ません。
executeAsyncScriptを利用すればrAFの検知が用意となります。

waitRendering.js
// Seleniumに非同期実行の完了を通知するコールバック
var callback = arguments[arguments.length - 1];

// 待機時間(ミリ秒)
var waitMillis = arguments.length > 2 ? arguments[0] : 0;

// 指定のミリ秒後にrequestAnimationFrameを呼び出すコールバックを実行する
setTimeout(function () {
  requestAnimationFrame(waitFirstRAF);
}, waitMillis);

/**
 * 指定ミリ秒待機した後の最初のrequestAnimationFrameで呼ばれる関数。
 * このrAFでWebGLの描画が更新される。
 */

function waitFirstRAF() {
  requestAnimationFrame(waitSecondRAF);
}

/**
 * 指定ミリ秒待機した後の二回目のrequestAnimationFrameで呼ばれる関数。
 * WebGLの描画が確実に更新されている。
 */

function waitSecondRAF() {
  callback();
}

waitRendering.jsは2回のrAFを利用して、指定ミリ秒経過後にWebGLのレンダリングが確実に行われたことを検出する非同期スクリプトです。
下記の様にexecuteAsyncScriptと組み合わせることでレンダリング後にスクリーンショットを撮影することができます。

// 画面が読み込まれた後のrAFを確実に待機する
driver.executeAsyncJavaScript(loadJS("waitRendering.js"), 0);
assertionView.assertView("0_初期状態");

ss_0.png

3.マウス操作を行う

Seleniumを利用してマウス操作を行うにはActionを利用します。
マウスを画面の左端から右端へ、左ボタンを押した状態で移動するには以下の様にします。

// 画面の幅と高さを取得
int width = (int) driver.getWindowWidth();
int height = (int) driver.getWindowHeight();

// Actionを使用してマウス操作を行う
new Actions(driver)
       // 画面全体の、横方向は左から全体の10%、縦方向は中心にマウスを移動する
       .moveToElement(driver.findElementByTagName("body"), width / 10, height / 2)
       // 左ボタンを押した状態にする
       .clickAndHold()
       // 画面の一番右まで移動させる(画面絶対位置ではなく相対距離を指定する)
       .moveByOffset(width * 9 / 10, 0)
       // 左ボタンを離す
       .release()
       .build()
       .perform();

4.マウス操作後のスクリーンショットを撮影する

上記のActionによるマウス操作後にスクリーンショットを撮影します。
実際には一瞬で操作は終わりますが、1秒(1000ミリ秒)後のWebGLレンダリングを待機した後に撮影します。

driver.executeAsyncJavaScript(loadJS("waitRendering.js"), 1000);
assertionView.assertView("1_変更後");

ss_1.png

5.テストコード全体

手順1~4までをまとめた全体のテストコードです。

public class ThreeDimensionsTest extends PtlTestBase {

   /**
     * 特定の名前のリソースファイルを読み込みます。
     *
     * @param fileName リソースファイル名
     * @return リソースファイルの中身(文字列)
     */

   private static String loadJS(String fileName) throws IOException {
       try (InputStream in = ThreeDimensionsTest.class.getResourceAsStream(fileName)) {
           return IOUtils.toString(in, StandardCharsets.UTF_8);
       }
   }

   @Test
   public void カメラの位置を変更() throws Exception {
       // 1.ページを読み込みます
       driver.get("");

       // 2.WebGLのレンダリングを待機してスクリーンショットを撮影します
       driver.executeAsyncJavaScript(loadJS("waitRendering.js"), 0);
        assertionView.assertView("0_初期状態");

       // 3.マウスを左から右へホールド状態で移動します
       int width = (int) driver.getWindowWidth();
       int height = (int) driver.getWindowHeight();
       new Actions(driver)
               .moveToElement(driver.findElementByTagName("body"), width / 10, height / 2)
               .clickAndHold()
               .moveByOffset(width * 9 / 10, 0)
               .release()
               .build()
               .perform();

       // 4.マウス移動後のWebGLレンダリングを待機してスクリーンショットを撮影します
       driver.executeAsyncJavaScript(loadJS("waitRendering.js"), 1000);
        assertionView.assertView("1_変更後");
   }

}

ソースコード

このサンプルのソースコードはGithubレポジトリの次のパスを参照ください。


Copyright (C) 2012-2017 NS Solutions Corporation, All Rights Reserved.