CypressでFirebase Authenticationのe2eテスト書いてみる
概要
前々からスプレッドシートにテストケースかいて
何度もおててでテストするの辛いんじゃ。。。
と思いながらも中々e2eテストに手を出せてなかったので
ここいらで前から気になってたCypressに手を突っ込んでみる
導入
思ってた何倍か簡単だった🥸
packageをinstallしてopenすると...
yarn add -D cypress
yarn cypress open
cypressウィンドウが現れて
examples下のテスト用jsファイルを適当にクリックすると
Chromeブラウザが起動しテストが走り出す!全然設定とかいらない
テスト書いてみる
なんか最初から入ってたexampleのテストをやってみたので
自分で作った何かしらをテストしてみる!
対象のページ
とりあえずザクっと作ったこんな感じのページ
未認証でアクセスすると/
に飛ばされる
Google認証すると「このサイトについて」リンクが現れる
「このサイトについて」リンクを押下すると/about
ページに遷移し
ログインユーザーのemailが表示される
書くテストケース
とりあえずこの辺書いてみる
-
/
ページ- 認証済の動作
- 「このサイトについて」リンクが表示される
- 「このサイトについて」リンク押すと
/about
に遷移できる - ログインユーザーのemail表示される
- 認証済の動作
/
ページ
Google認証
「Googleでサインインボタン押してログインしてぇ...」みたいなことを考えたくなるけど
CypressではGoogle認証みたいにドメインの異なるサードパーティー系のページに
UIでアクセスするテストはやめときましょうねって書いてある(気がする
自分がコントロールできる範囲内のテストにとどめましょう!とのこと
認証状態を作りたいならStubかCy.request()
とかでAPIアクセスで認証状態作るのが良さそう
とはいえ、自分のローカルではFirebase AuthenticationのEmulatorが動いてて
APIで操作できる範囲は全然なさそうなので、SDKで頑張って実現してみる
やったこと
ざっくりこんな流れ
- .envの環境変数をCypressでも読めるようにする
- Cypressでfirebaseの初期化
- テスト作成
そもそもアプリで利用してる.envファイルがこんな感じであるので
それをCypress側でもそのまま使いたい
FIREBASE_API_KEY=xxx
FIREBASE_AUTH_DOMAIN=xxx
FIREBASE_PROJECT_ID=xxx
FIREBASE_STORAGE_BUCKET=xxx
FIREBASE_MESSEGING_SENDER_ID=xxx
FIREBASE_APP_ID=xxx
FIREBASE_MEASUREMENT_ID=xxx
FIREBASE_AUTH_EMULATOR_URL=xxx
.envの環境変数をCypressでも読めるようにする
require('dotenv').config();
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
config.env = process.env
return config
}
Cypressでfirebaseを初期化する
(後のテストで認証データ作るために2メソッド用意してます
import firebase from "firebase/app";
import "firebase/auth";
const config = {
apiKey: Cypress.env("FIREBASE_API_KEY"),
};
if (!firebase.apps.length) {
firebase.initializeApp(config);
}
if (process.env.NODE_ENV !== "production") {
const auth = firebase.auth();
auth.useEmulator(Cypress.env("FIREBASE_AUTH_EMULATOR_URL"));
}
export const signInWithEmailAndPassword = async (email, password) => {
return await firebase.auth().signInWithEmailAndPassword(email, password);
};
export const createUserWithEmailAndPassword = async (email, password) => {
return await firebase.auth().createUserWithEmailAndPassword(email, password);
};
テストケースを作成していく!
毎回ドメイン書かなくていいようにconfigに書いておく
{
"baseUrl": "http://localhost:3000"
}
beforeEachで認証データを作って
上部で書いたケースを手順に書いてく
import {
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
} from "../../support/firebase";
describe("Authentication test in index page", () => {
const email = "test@example.com";
const password = "123456";
beforeEach(() => {
signInWithEmailAndPassword(email, password)
.then(async (user) => {
await user.user.delete();
await createUserWithEmailAndPassword(email, password);
})
.catch(async (e) => {
await createUserWithEmailAndPassword(email, password);
});
});
it("Login by firebase authentication", () => {
cy.visit("/");
cy.get("[data-cy=link-to-about]").click();
cy.url().should("include", "/about");
cy.get("[data-cy=user-email]").should("contain", email);
});
});
Best Practiceにあるようにdata-cy
属性を指定して、それをcy.get()
で取得するようにしてる
<main className={styles.main}>
<h1 className={styles.title}>{t("title")}</h1>
{currentUser ? (
<Link href="/about">
<a data-cy="link-to-about" >{t("buttons.about")}</a>
</Link>
) : (
<GoogleSignInButton onClick={signIn} />
)}
</main>
実際にテストしてみる
作成したテストをクリックするとテスト動き出す!
うまいことテストできた!!
テスト結果の画像と動画が見れるという噂
Runsタブが怪しいところ
適当なプロジェクトを作成して選択!
(Cypressにサインアップしてサインインしとく必要はありそう
あとは言われた通りのコマンドをローカルで叩いてみると・・・
テスト走り出した!!
$ yarn cypress run --record --key xxx
yarn run v1.22.10
$ /Users/su/ghq/github.com/shintaro-uchiyama/base-app/frontend/node_modules/.bin/cypress run --record --key xxx
Missing baseUrl in compilerOptions. tsconfig-paths will be skipped
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 6.8.0 │
│ Browser: Electron 87 (headless) │
...
さっきのRunsに実行してる感じのクルクルが出てくるのでクリックするとブラウザに飛ぶ
テスト実行時のスナップショットやビデオが見れる!素敵!
なんかローカルにもscreenshotsとかvideosディレクトリができてファイルいっぱいできてる🤓
Dashboard使わなくてもローカルでscreenshot, videoは記録できそう
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
cypress/fixtures/profile.json
cypress/fixtures/users.json
cypress/screenshots/
cypress/videos/
CIで実行してみる
Github ActionsでCypress走らせてみる!
GitHub Actions
とりあえず結果から書くとこんな感じ
githubのsecretsにFIREBASE_API_KEY
とCYPRESS_RECORD_KEY
をセットしといてくだされ
name: Cypress Tests with Install Job and UI Chrome Job x 3
on:
pull_request:
branches:
- main
jobs:
install:
runs-on: ubuntu-20.04
container: cypress/browsers:node14.16.0-chrome89-ff86
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/cache@v2
id: yarn-and-build-cache
with:
path: |
~/.cache/Cypress
frontend/build
frontend/node_modules
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('frontend/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-build-
- name: Cypress install
uses: cypress-io/github-action@v2
with:
# Disable running of tests within install job
runTests: false
install-command: yarn --frozen-lockfile --silent
working-directory: frontend
ui-chrome-tests:
runs-on: ubuntu-20.04
container: cypress/browsers:node14.16.0-chrome89-ff86
needs: install
strategy:
fail-fast: false
matrix:
# run copies of the current job in parallel
containers: [1, 2, 3]
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/cache@v2
id: yarn-and-build-cache
with:
path: |
~/.cache/Cypress
frontend/build
frontend/node_modules
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('frontend/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-build-
- name: 'UI Tests - Chrome'
uses: cypress-io/github-action@v2
with:
# we have already installed all dependencies above
install: false
start: |
yarn firebase emulators:start --import=./firebase/data --export-on-exit --project ucwork --only auth,ui
yarn dev
wait-on: 'http://localhost:3000, http://localhost:4000'
wait-on-timeout: 120
browser: chrome
record: true
parallel: true
group: 'UI - Chrome'
spec: cypress/integration/**/*
working-directory: frontend
env:
FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
FIREBASE_AUTH_EMULATOR_URL: 'http://localhost:9099'
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
# Recommended: pass the GitHub token lets this action correctly
# determine the unique run id necessary to re-run the checks
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
やったことのポイントは以下
- main向けにPR作ったらGitHub Actions走らせてみる
- yarnでpackageをインストール。そしてキャッシュ
- package(yarn.lock)に変更がない場合、次のCIからキャッシュが効いて早くなる!
- cypressのテスト用に色々環境起動
- firebase emulator起動
- これが結構大変で詰まった
- 3000ポートで動くnuxtアプリの起動(yarn dev)
- firebase emulator起動
- cypressを並行で走らせる
ちょっとブログ書くの疲れてきたので細かい説明は諦める
検証
実際に動いてるとここんな感じ
並列でテスト実行できるのかなぁとか気になって実は以下テストを追加して検証してる
試しにこんな感じでもう一つabout
ページ向けのテストファイルを作って
describe("Unauthenticated test in about page", () => {
it("Access to about page w/o authenticated", () => {
cy.visit("/about");
cy.url().should("include", "/");
});
});
parallel: true
を指定して
3つのcontainerで3並列実行してみる!
(2ファイルしかないのに3並列したらどうなるかちょっとみたかった🤓
strategy:
fail-fast: false
matrix:
# run copies of the current job in parallel
containers: [1, 2, 3]
今回で言うと
1つコンテナで/
ページ用のテストが実行されて
2つコンテナで/about
ページ用のテストが実行されて
3つコンテナではどのテストも行われてなかった!!
この辺の並列度合いはCypressのダッシュボードに聞いて
どう言う並列するのが最適かよろしくやってくれるらしい!素敵!
(テスト実行数500超えて有料になっちゃったらどうなるんだろ・・・🧐
ブラウザ単位で並列化とか自分で恣意的に色々やることもできるみたい
まとめ
思ったよりも量が多くなってきて後半疲れました。すみません🙈
やる前は結構気合入れないと導入できないのかな・・・
とか思ってらけどやってみると案外スッとできた。
これからe2eテスト未導入プロジェクト見つけたら突っ込んでいこう!
Discussion