Repository navigation
test(frontend): Playwright による E2E テストを追加 #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
5d88e4b
Merge branch 'docs/update-readme' into test/playwright
d519b75
Merge branch 'feature/reconnecting-game-room' into test/playwright
4b51074
feat(frontend): frontendのテストを追加
ef09bea
chore: edge-case-tests.mdを削除
a1eafcc
Merge branch 'develop' into test/frontend-e2e
def28f5
fix: coderabbitの指摘を修正
098b5ed
feat: frontend/test:e2eをデフォルトで開発環境を選択する
c9cda81
fix(frontend): playwrightテストのXSSに関するものを修正
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| # フロントエンド E2E テスト (Playwright) | ||
|
|
||
| `frontend/tests/e2e/` にある Playwright テストは、実際に起動したスタック(nginx + | ||
| フロントエンド + バックエンド + Postgres)に対して、ブラウザからアプリを操作して | ||
| 検証する。 | ||
|
|
||
| ## ディレクトリ構成 | ||
|
|
||
| ```text | ||
| frontend/tests/e2e/ | ||
| ├── global-setup.ts 起動中のスタックが dev/prod どちらかを検査する | ||
| ├── helpers/ サインアップ・ルーム操作・コンソール監視などの共通処理 | ||
| └── specs/ テスト本体 | ||
| ``` | ||
|
|
||
| `specs/` の内訳: | ||
|
|
||
| | ファイル | 検証する範囲 | | ||
| |---|---| | ||
| | `auth.spec.ts` | サインアップ/サインイン/ログアウト、保護ルートのリダイレクト、パスワード変更 | | ||
| | `form-validation.spec.ts` | 各フォームのクライアント側バリデーションとエラー表示 | | ||
| | `xss.spec.ts` | 各フォームに入れた HTML/スクリプトが実行されずエスケープされる | | ||
| | `sql-injection.spec.ts` | 各フォームに入れた SQL がクエリではなく文字列として扱われる | | ||
| | `legal-pages.spec.ts` | プライバシーポリシー/利用規約への導線と内容 | | ||
| | `responsive.spec.ts` | desktop/tablet/mobile の各幅で横スクロールが発生しない | | ||
| | `console-clean.spec.ts` | 主要な画面遷移でコンソールにエラー・警告が出ない | | ||
| | `secure-connection.spec.ts` | ページから出る通信に混在コンテンツ (http/ws) が無い | | ||
| | `profile.spec.ts` | ユーザー名変更、アバターの選択・アップロード・拒否 | | ||
| | `friends.spec.ts` | ユーザー検索・フレンド申請・承認・削除・オンライン表示 | | ||
| | `notifications.spec.ts` | 通知バッジのリアルタイム更新、ルーム招待からの参加 | | ||
| | `room-lobby.spec.ts` | ロビー/待機室のリアルタイム同期、チャット、ホスト引き継ぎ | | ||
| | `concurrent-users.spec.ts` | 4人が同時にログインして別々の画面を操作する | | ||
| | `game-match.spec.ts` | 2人対戦の開始・操作・決着 | | ||
| | `game-multiplayer.spec.ts` | 4人対戦の開始と全画面の同期 | | ||
| | `game-reconnect.spec.ts` | 対戦中に切断しても再接続で試合に復帰できる | | ||
| | `match-history.spec.ts` | 試合結果が統計・戦闘履歴・ランキングに反映される | | ||
|
|
||
| ### インジェクション系テストの対象フォーム | ||
|
|
||
| `xss.spec.ts` と `sql-injection.spec.ts` は、ユーザー入力を受け取る次のフォームを | ||
| それぞれ同じ観点で攻撃する。ペイロードは `helpers/payloads.ts` に集約している。 | ||
|
|
||
| - SignUp / SignIn 画面 | ||
| - Room 内 Chat | ||
| - プロフィール編集モーダル(ユーザー名) | ||
| - 設定 > アカウント情報変更(メールアドレス・ユーザー名) | ||
| - Room 作成モーダル(ルーム名) | ||
|
|
||
| XSS 側は「描画結果」ではなく副作用でスクリプトの実行を検知する。`helpers/payloads.ts` の | ||
| `attachXssProbe()` が、インラインハンドラが立てるフラグ・`alert()` の dialog・ | ||
| ペイロードが要素として組み立てられていないかの 3 点を見る。 | ||
|
|
||
| SQL インジェクション側は、認証を突破できないこと・入力が文字列として保存され | ||
| そのまま表示されることに加え、`DROP TABLE` 系を流し込んだ後もサインアップ/ | ||
| サインイン/ロビーが動作することまで確認する。 | ||
|
|
||
| ## 前提 | ||
|
|
||
| - Docker / Docker Compose v2 (`docker compose` コマンドが使えること) | ||
| - ホストに Node.js が入っていること(Playwright 自体・実ブラウザの起動はホスト側で行う) | ||
|
|
||
| ## セットアップ | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
|
|
||
| # 依存関係のインストール(docker-compose.yml と同じ node:24-alpine イメージで実行する。 | ||
| # ホストの node バージョンが違うと package-lock.json に毎回差分が出てしまうため、 | ||
| # ホストで直接 npm install はしないこと) | ||
| make dev-deps | ||
|
|
||
| # Playwright が使うブラウザ本体をインストール(config は chromium のみ使用) | ||
| npx playwright install --with-deps chromium | ||
| ``` | ||
|
|
||
| `node_modules` は `docker-compose.yml`/`docker-compose.prod.yml` の各コンテナでは匿名ボリューム | ||
| としてマウントされており、image ビルド時に作られたものがコンテナ再起動をまたいで永続化される。 | ||
| そのためホスト側で生成した `node_modules` はコンテナには反映されないし、その逆もない。 | ||
| `make dev-deps` はコンテナとは無関係に、ホストのリポジトリへ直接 `node_modules` / | ||
| `package-lock.json` を書き込む使い捨てコンテナ実行であり、Playwright テストを動かす | ||
| ホスト環境の依存関係を Dockerfile と同じ Node バージョンで揃えるためだけに使う。 | ||
|
|
||
| ## テストの実行 | ||
|
|
||
| Playwright は既定で **開発用スタック**(`docker/docker-compose.yml`)を自動起動する。 | ||
| 本番相当スタック(`docker-compose.prod.yml`)でテストすると、本番環境にテスト用データが | ||
| 残ってしまうため。 | ||
|
|
||
| 本番相当スタックがポート 8443 で起動している場合は先に止めておく: | ||
|
|
||
| ```bash | ||
| make down | ||
| ``` | ||
|
|
||
| テスト実行: | ||
|
|
||
| ```bash | ||
| cd frontend | ||
| npm run test:e2e | ||
| ``` | ||
|
|
||
| 初回は `docker compose -f ../docker/docker-compose.yml up -d --build` が自動で走るため、 | ||
| イメージビルドに数分かかる。2回目以降は既に起動しているスタックを再利用する | ||
| (`reuseExistingServer: true`)。 | ||
|
|
||
| 本番相当スタックに対して実行したい場合: | ||
|
|
||
| ```bash | ||
| E2E_STACK=prod npm run test:e2e | ||
| ``` | ||
|
|
||
| この場合、フロントエンドのコードを変更した後は、再ビルドが走るようにコンテナを | ||
| 一度落とすか `make build` で作り直してから実行すること。 | ||
|
|
||
| `global-setup.ts` が起動中のスタックの種類(dev/prod)を実際の応答から判定し、 | ||
| `E2E_STACK` の指定と食い違っていれば実行前にエラーで止まる。 | ||
|
|
||
| ## 結果の確認 | ||
|
|
||
| ```bash | ||
| npm run test:e2e:report # Playwright の HTML レポートを開く | ||
| ``` | ||
|
|
||
| ## 注意点 | ||
|
|
||
| - テストは実際に Postgres へユーザー・ルーム・試合結果を書き込む。クリーンアップ処理は | ||
| 無いため、繰り返し実行するとデータが蓄積する(ユーザー名は毎回一意にしているので | ||
| 衝突はしない)。気になる場合は `make clean`(dev)または | ||
| `docker compose -f docker/docker-compose.prod.yml down -v` でボリュームごと削除してから | ||
| 再実行する。 | ||
| - テストの並列実行はしない(`workers: 1`)。ルーム/ゲーム状態はバックエンドの | ||
| in-memory `Map` で単一プロセス内に保持されており、並列実行すると互いに干渉するため。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,4 +24,9 @@ dist-ssr | |
| *.sln | ||
| *.sw? | ||
|
|
||
| .vite | ||
| .vite | ||
|
|
||
| # Playwright | ||
| /test-results/ | ||
| /playwright-report/ | ||
| /playwright/.cache/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { defineConfig, devices } from '@playwright/test' | ||
|
|
||
| // 本番用 compose(docker-compose.prod.yml)でテストすると、本番環境にテスト用データが | ||
| // 残ってしまう。既定は dev。`E2E_STACK=prod` を指定すれば本番用に切り替えられる | ||
| // (両方とも 8443 を使うため同時起動は不可)。 | ||
| const STACK = process.env.E2E_STACK === 'prod' ? 'prod' : 'dev' | ||
|
|
||
| const COMPOSE_FILE = | ||
| STACK === 'dev' | ||
| ? '../docker/docker-compose.yml' | ||
| : '../docker/docker-compose.prod.yml' | ||
|
|
||
| // prod は静的ビルド成果物を配信するため、フロントの変更を反映するには再ビルドがいる。 | ||
| const WEB_SERVER_COMMAND = `docker compose -f ${COMPOSE_FILE} up --build` | ||
|
|
||
| export default defineConfig({ | ||
| testDir: './tests/e2e', | ||
| // ルーム/ゲーム状態はバックエンドの in-memory Map(単一プロセス)で共有されているため、 | ||
| // テストを並列実行すると互いに干渉する。安定性優先で直列実行する。 | ||
| fullyParallel: false, | ||
| workers: 1, | ||
| retries: 0, | ||
| // 実サインアップ・実ソケット接続・複数ブラウザコンテキストをまたぐシナリオが多く、 | ||
| // デフォルトの 30s では余裕が無いテストがある(特に 4人対戦や複数コンテキストの同期)。 | ||
| timeout: 60_000, | ||
| globalSetup: './tests/e2e/global-setup.ts', | ||
| webServer: { | ||
| command: WEB_SERVER_COMMAND, | ||
| url: 'https://localhost:8443', | ||
| ignoreHTTPSErrors: true, | ||
| reuseExistingServer: true, | ||
| timeout: 180_000, | ||
| }, | ||
| use: { | ||
| baseURL: 'https://localhost:8443', | ||
| ignoreHTTPSErrors: true, | ||
| trace: 'retain-on-failure', | ||
| video: 'retain-on-failure', | ||
| screenshot: 'only-on-failure', | ||
| }, | ||
| reporter: [['html', { open: 'never' }], ['list']], | ||
| projects: [ | ||
| { | ||
| name: 'chromium', | ||
| use: { | ||
| ...devices['Desktop Chrome'], | ||
| launchOptions: { | ||
| // headless Chromium ではソフトウェア GL が無効な環境があり、R3F(WebGL)の | ||
| // canvas が真っ黒/未描画になることがある。SwiftShader を強制して描画させる。 | ||
| args: [ | ||
| '--use-gl=angle', | ||
| '--use-angle=swiftshader', | ||
| '--ignore-gpu-blocklist', | ||
| ], | ||
| }, | ||
| }, | ||
| }, | ||
| ], | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import type { FullConfig } from '@playwright/test' | ||
| import https from 'node:https' | ||
|
|
||
| const STACK = process.env.E2E_STACK === 'prod' ? 'prod' : 'dev' | ||
|
|
||
| function fetchHtml(url: string): Promise<string> { | ||
| return new Promise((resolve, reject) => { | ||
| https | ||
| .get(url, { rejectUnauthorized: false }, (res) => { | ||
| let body = '' | ||
| res.on('data', (chunk: Buffer) => (body += chunk.toString())) | ||
| res.on('end', () => resolve(body)) | ||
| }) | ||
| .on('error', reject) | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * dev 用 compose は Vite の HMR クライアントスクリプトを埋め込むため、 | ||
| * 「コンソールに警告・エラーが無いこと」を検査するテストが dev と prod で別結果になる。 | ||
| * | ||
| * webServer(playwright.config.ts)は既に起動済み・応答済みのはずだが、ポート 8443 を | ||
| * 手動で別スタックが握っていた場合(reuseExistingServer: true のため検知できない)、 | ||
| * 誤ったスタックをそのまま検査してしまう事故が起きる。ここで応答 HTML から実際の | ||
| * スタック種別を判別し、E2E_STACK の指定と食い違っていれば即座に失敗させる。 | ||
| */ | ||
| export default async function globalSetup(config: FullConfig) { | ||
| const baseURL = config.projects[0]?.use?.baseURL ?? 'https://localhost:8443' | ||
| const html = await fetchHtml(baseURL) | ||
|
|
||
| // dev: Vite が index.html にソースを直接埋め込む (`/src/main.tsx`) | ||
| // prod: nginx が vite build のハッシュ付きバンドルを配信する (`/assets/index-XXXX.js`) | ||
| const looksLikeDev = html.includes('/src/main.tsx') | ||
| const looksLikeProd = /\/assets\/index-[\w-]+\.js/.test(html) | ||
| const actualStack = looksLikeDev ? 'dev' : looksLikeProd ? 'prod' : 'unknown' | ||
|
|
||
| if (actualStack !== STACK) { | ||
| const fix = | ||
| STACK === 'prod' | ||
| ? '`make dev-down` で開発用スタックを止めてから、`E2E_STACK=prod npx playwright test` を実行してください。' | ||
| : '`make down` で本番スタックを止めてから、`npx playwright test`(E2E_STACK は既定で dev)を実行してください。' | ||
|
|
||
| throw new Error( | ||
| `E2E_STACK=${STACK} を要求しましたが、${baseURL} は "${actualStack}" スタックの応答でした。\n${fix}` | ||
| ) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.