Katalon Studio+MaterialsでWebサイトのスクリーンショットを整然と保存する


# はじめに

わたしはWebアプリケーションのUIをテストとする作業をソフトウェアで自動化する技術に関心があります。2018年1月以来わたしは [Katalon Studio](https://www.katalon.com/) というソフトウェアに注目しています。「[Katalon Studioとはどんなソフトウエアか](https://qiita.com/kazurayam/items/135cb30233d11c2f4840)」で概要を紹介しました。

Katalon Studioを使いはじめて真っ先にやったのがWebサイトのページのスクリーンショットを片っ端から撮ることでした。やってみてわかった。WebDriverでスクリーンショットを撮ることは簡単。しかしファイルを整理整頓するのがえらく面倒だということ。さてどうしたものかとあれこれ解決を試みてはや９ヶ月。やっと解決方法を見いだすことができました。

なおこの記事は GitHub上のレポジトリ [`UsingMaterialsInKatalonStudio`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio) のREADMEを日本語で書きなおしたものです。

# 概要

わたしは['Materials'](https://github.com/kazurayam/Materials)というプロジェクトを開発しGitHubで公開しました。このプロジェクトはGroovy言語で書かれておりjarファイルを作り出します。jarファイル の名前は `Materials-x.x.x.jar` です。[`Releases`](https://github.com/kazurayam/Materials/releases)ページからjarをダウンロードできます。このjarファイルは[Katalon Studio](https://www.katalon.com/) に組み込んで利用することを想定しています。

Katalon StudioのテストスクリプトがWebサイトのスクリーンショットを撮り画像ファイルとしてローカルディスクに保存する時に、保存先ファイルのパスを決定する処理と出来たファイルの一覧HTMLを出力する処理を[Materials](https://github.com/kazurayam/Materials)ライブラリに任せることができます。そのやり方をこの記事で説明します。

具体的なサンプルコードがGitHubの [`UsingMaterialsInKatalonStudio`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio) にあります。これはKatalon Studioのプロジェクトです。[Releases](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/releases)ページからZIPファイルを自分のPCにダウンロードして解凍し、Katalon Studioを立ち上げてプロジェクトを開いてください。そして下記の説明を読みながらテストケースやテストスイートを実行してみてください。

# 解決したい問題

わたしが 'material'（素材）と呼んでいるモノが何かというと、テストスクリプトが実行時に作り出したファイルのことです。たとえばWebDriverのAPIを使って撮ったWebページのスクリーンショットとか。その他にもサイトからダウンロードしたPDFファイル、テストスクリプトが生成したExcelファイル、RESTful APIが応答したXML/JSONをファイルに保存すればそれもmaterialのうちです。

Katalon StudioのテストスクリプトはSelenium WebDriverを利用してWebサイトから情報を取り出すことができます。取り出した情報をファイルをして保存したい。しかしどこに保存すればいいのか？この問題をWebDriverは解決してくれません。テストスクリプトの作者がそれぞれにファイルのPathを決めなければなりません。

その場かぎりのファイルのPathを指定するのはどおってことありません。たとえば `C:\Users\myname\tmp\sample_screenshot.png` とすればいいでしょう。ところが何十個ものファイルを作り、出来たmaterialを再利用したいならことはそう単純ではありません。たとえば自分のWebサイトのスクリーンショットを30枚撮って、本番環境と開発環境とそれぞれについて撮ろう。撮った後で見比べてどこか見た目違っているところがないか探し出したい。

「見比べる」という処理を実現するには比べるべきファイル2つを適切なPathに位置付けておくことが重要です。つまりmaterialのPathの形式を決めることが必要で、かつ、materialのレポジトリ(集合体)へのアクセスを実現したユーティリティ的なライブラリがほしい。

# 解決方法

[Materials](https://github.com/kazurayam/Materials)ライブラリは [`com.kazurayam.materials.MaterialRepository`](https://kazurayam.github.io/Materials/com/kazurayam/materials/MaterialRepository.html) オブジェクトを提供します。Katalon Studioのテストスクリプトが`MaterialRepository`オブジェクトのメソッドをcallしてmaterialのPathを決めさせることができます。Pathの形式では下記の通りです。

`${projectDir}/Materials/${testSuiteName}/${testSuiteTimestamp}/${testCaseName}/${subdirs}/${fileName}`

たとえば

`C:\Users\myname\project\Materials\TS07_visit_a_web_site\20180919_132138\TC07_visit_a_web_site\1 CURA_Homepage.png`

Windowsのエクスプローラーで Materials フォルダを見るとたとえば下記のようなファイル・ツリーを見ることができます。  
![TSC10_marked.png](https://qiita-image-store.s3.amazonaws.com/0/52995/a6bf163f-df20-42e7-7887-4085399f2ef0.png)

また テストスクリプトが `com.kazurayam.materials.MaterialRepository` の `makeIndex()` メソッドをcallすれば `${projectDir}/Materials/index.html` ファイルが作られます。このHTMLファイルはMaterialフォルダの内容を表示するビューワーで、リンクをクリックするだけの簡便な操作でファイルを開いて見ることができます。index.htmlをブラウザで開いた様子を下記に示します。

| index.html | screen shot in modal |
|:-----------|:---------------------|
| ![index.png](https://qiita-image-store.s3.amazonaws.com/0/52995/0bae6eab-f642-493a-c354-c925197c996d.png) | ![index_modal.png](https://qiita-image-store.s3.amazonaws.com/0/52995/b2a2feab-18d4-761a-14a8-19657e70b654.png)
 |

# 準備

GitHubの [`UsingMaterialsInKatalonStudio`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio) のソースのZIPを[`Release`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/releases) ページからダウンロードすることができます。

このプロジェクトは Materials のjarファイルに依存するのですが実は`${projectDir}/Drivers/Materials-0.1８.0.jar` がすでに組み込んであります。

# サンプルコードの説明

[`UsingMaterialsInKatalonStudio`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio)プロジェクトに含まれるテストスクリプトについて順をおって説明します。

## Test Cases/TC01_starter

ここでは、スクリーンショットをファイルに出力するにあたってテストスクリプトがPathを具体的な文字列によって指定する方法を説明します。

### ソース

[TC01_starter](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC01_starter/Script1536633564054.groovy)を参照のこと

### テストケースを実行する

Katalon Studioでプロジェクトを開き `Test Cases/TC01_starger` を開いてrunボタンを押します。
![run_TC01.png](https://qiita-image-store.s3.amazonaws.com/0/52995/a6c875cc-ff57-31d4-d172-a3f968f91d35.png)

### 出力

`TC01_starter`を実行すると下記のようにファイルが出力されます。

```
$ cd UsingMaterialsInKatalonStudio/
$ tree tmp
tmp
└── TC01_screenshot.png
```

### テストケースの説明

このテストケースはWebページ [http://demoaut.katalon.com](http://demoaut.katalon.com) をブラウザで開いてスクリーンショットを撮り、PNG画像を  `${UsingMaterialsInKatalonStudio}/tmp/TC01_screenshot.png` ファイルにWRITEします。

`TC01_starter`のなかの下記の行を見ればわかるように、出力先であるファイルのPathを明示的に指定しなければなりません:

```
Path pngFile = tmpDir.resolve('TC01_screenshot.png')
WebUI.takeScreenshot(pngFile.toFile().toString())
```

見てのとおり `TC01_starter` スクリプトはMaterialsライブラリを使っていません。このスクリプトは出発点です。ここから先、少しずつコードを書きかえていきます。Materialsの使い方を順を追って説明していきます。

テストケース `TC01_starter` を実行するとプロジェクトのディレクトリ直下に新しいディレクトリが作られます。すなわち: `UsingMaterialsInKatalonStudio/tmp` です。このディレクトリの中身をKatalon StudioのGUIのなかで閲覧することは残念ながらできません。別のGUIツールを使って見るしかありません。Windowsなら Explorer、MacならFinderないしはTerminalとか EmacsとかvimとかAtomとか。

`TC01_starter`スクリプトはJava 8のライブラリ `java.nio.Path`、`java.nio.Paths`、 `java.nio.Files`を多用します。`java.io.File`は使いません。 Java 8 でファイルのPathを扱うには `java.nio.file.*` が王道です。

## Test Cases/TC02_MaterialRepository

ここでは、`com.kazurayam.materials.MaterialRepository` オブジェクトを使ってスクリーンショットのファイルのPathを決めるという方法を説明します。

### ソース

[TC02_MaterialsRepository](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC02_MaterialRepository/Script1536642272611.groovy) を参照のこと

#### 抜粋

```
import com.kazurayam.materials.MaterialRepository
import com.kazurayam.materials.MaterialRepositoryFactory
// descide in which directory we will create a MaterialRepository
Path materialsDir = Paths.get(RunConfiguration.getProjectDir()).resolve('Materials')

// create an instance of MaterialRepository
MaterialRepository mr = MaterialRepositoryFactory.createInstance(materialsDir)

// resolve the location of PNG file to save the screenshot
Path pngFile = mr.resolveMaterialPath('TC02_MaterialRepository', 'TC02_screenshot.png')

// take a screenshot of the page
WebUI.takeScreenshot(pngFile.toFile().toString())
```

### 説明

テストケース `TC02_MaterialRepository` はWebページ http://demoaut.katalon.com のスクリーンショットを撮り、ファイルに出力します。ファイルは `${UsingMaterialsInKatalonStudio}/Materials` ディレクトリの下に出力されます。ファイルのパスは `${UsingMaterialsInKatalonStudio}/Materials/_/_/TC02_MaterialRepository/TC02_screenshot.png` というふうになります。.

抜粋されたテストケースのコードのなかで `resolveMaterialPath(String testCaseName, String fileName)` が呼び出されています。このメソッド呼び出しがmaterialのPathを決定します。Pathが決定されると同時に長さゼロのファイルが作成されます。そのあとで `WebUI.takeScreenshot(pngFile.toFile().toString())` が実行されることにより、Webページのスクリーンショット画像がファイルに出力されます。

### テストケースを実行する

Katalon StudioのGUIを起動し `Test Cases/TC02_MaterialRepository` を開いて実行ボタン![run_button.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/b4b20e47-a74e-3b25-5394-3b4d58d56a28.png)を押します。

### 出力

テストケースを実行すると下記のようなフォルダツリーが生成されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
└── _
    └── _
        └── TC02_MaterialRepository
            └── TC02_screenshot.png
```

ここで `Materials/_/_/` というフォルダが作られました。ここでアンダーバー '_' という妙な名前のフォルダが作られたのは何故か？といぶかる人がいるでしょう。後述する `Test Suites/TS06_GlobalVariable.MATERIAL_REPOSITORY` の場合には `Materials/TS06_GlobalVariable.MATERIAL_REPOSITORY/20180918_164113` というフォルダが作られます。つまり最初のアンダーバーはテストスイート名に対応し、二番目のアンダーバーはテストスイートが実行された時刻に対応します。ところKatalon StudioのGUIからテストケースを選んで実行した場合にはテストスート名もテストスイート実行時刻も不定になりますので、その場合はフォルダ名にアンダーバーが採用されます。


## Test Cases/TC03_subdirectories_under_testCaseName

ここでは、スクリーンショットのファイルを任意のサブディレクトリを作ってその下に格納するという方法を説明します。

### ソース

[TC03_subdirectories_under_testCaseName](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC03_subdirectories_under_testCaseName/Script1537147371115.groovy) を参照のこと。

#### 抜粋

書き方その１
```
Path pngFile = mr.resolveMaterialPath('TC03_subdirectories_under_testCaseName', 'subdirA/subdirA/TC03_screenshot.png')
```

書き方その２
```
// make one more file
pngFile = mr.resolveMaterialPath('TC03_subdirectories_under_testCaseName', Paths.get('subdirB/subdirB'), 'TC03_screenshot.png')
```

### 説明

`MaterialRepository` オブジェクトの `resolveMaterialPath()` メソッドをcallするときに第2引数として指定した文字列に `subdirA/subdirB/` という文字列が指定されていることに注意。これはサブディレクトリを表します。このように指定すればファイルをサブディレクトリの下に格納することができます。

書き方その２を参照。resolveMaterialPath()メソッドにはもうひとつ書き方があります。つまり `resolveMaterialPath(String testCaseName, Path subdirs, String fileName)` という書き方。どちらもOKです。


### テストケースを実行する

Katalon Studio の GUI を起動してテストケース `TC03_subdirectories_under_testCaseName` を開きます。そして実行ボタン![run_button.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/b4b20e47-a74e-3b25-5394-3b4d58d56a28.png)を押します。

### 出力

テストケースを実行すると下記のようなフォルダツリーが出力されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
└── _
    └── _
        └── TC03_subdirectories_under_testCaseName
            ├── subdirA
            │   └── subdirA
            │       └── TC03_screenshot.png
            └── subdirB
                └── subdirB
                    └── TC03_screenshot.png
```
テストケース名 `TC03_subdirectories_under_testCaseName` と同じ名前のフォルダの下に `subdirA/subdirA` とか `subdirB/subdirB` といったサブフォルダが作られていることに着目してください。

## Test Cases/TC04_URL-based_filename

ここでは、スクリーンショットのファイル名をWebページのURLに基づいて決定するという方法を説明します。

### ソース

[TC04_URL-based_filename](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC04_URL-based_filename/Script1537147401510.groovy)

#### 抜粋

```
String urlString = 'http://demoaut.katalon.com'
...
File pngFile = mr.resolveScreenshotPath('TC04_URL-based_filename', new URL(urlString))
...
pngFile = mr.resolveScreenshotPath('TC04_URL-based_filename', Paths.get('subdir'), new URL(urlString))
```

### テストケースの説明

Webページのスクリーンショットを撮ったあとファイルに保存するときどういうファイル名にしようか？たとえば 'sample1.png' とか 'demoX.png' でももちろんかまわない。しかしいちいちファイル名を案出するのが面倒ならば、WebページのURLをそのままファイル名にしてしまうのが一番簡単です。

[`com.kazurayam.materials.MaterialRepository`](https://kazurayam.github.io/Materials/com/kazurayam/materials/MaterialRepository.html) オブジェクトは `resolveScreenshotPath(String testCaseName, URL url)` と `resolveScreenshotPath(String testCaseName, Path subdir, URL url)` というメソッドも提供します。メソッド呼び出し引数に `java.net.URL` が含まれていることに注意。このURLに基づいてファイル名が導出されます。URLに含まれる特殊文字は URLエンコードされてファイル名としてふさわしい文字に変換されます。たとえば

`http://demoaut.katalon.com/` --> `http%3A%2F%2Fdemoaut.katalon.com%2F`

というふうに。

### 出力

テストケース TC04_URL-based_filename を実行すると下記のようなツリーが出力されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
└── _
    └── _
        └── TC04_URL-based_filename
            ├── http%3A%2F%2Fdemoaut.katalon.com.png
            └── subdir
                └── http%3A%2F%2Fdemoaut.katalon.com.png
```

## Test Cases/TC05_GlobalVariable.CURRENT_TESTCASE_ID

テストケースのコードが自分自身の名前を参照したい場合があります。たとえば `resolveMaterialPath()` メソッドが第一引数としてテストケース名を指定しなければならない、とか。ところがKatalon Studioではテストケース名を簡単に調べる方法が無くて、少しまわり回りくどいコードを書かねばなりません。その答えをここで紹介します。

### ソース

[`MyTestListener`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Test%20Listeners/MyTestListener.groovy) と [`TC05_GlobalVariable.CURRENT_TESTCASE_ID`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC05_GlobalVariable.CURRENT_TESTCASE_ID/Script1536640253323.groovy) を参照のこと。

### 説明

まず、Katalon StudioのGUIでProfiles>defaultを選択して、`CURRENT_TESTCASE_ID` という名前の GlobalVariable を定義します。データ型はString、初期値は '' としましょう。
![Profile_default_CURRENT_TESTCASE_ID.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/8cbd7434-d8b8-6801-87e0-66ad3d96965f.png)

つぎにテストリスナー `MyTestListerner` を作ります。`@BeforeTestCase` でアノテートしたメソッドを作ります。ここで「いまのTest Caseの名前」を取り出して `GlobalVariable.CURRENT_TESTCASE_ID` に設定します。

```
@BeforeTestCase
def beforeTestCase(TestCaseContext testCaseContext) {
    GlobalVariable.CURRENT_TESTCASE_ID = testCaseContext.getTestCaseId()
```

いっぽうテストケース `TC05_GlobalVariable.CURRENT_TESTCASE_ID` はGlobalVariableを参照してテストケース名を認識します。

```
String testCaseId = (String)GlobalVariable.CURRENT_TESTCASE_ID
assert testCaseId != null
assert testCaseId.length() > 0
Path pngFile = mr.resolveMaterialPath(testCaseId, 'TC05_screenshot.png')
```

前述のテストケース 'TC02_MaterialRepository' ではテストケース名を文字列定数としてハードコードしていましたが、GlobalVariableを利用したこのコーディング手法を用いればテストケース名をハードコードする必要が無くなります。

### 出力

テストケース `TC05_GlobalVariable.CURRENT_TESTCASE_ID` を実行すれば下記のようなツリーが出力されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
└── _
    └── _
        └── TC05_GlobalVariable.CURRENT_TESTCASE_ID
            └── TC05_screenshot.png
```

## Test Suites/TS06_GlobalVariable.MATERIAL_REPOSITORY

ここまでは、`com.kazurayam.materials.MaterialRepository`オブジェクトをテストケースの冒頭で生成しテストケースの終了とともに破棄していました。しかしテストスイート `TS06_GlobalVariable.MATERIAL_REPOSITORY` ではテストリスナーが `com.kazurayam.materials.MaterialRepository` オブジェクトを生成してGlobalVariableに保存し、テストケースがGlobalVariableを参照するというやり方を導入します。これによって`com.kazurayam.materials.MaterialRepository` オブジェクトを生成するコードをテストケース群のあちこちに散らかすのではなく、TestListener一箇所に集約することができます。全体としてコードが短くなり、読みやすくメンテナンスしやすくなります。

### ソース

GlobalVariable として `MATERIAL_REPOSITORY` を定義します。 ![GlobalVariable.MATERIAL_REPOSITORY.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/5dd6bd07-273a-45b6-7cb5-11838107d64e.png)

ここで注意。 `MATERIAL_REPOSITORY` の型として Null を指定します。GUI上は Null という名前を選択しますが、Groovyソースコードを見ると `MATERIAL_REPOSITORY` 変数が `java.lang.Object` 型のオブジェクトとして宣言されることがわかります。`MATERIAL_REPOSITORY` をStringなど他の型で宣言してはいけません。

テストリスナー `Test Listeners/MyTestListener.groovy` を見ましょう。ここで `com.kazurayam.materials.MaterialRepository` オブジェクトを生成し、`GlobalVariable.MATERIAL_REPOSITORY` に格納します。

`@BeforeTestSuite` とアノテーションされたメソッドでこうします:

```
@BeforeTestSuite
def beforeTestSuite(TestSuiteContext testSuiteContext) {
    // prepare instance of MaterialRepository
    // The directory 'Materials' will be created if not present by the MaterialRepository
    MaterialRepository mr = MaterialRepositoryFactory.createInstance(materialsDir)

    // Find out the Test Suite ID
    String testSuiteId = testSuiteContext.getTestSuiteId()

    // Find out the Test Suite Timestamp
    Path reportDir = Paths.get(RunConfiguration.getReportFolder())
    String testSuiteTimestamp = reportDir.getFileName().toString()
    // e.g. '20180618_165141'

    // inform the MaterialRespository of the current Test Suite
    mr.putCurrentTestSuite(testSuiteId, testSuiteTimestamp)

    // save the instance into GlobalVariable so that it is visible
    // for all test cases activated by the Test Suite
    GlobalVariable.MATERIAL_REPOSITORY = mr
```

次の一行があやしげです:

```
mr.putCurrentTestSuite(testSuiteId, testSuiteTimestamp)
```

これが何をやっているかはあとで「出力」の節で説明します。

もう一箇所 `@BeforeTestCase` とアノテートされたメソッドでこうします:

```
@BeforeTestCase
def beforeTestCase(TestCaseContext testCaseContext) {
    ...
    // prepare an instance of MaterialRepository
    // The directory 'Materials' will be created if not present by the MaterialRepository
    // This is necessary for the case a Test Case is executed directly without being wrapped by a Test Suite.
    if (GlobalVariable.MATERIAL_REPOSITORY == null) {
        // If wrapped by a Test Suite, the handler method annotated with @BeforeTest suite will be called
        // and it will instantiate a MaterialRepository and store it into GlobalVariable. Therefore
        // we check if the GlobalVariable is null or not. We will instantiate new one
        // only when the GlobalVariable is null.
        MaterialRepository mr = MaterialRepositoryFactory.createInstance(materialsDir)
        GlobalVariable.MATERIAL_REPOSITORY = mr
    }
}
```

テストリスナーによって `GlobalVariable.MATERIAL_REPOSITORY` に `com.kazurayam.materials.MaterialRepository` オブジェクトが格納されました。

いっぽうでテストケース `TC06_GlobalVariable.MATERIAL_REPOSITORY` がGlobalVaribleを参照します。下記のように:

```
MaterialRepository mr = (MaterialRepository)GlobalVariable.MATERIAL_REPOSITORY
assert mr != null
...
Path pngFile = mr.resolveMaterialPath(testCaseId, 'TC06_screenshot.png')
```

テストケース [TC06_GlobalVariable.MATERIAL_REPOSITORY](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC06_GlobalVariable.MATERIAL_REPOSITORY/Script1536640238920.groovy) と [TC07_visiting_a_web_site](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC07_visit_a_web_site/Script1537330820326.groovy) と [TC08_makeIndex](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC08_makeIndex/Script1536651022281.groovy) --- これらはもはや `com.kazurayam.materials.MaterialRepository` オブジェクトを自分で作成しません。そのかわりテストリスナー `MyTestListener` が作成し、テストケースはGlobalVariablを参照する、というやり方をとっています。

### テストスイートの実行の仕方

Katalon Studioでテストスイートを実行するには下記の操作をします。

1. Katalon StudioのGUIでテストスイート `TS06_GlobalVariable.MATERIAL_REPOSITORY` を開きます。
2. runボタン(ツールバーのなかの緑矢印)を押します ![TS06.png](https://qiita-image-store.s3.amazonaws.com/0/52995/e3bd35bf-9773-998b-50a4-bf39b528648f.png)


### 出力

テストスイート `TS06_GlobalVariable.MATERIAL_REPOSITORY` を実行すると下記のようなツリーが出力されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
└─TS06_GlobalVariable.MATERIAL_REPOSITORY
    └─20180918_164113
        └─TC06_GlobalVariable.MATERIAL_REPOSITORY
                TC06_screenshot.png
```

ここで注目。`TS06_GlobalVariable.MATERIAL_REPOSITORY` というディレクトリと `20180918_164113` というディレクトリが作られている。テストスイート名と実行時タイムスタンプにほかなりません。タイムスタンプの書式は `yyyyMMdd_hhmmss` です。テストスイートが二度実行されれば別別のタイムスタンプになりますからディレクトリが２つ別別に作られます。

このタイムスタンプの値がどうやって決定されるのか？じつは テストリスナー`MyTestListener`　の `@BeforeTestSuite` とアノテートされたメソッドのなかで `com.kazurayam.materials.MaterialRepository` オブジェクトに対してテストスイート名とタイムスタンプ値を教えていました。下記の一行がそれです:

```
    mr.putCurrentTestSuite(testSuiteId, testSuiteTimestamp)
```

タイムスタンプにもとづくディレクトリの階層があるので、過去にテストスイートが実行された時に生成されたすべてのmaterialsを時系列に保存されます。こうして過去分のテストスイートの実行結果を再利用することが可能になります。

このようなディレクトリツリーの構造はKatalon Studioの `Reports` ディレクトリの構造を真似したものです。
![Reports.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/f463b3ff-67d0-f588-bd4e-dd2f06c4bf3a.png)



## Test Suites/TS07_visit_a_web_site

テストスイート `TS07_visit_a_web_site` は http://demoaut.katalon.com/ を入り口とするWebサイトを訪問してスクリーンショットを5つ作成します。PNGファイルのPathは下記のような形式をとります。

`./Materials/TS07_visit_a_web_site/`*yyyyMMdd_hhmmss*`/TC07_visit_a_web_site/`*fileName*`.png`

実際にWebサイトを訪問するコードはテストケース [`TC07_visit_a_web_site`](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC07_visit_a_web_site/Script1537330820326.groovy) に書いてあります。このテストケースはKatalon StudioでひとつのWebサイトを巡ってスクリーンショットを撮る操作の実例となるものです。

### 出力

テストスイート `TS07_visit_a_web_site` を実行すると下記のようなツリーが出力されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
└─TS07_visit_a_web_site
    ├─20180919_132138
    │  └─TC07_visit_a_web_site
    │          1 CURA_Homepage.png
    │          2 CURA_Login.png
    │          3 CURA_Appointment.png
    │          4 CURA_AppointmentConfirmation.png
    │          5 CURA_Homepage_revisited.png
    │          
    └─20180919_132139
        └─TC07_visit_a_web_site
                1 CURA_Homepage.png
                2 CURA_Login.png
                3 CURA_Appointment.png
                4 CURA_AppointmentConfirmation.png
                5 CURA_Homepage_revisited.png
```

テストスイートのタイムスタンプにもとづいて2つのディレクトリができ、各々のなかに5つのスクリーンショットが保存されていることに注目してください。


## Test Suites/TS08_makeIndex

`com.kazurayam.materials.MaterialRepository` オブジェクトの `makeIndex()` メソッドをcallすれば `./Materials/index.html` ファイルが生成されます。materialファイルの一覧画面です。

### ソース

[TC08_makeIndex](https://github.com/kazurayam/UsingMaterialsInKatalonStudio/blob/master/Scripts/TC08_makeIndex/Script1536651022281.groovy) を参照のこと。

#### 抜粋

```
MaterialRepository mr = (MaterialRepository)GlobalVariable.MATERIAL_REPOSITORY

// make ./Material/index.html file
mr.makeIndex()
```
これだけです。

### 出力

テストスイート `TS08_makeIndex` を実行すると下記のようなツリーが出力されます。

```
$ cd UsingMaterialsInKatalonStudio
$ tree Materials
Materials
│  index.html
│  
└─TS07_visit_a_web_site
    ├─20180919_132139
    │  └─TC07_visit_a_web_site
    │          1 CURA_Homepage.png
    │          2 CURA_Login.png
    │          3 CURA_Appointment.png
    │          4 CURA_AppointmentConfirmation.png
    │          5 CURA_Homepage_revisited.png
    │          
    └─20180919_135957
        └─TC07_visit_a_web_site
                1 CURA_Homepage.png
                2 CURA_Login.png
                3 CURA_Appointment.png
                4 CURA_AppointmentConfirmation.png
                5 CURA_Homepage_revisited.png
```

Webブラウザで `index.html` ファイルを開いてください。`Materials`フォルダの下のファイルツリーをそのままHTMLで表現しています。このHTMLは[`bootstrap-treeview`](https://github.com/jonmiles/bootstrap-treeview)でスタイルづけされています。 
![Materials_index.png](https://qiita-image-store.s3.amazonaws.com/0/52995/88047862-89fa-1bee-6793-d037a7532b5a.png)

上記の画面例ではテストスイートに対応するバーが2つあってそれぞれ 'production' と 'development' とラベルづけされています。このラベルはテストスイートを実行するときに適用された Execution Profile の名前です。

上記の画面例のなかで、テストケースに対応するバーが2つあってそれぞれに *PASSED* とか *FAILURE* と表示されています。ここで各テストケースが実行されたときに成功したか失敗したかの区別を表示しています。

ひとつひとつのmaterialに対応するバーがあって、ファイル名がリンクになっています。それをクリックすると画像ファイルがモダールなダイアログのなかに表示されます。 
![Materials_index_modal.png](https://qiita-image-store.s3.amazonaws.com/0/52995/addf25bc-d33d-3733-faf2-0a6dcc74fca6.png)

## Test Suite Collections/TSC09_visit_a_web_site_and_make_index

テストスイートコレクションとは何かについてはKatalonのドキュメント [Test Suite Collection](https://docs.katalon.com/display/KD/Test+Suite+Collection)を参照のこと

### 説明

そろそろ大詰めです。テストスイートコレクション `TSC09_visit_a_web_site_and_make_index` を見てみましょう。これは下記3つのテストスイートを順番に実行します。

1. `TS00_clearMaterials` --- `<project>/Materials` ディレクトリの内容を削除して空にします
2. `TS07_visit_a_web_site` --- Webサイトを訪問してスクリーンショットを撮ります
3. `TS08_makeIndex` --- `<project>/Materials/index.html` を作成します。

テストスイートコレクション `TSC09_visit_a_web_site_and_make_index` ではテストスイートを実行するときにExecution Profile として `default` を指定します。 
![TSC09.png](https://qiita-image-store.s3.amazonaws.com/0/52995/35ee491d-49cd-9f54-548d-4e459fcdb9bd.png)

### テストスイートコレクションを実行するやり方

テストスイートコレクションを選択し開いた状態で画面のなかをみると ![How_to_run_TSC09.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/0b35f40d-7a43-8e95-c8d3-14ee9b0dcfc5.png) `Execute` というボタンがあります。このボタンをクリックするとテストスイートコレクションが実行されます。テストケースやテストスイートを実行するときに使うボタン ![run_button.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/4d21eb55-20eb-1881-d655-f11ad6d162e1.png) とは別のボタンなので混同しないよう注意すること。

### テストスイートコレクション実行時のログを見る方法

テストスイートコレクションは複数のテストスイートをまとめていっぺんに実行するための概念です。テストスイートコレクションを実行したとき、当然ながら、個々のテストスイートの実行ログを閲覧したくなるでしょう。ところがKatalon StudioのGUIはちょっと不親切なデザインになっていて、ログをどうやってみればいいのかわかりにくくなっています。説明を補います。テストスイートコレクションを実行したとき画面右下に目玉のような形のアイコンが表示されます。下記の画面キャプチャを参照のこと。この目玉アイコンをクリックするとドロップダウンリストが表示され、そのなかに実行されたテストスイートが列挙されます。マウスでひとつを選ぶと当該テストスイートの実行時ログを検索し表示することができます。ちょっと回りくどいですが。 
![How_to_retrieve_testsuite_log_in_testsuitescollection.png](https://qiita-image-store.s3.amazonaws.com/0/52995/2b52806f-cf56-fab6-6848-525c790e8558.png)


## Test Suite Collections/TSC10_visit_2_environments

### 説明

テストスイートコレクション `TSC10_visit_2_environments` はテストスイート `TS07_visit_a_web_site` を2度実行します。まず `production` という名前のProfileを指定して実行し、次に `development` という名前のProfileを指定して実行します。 
![TSC10_2_profiles.png](https://qiita-image-store.s3.amazonaws.com/0/52995/45f2959d-c5a7-2773-0c4e-7c83a4282005.png)

#### 入力

プロファイルには `GlobalVariable.Hostname` が定義してあります。GlobalVariable.HostnameはテストスクリプトがアクセスすべきURLのホスト名部分です。だからプロファイルを切り替えることで本番環境と開発環境と二つの環境を同じテストスクリプトでテストするができます。

| Production | Development |
|:-----------|:------------|
| `GlobalVariable.Hostname=http://demoaut.katalon.com/` ![demoaut.katalon.com.png](https://qiita-image-store.s3.amazonaws.com/0/52995/54e06e1b-c1d9-c705-d3a7-4a2c2eeb1535.png) | `GlobalVariable.Hostname=http://demoaut-mimic.kazurayam.com/` ![demoaut-mimic.kazurayam.com.png](https://qiita-image-store.s3.amazonaws.com/0/52995/44afc7a6-7121-9282-0f32-c78ac70d311c.png) |

二つの画像を見比べれば少しだけ違っていることがわかります。

#### 出力

テストスイートコレクション `TSC10_visit_2_environments` を実行すると `./Materials/index.html` ファイルができます。 
![TSC10_index.PNG](https://qiita-image-store.s3.amazonaws.com/0/52995/eaafead1-ab21-dab2-aa49-c52443cf9161.png)

この画面の中で `TS07_visit_a_web_site` のバーが2つあって、それぞれにプロファイル名(`development`と`production`)が表示されていることに注目してください。同じテストスイートを2つの環境に向けて２度実行した結果が区別できます。


# 補足

本記事は ['Materials'](https://github.com/kazurayam/Materials) をKatalon StudioでWeb画面のスクリーンショットを撮ってファイルに保存するのに使うことを説明しました。Materialsのjarはこれ以外の用途にも応用できます。

## スクリーンショット以外のファイルも扱えること

`com.kazurayam.materials.MaterialRepositor` オブジェクト `resolveMaterialPath()` メソッドは `java.nio.file.Path` をリターンします。つまりMaterialsライブラリはファイルの中身が何であるかを気にしません。だからスクリーンショットを画像ファイルに保存するだけでなく、WebサイトからダウンロードしてきたPDFファイルを./Materialsフォルダの中に格納するとか、Excelファイルをテスト実行時に動的に作製してMaterialsフォルダの中に格納するとか、RESTful APIが応答したXML/JSONを./Materialsフォルダに保存といった用途につかうことができます。


## Katalon Studio以外のテスティングフレームワークとも組み合わせられること

`Materials-x.x.x.jar` は Java8 とくに `java.nio.file.*` と [slf4j](https://www.slf4j.org/) に依存しますが、それだけです。[Katalon StudioのAPI](https://api-docs.katalon.com/) には全く依存していません。だから `Materials`を 他のテスティング・フレーム（ただしJVMで動くもの）と組み合わせることができます。たとえば [JUnit](https://junit.org/junit5/) とか [Spock](http://spockframework.org/) とか。

# まとめ

Webアプリの本番環境と開発環境のスクリーンショット２組を撮りファイルに出力することができました。次に同じ画面のPNGファイル2つを取り出して比較することをしてみたくなります。次回は Visual Testing を Katalon Studio でやってみました、を報告します。