Seleniumのテストケースにスクリーン録画の機能を追加する

#Seleniumのテストケースにスクリーン録画の機能を追加する

Selenium ＋ Junitでのオートパイロットを利用していろいろな操作を自動化したり、Webアプリの納品前テストなどに利用したりと最近利用機会が増えているのですが、特に他人の作ったサービスの操作をSeleniumで自動化している場合にはページレイアウトの変更やtypoの修正などのちょっとした変更があっただけでエレメントの特定などができなくなってしまい、自動操作が続かなくなることも多々あります。
そんな時は大抵はもう一度目視で画面を監視しながら同じ操作を行わせ、どこで不具合が発生しているのかを確認するわけですが、場合によっては、それは稀にしか起きない現象だったり、再現に時間がかかるものだったりと、「実際に現象が起きた時のスクリーンの状態を録画できたらべんり出来たら便利だろうな」と思っていたらQiitaで次の記事を見つけました。

[Selenium(Selenide)で実行時の録画を行う](https://qiita.com/shimashima35/items/0575ac5488edd6942d5a)

で、実際にこの記事にかかれている手法を取り入れてみたら思った以上に修正作業の効率アップを図れたので自分のコードにスクリーン録画機能を組み込む際の手順を纏めてみました。

スクリーンの録画に必要なクラスをインポート

```java:
import java.io.File;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.monte.media.Format;
import org.monte.media.math.Rational;
import static org.monte.media.FormatKeys.*;
import static org.monte.media.VideoFormatKeys.*;
```

スクリーン録画用のメソッドとルールを定義

2行目の`File file = new File("record");`の部分で録画ファイルを格納するフォルダを指定できます。
14行目からのメソッド`ScreenRecorder`へと与えるパラメータの`EncodingKey`と`CompressorNameKey`で[Monte Media Library](http://www.randelshofer.ch/monte/)による録画の際のエンコーディングとコンプレッサー（圧縮方式)を指定できます。
Monte Media Libraryでは以下の参考URLにあるようなエンコーディングとコンプレッサーが使える様ですが、どの組み合わせが有効なのかについての記述が見当たらなかったので、その辺りは試行錯誤です。

自分が試行錯誤した結果ではエンコーディングとコンプレッサー共にサンプル中で指定している[TechSmith Screen Capture Codec](https://www.techsmith.com/codecs.html)(`ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE`)を使った場合が最も録画ファイルのサイズが小さくなったのですが、`ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE`で録画されたファイルの再生には専用のプレーヤーかQuickTime Player用の[追加コーデック](https://www.techsmith.com/codecs.html)のインストールが必要となります。
※macOSの場合、このぺーじの`EnSharpen decoder`の中から自分の環境に合わせたパッケージをインストールすれば`TechSmith Screen Capture Codec`の録画ファイルの再生が可能です。

QuickTime Player用追加コーデック(TechSmith Screen Capture Codec)
[https://www.techsmith.com/codecs.html](https://www.techsmith.com/codecs.html)

Monte Media LibraryのJavaDocページ
[http://www.randelshofer.ch/monte/javadoc/index.html](http://www.randelshofer.ch/monte/javadoc/index.html)

エンコーディングに`ENCODING_QUICKTIME_CINEPAK`をコンプレッサーに`COMPRESSOR_NAME_QUICKTIME_CINEPAK`を指定した場合にはmacOSのQuickTime Playerでそのまま再生が可能な状態で録画ファイルが作られるのですが、TechSmith Screen Capture Codecを使用した場合に比べてファイルのサイズはかなり大きくなるのでエンコーディングやコンプレッサーの指定は適材適所で工夫してみてください。

```java:
  public void startRecording(String name) throws Exception {
    File file = new File("record");

    Dimension screenSize = driver.manage().window().getSize();
    int width = screenSize.width;
    int height = screenSize.height;

    java.awt.Rectangle captureSize = new java.awt.Rectangle(0,0, width, height);

    GraphicsConfiguration gc = GraphicsEnvironment
      .getLocalGraphicsEnvironment()
      .getDefaultScreenDevice()
      .getDefaultConfiguration();

    this.screenRecorder = new ScreenRecorder(gc, captureSize,
      new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_QUICKTIME),
      new Format(MediaTypeKey, MediaType.VIDEO, EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE,
        CompressorNameKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE,
        DepthKey, 24, FrameRateKey, Rational.valueOf(15),
        QualityKey, 1.0f,
        KeyFrameIntervalKey, 15 * 60),
      new Format(MediaTypeKey, MediaType.VIDEO, EncodingKey, "black",
        FrameRateKey, Rational.valueOf(30)),
      null, file, name);
    this.screenRecorder.start();
  }

  public void stopRecording() throws Exception {
    this.screenRecorder.stop();
  }

  private ScreenRecorder screenRecorder;

  @Rule
  public TestName testName = new TestName();

  @Rule
  public TestWatcher watchman = new TestWatcher() {

    @Override
    protected void succeeded(Description d) {
      System.out.println("I am succeeded() method. name -> " + d.getMethodName());
      // テストに成功しても録画ファイルを残すなら次の1行をコメントアウト
      screenRecorder.getCreatedMovieFiles().stream().filter(a -> a.toPath().toString().contains(testName.getMethodName())).forEach(a -> a.deleteOnExit());
    }

    @Override
    protected void failed(Throwable th, Description d) {
      System.out.println("I am failed() method. name -> " + d.getMethodName());
      System.out.println(th.toString());
    }
  };
```

初期化処理にスクリーン録画の開始メソッド呼び出しを追加

```java:
  @Before
  public void setUp() throws Exception {

    driver = new FirefoxDriver();

    baseUrl = "https://console.firebase.google.com";
    driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    startRecording(testName.getMethodName());
  }
```

```java:
  @After
  public void tearDown() throws Exception {
    // テスト終了後すぐに録画を止めると結果が最後まで録画されないことがあるのでスリープ
    Thread.sleep(5000);
    stopRecording();
    driver.quit();
    String verificationErrorString = verificationErrors.toString();
    if (!"".equals(verificationErrorString)) {
      fail(verificationErrorString);
    }
  }
```

mavenのプロジェクトファイル(pom.xml)に[Monte Media Library](http://www.randelshofer.ch/monte/)のrepositoryとdependencyを追加

```xml:pom.xml
  <repositories>
      ...
    <repository>
      <id>additional-repo1</id>
      <name>Alfresco Public Repository</name>
      <url>https://artifacts.alfresco.com/nexus/content/repositories/public/</url>
    </repository>
  </repositories>
```

```xml:pom.xml
  <dependencies>
    <dependency>
       ...
    </dependency>
    <dependency>
      <groupId>org.monte</groupId>
      <artifactId>monte-screen-recorder</artifactId>
      <version>0.7.7</version>
    </dependency>
  </dependencies>
```

スクリーンショット用のクラスを追加

Monte Media LibraryのScreenRecorderを継承して、createMovieFileをOverrideすることで録画ファイルを出力する場所とファイル名をmovieFolderで指定されたフォルダ以下のname ＋ yyyy-MM-dd HH.mm.ss（日付と時刻)へと変更しています。

```Java:ScreenRecorder.java
package net.hoge2.test;

import java.awt.AWTException;
import java.awt.GraphicsConfiguration;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.monte.media.Format;
import org.monte.media.Registry;

public class ScreenRecorder extends org.monte.screenrecorder.ScreenRecorder {
  private String name;

  public ScreenRecorder(GraphicsConfiguration cfg,
    Rectangle captureArea, Format fileFormat, Format screenFormat,
    Format mouseFormat, Format audioFormat, File movieFolder,
    String name) throws IOException, AWTException {
    super(cfg, captureArea, fileFormat, screenFormat, mouseFormat,
      audioFormat, movieFolder);
    this.name = name;
  }

  @Override
  protected File createMovieFile(Format fileFormat) throws IOException {
    if (!movieFolder.exists()) {
      movieFolder.mkdirs();
    }
    else if (!movieFolder.isDirectory()) {
      throw new IOException("\"" + movieFolder + "\" is not a directory.");
    }

    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH.mm.ss");
    return new File(movieFolder,
      name + "-" + dateFormat.format(new Date()) + "."
      + Registry.getInstance().getExtension(fileFormat));
  }
}
```

Seleniumでのテストケースの実行中にスクリーンを録画するのに[Monte Media Library](http://www.randelshofer.ch/monte/)を使っていますが、このライブラリはリモートでsshログインなどを行なった状態でテストケースの実行を行なおうとするとエラーになるので、リモートログインしての実行が必要な場合には関連する箇所をコメントアウトする必要があります。