Cypress を利用した E2E テスト

テストエンジニアテスト手法テスト自動化品質保証QA

Cypress とは、ブラウザをクライアントとする web アプリケーションの E2E テスト【End to End Test】を実現するためのテストツールです。
E2E テストに必要なものが網羅されており、テストコードを記述するだけでシステム全体を通した結合テストを容易に実施することができます。
CLI からの実行も可能であり、テストを自動化したい場合にも最適です。

Cypress は Node.js を利用したアプリケーションであり、開発言語は JavaScript、対応ブラウザは chrome、chromium、edge、electron、firefox です。

参考: Webシステムにおける結合テスト

Cypress の公式サイト
https://www.cypress.io/


Cypress のインストール
Cypress は Node.js のパッケージなので、npm でインストールします。
今回は npm のローカルインストールにて例示します。

>cd C:\NodeJS\local # インストール先ディレクトリ
>npm init # パッケージ情報を初期化する場合
>npm install cypress


カレントディレクトリのnode_modulesに実体がコピーされ、package.jsonpackage-lock.jsonファイルにパッケージ情報が追記されます。
これでインストールは完了です。

参考: Node.js のパッケージ管理ツール npm とは

npmjs.com
https://www.npmjs.com/package/cypress


プロジェクトの作成
プロジェクトとは、テストセットの固まり、いわゆるテストスイートです。
まず、Cypress のインストールディレクトリへ移動します。
npx で Cypress を起動すると、カレントディレクトリにcypressというディレクトリとcypress.jsonというファイルが作成され、コンソール画面が立ち上がります。

>cd c:\NodeJS\local
>npx cypress open
Cypress起動画面


OK をクリックすると、cypress/integration/examples 配下にあるテストプログラムの一覧が表示されます。


テストプログラムの作成
テストコードはcypress/integration配下に配置します。
このディレクトリは構成ファイル(cypress.json)の設定integrationFolderで変更できます。
配置されたテストコードは Cypress のコンソール画面上に現れ、右上の Run all specs をクリックするだけですべてのテストコードを実行できます。

Cypress の言語仕様は、Javascript のテストフレームワークである Mocha の BDD スタイルがベースとなっています。

describe("テストセットタイトル", function(){
	it("テストタイトル", function(){
		// ここにテスト内容を記述します。
	});

	before(function(){
		// describe() の最初に実行されます。
		// 前処理系を記述します。
	});

	after(function(){
		// describe() の最後に実行されます。
		// 後処理系を記述します。
	});

	beforeEach(function(){
		// 各 it() の前に実行されます。
		// it() 実行のたびに行いたい前処理系を記述します。
	});

	afterEach(function(){
		// 各 it() の後に実行されます。
		// it() 実行のたびに行いたい後処理系を記述します。
	});
});

describe()
テストセットの宣言です。
テストセットのタイトルとコールバックメソッドが引数になります。
describe()はネスト可能です。

it()
実行するテストコードです。
テストタイトルとコールバックメソッドが引数になり、コールバックの中にテストコードを記述します。
1つのdescribe()の中に複数のit()を含めることができます。

before()
describe()実行時に最初に1回だけ実行されます。
前提条件や前処理を記述します。

after()
describe()終了時に1回だけ実行されます。
後処理を記述します。

beforeEach()
it()の実行時に1回だけ実行されます。

afterEach()
it()終了時に1回だけ実行されます。

以下、テストコードの例です。

describe("Test Suite", function(){
	it("Test 01", function(){
		// ページ移動
		cy.visit("https://dev.softwarenote.info/test/test_exp01.php");

		// 要素の取得にはjQueryセレクタが使えます
		cy.get("#id");
		cy.get(".class");

		// フォームの操作
		cy.get('input[type="text"][name="post_textbox"]').type("cypress.io"); // テキストボックス
		cy.get('textarea[name="post_textarea"]').type("cypress.io"); // テキストエリア
		cy.get('input[name="post_checkbox"]').check(); // チェックボックス
		cy.get('input[name="post_radio"]').check("val2"); // ラジオボタン value="val2"を選択する
		cy.get('select[name="post_select"]').select("val2"); // プルダウン value="val2"を選択する

		// メソッドチェーンによる実装
		cy.get('textarea[name="post_textarea"]')
			.type("cypress.io") // 「cypress.io」と入力し
			.type("{enter}") // エンター
			.type("cypress.io"); // 「cypress.io」と入力

		// ラジオボタン、チェックボックス
		cy.get('input[type="radio"]').first().check(); // ラジオボタン 最初の項目をチェックする
		cy.get('input[type="checkbox"]') // チェックボックス
			.each(function($el, index, $list){
				if($el.prop("checked")){
					$el.prop("checked", false); // チェックをはずす
				}else{
					$el.prop("checked", true); // チェックする
				}
			});

		// ボタンのクリックなど
		cy.get('[type="button"]').click(); // ボタンをクリックする
		cy.contains("送信").click(); // 文字列から要素を取得しクリック
		cy.get('form[name="form_test"]').submit(); // POSTする

		// アサーション(Chai BDD TDD Assertions)
		cy.get('[name="post_textbox"]').should("have.value", "cypress.io"); // TDD アサーション
		cy.get('[name="post_textbox"]').then(($el) => {
			expect($el).to.have.value("cypress.io"); // BDD アサーション
		});

		cy.get("#result_text").should("have.text", "結果");
		cy.get("#result_text").then(($el) => {
			expect($el).to.have.text("結果");
		});

		// その他の操作
		cy.screenshot(); // スクリーンショット
		cy.reload(); // ページのリロード
		cy.log("test log"); // ログ出力

		cy.setCookie("ses_id", "dabde"); // クッキーの設定
		cy.getCookie("ses_id"); // クッキーの取得
		cy.clearCookie("ses_id"); // クッキーの削除
	});
});

Cypress – Writing Your First Test
https://docs.cypress.io/guides/getting-started/writing-your-first-test.html

Cypress – Best Practices
https://docs.cypress.io/guides/references/best-practices.html

Cypress – アサーション
https://docs.cypress.io/guides/references/assertions.html


テストプログラムの保守性を高めるために
プロジェクトの保守期間が長くなると、しだいにテストセットは肥大化し、管理が難しくなっていきます。
なるべくテストプログラムも作りっぱなしにせず、共通処理の整理やモジュール化、リファクタリングを行いましょう。

例えばcypress/support/commands.jsには、どのテストプログラムからでも利用できるメソッドを定義することができます。

Cypress.Commands.add("login", function(uid, passwd){
	cy.visit("/login");
	cy.get('input[name="uid"]').type(uid);
	cy.get('input[name="password"]').type(passwd);
	cy.get('input[type="submit"]').click();
});
describe("Test1", function(){
	it("Commands Test", function(){
		cy.login("userId", "passWord");
	});
});


class のインポートも可能です。

export default class Login{
	login(uid, passwd){
		cy.visit("/login")
		cy.get('input[name="uid"]').type(uid);
		cy.get('input[name="password"]').type(passwd);
		cy.get('input[type="submit"]').click();
	}

	logout(){
		cy.visit("/logout")
	}
}
import Login from "../classes/Login"

describe("Test2", function(){
	it("Import Test", function(){
		const lg = new Login();
		lg.login("userId", "passWord");
	});
});


class の定義もできます。

class Login{
	login(){
		cy.visit("/login")
		cy.get('input[name="uid"]').type("userId");
		cy.get('input[name="password"]').type("passWord");
		cy.get('input[type="submit"]').click();
	}
}

describe("Test3", function(){
	it("Class Test", function(){
		const lg = new Login();
		lg.login();
	});
});


サンプルデータは、cypress/fixtureを利用しましょう。

{
	"userid": "userId",
	"password": "passWord";
}
class Login{
	login(){
		cy.fixture("profile").then(function(Profiles){
			let uid = Profiles.userid;
			let passwd = Profiles.password;
		}
}


ファイルを直接読み込むこともできます。

describe("Test4", function(){
	it("readFile Test", function(){
		cy.readFile("profile.json").its("loginid").should("eq", "loginId");
	});
});

参考: Cypress – readFile()

実際に動作するサンプルプログラムを作ってみました。
Cypress の実行環境が整ったらお試しください。

参考: Cypress を利用したサンプル


テストの実行
コマンドラインからの実行は以下の通りです。

>cd c:\NodeJS\local
>npx cypress open # GUIを起動する場合
>npx cypress run # すべてのテストが自動実行されます
>npx cypress run --spec (-s) cypress/integration/test1.js # テストを選択して実行
>npx cypress run -s path/to/*/test*.js # テストの選択にはワイルドカードが使用可能


何も指定しないとcypress/integration配下のすべてのテストプログラムが実行されます。
--spec, -sオプションで実行するテストプログラムを指定します。
ワイルドカードによって複数のプログラムを自動的に実行できます。

–browser, -b {ブラウザ名}
ブラウザを指定します。
chromechromiumedgeelectronfirefoxが指定可能です。
デフォルトは chrome です。

–config-file, -C {構成ファイル名}
構成ファイルを読み込みます。
デフォルトはカレントディレクトリにあるcypress.jsonを読みに行きます。

–headed
ブラウザを表示してテストを実施します。

–headless
ブラウザが非表示の状態でテストを実施します。

–record
テストの実行結果を記録します。
cypress/videosディレクトリにブラウザの動作が録画された動画が保存されます。
エラーが発生した場合は、cypress/screenshotsにブラウザのキャプチャ画像が保存されます。

Cypress – コマンドライン
https://docs.cypress.io/guides/guides/command-line.html



構成ファイル
Cypress はテスト実行時にカレントディレクトリにある構成ファイル(cypress.json)を読み込み、そこに設定された構成値によって動作を制御します。
構成ファイルは、コマンドオプション--config-file, -Cで切り替えることができます。
以下は主な構成値を記述した構成ファイルの設定例です。

{
	"baseUrl": "softwarenote.info", // cy.visit() または cy.request() の URL プレフィックス
	"numTestsKeptInMemory": 50, // メモリに保持されるテスト数。使用メモリ量が高い場合はこの数を減らす

	"pageLoadTimeout": 60000, // cy.visit() や cy.go()、cy.reload() のタイムアウト値 [ms]
	"responseTimeout": 30000, // cy.request() などが応答するまでのタイムアウト値 [ms]

	"integrationFolder": "cypress/integration", // テストプログラムの参照場所
	"screenshotsFolder": "cypress/screenshots", // cy.screenshot() やテスト失敗時のキャプチャ画像の保存場所
	"testFiles": "*/*.*", // 実行するテストファイルのフィルタ

	"videoFolder": "cypress/videos", // ビデオの保存場所
	"screenshotOnRunFailure": true, // テスト失敗時にキャプチャ画像を撮るかどうか
	"video": true, // ビデオを録画するかどうか
	"trashAssetsBeforeRuns": true, // cypress run 実行時に保存されている画像やビデオファイルを消去

	"userAgent": null, // ユーザエージェントの変更

	"viewportWidth": 1000, // ブラウザのビューポートの幅
	"viewportHeight": 660 // ブラウザのビューポートの高さ
}


Cypress – 構成オプション
https://docs.cypress.io/guides/references/configuration.html



環境変数
構成ファイルでは、オプションenvを利用してテストプログラムに設定値を渡すことができます。

{
	"env": {
		"user_env1": 1,
		"user_env2": 2
	}
}
	Cypress.env("user_env1"); // =1
	Cypress.env("user_env2"); // =2

コメント