Amazon Web Services ブログ
Selenium, AWS Lambda, AWS Fargate, AWS Developer Tools を使ったサーバーレスなUIテスト
(この記事は、 Serverless UI testing using Selenium, AWS Lambda, AWS Fargate, and AWS Developer Tools を翻訳したものです。)
以前、Using AWS CodePipeline, AWS CodeBuild, and AWS Lambda for Serverless Automated UI Testing (日本語版 ) を公開してから、Chrome headless とFirefox headless が各ブラウザでネイティブにサポートされるようになったことで、事態は大きく変わりました。 AWS Lambda は今やコンテナイメージをサポートし、 AWS Step Functions はLambda と統合された Map state のサポートを追加し、AWS Fargate は完全にサーバーレスのテクノロジを利用した、UIテストを可能にしました。
このブログの目的は、AWS Developer Tools を使ってどのようにテスト環境を自動的にデプロイする継続的デリバリパイプラインを構築し、それに対してUIテストを実行するのかをお見せすることです。また、AWS Lambda や AWS Fargate 上でテストを実行するために利用される、Chromium browserとFirefox browser、Selenium とそれらの依存関係を含んだコンテナを構築する方法も説明します。Step Functions のMap stateを利用することで、複数のテストケースを並行で実行し、テストの速度を加速することができます。
ソリューションの概要
下記の図は私たちのソリューションのアーキテクチャを表しています。私たちにはソースステージとして全てのソースコードが含まれたリポジトリである AWS CodeCommit が設定された、AWS CodePipeline に定義されたパイプラインがあります。ソースが変更されると、このパイプラインが自動的にトリガーされます。続くビルドステージではAWS CodeBuild をつかってDockerfileからコンテナイメージをビルドし、Amazon ECR のリポジトリにイメージをプッシュします。イメージがアップロードされたら、テストステージでは、テストWeb サイトとステータスWebサイトを AWS CloudFormation を使ってAWS Amplify にデプロイします。次のアクションとして、Step Functions を通じてAWS Lambda やAWS Fargate で実行されるテストがトリガーされます。テストステージの完了後、承認のためのEmail がステータスWebサイトのリンクとともに送付されます。承認者がその結果を承認すると、Webサイトは本番環境にデプロイされます。
このソリューションの一部として、UIテストのためのWebサイトと、テスト結果を表示するためのステータスWebサイトを構築し、AWS Amplify でホストするようにしました。
ここからはセクションごとに下記を見ていきましょう。
- コンテナイメージを作成するために利用するDockerfile のステップバイステップガイド
- 継続的デリバリパイプラインの様々なステージ
- Step Functions で作られるステートマシンの詳細について
- Lambda やFargate で実行されたテストの録画方法
Chromium やFirefox ブラウザは、基盤となるオペレーティングシステムで利用可能でアクセス可能であることが期待される特定のライブラリに依存します。 Lambdaのコンテナサポート を使うと依存するパッケージ全てをコンテナの一部としてパッケージするのがとても簡単になります。Chromium は共有メモリを無効化するフラグ (/dev/shm
) を提供しているので、Lambda で実行できます。Firefox は fallocate システムコールと /dev/shm
に依存しています。この投稿では、Firefox 上のテストを実行するためにFargate を利用します
この投稿で言及するリソースには、GitHub のserverless-ui-testing-using-selenium リポジトリで公開されている AWS CloudFormation のテンプレート、テスト対象とステータスのWebサイト、 AWS CodeBuild のビルド仕様ファイル、Dockerfile、そして、このテストを実行するPythonスクリプト が含まれます。
前提条件
このサンプルのソリューションをデプロイするには、あなたは、下記のサービスへの AWS Identity and Access Management (IAM) アクセスが必要です
- AWS Amplify
- AWS CodeBuild
- AWS CodeCommit
- AWS CodePipeline
- AWS CloudFormation
- Amazon DynamoDB
- Amazon Elastic Container Service(Amazon ECS)
- Amazon ECR
- AWS Key Management Service(AWS KMS)
- AWS Lambda
- Amazon Simple Notification Service(Amazon SNS)
- AWS Step Functions
また、リポジトリから、clone しpush するためにGit のクライアントが必要です。
コンテナイメージのためのDockerfile の詳細
下記のDockerfile から分かる通り、Lambda とFargateで利用するコンテナイメージをビルドするために、multi-stage ビルドを実行します。プロセスは下記の3つのステージがあります。
lambda-base
ステージでは、Python3.8 のAWS base image for Lambda をベースイメージとして、ブラウザや他の依存するソフトウェアをインストールします。このステージでは下記のステップを行います。- あとで使うために、
requirements.txt
とinstall-browsers.sh
をコンテナにコピーします。 - Chromium とFirefox が必要とする、オペレーティングシステムレベルの依存関係をインストールします。
- Selenium WebDriver を含むPython の依存関係をインストールします。
- これまでのステップで利用された不要なパッケージを削除します。
- あとで使うために、
ffmpeg
ステージではビデオを録画するために必要なFFmpeg をビルドするためのコンテナのベースイメージとして、 AWS base image for LambdaのPython3.8 を使用します。- 最終ステージでは、最初のステージで構築されたlambda-base イメージを使用し、Lambda とFargate で必要な最終イメージを生成します。
- このコンテナの
/usr/bin
ディレクトリに、先ほどのステージでコンパイルされたFFmpeg バイナリをコピーします。 - Lambda のハンドラと、Fargate 用のハンドラを含むアプリケーションコードと、他のテストケースを
/var/task
ディレクトリにコピーします。
- このコンテナの
コンテナのエントリーポイントを、Lambda のイメージの設定のオーバーライドとAmazon ECS のタスク定後のコンテナ定義を使って設定します。
# Install Browser, OS dependencies and Python modules
FROM public.ecr.aws/lambda/python:3.8 as lambda-base
COPY requirements.txt /tmp/
COPY install-browsers.sh /tmp/
# Install dependencies
RUN yum install xz atk cups-libs gtk3 libXcomposite alsa-lib tar \
libXcursor libXdamage libXext libXi libXrandr libXScrnSaver \
libXtst pango at-spi2-atk libXt xorg-x11-server-Xvfb \
xorg-x11-xauth dbus-glib dbus-glib-devel unzip bzip2 -y -q
# Install Browsers
RUN /usr/bin/bash /tmp/install-browsers.sh
# Install Python dependencies for function
RUN pip install --upgrade pip -q
RUN pip install -r /tmp/requirements.txt -q
# Remove not needed packages
RUN yum remove xz tar unzip bzip2 -y
# Build ffmpeg
FROM public.ecr.aws/lambda/python:3.8 as ffmpeg
WORKDIR /ffmpeg_sources
RUN yum install autoconf automake bzip2 bzip2-devel cmake libxcb libxcb-devel \
freetype-devel gcc gcc-c++ git libtool make pkgconfig zlib-devel -y -q
# Compile NASM assembler
RUN curl -OL https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.bz2
RUN tar xjvf nasm-2.15.05.tar.bz2
RUN cd nasm-2.15.05 && sh autogen.sh && \
./configure --prefix="/ffmpeg_sources/ffmpeg_build" \
--bindir="/ffmpeg_sources/bin" && \
make && make install
# Compile Yasm assembler
RUN curl -OL https://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
RUN tar xzvf yasm-1.3.0.tar.gz
RUN cd yasm-1.3.0 && \
./configure --prefix="/ffmpeg_sources/ffmpeg_build" \
--bindir="/ffmpeg_sources/bin" && \
make && make install
# Compile FFMpeg
RUN curl -OL https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2
RUN tar xjvf ffmpeg-snapshot.tar.bz2
RUN cd ffmpeg && \
export PATH="/ffmpeg_sources/bin:$PATH" && \
export PKG_CONFIG_PATH="/ffmpeg_sources/ffmpeg_build/lib/pkgconfig" && \
./configure \
--prefix="/ffmpeg_sources/ffmpeg_build" \
--pkg-config-flags="--static" \
--extra-cflags="-I/ffmpeg_sources/ffmpeg_build/include" \
--extra-ldflags="-L/ffmpeg_sources/ffmpeg_build/lib" \
--extra-libs=-lpthread \
--extra-libs=-lm \
--enable-libxcb \
--bindir="/ffmpeg_sources/bin" && \
make && \
make install
# Final image with code and dependencies
FROM lambda-base
# Copy FFMpeg binary
COPY --from=ffmpeg /ffmpeg_sources/bin/ffmpeg /usr/bin/
# Copy function code
COPY app.py /var/task/
継続的デリバリのパイプライン
次にソースコードリポジトリの変更をもとにして自動的にテストと本番環境へのデプロイメントを実施するために、継続的デリバリのパイプラインを作成します。pipeline.yaml
テンプレートを使ってAWS CloudFormation でこのパイプラインを作成します。このスタックは、このパイプラインと、それが依存するリソース、そして、モジュールの詳細とテストの結果を保存するためのDynamoDB テーブルを作成します。
このパイプラインは下記のステージを含んでいます。
- ソースステージ – 設定されたCodeCommit のリポジトリのコードの変更を監視し、CodePipeline のサーバーレスのUI テストのパイプラインをトリガーします。
- ビルドステージ – CodeBuild でコンテナのイメージをビルドし、CodeBuild の実行 ID を利用してそれを適切にタグづけし、Amazon ECR レジストリにそのイメージをpushします。
- ビルドステージ – CodeBuild でコンテナのイメージをビルドし、CodeBuild の実行 ID を利用してそれを適切にタグづけし、Amazon ECR レジストリにそのイメージをpushします。
- 最初のアクションはAWS CloudFormation を使ってLambda 関数、Fargate のタスク、テストの実行をオーケストレーションする、Step Functions のステートマシーンが含まれたテスト環境をデプロイします。
- テスト環境がデプロイされた後、2つ目のアクションがステートマシーンをトリガーします。このステートマシーンはDynamoDB テーブルをクエリし、実行するべきテストの一覧を取得し、Map stateを使ってこれらのすべてのテストを並行して実行するようにトリガーします。( AWS Management Console かAPI によってパイプラインの一部として作成された、
ModulesTable-
からはじまるDynamoDB テーブル を更新することで、テストの一覧やテストモジュールを更新することができます)
- 承認ステージ – テストステージが完了したら、このステージがテスト結果のページへのリンクが記載された通知のためのE-mail を送信します。承認者は、このテストの結果を確認し、本番環境へデプロイを承認、もしくは拒否できます。もし承認者がレビューを拒否した場合、このパイプラインはこのステージで停止します。
- 本番環境へのデプロイステージ – レビューで承認された場合、パイプラインはアプリケーションを本番環境にデプロイします。
パイプラインが承認ステージに到達した時、(下記のスクリーンショットのような) テストの結果のレビュー用のリンクと、承認、もしくは拒否のためのパイプラインへのダイレクトリンクが記載されたメールが送信されます。
レビューするコンテンツのリンクを選択すると、テストケースの一覧、テスト結果、ブラウザのバージョン、経過時間などの詳細が表示されるステータスページ(下記のスクリーンショットを見てください)が表示されます。
Step Functions のステートマシーン
さて、テストをオーケストレーションするStep Functions のステートマシーンを見てみましょう。下記のスクリーンショットを見てください。
このステートマシーンには Task state と、複数の Map state を持つ Parallel state があります。
- Task state – DynamoDB テーブルは特定のテストモジュールに関連するテストケースの詳細を保持しています。タスクステートでは、テストモジュールをインプットとして受け取り、DynamoDB からテストケースのリストを取得するためのクエリで利用し、特定のテストモジュールに関連するテストケースIDの配列、またはリストを取得します。これらのテストケースはこれら全てを並列で実行する次のステートの入力として渡されます。
- Parallel state – このステートはChrome 安定版、Chrome ベータ版、Firefox 安定版、Firefox ベータ版の各ブラウザバージョンでテストを並行して実施し、また、他にChrome とFirefoxでビデオとして録画(これは、必要な時にデバッグのために利用できます)するためのテスト実行を起動します。各Parallel stateはTask stateから得られたテストケースの数に応じて、テストケース毎に個別にLambda 関数やFargate タスクをトリガーするMap stateを起動します。実際に実施できる同時実行数のより詳しい情報は Lambda quotas とAmazon ECS service quotas をご確認ください。(訳注: StepFunctions のMap state の同時実行数の制約も合わせて考慮する必要があります。)
テスト実行をビデオ録画する
Parallel stateの1つはテスト実行のビデオ録画のためのステートマシンです。テスト実行をビデオとして記録するために、Chrome とFirefoxのヘッドフルブラウザとそれらが起動できるディスプレイが必要です。Lambda とFargate にディスプレイはないので、Xvfb のPython によるラッパーである、 X virtual framebuffer (Xvfb) とPyVirtualDisplay を使用します。この例では、Lambda 関数とFargate コンテナでDISPLAY 環境変数の値を:25 に設定しているので、Xvfb は25番のディスプレイでスタートします。FFmpegを使用して、25番のディスプレイ上の入力をx11grab形式で取得し、出力を.mp4の形式でファイルに書き込みます。録画が完了すると、ローカルファイルシステムから、Amazon S3 にアップロードします。ビデオ録画はテストを実行するだけよりもコンピュートリソースと実行時間を必要とするでしょう。そのため、条件によって、ビデオを録画することができます(失敗したテストをデバックのために再実行して録画します)。
クリーンアップ
このソリューションの一部として作成されたリソースをクリーンアップするには、下記のリソースを削除してください。
SUIT-Prod-Stack-
での接頭辞で始まる、CloudFormation スタックpipeline.yaml
テンプレートで作成したCloudFormation スタック- pipeline スタックによって下記の命名規則で作成された2つのS3 バケット
<PIPELINE_STACK_NAME>-codepipeline-artifact-<ACCOUNT_ID>-<REGION>
<PIPELINE_STACK_NAME>-test-output-<ACCOUNT_ID>-<REGION>
まとめ
この投稿では、Lambda とFargate 両方で利用できるコンテナイメージをどのようにビルドするのかをお見せしました。私たちは、CodePipeline, CodeBuild, AWS CloudFormation, そしてStep Functionsを使って、UI テストを継続的デリバリのパイプラインに統合しました。ここから、このアプローチをUIテストを超えて拡張し、AWS Developer Tools とサーバーレステクノロジーを利用して、完全に自動化されたテストを統合することができます。
翻訳はソリューションアーキテクトの金森 政雄が担当しました。原文はこちらです。