自動テストを実行した結果、正常なのか何かのエラーが発生したのかを判定するのはテストシナリオの仕事です。合否の判定はテスト内容により様々な基準で判断されるため、テストシナリオの作成者しか決めることができないためです。
ブラウザ上に表示されている内容を期待した値と比較することで判定する方法です。
例えば、以下のページで「トップメニュー」と表示されていることを確認する場合です。
確認するページをPC上のブラウザ(ここではChromeブラウザ)で表示し「デベロッパーツール」を表示します。
右側ペインの「Elements」タブを選択して、赤丸の選択アイコンをクリックすると、左側ペインのHTMLページ上で確認したい要素にマウスカーソルを重ねると、右側ペインに対応するエレメントが選択表示されます。
右ペインのエレメント上でコンテキストメニューを表示し、「Copy」> 「Copy XPath」を選択すると、該当エレメントを指すXPath文字列がクリップボードにコピーされます。
コピーしたXPathでエレメントを検索し、文字列を取得するためには以下のように記述します。
const {Builder,Capabilities,Key,until,By} = require("selenium-webdriver"); let driver = null; try { console.log("capabilitiesの設定"); const capabilities = new Capabilities() .setBrowserName("safari+G.O"); console.log("WebDriverへ接続"); driver = new Builder() .usingServer("http://192.168.1.147:7000/wd/hub") .withCapabilities(capabilities) .build(); console.log("テスト対象アプリのindex.htmlをロード"); await driver.get( "http://dev.tobesoft.co.jp/GHOST_Operator/demo/index.html"); console.log("コピーしたXPathでelementの配置を3秒待機する"); const elm = await driver.wait( until.elementLocated( By.xpath("/html/body/div[1]/header/div/h1")), 3000); console.log("elementのテキストを取得する"); const text = await elm.getText(); console.log("結果=<"+text+">"); // 合否判定 if(text !== "トップメニュー") throw new Error("トップメニュー不一致 <" + text+ ">"); } catch (e) { console.log(e); } finally { if(driver !== null) await driver.quit(); }
実行結果は以下のようになります。
テストシナリオでalertの表示を待つことができ、alertの表示有無やその表示内容を取得し期待する値と比較することでテストの合否判定を行うことができます。
また、alertが表示されると、テストシナリオでalertを閉じるまで後続WebDriver API呼び出しは失敗するので必ず明示的にalertを処理する必要があります。
サンプルアプリでは、「トップメニュー」クリックするとalertを表示するようになっており、そのalertを捉えるためには以下のように記述します。
const {Builder,Capabilities,Key,until,By} = require("selenium-webdriver"); let driver = null; try { console.log("capabilitiesの設定"); const capabilities = new Capabilities() .setBrowserName("safari+G.O"); console.log("WebDriverへ接続"); driver = new Builder() .usingServer("http://192.168.1.147:7000/wd/hub") .withCapabilities(capabilities) .build(); console.log("テスト対象アプリのindex.htmlをロード"); await driver.get( "http://dev.tobesoft.co.jp/GHOST_Operator/demo/index.html"); console.log("コピーしたXPathでelementの配置を3秒待機する"); const elm = await driver.wait( until.elementLocated( By.xpath("/html/body/div[1]/header/div/h1")), 3000); console.log("elementのテキストを取得する"); const text = await elm.getText(); console.log("結果=<"+text+">"); // ここからalertの処理です console.log("トップメニューをクリック"); await elm.click(); console.log("Alertの表示を1秒だけ待つ"); await driver.wait(until.alertIsPresent(), 1000); console.log("Alertのテキストを取得する"); let alert = await driver.switchTo().alert(); const alertText = await alert.getText(); console.log("alert=<"+alertText+">"); console.log("Alertを閉じる"); await alert.dismiss(); } catch (e) { console.log(e); } finally { if(driver !== null) await driver.quit(); }
実行結果は以下のようになります。
この処理で、もしalertが表示されない場合には33行目でタイムアウトの例外がスローされテストは失敗します。
表示されたページ全体の画像、または指定したエレメントの画像を取得して、期待する画像と比較して合否を判定することができます。ただし、この方法はiPadの高解像度スクリーンでは負荷が高くなりますし、1ピクセル未満のアンチエイリアシングの状況次第で人が見ても違いを認識できない画像でも不一致と判断されることがあります。
また、GHOST OperatorではHTMLエレメント単位のキャプチャをサポートしてないため、テストケース側で少し工夫が必要になります。
画面キャプチャイメージと正解画像との比較で合否判定を行う場合、グラデーションや文字のアンチエイリアシングの微細な画像の差異による不一致を抑止する工夫が必要となりますが、色差を求めて閾値以下の違いであれば一致と判断するような比較処理が有効です。
具体的には、比較対象画像の同一座標の2点の色(R1,G1,B1とR2,G2,B2)について、ユークリッド距離( Math.sqrt((R2-R1)*(R2-R1)+(G2-G1)*(G2-G1)+(B2-B1)*(B2-B1)) )を求め、結果を閾値(通常は5程度)未満であれば一致とします。この判定を画像の全画素について行い、すべての画素で一致と判断されれば画像は同じと見なすことができます。
const path = require('path'); const sharp = require('sharp'); : const validImageFile = "valid.png"; const img_b64 = await driver.takeScreenShot(); let png1 = Buffer.from(img_b64, "base64"); let raw1 = []; let raw2 = []; await sharp(png1) .removeAlpha() .raw() .toBuffer() .then(data => raw1 = data); await sharp(validImageFile) .removeAlpha() .raw() .toBuffer() .then(data => raw2 = data); if (raw1.length != raw2.length) { throw new Error("screenshot compare error (size)"); } for (let p = 0; p < raw1.length; p += 3) { const r = raw1[p] - raw2[p]; const g = raw1[p + 1] - raw2[p + 1]; const b = raw1[p + 2] - raw2[p + 2]; const d = Math.sqrt(r * r + g * g + b * b); if (d > 4) { throw new Error("screenshot compare error (color)"); } }
スクリーン全体の画像比較では、前述のように負荷が高く不必要な部分の比較に結果が左右され望ましくありません。
GHOST OperatorでHTMLエレメント単位のキャプチャ画像を取得する場合は以下のようにクロップします。
const sharp = require('sharp'); : // HTMLエレメントの領域を求める const elm = await driver.findElement(By.id("Button00")); const rect = await elm.getRect(); const cropRect = { left:rect.x * 2, top:rect.y * 2, width:rect.width * 2, height:rect.height * 2}; const img_b64 = await driver.takeScreenShot(); let png1 = Buffer.from(img_b64, "base64"); let raw1 = []; await sharp(png1) .removeAlpha() .extract(cropRect) // クロップ .raw() .toBuffer() .then(data => raw1 = data);
上記の画素の比較は、負荷のかかる処理で多用するとテスト時間が長くなり、正解画像の管理面でも手間のかかるテストとなります。従って、もう少し簡易的に色差を考慮せずに完全一致で合否判定を行う事も考慮する価値があります。
例えば、キャプチャイメージのビットマップからハッシュを算出し、正解画像からあらかじめ算出しておいたハッシュ値と比較する方法であれば、より手軽にキャプチャイメージによる合否判定が可能となります。
以下は、キャプチャイメージからSHA256ハッシュを算出して、正解画像のハッシュ値と比較して合否判定する例です。
const crypto = require('crypto'); : const validHash = "f2676204f810122556b9e4505b812179555835a88e0d3d184187426acc2b4a6a"; let img_b64 = await driver.takeScreenShot(); const hashHex = crypto.createHash('sha256') .update(img_b64, 'utf8') .digest('hex'); if(hashHex !== validHash) { throw new Error("screenshot compare error hash=" + hashHex); }
現在表示されているページのURLが意図したURLなのかを比較することで、テストの合否を判定する方法です。
console.log("「送信する」を検索"); const buttonElm = await driver.findElement( By.xpath("/html/body/div[2]/form/div/button")); await buttonElm.click(); console.log("ページ遷移を待つ"); await driver.wait( until.urlMatches(/^http.*\/demo\/result\.html\?.*$/),3000); console.log("ページ遷移完了");
この例では「送信する」ボタンをクリック後、3秒以内に想定したURLに遷移しない場合タイムアウト例外がスローされテストは失敗します。
WebDriverには様々な状態把握用のAPIが用意されていますが、すべてHTMLレベルの状態を把握できる機能です。Webブラウザ内のJavaScriptの変数の状態などWebDriver APIでは取得できない情報により合否判定を行いたい場合、JavaScriptプログラムをWebブラウザ内に転送して実行して合否判定を行う事も可能です。
const appData = await driver.executeScript( 'return appData=="sample"'); if(!appData) { throw new Error("appData is not true. appData=" + appData); }