
CypressでE2Eテストどやってみた!

IT技術
はじめに
プロジェクト概要
- Docker Composeで構築
- フロントエンドはRemix、バックエンドはGoで実装
- PostgreSQLを使用
- Nginxをリバースプロキシとして設定
- Cypressを使ってE2Eテストを実装
- GitHub ActionsでCI/CDを設定
やりたいこと
- APIテスト
- E2Eテスト
- ビジュアルテスト
- GitHub Actionsで自動テスト
さっそく始めよう!
ヘルスチェック: GET /api/health
ユーザーログイン: POST /api/user/signin
ユーザー登録: POST /api/user/signup
記事を取得: GET /api/posts/user
記事を作成: POST /api/post
フロントはRemixで、ログインと記事一覧ページを作ってみました。
テストやってみよう!
今回のメインイベント、Cypressをインストールします!
npm install cypress --save-dev
次に、Cypressの初期設定をやります。
npx cypress open
これでCypressのGUIが開きます!
初回起動時にサンプルテストがいくつか生成されて、テストの雛形が用意されます。

APIテスト
まずはAPIのヘルスチェックから。
1describe("サーバーが動いてるかチェック", () => {
2 it("ステータス200が返ってくる", ()=> {
3 cy.request("GET", "http://localhost/api/health")
4 .then((response)=> {
5 expect(response.status).to.eq(200);
6 });
7 });
8});

1describe("ユーザールート", () => {
2 it("有効なメールアドレスでサインインに成功するべき", () => {
3 cy.request({
4 method: "POST",
5 url: `http://localhost/api/user/signin`,
6 body: {
7 email: "sample@mail.com" // 既存のテストユーザーを使用
8 }
9 }).then((response) => {
10 expect(response.status).to.eq(200);
11 // クッキーが設定されているかチェック
12 expect(response.headers).to.have.property("set-cookie");
13 cy.getCookie("sample-token").should('exist');
14 });
15 });
16});
続いては、ユーザがログインし、投稿までの流れでテストを書いてみたいです。
1describe("ユーザがログインして投稿する", () => {
2 const baseUrl = "http://localhost/api";
3 const userEmail = "sample@mail.com";
4 const testPost = {
5 content: "この投稿はCypressからのテストです"
6 };
7 // まず、認証トークンを取得するためにサインイン
8 before(() => {
9 cy.request({
10 method: "POST",
11 url: `${baseUrl}/user/signin`,
12 body: {
13 email: userEmail
14 }
15 }).then((response) => {
16 expect(response.status).to.eq(200);
17 });
18});
19
20it("新しい投稿を作成するべき", () => {
21 cy.request({
22 method: "POST",
23 url: `${baseUrl}/post`,
24 body: testPost
25 }).then((response) => {
26 expect(response.status).to.eq(201);
27 expect(response.body).to.have.property("id");
28 expect(response.body.content).to.eq(testPost.content);
29 });
30 });
31})

E2Eテスト
E2Eテストでは、実際のユーザーの操作をマネして、アプリ全体の動きをチェックしていきます。
まずは、ログインページにちゃんとアクセスできるかをテストします。
1describe("ログインE2Eテスト", () => {
2 it("ログインページにアクセスできる", () => {
3 cy.visit("http://localhost");
4 cy.get('input[name="email"]').should("exist");
5 cy.get('button[type="submit"]').should("exist");
6 });
7});

APIのテストとは違って、実際のブラウザが右側に表示されて、操作を確認しながらテストができます、Cypressの強力なところですね!
次に、ログインフォームに入力して、ログインが成功してブログ一覧のページに遷移することをテストしたいです。
1describe("ログインE2Eテスト", () => {
2 it("ログインして、ブログ一覧に遷移", () => {
3 cy.visit("http://localhost");
4 cy.get('input[name="email"]').should("exist");
5 cy.get('button[type="submit"]').should("exist");
6 // インプットフィールドにメールアドレスを入力
7+ cy.get('input[name="email"]').type("sample@mail.com");
8 // ログインボタンをクリック
9+ cy.get('button[type="submit"]').click();
10 // ログイン後のクッキーが存在することを確認
11+ cy.getCookie("sample-token").should("exist");
12 // ログイン後のページが正しいことを確認
13+ cy.location("pathname").should("eq", "/blog");
14 });
15});
1it("存在しないメールはログインに失敗する", () => {
2 cy.visit("http://localhost");
3 cy.get('input[name="email"]').should("exist");
4 cy.get('button[type="submit"]').should("exist");
5 cy.get('input[type="email"]').type("notfound@mail.com");
6 cy.contains("ログイン失敗").should("not.exist");
7 cy.get('button[type="submit"]').click();
8 cy.contains("ログイン失敗").should("be.visible");
9});

ビジュアルテスト
ビジュアルテストでは、アプリの見た目が正しいかをチェックします。Cypressでは、プラグインを使って簡単にビジュアルテストができます。
今度は`cypress-image-diff-js`を使います。
テーマの切り替えとモバイルビューでのレイアウトを確認するためのビジュアルテストを書いてみます。
1describe("ビジュアルテスト", () => {
2 it("テーマ切り替えのビジュアル確認", () => {
3 cy.visit("http://localhost");
4 // スクリーンショット比較のため一貫したビューポートに設定
5 cy.viewport(1280, 720);
6 cy.get("div[data-testid='wrapper']").should('be.visible');
7 // 初期状態のスクリーンショット比較を行う
8 cy.compareSnapshot({
9 name: '01-theme-dark-initial',
10 });
11 // テーマボタンの存在確認
12 cy.get('button[theme-value]')
13 .should("exist")
14 .should("be.visible").click();
15 // テーマ切り替え後のスクリーンショット比較を行う
16 cy.compareSnapshot({
17 name: '02-theme-light-after',
18 });
19 });
20
21it("モバイルレイアウトのビジュアル確認", () => {
22 cy.signin();
23
24 // スクリーンショット比較のため一貫したビューポートに設定
25 cy.viewport(1280, 720);
26
27 let userInfo = cy.get("div[data-testid='user-info']");
28 userInfo.should('be.visible');
29 // 特定要素のみのスクリーンショット比較を行う
30 userInfo.compareSnapshot({
31 name: '03-user-info',
32 });
33 // モバイルのビューポート設定
34 cy.viewport(375, 667);
35 userInfo = cy.get("div[data-testid='user-info']");
36 // モバイルビューでの特定要素のスクリーンショット比較を行う
37 userInfo.compareSnapshot({
38 name: '04-user-info-sp',
39 });
40 });
41});
すると、Cypressがスクリーンショットを撮ってくれました。




1- const bg = theme === "dark" ? "bg-gray-900" : "bg-stone-100";
2+ const bg = theme === "dark" ? "bg-gray-900" : "bg-stone-300"; // ここは値を変更
もう一回テストを実行すると、差分があることを検知してくれます。

GitHub ActionsでCI/CD
1- name: Cypress テスト実行
2 uses: cypress-io/github-action@v6
3 with:
4 wait-on: http://localhost
5 wait-on-timeout: 60
6 working-directory: frontend
7 start: npm run dev
8 browser: chrome
9 headed: false
10 record: false
まとめ
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ