WebページをMIME HTMLに保存する話（CDPとKatalon Studioを使って）

この記事はQiita Advent Calendar 2020の [Selenium/Chrome DevTools Protocol/Appium](https://qiita.com/advent-calendar/2020/selenium_cdp_appium) カレンダーの1日目の記事です。

# はじめに

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

今回、WebページのスナップショットをMIME HTMLファイルに保存すること、Chrome DevTools Protocolを呼び出すGroovyスクリプトをKatalon Studioで開発して自動化することを試みました。その内容を共有します。

# Chrome DevToolsを使って手動でWebページをMIME HTMLファイルに保存できること

MIME HTML（またはMHTML）とは何でしょうか？[Wikipedia](https://ja.wikipedia.org/wiki/MHTML)によれば
>MIME Encapsulation of Aggregate HTML (MHTML) は、MIMEのマルチパートを利用することで、元となるHTML文書と、それからリンクされる画像や動画などのリソースを1つのデータとしてまとめるフォーマットである。電子メールでの利用を想定しているが、ウェブページを一つのファイルとして保存する用途にも用いられている。

Google ChromeブラウザのDevToolsを使ってWebページを一つのMHTMLファイルに保存することができます。実際にやってみましょう。

Google Chromeブラウザを起動し適当なWebページを開きます。どのURLでもいいのですが一例として http://demoaut-mimic.kazurayam.com/ を開きましょう。ツールバーから *表示 > 開発・管理 > デベロッパーツール* と手繰ると Chrome DevToolsが起動します。

![Chromeデベロッパツール](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/docs/images/ChromeDevTools_callup.png?raw=true)

Chrome DevToolsが起動できたら、Webページのうえどこかをマウスで右クリックしてメニューを開き *別名で保存* を選択します。

<img width="260" alt="別名で保存" src="https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/docs/images/Save%20as.png?raw=true">

次に表示されるダイアログで出力先ディレクトリとファイル名を指定し、形式として *ウエブページ、１つのファイル* を選択しましょう。

![ひとつのファイルに保存](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/docs/images/Save%20as%20MHTML.png?raw=true)

すると出力先フォルダの中に `.mht` というファイル名拡張子を持ったファイルができます。これでWebページがMIME HTML形式のテキストファイルに保存されました。

実際作られた.mhtファイルの中身はどんなでしょうか？下記を参照願います。全体がプレーンなテキストであること、MIMEヘッダによって区切られて、HTMLとCSSとJavaScript、そしてBase64エンコードされたラスタ画像が一つに束ねられていることが見て取れます。

- [.mhtファイルのサンプル](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/tmp/snapshot.mht)

```
From: <Saved by Blink>
Snapshot-Content-Location: https://katalon-demo-cura.herokuapp.com/
Subject: CURA Healthcare Service
Date: Wed, 18 Nov 2020 14:11:01 -0000
MIME-Version: 1.0
Content-Type: multipart/related;
	type="text/html";
	boundary="----MultipartBoundary--JUGRHw9UUbWsvJo7x3kAf40eOZp8hHIdkhkdsrnWou----"


------MultipartBoundary--JUGRHw9UUbWsvJo7x3kAf40eOZp8hHIdkhkdsrnWou----
Content-Type: text/html
Content-ID: <frame-2FB8E2C801C9E0A13267EE36DB54EF75@mhtml.blink>
Content-Transfer-Encoding: quoted-printable
Content-Location: https://katalon-demo-cura.herokuapp.com/

<!DOCTYPE html><html lang=3D"en"><head><meta http-equiv=3D"Content-Type" co=
ntent=3D"text/html; charset=3DUTF-8">
   =20
    <meta http-equiv=3D"X-UA-Compatible" content=3D"IE=3Dedge">
    <meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1">
    <meta name=3D"description" content=3D"CURA Healthcare Service; (678) 81=
3-1KMS">

    <title>CURA Healthcare Service</title>

    <!-- Bootstrap Core CSS -->

...
```

ちなみに.mhtファイルがどれぐらいのサイズかというと、例えば http://demoaut-mimic.kazurayam.com/ を保存して作った.mhtファイルは約３万行、全体のサイズが約740キロバイトでした。

**.mhtファイルをChromeにドラッグ＆ドロップすれば復元できること**

.mhtファイルをMacのFinderで掴んでChromeブラウザのウインドにドラッグ＆ドロップしましょう（Windowsならばエクスプローラから）。元とそっくり同じな画面が表示されます。ただしアドレスバーをみると http:// なURLではなくて /Users/username/... のようなファイルのパスが表示されている点が異なります。

![ReloadedPage](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/docs/images/ReloadedPage.png?raw=true)

以上で、Chrome DevToolsを手動操作すれば、WebページをMIME HTMLファイルに保存できるとわかりました。

さてここから先が本題です。

# 解決したい問題

WebページのスナップショットをMIME HTMLファイルに保存するのを自動化したい。Katalon StudioでWeb UIをテストする一連のスクリプトによる処理の一部としてページをMHTMLへ保存したい。Webページのフルサイズのスクリーンショットを撮ってpng画像ファイルに保存することは[すでにできる](https://qiita.com/kazurayam/items/ec9015e781819a5734b1)。あれと同じ手軽さで（pngファイルではなく）mhtファイルを作成したい。


## 何のために？

**Visual Testingのために**

Web UIテスト自動化ツールのサイトで *Visual Testing* という言葉が使われています。たとえば[percy.io](http://percy.io/)とか。Visual Testingとは *二つの画面を見比べて見た目の食い違いを発見しそれに基づいてソフトウェアの品質を判定する* というテスト手法を意味します。開発者が予期していなかったプログラムの不具合に気づくことを目標とするテスト手法です。

わたしは以前 [VisualTestingInKatalonStudio](https://github.com/kazurayam/VisualTestingInKatalonStudio) を開発し公開しました。そしてQiitaに[「Katalon StudioでVisual Testingを実現した」](https://qiita.com/kazurayam/items/bcf72a03f50fc5db4373)と[「あなたのWebサイトをVisual Testingする方法」](https://qiita.com/kazurayam/items/7da4bd82b52fe18209db)の2本の記事を書いた。VisualTestingInKatalonStudioはKatalon Studioのプロジェクトです。Webサイトのページをブラウザで表示しスクリーンショットをpng形式の画像ファイルに保存し、開発環境と本番環境のようにひとつのWebシステムの二つの環境の画面を見比べたり、本番環境のソフトウェアを入れ替える作業の前と後のように２時点の画面を見比べて、見た目の食い違いをツールが見つけ出す、それを自動化することができる。例えば1000画面を50分間で見比べた実績があります。1画面あたり約3秒。人間が1000画面を目視で確認するなんてやる気にもならないが、このツールがあれば毎日でも繰り返し実行可能になります。

さて、自分の職場でVisual Testingの手法を応用しそれなりの成果をあげて喜んでいたら、同僚がこんなことを言いました。

>*"このWebシステムはレスポンシブ・デザインを採用している。パソコン、タブレット、スマホでも表示できるように。複数の画面サイズ（LandscapeとPortraitどちらも）について目視確認をしなければならない。画面の数が多いから作業が大変。サボりがちです。目視作業をこのツールで自動化できたらすっごく嬉しい。できますか？"*


**PCとタブレットとスマホ、垂直と水平と**

Katalon Studioには[`WebUI.setViewPortSize(width,height)`](https://docs.katalon.com/katalon-studio/docs/webui-set-view-port-size.html)コマンドがあって、ブラウザの画面サイズを任意に設定することができる。だから一つのWeb画面をPC向けの画面サイズだけでなくiPadやスマホ向けの画面サイズで表示するスクリプトを書くことはできる。そしてスクリーンショットを撮ることもできる。だが今の[VisualTestingInKatalonStudio](https://github.com/kazurayam/VisualTestingInKatalonStudio)では、もしも５通りの画面サイズをテストしたければpng画像ファイルを５倍多く作るしかない。処理時間が５倍かかりディスクの所要量も５倍かかるだろう。重すぎる。軽くする工夫はないものか？

PNG画像のかわりにMIME HTMLでWebページの状態を記録するのはどうか。WebページのスナップショットをMIME HTMLファイルに保存し、後でMHTMLファイルをブラウザにロードして画面を復元しよう。復元した画面を`WebUI.setViewPortSize(width,height)`コマンドでサイズ切り替えしながら見比べよう。MHTMLファイル一つを元にして５通りでも１０通りでも複数の画面サイズを再現できるはず。処理時間を短縮しディスク所要量をスリムにできるのではないか？と思った。WebページをMIME HTMLファイルに保存する処理をスクリプトで自動化したい。Katalon Studioのスクリプトからやりたい。どうすれば実装できるか？これがわたしの課題となりました。

# 解決方法

前述したように、手動でChromeブラウザのDevToolsでWebページのスナップショットをMIME HTMLファイルに保存することができる。それは実証済みだ。あれを手動でする代わりにスクリプトから実行できればいい。調べたら下記のことがわかりました。

1. [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)(以下、**CDP** と略記)というプロジェクトがある。CDPが[`Page.captureSnapshot()`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureSnapshot)コマンドを定義している。このコマンドがWebページからMIME HTMLを生成してくれる。
2. この記事 https://github.com/puppeteer/puppeteer/issues/3575 によればChromeブラウザが`Page.captureSnapshot()`を実装済み。
3. Javaベースのクライアント・プログラムがCDPのプロトコルを介してブラウザとやりとりするためのライブラリ [Chrome DevTools Java Client, by Kenan Klisura](https://github.com/kklisura/chrome-devtools-java-client) があった。
4. Katalon Studioのplug-inとして[Chrome DevTools Protocol Integration](https://store.katalon.com/product/144/Chrome-DevTools-Protocol-Integration)がリリース済みだった。

このplug-inをKatalon StudioにインストールすればCDPプロトコルを介してChromeブラウザにMHTMLファイルを作らせるようなスクリプトを書いて実行できるはずだ。

作ってみたら動きました。

# コードの説明

## サンプルのありか

GitHubにレポジトリを作り公開しました。

- [Saving Web Page as MHTML in Katalon Studio](https://github.com/kazurayam/SaveWebPageAsMHT)

このレポジトリの[Releases](https://github.com/kazurayam/SaveWebPageAsMHT/releases)ページからプロジェクト一式をまとめたZIPファイルをダウンロードすることができます。

# サンプルの説明

## 動作環境

わたしは下記の環境でサンプルを開発しました。

- macOS Big Sur 11.0.1
- Google Chrome 86.0.4240.198
- [Katalon Studio 7.7.2](https://www.katalon.com/download/) --- [Chrome DevTools Protocol Integration](https://store.katalon.com/product/144/Chrome-DevTools-Protocol-Integration)を追加した

Katalon StudioはJavaVMベースのソフトウェアでWindowsでもLinuxでも動きます。今回のサンプルコードはKatalon Studioの古いバージョン（ただし7.0.x以降）でも動くはずです。

## 動かしてみよう

あなたがKatalon Studioを自分のPCにインストール済みであると前提します（インストール方法については後述）。

上記のReleasesページからZIPファイルをダウンロードしましょう。ZIPを展開して`SaveWebPageAsMHT`フォルダを任意の場所に位置付けます。Katalon Studioをstartしてプロジェクトをopenしましょう。

Katalon Studioのウインドウの左がわに `Tests Explorer` とラベルされた四角い面があってその中をマウスでいじると `Test Cases/Save web page as MHTML` を選択することができます。

![Save web page as MHTML](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/docs/images/TestCases_SaveWebPageAsMHT.png?raw=true)


`Test Cases/Save web page as MHTML`をマウスで選択したあと、GUIの右上の方にある緑色の三角形のボタン <img width="60" alt="GreenArrowButton.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52995/6607f64a-1a77-c360-d046-81ed1d6c6b62.png"> をクリックするとスクリプトが実行されます。

なおスクリプトを実行するときにGoogle Chromeブラウザを起動することが必要です。FireFoxなどの他のブラウザを指定するとエラーが発生します。

## Test Cases/Save web page as MHTMLは何をするか

[`Test Cases/Save web pages as MHTML`](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/Scripts/Save%20web%20page%20as%20MHTML/Script1605707377828.groovy)のソースコードは下記の通り。

```
import com.github.kklisura.cdt.protocol.commands.Emulation as Emulation
import com.github.kklisura.cdt.protocol.commands.Page as Page
import com.github.kklisura.cdt.services.ChromeDevToolsService as ChromeDevToolsService
import com.katalon.cdp.CdpUtils
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

WebUI.openBrowser('')
WebUI.setViewPortSize(1024,768)
WebUI.navigateToUrl('http://demoaut.katalon.com/')

ChromeDevToolsService cdts = CdpUtils.getService()

saveWebPageAsMHTML(cdts, 'tmp/snapshot.mht')

WebUI.closeBrowser()

def saveWebPageAsMHTML(ChromeDevToolsService devToolsService, String outputFileName) {
	Page page = devToolsService.getPage()
	double width = 960
	double height = WebUI.executeJavaScript('return document.body.scrollHeight', null)
	Emulation emulation = devToolsService.getEmulation()
	emulation.setDeviceMetricsOverride(Double.valueOf(width).intValue(), Double.valueOf(height).intValue(), 1.0, Boolean.FALSE)
	emulation.setScrollbarsHidden(Boolean.TRUE)
	// convert the loaded web page into an instance of MHTML
	String mhtml = page.captureSnapshot()
	save(outputFileName, mhtml)
	emulation.setScrollbarsHidden(Boolean.FALSE)
}

def save(String filename, String data) {
	FileOutputStream fos = null
	try {
		File file = new File(filename)
		fos = new FileOutputStream(file)
		fos.write(data.getBytes())
	}
	catch (IOException e) {
		e.printStackTrace()
	}
	finally {
		if (fos != null) {
			try {
				fos.flush()
				fos.close()
			}
			catch (IOException e) {
				e.printStackTrace()
			}
		}
	}	
}
```

- このスクリプトはKatalon Studioの `WebUI.openBrowser('')` メソッドによってブラウザを起動し、`WebUI.navigateTo(url)`メソッドによってWebページ http://demaut-katalon.kazurayam.com を開きます。ページが完全にロードされるまで待ちます。
- [`CdpUtils.getService()`](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/Include/scripts/groovy/com/katalon/cdp/CdpUtils.groovy) を呼び出すことによりChrome DevTools Protocolを介してブラウザと通信するクライアントを生成します。
- Chrome DevTools Protocolの `Page.captureSnapshot` をcallします。するとブラウザがWebページをMIME HTML形式のテキストに変換して応答します。 
- 応答されたテキストをローカルなファイルに保存します。

## 出力例

サンプルのスクリプトを実行すると下記のファイルが出力されました。MIME HTMLです。

- [tmp/snapshot.mht](https://github.com/kazurayam/SaveWebPageAsMHT/blob/master/tmp/snapshot.mht)

# CDPって面白い

Katalon StudioがWebブラウザを遠隔操作してUIをテストするにはSelenium WebDriverを使います。Katalon Studioでできるのは基本的にWebDriverでできることの範囲にとどまる。ところがCDPを使えばWebDriverでできなかったことがやれる。MIME HTMLへの保存はほんの一例です。下記のコード群を読むと驚きます。

- https://github.com/kklisura/chrome-devtools-java-client/tree/master/cdt-examples/src/main/java/com/github/kklisura/cdt/examples

これによればHTMLからリンクしているリソース（cssやjsや画像など）へのリクエストに介入してHTTP通信の内容を傍受するようなことがCDPを使えばできるらしい。こういうブラウザの内部動作に関してはWebDriverは全く手出しできない。Katalon StudioのなかでCDPを使えば何か面白いことができそうです。

# 補足説明

## Katalon Studioのインストール、チュートリアル

Katalon Studioをどうやってインストールするか、最初の一歩を学ぶにはどこから行けばいいか？ 公式ドキュメントは下記にあります。

- [Documentations index](https://docs.katalon.com/katalon-studio/docs/index.html)

充実していますが全部英語です。ちょっと英語は...という方は下記の記事も参考にしてください。

- [Katalon Studioのダウンロードとインストール](https://yoshikiito.net/blog/archives/2000/) by Yoshiki Itoさん
- [Katalon StudioでRecordしてみた](https://qiita.com/kazurayam/items/433876c0b3bc7f33f019) by kazurayam
- [Katalon Studioでテストを自作した](https://qiita.com/kazurayam/items/bdf71ed70fdc32bb1758) by kazurayam
- [Katalon Studioでテストを自作した(2)](https://qiita.com/kazurayam/items/29282b749def80a806de) by kazurayam
- [Katalon Studio with aShotでスクリーンショットを取る](https://qiita.com/kazurayam/items/ec9015e781819a5734b1) by kazurayam
- [Katalon Studio+MaterialsでWebサイトのスクリーンショットを整然と保存する](https://qiita.com/kazurayam/items/6d76148fa866129696b7) by kazurayam
- [Katalon StudioでVisual Testingを実現した](https://qiita.com/kazurayam/items/bcf72a03f50fc5db4373) by kazurayam
- [あなたのWebサービスをVisual Testingする方法](https://qiita.com/kazurayam/items/7da4bd82b52fe18209db) by kazurayam

## Proxyのこと

Katalon Studio ver7.xはインターネット上のKatalon社のサーバと通信してライセンスを認証しようとします。もしもあなたが職場のイントラネット上にあるPCを使っていて会社のProxyサーバを経由してインターネットにアクセスする必要があるならば、Katalon StudioにProxyの設定をする必要があります。Proxyの設定ができていなくてライセンスの認証が失敗するとKatalon Studioを使い始めることすらできません。下記の公式ドキュメントの説明を参考にしてください。

- [Proxy preferences](https://docs.katalon.com/katalon-studio/docs/katalon-studio-preferences.html#proxy-preferences)