はじめに
岩手県立大学とか、岩手の人たち Advent Calendar 2022の14日目の記事です。
@gazeuse1442と申します。
情報系の専門学生でセキュリティやバックエンドなど広く浅く学習しています。
今回、岩手県立大学には在籍していませんが、岩手の某専門学校に在籍しているため参加させていただきました。
𝑰𝑾𝑨𝑻𝑬 𝑩𝑰𝑮 𝑳𝑶𝑽𝑬______
記事について
DockerでNextjs+selenumの環境を構築した時の備忘録です。
この記事ではNextJS + selenium + mochaを使ったDocker Composeでのテスト環境の構築・シェルスクリプトでの実行を解説します。
本題
まずは今回組むDockerのコンテナ構成について、
実行するテストのWebページについてはnpx create-next-app@latest
で生成されるnextJSのデフォルトページを使用します。
使用するコンテナは以下の通り
-
next-app
- NextJSを動作させるコンテナ
- node-slimを使用します
-
selenium-hub
- 複数のseleniumを管理できる便利なコンテナ
-
selenium-node
- node(chromium)をテスト環境にして実行するコンテナ
-
selenium-test-runner
- テストを実行させるコンテナ
- node-slimを使用します
next-appで生成・取得できるデータをテストに回します
大まかなフォルダ構成について
.
├── docker-compose.yml
├── selenium_runner
│ #testを動作させるコンテナ
└── selenium_test_next
#nextjsを動作させるコンテナ
Docker-compose について
M1macなどのARMプロセッサの場合、selenium-hubがうまく起動しない場合があるのでseleniarm-node
やseleniarm/hub
を代わりに指定します。depends_onを指定しないと他の依存コンテナより早く起動してしまうため、そのコンテナの名前を指定しないといけません。next-appとselenium-test-runnerについては、パッケージのインストールなど別途Dockerfileに記述しますが今回は省略。
version: "3.9"
services:
next-app:
build: ./selenium_test_next
ports:
- "8080:3000"
volumes:
- ./selenium_test_next:/app
- next-node-modules:/app/node_modules
networks:
- selenium-grid
selenium-hub:
image: selenium/hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
environment:
GRID_BROWSER_TIMEOUT: 10000
GRID_NEW_SESSION_WAIT_TIMEOUT: 20000
GRID_NODE_POLLING: 300
GRID_TIMEOUT: 10000
networks:
- selenium-grid
depends_on:
- next-app
selenium-node:
image: selenium/node-chromium:latest
shm_size: 2g
depends_on:
- selenium-hub
ports:
- "7901:7900"
environment:
SE_EVENT_BUS_HOST: selenium-hub
SE_EVENT_BUS_PUBLISH_PORT: 4442
SE_EVENT_BUS_SUBSCRIBE_PORT: 4443
networks:
- selenium-grid
selenium-test-runner:
tty: true
build: ./selenium_runner
volumes:
- ./selenium_runner:/app
- test-runner-node-modules:/app/node_modules
depends_on:
- selenium-node
networks:
- selenium-grid
networks:
selenium-grid:
driver: bridge
volumes:
next-node-modules:
test-runner-node-modules:
selenium-test-runnerについて
テスト環境には、
- selenium-webdriver
- mocha
- chai
を使用します。
今回はタイトルとh1タグのテキストが指定されたものかをテストします。
2つ目のテストは失敗するようにしています。
import {
Builder,
WebDriver
} from 'selenium-webdriver';
import { assert } from 'chai';
const TIMEOUT_MILLISEC = 10000;
let driver: WebDriver;
describe("サンプルページ", () => {
before(() => {
driver = new Builder()
.forBrowser("chrome")
.usingServer("http://selenium-hub:4444/wd/hub")
.build();
});
after(() => {
return driver.quit();
});
it("タイトルを検証する", async () => {
await driver.get("http://next-app:3000");
const title = await driver.getTitle();
assert.strictEqual(title, "Create Next App");
});
it("h1タグの中身を検証する", async () => {
await driver.get("http://next-app:3000");
const h1 = await driver.findElement({ tagName: 'h1' });
const h1_text = await h1.getAttribute("innerText")
assert.strictEqual(h1_text, "Create Next App");
});
});
テストを実行したいWebサーバーのURLやポートを適宜変更してください。
"scripts": {
"test": "mocha index.mjs --timeout 10000"
}
実行について
以下のコマンドで実行。
$ docker compose up
以下のテスト実行結果を取得することができます。
> test
> mocha index.mjs --timeout 10000
サンプルページ
✔ タイトルを検証する (1087ms)
1) h1タグの中身を検証する
1 passing (2s)
1 failing
1) サンプルページ
h1タグの中身を検証する:
AssertionError: expected 'Welcome to Next.js!' to equal 'Create Next App'
+ expected - actual
-Welcome to Next.js!
+Create Next App
at Context.<anonymous> (file:///app/index.mjs:38:12)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
selenium-test-runner
以外のコンテナが実行されていれば、問題なくテストを行うことができますが、ビルドから実行を行った場合NextJSやselenium-hubの起動が遅く、selenium-test-runnerで実行するテスト
が先に実行されてしまうため、エラーが発生することがあります。
そこで、シェルスクリプトを使ってWebサーバーが起動を完了してからテストを実行できるように設定していきます。
シェルスクリプトの作成
Webサーバーが起動したかどうかを判断するために、今回はcurl
を使用しました。
テストの実行後、テストの標準出力を./selenium_log/node{yymmddhhmm}.log
に保存できるようにします。
#!/usr/bin/env bash
set -e
if [ -z "$(docker container ls -q -f name='next-app')" ]; then
echo "container build"
nohup docker compose -f docker-compose.yml up next-app selenium-hub selenium-node &>/dev/null &
while :; do
#nextが起動するまで待機する
if [ "$(curl -fs localhost:8080 | grep '<html>')" ] &&
[ "$(curl -fs http://localhost:4444/ui | grep '<!doctype html>')" ]; then
echo "next-app container build complete."
break # 起動したら抜ける
fi
sleep 1
done
fi
echo "start selenium test."
if [ ! -e ./selenium_log ]; then
# 存在しない場合
mkdir ./selenium_log
fi
#いい感じにlogファイルに保存させる
docker compose up selenium-test-runner | (
v=$(cat)
printf "$v"
echo "$v" | grep --line-buffered 'selenium-test-runner' |
awk '/> test/,/exited with code/' |
cut -f 2 -d "|" |
sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" > ./selenium_log/node-$(date +%Y%m%d%H%M).log)
一部不要なログが保持されてしまいますが、これでワンライナーでコンテナの起動からテストのログを保持することができます。