中級者のためのCypress入門:E2Eテスト自動化
Cypressは軽量・直感的な上、安定したテスト自動化を実現する工夫が施されたWebアプリ用テストフレームワークです。JavaScriptで構築されておりブラウザ上の動作や変更の検知に強みがある反面、マルチブラウザ非対応などの制約事項も存在します。この記事では、Cypressの特徴における全体像や強み・制約事項、高速化・安定化のためのデザインパターン、選定のための基準を紹介します。
記事のおおまかな構成
- Cypressとは何か?特徴・他の技術セットとの違い
- Cypressを選ぶと何が嬉しいのか?
- テスト高速化・安定化のためのデザインパターン: Application Actionsとは?
- Cypressを使うべき条件・使わないべき条件は?
想定する読者
- Web開発やE2Eテスト実装の経験があり、基礎は特に求めていない方
- UIテスト自動化の選定に悩んでいる方
- Cypressを概要レベルで理解したい方
1. Cypressとは何か?
Cypressは高速でインタラクティブなE2Eテスト自動化フレームワーク(JavaScript)であり、2019年にローンチ後その手軽さや安定性から人気を集めています。主な特徴をリストアップします。
主な特徴リスト
- SelenideやPlaywrightと同様、Automatic Waitingによって要素取得や動作の安定性を向上する。
await
やsleep
などの付加は(ほとんどの場合)不要。 -
Chainable
によって要素を操作→アサーションなどのメソッドを動作の順に繋げる形式のため、読みやすくシンプルな構造で要素の特定や操作がしやすい。
descrive('ECカート機能E2Eテスト',() => {
it('商品の追加ボタンを押すと、カート内商品一覧に商品名が追加される', () => {
cy.visit('http://localhost:3000/products');
const testProductName = 'FANCYな帽子'
cy.contains(testProductName)
.parent()
.get('.product-add-button')
.click();
cy.visit('http://localhost:3000/cart')
cy.contains(testProductName)
.should('be.visible')
});
});
- E2Eテストに特化して構成されており、一般的な自動化ツールとしての利用は想定しておらず、Webスクレイピングなどの利用は想定されていない。
- 対応ブラウザはChrome, Edge (, Edge Dev),Electron
-
npm install
でデスクトップUIとCLIが同時にインストールされ、環境構築のストレスが少ない。 - デスクトップUIはテスト実行に対し高いトレーサビリティを提供している
-
.spec
ファイルの変更を検知すると、自動Re-Runするため、テストの実装効率が高い - テスト後、各ステップ実行時の状態を保存するため、失敗時の原因特定が容易
CypressのデスクトップUI
-
- CLIでは、
npx cypress run
でコマンドによる実行が可能。CI/CDパイプラインへの組み込みは容易。 - レポートはjson/junit/html形式に対応している上、
Cypress Cloud
での拡張も可能(一定規模に達すると有料となる)。 - 実行時、デフォルトで動画の取得が可能(規模にもよるが、10秒で100KB程度であり、ビデオ品質もConfigが可能。)
- 失敗した場合、失敗したタイミングのスクリーンショットが取得される
他のフレームワークとの違い
例えばSeleniumと比べた時に、Cypressには、「FrontendのWebアプリ(React, Vueなど)と同じイベントループ内で実行される」という大きな特徴があります。それにより、アプリケーションによるブラウザ内の細かい動作を検知、制御することができる仕組みになっており、下記のような制御が可能です。
- アプリの動作中、特定のAPIに対する呼び出しを監視し、responseのステータスコードが"200"であることを確認して操作実行
- APIレスポンスをinterceptし、スタブデータを返却する
- Componentの状態の変更を監視/操作する
SeleniumなどのE2Eテストツールは、テストコードがブラウザ外部で実行されるため、非同期操作を処理したり、ブラウザの動作の監視・制御をする場合は追加で実装が必要ですが、Cypressはテストコードをブラウザ内で実行する特徴を活かし、アプリケーションの状態をWatchしたり、実際に変更したりすることができます。
2. Cypressで実現する保守性の高いテストのデザイン
Page Object Modelは不要?
UIテストの保守性を高めるためのデザインパターンの代表的な例として、Page Object Model(POM)があります。しかし、公式は、CypressではPage Objectは不要であるという考え方を発信しており、その代替としてApplication Actionsというデザインパターンを提唱しています。
Page Object Modelとは、HTMLファイルやアプリのTemplate/Componentファイルに対し、それに対するセレクタや動作をPage Objectとしてカプセル化することで、テストコードの中で再利用可能にする方法です。オブジェクト指向的な書き方ができるため、保守対象のテストコードが多くても、画面要素の変更や操作の変更に対するレジリエンスが向上します。
Application Actions というデザインパターン
前述の通り、Cypressはブラウザ内部の動作について詳しく知ることができます。その特性を利用して、実際のアプリケーションの機能に立脚して一連の動作をActionとして一般化する、という考え方を提唱しています。
...この記載だけだとなかなか理解が難しいので、実例を用いて説明します。
実際のアプリでの実装例
「ログインして、商品をカートへ追加/削除するWebアプリ」の場合を考えてみましょう。
まず、このアプリは、機能として、下記の2つに分かれます。
- ログイン機能
- カートへの追加、削除を行う機能 (ログインが前提条件となる)
第一ステップとして、ログイン機能のE2Eテストをlogin.spec.ts
として開発してみることとします。テストケースは1つの例のみですが、他のエラーパターンなどを再現したテストも記載して、ログイン機能の品質には100%の自信があるという状態とします。
describe('ログイン機能テスト', () => {
it('Successful login with admin user', () => {
// ログイン画面への遷移
cy.visit("http://localhost:3000/login");
// Email・パスワードの入力
cy.get("#login-email-input").type("kei@example.com");
cy.get("#login-password-input").type("xxxxxxxx");
// Submitボタンのクリック
cy.get("#login-button").click();
// ホームページのタイトルが見えることを確認
cy.get('#home-page-title').should('be.visible')
})
})
これで、ログイン機能のテスト実装は完了したので、カート機能に関するテスト自動化を実装していきます。カート機能の利用にはログインが必須となります。カート機能を操作する自動化コードを記載する場合、ログイン処理を挟まなければなりません。
Cypressの、UI操作の共通化のための仕組みとしてCypress.commands.add()
というAPIが用意されているので、これでlogin
処理として共通化します。
Cypress.Commands.add('login', (email:string, password:string) => {
// ログイン画面への遷移
cy.visit("http://localhost:3000/login");
// Email・パスワードの入力
cy.get("#login-email-input").type(email);
cy.get("#login-password-input").type(password);
// Submitボタンのクリック
cy.get("#login-button").click();
});
上記実装の問題点
上記の実装には、大きな問題点があります。
- すでにテスト自動化を終え、100%自信があるはずのログイン機能のテストステップが、カート機能のほか、すべての機能のテストステップとして含まれています。このステップたちが残っていることで、ネットワーク通信状況、実行マシンのメモリなどの状況によってテストの実行速度を遅くし、安定性を下げています。
- カート機能のテストコードは、「カート機能の検証」というスコープに極力閉じるべきであり、ログイン機能の操作はテスト前のstate(=アプリの状態)を作り込むための操作です。テスト前の状態を作るために、テストの安定性を下げるべきではありません。
Don't use UI to build up state.
Don't limit yourself trying to act like a user. - Brian Mann, Founder of Cypress
Application Actionsでアプリの動作を再現して、安定性を向上させる
上記の問題点を解決する考え方が、Application Actionsという考え方です。
テストのためのstate(=アプリの状態)を作るために、重く安定しないUI操作を自動化するのではなく、アプリケーションの状態を作るための動作を「アプリケーションのように」行うことで安定化・高速化を図ります。
Cypress.Commands.add('login', (email:string, password:string) => {
cy.request({
method: 'POST',
url: 'http://backend.example.com/api/login',
headers: {
"Content-Type": "application/json"
},
body: {
username: email,
password: password,
}
}).then((response) => {
const token = response.body.access_token;
cy.setCookie('auth-token', token);
});
});
上記では、cy.request()
を使ってバックエンドのログインAPIを呼び出し、cy.setCookie()
を用いてブラウザのCookieに認証用のトークンをセットしています。これはフロントエンドアプリの挙動と同様の動きであり、UI操作よりも実行速度が数秒程度速くなり、カート機能のテストコードは、「カート機能の検証」に集中してコーディングすることが可能になり、すなわちテストの独立性を高めることに繋がります。
このように、CypressではApplication Actionsとして実際のアプリケーションの機能に立脚して一連の動作を一般化するという考え方を導入することによって、E2Eテストの高速化・安定化だけでなく、テストの独立性を高める方法を推奨しています。
CypressのFounderが説明している動画では、上記のような例が紹介されています。
3. Cypressを選ぶ理由・選ばない理由
銀の弾丸はない
基本的に、テスト自動化のフレームワーク・ライブラリは、採用しているテクノロジースタック・求められる品質水準・テスト体制・保守サイクルなどのプロジェクト特性を理解した上で選択します。基本的には、どんなケースでも導入すれば圧倒的に効果が出るようなライブラリ(Silver Bullet)は存在しません。
Cypressが適する場合
その書き方やアプリケーションとの連動性から、下記のようなケースで特に実力を発揮します。
- モダンJSフレームワーク(React, Next, Vue, Nuxt..)を採用している
- フロントエンド開発者が、E2Eテストの実装・保守を担当する
- プロダクトライフサイクルの初期段階で、TDD的にテスト開発を進める場合
Cypressが適さない可能性がある場合
逆に、下記のようなケースでは、Cypressの導入や既存フレームワークからの移行は向いていない場合があります。
- モノリシックな大規模アプリケーション
- DeveloperとQA Engineer/SDETチームが明確に切り分けられている場合
- 多くのQA Engineer/Tester がアウトソースされる組織構成を取っている場合
- HoverによるUXの自動化が多く求められている場合(2023/08現在、
.hover()
コマンドが存在しない) - 複数タブや複数ブラウザにわたる操作が多く存在する場合
まとめ
Cypressは軽量・直感的な上、安定したテスト自動化を実現する工夫が施されたテストフレームワークです。具体的な実装やコマンドの使い方などについても、今後また紹介していこうと思います。
ここまで読んでいただき、ありがとうございました。
それでは皆様、Happy Testing!
参考・引用リンク集
Accenture Japan QE&Aでは、テスト自動化を通じてお客様の変革を支援しています。
同じビジョンを共有する仲間を募集しています!
Discussion