Puppeteer を利用した E2E テスト

テストエンジニアテスト手法テスト自動化品質保証QA

Puppeteer(パペティア)とは、スクリプトプログラム上からヘッドレス(GUI が起動しない状態)でブラウザを制御できる Node.js ライブラリです。
Web サイトのスクレイピングツールや Web システムの E2E テストプログラムを作成することができます。
テストを自動化したい場合に最適です。
開発言語は JavaScript で、Google Chrome の元となっている Chromium というブラウザプログラムが同梱されています。

参考: Webシステムにおける結合テスト

Puppeteer の公式サイト
https://developers.google.com/web/tools/puppeteer


Puppeteer のインストール
Puppeteer は Node.js のパッケージなので、npm でインストールします。
今回は npm のローカルインストールにて例示します。

>cd C:\NodeJS\local # インストール先ディレクトリ
>npm init # パッケージ情報を初期化する場合
>npm install puppeteer


カレントディレクトリのnode_modulesに実体がコピーされ、package.jsonpackage-lock.jsonファイルにパッケージ情報が追記されます。
これでインストールは完了です。

参考: Node.js のパッケージ管理ツール npm とは

npmjs.com
https://www.npmjs.com/package/puppeteer


テストの実行
まずは、Puppeteer をインストールしたディレクトリに、NODE_PATH環境変数を通します。
通常、requireする対象が見つからなかった場合、Node.js はnode_modulesディレクトリを探しに親階層を辿っていきます。
したがって参照先パッケージがインストールされているディレクトリ配下に参照元ソースファイルがあれば問題ありませんが、異なるディレクトリにソースファイルを置くと、cannot find moduleになってしまいます。
NODE_PATH環境変数を設定しておくことで、ソースファイルを置く場所を意識せずにすみます。

>set NODE_PATH=C:\NodeJS\local\node_modules

テストの実行は、nodeコマンドで行います。
テストコードはどこに存在していても構いません。

>cd C:\tests # テストコードの存在するディレクトリ
>node example.js # example.js を実行


テストプログラムの作成
以下、Puppeteer 公式サイトのサンプルコードを例にとって説明します。

const puppeteer = require('puppeteer');

(async () => {
	const browser = await puppeteer.launch();
	const page = await browser.newPage();
	await page.goto('https://example.com');
	await page.screenshot({path: 'example.png'});

	await browser.close();
})();

1行目
Puppeteer パッケージをrequireします。
letconstは、ES2015 から採用されたブロックスコープを持つ変数の宣言キーワードです。
constは再宣言・値の変更が不可であり、ここでは安全のためrequireされた値をconst変数に代入しています。
ちなみにletは、再宣言は不可ですが値の変更は可能です。

3行目
Puppeteer のソースコードでは、慣例的にプログラム本体をasync修飾子を付けたアロー関数(や無名関数)内に記述し、即時関数で囲みます。
もちろん異なる書き方もできますし、当該関数外のトップレベルに別のクラスやメソッドを定義することもできます。
async(非同期)なメソッド内では、処理完了を待つよう指定するawait演算子を利用することができるようになります。

4~5行目
ブラウザを起動し、新しいページを開きます。

Node.js は、非同期型のイベント駆動モデルのアーキテクチャを採用しています。
したがって基本的に非同期的な動作が前提となっていますので、処理完了を待ちたい場合はawait演算子を指定する必要があります。
テストプログラムでは、処理のほとんどにawaitを付けるような感じになるでしょう。
少々面倒ですね。

参考: 非同期型のイベント駆動モデル

6行目
goto()
は URL を引数にとり、指定された URL へページ移動します。
処理完了を待ちたいので、await演算子が指定されています。

7行目
screenshot()はファイル名を引数にとり、現在ブラウザが開いているページのキャプチャ画像を出力します。
処理完了を待ちたいので、await演算子が指定されています。

9行目
ブラウザを閉じます。

JavaScript の言語仕様を担う ECMAScript は、ES2017 で非同期処理系の機能である Async Functions を規定しました。
これにより Node.js は、v7.6.0 でasync/awaitをサポートするようになりました。
現行の Puppeteer はasync/awaitを使用するため、少なくとも v7.6.0 以降の Node.js を必要とします。
Puppeteer v2.1.0 までは、v8.9.0 以降のNode.js に依存し、Puppeteer v3.0.0 では、v10.18.1 以降に依存します。


以下、テストコードの例です。

const puppeteer = require("puppeteer");

(async function(){
	// ブラウザの起動にはいくつかのオプションがある
	const browser = await puppeteer.launch({
		headless: false,			// ブラウザが立ち上がらない(defalut: true)
		slowMo: 5,					// Puppeteerの動作を遅延させる [ms]
		devtools: true,				// Chrome DevToolsを開く(default: false)
		args: [
			'--window-size=1600,950',	// ウィンドウサイズ
			'--window-position=100,50'	// ウィンドウポジション
		]
	});

	// ページ移動
	const page = await browser.newPage();
	await page.setViewport({width:1200, height:800}); // ビューポート指定
	await page.goto("https://dev.softwarenote.info/test/test_exp01.php");

	// 要素の取得にはCSSセレクタが使えます
	const div = await page.$("#id"); // Object
	const divs = await page.$$(".class"); // Array

	// フォームの操作
	await page.type('input[type="text"][name="post_textbox"]', "cypress.io") // テキストボックス
	await page.type('textarea[name="post_textarea"]', "cypress.io"); // テキストエリア
	await page.click('input[name="post_checkbox"]'); // チェックボックス
	await page.click('input[name="post_radio"][value="val2"]'); // ラジオボタン value="val2"を選択する
	await page.select('select[name="post_select"]', "val2"); // プルダウン value="val2"を選択する
	await page.$eval('input[name="post_checkbox2"]', check => check.checked=true);

	await page.click('[type="button"]'); // ボタンをクリックする
	await page.type('input[name="post_textbox"]', "cypress.io") // 「cypress.io」と入力し
	await page.keyboard.press("Enter"); // エンター

	// その他の操作
	await page.screenshot({path:"capture.png"}); // スクリーンショット
	console.log("test log"); // ログ出力
	await browser.close(); // ブラウザのクローズ

})();

コメント