Github ActionsとSelenoidでファイルダウンロードや録画をやってみたい
こんにちは、テスト自動化アーキテクトの森川です。
みなさん自動テストされていますか?
突然ですが、私はE2E自動テストをまわしていくうえで欠かせない要素は3つあると思っています。
・よりスケーラブルに
・よりステーブルに
・よりサスティナブルに
本日はひとつめの「スケーラブル」について
Seleniumを軽量・高速にスケールしてくれる技術「Selenoid」を使ったエントリーをお届けしましょう。
具体的には、GitHub ActionsにSelenoid環境を構築してテスト実行時のビデオ録画とファイルダウンロードにトライしてみます。
本エントリーでやってみること
これらを1つのクラウド・インスタンス上で実行します。
『全然スケールしてないじゃないか』というお叱りの声が聞こえそうですね。
はい。すみません。でも、まずはSelenoidがE2Eテストのユースケースに耐えられるかを見極めさせていただきたいと思います。
SelenoidのIaCな環境構築については当ブログのこちらのエントリーを御覧ください。
【IaCで作るselenium環境】01_selenoidを構築する
リモートテスト環境の課題
SelenoidはSelenium Grid的なリモートテスト環境をスケールする場合に非常に優れたライブラリですが、リモートゆえの弱点もあります。
たとえば、OSのダイアログ操作はできません。ブラウザ操作でOSのダイアログが開いたらそこから先はテストが進められなくなります。
また、ファイルダウンロードにひと手間が必要です。そしてリモートサーバ内のコンテナ上で実行されているため、トラブル時の原因調査が難しくなりがちです。
過去の自動テスト案件でもこういった弱点のために、Selenium Grid(Selenoid)なテスト環境を諦めたことが何度かありました。対案としてはCIサーバとCI Agentでスケールしたり、ということになりますがこれはこれで別の難点があります。
Selenoidにはファイルダウンロードやビデオ録画のAPIが用意されています。これらをうまく使えば弱点を回避できるのでは?と思いサンプルテストコードを書いてみました。
ソースコードはすべて公開しています。https://github.com/tmshft/SelenoidTest
検証環境
・言語:Java:AdoptOpenJdk11
・ビルドテスト実行:Gradle:6.8.3
・Actionsランナー:Ubuntu latest(20.04)
・テストFW:Selenium beta-3
・Selenoid 1.10.1
・レポーティング: Allure Framework
実行結果
いきなりですが実行結果です。
https://tmshft.github.io/SelenoidTest
失敗しているのは、OSのダイアログが表示されるためにエラーとなるケース(敢えてやってみたかったです。)と、クリック対象が存在しないためにエラーとなるケースです。(こちらはSelenoidのブラウザログにエラーが出力されることを確認するためです)
レポートにはダウンロードされたファイルやビデオなどが添付されています。
ファイルダウンロードテストの成功時ビデオはこちらです。
しっかりダウンロードされていますね。
次は失敗ケースです。ダウンロード時のダイアログが表示されています。(ブラウザだけでなくデスクトップ全体を撮っているのが萌えますね!)
余談ですがSelenoidをGoogle検索していると「もしかしてSelenide?」とサジェストされることが多いのが辛いです。SelenideはSeleniumのラッパーライブラリです。SelenoidとSelenide、確かに似ていますが北海道とサッポロ一番ぐらい似て異なるものなんですよね。
Actionsパイプライン
GitHub Actionsのパイプラインはこちらです。
name: Selenoid Test Example
on:
pull_request:
branches:
- "*"
jobs:
build:
name: Selenoid Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
name: checkout
- name: run-selenoid
run: |
docker-compose up -d
- name: execute test
run: |
chmod +x ./gradlew
./gradlew -Dselenoid.base.url=http://127.0.0.1:4444 -Dselenoid.path=/wd/hub \
clean test --tests ExampleTest
hostname=`echo "$GITHUB_REPOSITORY" | sed -e "s/\//.github.io\//g"`
echo "::set-output name=HOSTNAME::${hostname}"
id: execute-test
- name: get report history
uses: actions/checkout@v2
with:
ref: test-report
path: test-report
- name: create report with history
uses: simple-elf/allure-report-action@master
with:
allure_results: build/allure-results
gh_pages: test-report
allure_report: build/allure-report
allure_history: build/allure-history
- name: deploy to pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: build/allure-history
publish_branch: test-report
- name: comment to pr
uses: actions/github-script@0.3.0
if: github.event_name == 'pull_request'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { issue: { number: issue_number }, repo: { owner, repo } } = context;
hostname=`echo "$GITHUB_REPOSITORY" | sed -e "s/\//.github.io\//g"`
github.issues.createComment({ issue_number, owner, repo, body: "ci-test report 👋 <br/><a href=https://${{ steps.execute-test.outputs.HOSTNAME }}/>see report</a>" });
かいつまんで見ていきましょう。
run-selenoid
docker-composeでSelenoidをデプロイ・起動しています。
execute test
Gradleでテストコードのビルドとテスト実行をしています。
VMoptionのselenoid.base.url、selenoid.pathはローカル/クラウド実行を切り替えるためのインターフェースとして使っています。
get report history
ブランチtest-reportにコミットしたレポート資産をいったんチェックアウトしています。履歴を含んだレポートページを作成するためです。
create report with history
履歴込みでテスト結果レポートを作成しています
deploy to pages
ブランチtest-reportの内容をGitHub Pagesにデプロイします。Allure RerportのPages化については、以前のエントリーで詳しく書いています。
Selenoidの起動
Selenoidの起動ははdocker-composeでひとまとめにしています。
docker-compose.yml
version: '3'
services:
selenoid:
network_mode: bridge
container_name: selenoid
image: aerokube/selenoid:1.10.1
volumes:
- "$PWD/config:/etc/selenoid"
- "$PWD/config:/opt/selenoid"
- "/var/run/docker.sock:/var/run/docker.sock"
environment:
- OVERRIDE_VIDEO_OUTPUT_DIR=$PWD/config/video
command: ["-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs", "-session-attempt-timeout", "180s"]
ports:
- "4444:4444"
chrome-latest:
image: selenoid/chrome:latest
video-recorder:
image: selenoid/video-recorder:latest-release
SelenoidのDockerイメージをPullして起動しています。あわせてselenoid/chrome、selenoid video-recorderのイメージも必要となるので、ここでPullしておきます。
ノード側の設定ファイルとしてconfig/browsers.jsonが別途必要です。
Selenoidのノード接続
SelenoidのノードをRemoteWebDriverで起動しています。
ファイルダウンロード時のダイアログ制御やダウンロード先のディレクトリをあらかじめ指定してからChromeを起動しています。特に変わったことはしておらず、いずれもSeleniumやSelenoid関連でググればすぐにでてくるような内容です。
// jp.shiftinc.automation.ExampleTest
void setUp(Boolean allowPopup) {
videoName = String.format("%s.mp4", RandomStringUtils.randomAlphanumeric(10));
driver = new RemoteWebDriver(nodeUrl,setChromeOption(allowPopup));
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
sessionId = driver.getSessionId().toString();
}
ChromeOptions setChromeOption(Boolean allowPopup) {
ChromeOptions options = new ChromeOptions();
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.default_content_settings.popups", 0);
prefs.put("download.default_directory", "/home/selenium/Downloads");
prefs.put("download.prompt_for_download", allowPopup);
options.setExperimentalOption("prefs", prefs);
return options.merge(setCapabilities());
}
DesiredCapabilities setCapabilities() {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("chrome");
capabilities.setCapability("enableVideo", true);
capabilities.setCapability("enableVNC", true);
capabilities.setCapability("enableLog", true);
capabilities.setCapability("videoName", videoName);
return capabilities;
}
ファイルダウンロードのテスト
Seleniumのサイトでzipファイルをダウンロードするテストを書いてみました。あらかじめ用意したファイルとMD5で比較します。
@ParameterizedTestで、ダウンロード時にダイアログが表示されないパターン(=成功)と、表示されるパターン(=失敗)を展開しています。
// jp.shiftinc.automation.ExampleTest
@Story("Example for download file")
@ParameterizedTest(name = "allow popup when download => {0}")
@ValueSource(booleans = {false, true})
void canDownloadCorrectFile(boolean allowPopup) throws IOException, InterruptedException {
String IE_ZIP = "IEDriverServer_Win32_3.150.1.zip";
String REFER_PATH = "src/test/resources/" + IE_ZIP;
setUp(allowPopup);
// store original file MD5
assertMD5(new File(REFER_PATH), false);
// access url
driver.get(TEST_URL);
attachFileToReport(driver.getScreenshotAs(OutputType.FILE), "img01","image/png","png");
// download file
WebElement elm = driver.findElement(By.cssSelector("a[href*=\"IEDriverServer_Win32_3\"]"));
elm.click();
// get file by using selenoid api
URL url = new URL(String.format("%s/download/%s/%s", baseUrl, sessionId, IE_ZIP));
File download = downloadFile(url);
attachFileToReport(download, "IEDriverServer", "application/octet-stream", "exe");
// assert file by MD5
assertMD5(download, true);
}
SelenoidのファイルダウンロードはAPI経由で行います。RemoteWebDriverのセッションIDとファイル名が必要です。
Selenoid - A cross browser Selenium solution for Docker
このURLを与えてファイルを取ってきてくれるサービス関数を書きます。URLにセッションIDが含まれているのは並列化したときに強そうで良いですね。
// jp.shiftinc.automation.ExampleTest
File downloadFile(URL url) throws IOException, InterruptedException {
String path = url.getPath();
Thread.sleep(5000);
String downloadFile = "build/tmp/" + path.substring(path.lastIndexOf("/") + 1);
try (DataInputStream in = new DataInputStream(url.openStream());
DataOutputStream out = new DataOutputStream(new FileOutputStream(downloadFile))) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.flush();
}
return new File(downloadFile);
}
あらかじめファイル名がわかっている場合は良いですが、動的なファイル名のように把握できない場合もあります。下記のURLでいったんファイルリストを取得してからダウンロードすることなるようです。
ビデオの取得
テスト実行時のビデオもAPI経由でダウンロードします。
Selenoid - A cross browser Selenium solution for Docker
ビデオファイル名はあらかじめ適当に決めておきます。こちらも並列化する際にはセッションIDなどで一意にしたほうが良さそうです。
// jp.shiftinc.automation.ExampleTest#setUp
videoName = String.format("%s.mp4", RandomStringUtils.randomAlphanumeric(10));
ダウンロードしたビデオファイルはレポートに添付します。テストが失敗した場合でも取得するように、JUnit5のTestWatcherで成功時/失敗時をフックしておきます。
class SelenoidTestWatcher implements TestWatcher {
@Override
public void testSuccessful(ExtensionContext context) {
try {
tearDown(context);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
try {
tearDown(context);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
private void tearDown(ExtensionContext context) throws IOException, InterruptedException {
ExampleTest exampleTest = (ExampleTest)context.getRequiredTestInstance();
exampleTest.downloadVideo();
exampleTest.downloadLog();
}
}
ログファイルの取得
SelenoidではNodeコンテナ内のブラウザログを取得できます。原因がわからないときに役に立ってくれそうです。
Selenoid - A cross browser Selenium solution for Docker
要素が見つからずに失敗するテストを書いてみました。ブラウザのログがエラー時にどこまで出るかを確認するためです。
// jp.shiftinc.automation.ExampleTest
@Story("Example for failed test(please check browser log)")
@Test
void cannotClickElement() throws IOException {
setUp(true);
driver.get(TEST_URL);
attachFileToReport(driver.getScreenshotAs(OutputType.FILE), "img02","image/png","png");
driver.findElement(By.cssSelector("a[href*=\"not_exists\"]")).click();
}
ビデオファイルと同様にAPI経由で取得してレポートに添付します。
結果を見るとしっかりとログにエラーが出力されていました。
browers.jsonであらかじめログの詳細(VERBOSE)を設定しておきます。(この設定はテストコード側では制御できないようです。)
# config/browers.json
"chrome": {
"default": "latest",
"versions": {
"latest": {
...
"env": ["VERBOSE=true"],
...
}
}
}
補足
本エントリーの趣旨とは少しはなれますが、レポーティングについて補足です。
GitHub PagesにAllureのテスト結果レポートを出力していますが、単純にレポートを作成するだけでは過去分は上書きされてしまいます。
リグレッションテストの運用では過去データと比較する場合も考えられますので、履歴を保持するようにActions Marketplaceからsimple-elf/allure-report-actionを利用させていただきました。
これで過去分のレポートをいつでも見られるようになりました。
まとめ
SelenoidのAPIを使用したファイルダウンロードはそれほど難しくなさそうです。ビデオ・ログファイルについても同様です。スケールした場合にテスト間でコンフリクトしないように設計されていると思います。
今回はChromeだけでのトライアルでしたが、他ブラウザへの対応は課題です。(IEへの対応はきっと難しいことでしょう)
ビデオ録画はノードコンテナのOSデスクトップを撮るので異常発生時の調査に役立ちそうです。ブラウザのレンダリング範囲しか撮れないWebDriverのスクリーンショット機能とは大きな違いを感じます。
ただし、ビデオやログファイルはストレージを圧迫することになるのでツール設計時には配慮が必要です。今回は未検証ですがSelenoidではAWSのS3バケットにデータを投げ込むAPIも用意されているようですので、こちらをうまく使えばよいのかもしれません。
次回はスケーラブルな自動テストの作成についてトライしてみたいと思います。
それではみなさん Happy Automation Testing Journey!!
__________________________________
■SHIFTのサービス一覧
品質に関するお悩みにぴったりなサービスを揃えております。
■SHIFTについて
私たちはソフトウェアテスト(第三者検証)のプロ集団です。
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/