Cypressとは
CypressでE2E(End-to-End)テストは「ユーザーがブラウザで行う操作を自動化して、アプリを“本物の環境に近い形”で丸ごと検証するテスト」です。
ブラウザを立ち上げて、ページ遷移・入力・クリック・ネットワーク通信・表示結果までを一連で確認します。
インストール方法
$ npm install cypress --save-dev
GUIはこちらです:
準備
簡単な触るために、簡単のサイトを準備しましょう
テスト用のappを作りましょう :
/
cypress/
e2e/
login.cy.js
items.cy.js
smoke.cy.js
src/
server.js
public/
app.js
index.html
cypress.config.js
package.json
/cypress.config.js :
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.js',
supportFile: false,
retries: { runMode: 2, openMode: 0 },
video: true,
},
})
/src/server.js :
import express from 'express';
import path from 'path';
import cors from 'cors';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.use(cors());
app.use(express.json());
const USERS = [{ id: 1, email: 'demo@example.com', password: 'password123', name: 'Demo User' }];
const ITEMS = [{ id:1,name:'Sample Item A'},{ id:2,name:'Sample Item B'},{ id:3,name:'Sample Item C'}];
app.post('/api/login', (req,res) => {
const { email, password } = req.body || {};
const u = USERS.find(x => x.email===email && x.password===password);
if (!u) return res.status(401).json({ error: 'Invalid credentials' });
const token = Buffer.from(`${u.id}:${Date.now()}`).toString('base64');
res.json({ token, user:{ id:u.id, email:u.email, name:u.name } });
});
app.get('/api/items', (req,res) => {
const auth = req.headers.authorization || '';
if (!auth.startsWith('Bearer ')) return res.status(401).json({ error:'Unauthorized' });
res.json({ items: ITEMS });
});
app.use(express.static(path.join(__dirname, '..', 'public')));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server http://localhost:${PORT}`));
/public/app.js :
const loginForm = document.getElementById('login-form');
const loginInfo = document.getElementById('login-info');
const itemsBtn = document.getElementById('load-items');
const itemsList = document.getElementById('items-list');
const setToken = t => localStorage.setItem('token', t);
const getToken = () => localStorage.getItem('token');
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const res = await fetch('/api/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ email, password }) });
if (!res.ok) return loginInfo.textContent = 'Login failed';
const data = await res.json();
setToken(data.token);
loginInfo.textContent = `Welcome, ${data.user.name}!`;
});
itemsBtn.addEventListener('click', async () => {
const res = await fetch('/api/items', { headers:{ Authorization: `Bearer ${getToken()||''}` } });
itemsList.innerHTML = '';
if (!res.ok) return itemsList.innerHTML = '<li>Unauthorized. Please login first.</li>';
const data = await res.json();
data.items.forEach(it => {
const li = document.createElement('li');
li.textContent = it.name;
itemsList.appendChild(li);
});
});
あとは簡単のhtml :
/public/index.html :
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Cypress Starter App</title>
</head>
<body>
<main>
<h1>Cypress Starter</h1>
<section id="auth">
<h2>Login</h2>
<form id="login-form">
<label>Email <input id="email" type="email" value="demo@example.com" required /></label>
<label>Password <input id="password" type="password" value="password123" required /></label>
<button data-testid="login-button" type="submit">Log In</button>
</form>
<p id="login-info"></p>
</section>
<hr/>
<section id="items">
<h2>Items</h2>
<button id="load-items">Load Items</button>
<ul id="items-list"></ul>
</section>
</main>
<script type="module" src="./app.js"></script>
</body>
</html>
起動
それでインストールして
$ npm install
$ npm run dev
それで無事にテストを始まれる!
cypressの起動
$ npm run cypress:open
/cypress/e2e/items.cy.js :
describe('Items list', () => {
beforeEach(() => {
cy.request('POST', '/api/login', { email: 'demo@example.com', password: 'password123' })
.then(({ body }) => { window.localStorage.setItem('token', body.token); });
});
it('loads items after clicking the button', () => {
cy.visit('/');
cy.intercept('GET', '/api/items').as('getItems');
cy.get('#load-items').click();
cy.wait('@getItems');
cy.contains('Sample Item A').should('be.visible');
cy.contains('Sample Item B').should('be.visible');
});
});
/cypress/e2e/login.cy.js :
describe('Login flow', () => {
it('logs in with demo user', () => {
cy.visit('/');
cy.get('#email').clear().type('demo@example.com');
cy.get('#password').clear().type('password123');
cy.get('[data-testid="login-button"]').click();
cy.contains('Welcome, Demo User!').should('be.visible');
cy.window().then(win => {
const token = win.localStorage.getItem('token');
expect(token).to.be.a('string').and.not.be.empty;
});
});
});
今回はテストの説明の為にですが、必要であればログイン情報はCypressの環境変数などに格納してご利用ください。
/cypress/e2e/smoke.cy.js :
describe('Smoke', () => {
it('shows title and login button', () => {
cy.visit('/');
cy.title().should('include', 'Cypress Starter App');
cy.get('[data-testid="login-button"]').should('be.visible');
});
});
GUI
スタートすると、ブラウザーが選べます:
やっとテストが見えるようになります:
specsでクリックすると、テスト開始になります:
コマンド
コマンドも出来ます!
$ npm run cypress:run
レポート
テストが多いだと、コマンドラインで見にくいなのでレポートも作りましょ!
設定:
/package.json :
{
"scripts": {
...
"delete:reports": "rm cypress/results/* || true",
"combine:reports": "jrm cypress/results/combined-report.xml \"cypress/results/*.xml\"",
"prereport": "npm run delete:reports",
"report": "cypress run --reporter cypress-multi-reporters --reporter-options configFile=reporter-config.json",
"postreport": "npm run combine:reports"
}
}
mochawesomeってスタイルをおすすめします:
npm install mochawesome mochawesome-merge mochawesome-report-generator --save-dev
/cypress.config.ts :
export default defineConfig({
...
reporter: 'mochawesome',
reporterOptions: {
reportDir: 'cypress/results',
overwrite: false,
html: false,
json: true,
},
})
実行:
$ npx cypress run --reporter mochawesome \
--reporter-options reportDir="cypress/results",overwrite=false,html=false,json=true
各テストについて、結果のファイルが発生されます:
$ npx mochawesome-merge "cypress/results/*.json" > mochawesome.json
各ファイルをマージして、一個にまとめます:
$ npx marge mochawesome.json
それでローカルでhtmlのファイルが生成されます!
テストの説明
今回はloginのテストフローを簡単に説明します。
describe('Login flow', () => {
it('logs in with demo user', () => {
cy.visit('/');
トップページへアクセス。
以後のコマンドはこのページを前提に動きます。cy.visit はページ読み込み完了まで適切に待機します(固定の wait は不要)。
cy.get('#email').clear().type('demo@example.com');
cy.get('#password').clear().type('password123');
メールとパスワードを入力。
cy.get は要素取得(デフォで自動リトライ)、それで .clear().type で既存文字があっても確実に上書きできます。
cy.get('[data-testid="login-button"]').click();
ログインボタンをクリック。
data-testid などの 壊れにくいセレクタ を使っていて、CSSクラスやテキストに依存しないのでUI変更に強いです。
cy.contains('Welcome, Demo User!').should('be.visible');
ログイン成功のUI確認。
画面に成功メッセージが表示されるまで自動で待機してから可視性を検証します。
cy.window().then(win => {
const token = win.localStorage.getItem('token');
expect(token).to.be.a('string').and.not.be.empty;
});
副作用(認証トークン)を検証。
cy.window() はアプリの window を取得します。localStorage に token が保存されたかを Chai のアサーションでチェックします。
メリット / 注意点
メリット
実利用に近い信頼性の高いフィードバック
バックエンド・フロント間の“つなぎ目”のバグを拾いやすい
注意点
実行時間が長くなりがち → 重要シナリオに絞る/並列化
不安定要因(外部API・時間依存) → cy.intercept / cy.clock で制御
セレクタは data-testid など壊れにくいものを使う
まとめ
今まで色んなテストをやっており、特にユニットテストが多かったですが、ちゃんと画面で確認出来るので本当に安心します。
本番に一番近いし、ミスが発見しやすく、レポートもちゃんとして、確認しやすいです。
ただ、大きなサイトだと重くなる可能性があります。








