diff --git a/.github/scripts/playwright-comment.cjs b/.github/scripts/playwright-comment.cjs
new file mode 100644
index 00000000..8aeba9e2
--- /dev/null
+++ b/.github/scripts/playwright-comment.cjs
@@ -0,0 +1,207 @@
+module.exports = async ({ github, context }) => {
+ const fs = require('fs');
+
+ const buildUrl = process.env.BUILD_URL || '';
+ const now = new Date().toISOString().replace('T', ' ').split('.')[0];
+
+ const title = '## π Playwright Report';
+ const descriptionSuccess = `
+β¨ **E2E Testκ° μ±κ³΅μ μΌλ‘ μλ£λμμ΅λλ€.**
+
+Test μμ½ λ΄μ©μ νμΈν΄μ£ΌμΈμ.
+`;
+ const descriptionSkipped = `
+β¨ **E2E Test νμΌμ΄ κ°μ§λμ§ μμμ΅λλ€.**
+
+μ΄ PRμλ Test Fileμ΄ μμ΄μ Reportλ₯Ό μμ±νμ§ λͺ»νμ΅λλ€.
+`;
+ const descriptionFailed = `
+β **E2E Testμ μ€ν¨νμ΅λλ€.**
+
+Test Logλ₯Ό νμΈνμκ³ λ‘μ§μ μμ ν΄μ£ΌμΈμ.
+`;
+
+ const statusTableSuccess = `
+| Status | Build Log | Updated (UTC) |
+|--------|-----------|---------------|
+| β
Ready | [View Build](${buildUrl}) | ${now} |
+`;
+
+ const statusTableSkipped = `
+| Status | Build Log | Updated (UTC) |
+|--------|-----------|---------------|
+| βοΈ Skipped | - | ${now} |
+`;
+
+ const statusTableFailed = `
+| Status | Build Log | Updated (UTC) |
+|--------|-----------|---------------|
+| β Failed | [View Logs](${buildUrl}) | ${now} |
+`;
+
+ const results = JSON.parse(fs.readFileSync('test-results/results.json', 'utf8'));
+
+ const passed = results.stats.expected;
+ const failed = results.stats.unexpected;
+ const duration = (results.stats.duration / 1000).toFixed(1);
+
+ // μ±κ³΅ν ν
μ€νΈ λͺ©λ‘
+ const successTestsByFile = results.suites.reduce((acc, suite) => {
+ const fileName = suite.file;
+ const passedSpecs = suite.specs.filter((spec) => spec.ok);
+
+ if (passedSpecs.length > 0) {
+ acc[fileName] = passedSpecs.map((spec) => ({
+ project: spec.tests[0].projectName,
+ title: spec.title,
+ }));
+ }
+
+ return acc;
+ }, {});
+
+ const totalPassedCount = Object.values(successTestsByFile).flat().length;
+
+ const passedTestsList = Object.entries(successTestsByFile)
+ .map(([file, tests]) => {
+ const testList = tests.map((test) => ` - [${test.project}] ${test.title}`).join('\n');
+ return `- **${file}** (${tests.length})\n${testList}`;
+ })
+ .join('\n');
+
+ // μ€ν¨ν ν
μ€νΈ λͺ©λ‘
+ const failedTestsByFile = results.suites.reduce((acc, suite) => {
+ const fileName = suite.file;
+ const failedSpecs = suite.specs.filter((spec) => !spec.ok);
+
+ if (failedSpecs.length > 0) {
+ acc[fileName] = failedSpecs.map((spec) => ({
+ project: spec.tests[0].projectName,
+ title: spec.title,
+ error: spec.tests[0].results[0].errors[0]?.message || 'Unknown error',
+ }));
+ }
+
+ return acc;
+ }, {});
+
+ const totalFailedCount = Object.values(failedTestsByFile).flat().length;
+
+ // μ€ν¨ν ν
μ€νΈ λͺ©λ‘
+ const failedTestList = Object.entries(failedTestsByFile)
+ .map(([file, tests]) => {
+ const testList = tests
+ .map(
+ (test) =>
+ ` - [${test.project}] ${test.title}\n \`\`\`\n ${test.error.split('\n')[0]}\n \`\`\``,
+ )
+ .join('\n');
+ return `- **${file}** (${tests.length})\n${testList}`;
+ })
+ .join('\n');
+
+ let resultType;
+
+ if (passed + failed === 0) resultType = 'skipped';
+ else if (failed > 0) resultType = 'failed';
+ else resultType = 'success';
+
+ let nextDescription;
+ let nextStatusTable;
+ switch (resultType) {
+ case 'success':
+ nextDescription = descriptionSuccess;
+ nextStatusTable = statusTableSuccess;
+ break;
+ case 'skipped':
+ nextDescription = descriptionSkipped;
+ nextStatusTable = statusTableSkipped;
+ break;
+ case 'failed':
+ nextDescription = descriptionFailed;
+ nextStatusTable = statusTableFailed;
+ break;
+ }
+
+ const testSummary = `
+### π Test Summary
+- β
Passed: ${totalPassedCount}
+- β Failed: ${totalFailedCount}
+- β±οΈ Duration: ${duration}s
+`;
+
+ //prettier-ignore
+ const successTestListSection = totalPassedCount > 0 ?
+`
+β
Passed Tests (${totalPassedCount})
+
+${passedTestsList}
+
+
+` : '';
+
+ //prettier-ignore
+ const failedTestListSection = totalFailedCount > 0 ?
+`
+β Failed Tests (${totalFailedCount})
+
+${failedTestList}
+
+
+` : '';
+
+ //prettier-ignore
+ const testResults = totalPassedCount + totalFailedCount > 0 ? `
+### π Test Details
+${successTestListSection}
+
+${failedTestListSection}
+` : '';
+
+ const comment = `
+${title}
+
+${nextDescription}
+
+${nextStatusTable}
+
+${testSummary}
+
+${testResults}
+`;
+
+ /**
+ * PRμ μ½λ©νΈλ₯Ό μμ±νκ±°λ μ
λ°μ΄νΈνλ ν¨μ
+ */
+ async function postOrUpdateComment(commentBody) {
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ });
+
+ const botComment = comments.find(
+ (comment) => comment.user.type === 'Bot' && comment.body.includes(title),
+ );
+
+ if (botComment) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: botComment.id,
+ body: commentBody,
+ });
+ console.log('β
Playwright μ½λ©νΈκ° μ
λ°μ΄νΈλμμ΅λλ€.');
+ } else {
+ await github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: commentBody,
+ });
+ console.log('β
Playwright μ½λ©νΈκ° μμ±λμμ΅λλ€.');
+ }
+ }
+
+ await postOrUpdateComment(comment);
+};
diff --git a/.github/scripts/playwright-initial-comment.cjs b/.github/scripts/playwright-initial-comment.cjs
new file mode 100644
index 00000000..23afdec0
--- /dev/null
+++ b/.github/scripts/playwright-initial-comment.cjs
@@ -0,0 +1,44 @@
+module.exports = async ({ github, context }) => {
+ const buildLogUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
+ const now = new Date().toISOString().replace('T', ' ').split('.')[0];
+
+ const commentBody = `## π Playwright Report
+
+π **Playwright Reportλ₯Ό μμ± μ€μ
λλ€.**
+
+μ μλ§ κΈ°λ€λ €μ£ΌμΈμ...
+
+| Status | Build Log | Updated (UTC) |
+|--------|-----------|---------------|
+| π Testing... | [View Logs](${buildLogUrl}) | ${now} |`;
+
+ // κΈ°μ‘΄ μ½λ©νΈ μ°ΎκΈ°
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ });
+
+ const botComment = comments.find(
+ (comment) => comment.user.type === 'Bot' && comment.body.includes('## π Playwright Report'),
+ );
+
+ // κΈ°μ‘΄ μ½λ©νΈ μμΌλ©΄ μμ , μμΌλ©΄ μμ±
+ if (botComment) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: botComment.id,
+ body: commentBody,
+ });
+ console.log('β
κΈ°μ‘΄ μ½λ©νΈ μ
λ°μ΄νΈ μλ£');
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: commentBody,
+ });
+ console.log('β
μ μ½λ©νΈ μμ± μλ£');
+ }
+};
diff --git a/.github/workflows/playwright-pr.yml b/.github/workflows/playwright-pr.yml
new file mode 100644
index 00000000..59c632e4
--- /dev/null
+++ b/.github/workflows/playwright-pr.yml
@@ -0,0 +1,74 @@
+name: PR Playwright Report
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Create initial Report
+ if: always()
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const script = require('./.github/scripts/playwright-initial-comment.cjs');
+ await script({ github, context });
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Install Playwright browsers
+ run: pnpm exec playwright install --with-deps
+
+ - name: Check if tests exist
+ id: check-tests
+ run: |
+ if [ -d "e2e/tests" ] && [ "$(ls -A e2e/tests)" ]; then
+ echo "has_tests=true" >> $GITHUB_OUTPUT
+ else
+ echo "has_tests=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Run Playwright tests
+ id: test
+ if: steps.check-tests.outputs.has_tests == 'true'
+ run: pnpm test:playwright
+ continue-on-error: true
+ env:
+ NEXT_PUBLIC_API_BASE_URL: ${{ vars.NEXT_PUBLIC_API_BASE_URL }}
+
+ - name: Create Playwright report comment
+ if: always()
+ uses: actions/github-script@v7
+ env:
+ TEST_OUTCOME: ${{ steps.test.outcome }}
+ BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ with:
+ script: |
+ const script = require('./.github/scripts/playwright-comment.cjs');
+ await script({ github, context });
+
+ - name: Fail if tests exist but failed
+ if: steps.check-tests.outputs.has_tests == 'true' && steps.test.outcome == 'failure'
+ run: |
+ echo "::error::Playwright tests failed! Please fix the failing tests."
+ exit 1
diff --git a/e2e/tests/profile.test.ts b/e2e/tests/profile.test.ts
index 1000f4a9..e7d157ed 100644
--- a/e2e/tests/profile.test.ts
+++ b/e2e/tests/profile.test.ts
@@ -1,12 +1,12 @@
import { expect, test } from '@playwright/test';
-test('λμ νλ‘ν νμ΄μ§λ‘ μ μ μ λ§μ΄νμ΄μ§λ‘ 리λ€μ΄λ νΈ λλ μ§ ν
μ€νΈ', async ({ page }) => {
- // λμ νλ‘ν νμ΄μ§ λ°©λ¬Έ
- await page.goto('/profile/1');
+// test('λμ νλ‘ν νμ΄μ§λ‘ μ μ μ λ§μ΄νμ΄μ§λ‘ 리λ€μ΄λ νΈ λλ μ§ ν
μ€νΈ', async ({ page }) => {
+// // λμ νλ‘ν νμ΄μ§ λ°©λ¬Έ
+// await page.goto('/profile/1');
- // redirect λκΈ°
- await expect(page).toHaveURL('/mypage');
-});
+// // redirect λκΈ°
+// await expect(page).toHaveURL('/mypage');
+// });
test('μ‘΄μ¬νμ§ μλ νλ‘ν νμ΄μ§λ‘ μ μ μ 404 리λ€μ΄λ νΈ λλ μ§ ν
μ€νΈ', async ({ page }) => {
// μ‘΄μ¬νμ§ μλ νλ‘ν νμ΄μ§ λ°©λ¬Έ
diff --git a/playwright.config.ts b/playwright.config.ts
index 9f999ce3..31d56b14 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -3,9 +3,9 @@ import { defineConfig, devices } from '@playwright/test';
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
-import dotenv from 'dotenv';
-import path from 'path';
-dotenv.config({ path: path.resolve(__dirname, '.env.test') });
+// import dotenv from 'dotenv';
+// import path from 'path';
+// dotenv.config({ path: path.resolve(__dirname, '.env.test') });
/**
* See https://playwright.dev/docs/test-configuration.
@@ -21,7 +21,7 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
- reporter: 'html',
+ reporter: [['html'], ['json', { outputFile: 'test-results/results.json' }]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
@@ -74,5 +74,8 @@ export default defineConfig({
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
+ env: {
+ NEXT_PUBLIC_MSW_ENABLED: 'true',
+ },
},
});
diff --git a/src/app/(user)/profile/[userId]/layout.tsx b/src/app/(user)/profile/[userId]/layout.tsx
index 362caf09..a358c8f0 100644
--- a/src/app/(user)/profile/[userId]/layout.tsx
+++ b/src/app/(user)/profile/[userId]/layout.tsx
@@ -1,5 +1,3 @@
-import { redirect } from 'next/navigation';
-
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
import { API } from '@/api';
@@ -16,9 +14,9 @@ const ProfileLayout = async ({ children, params }: Props) => {
const userId = Number(id);
// λ³ΈμΈ idμ κ°μμ§ νμΈ ν κ°μΌλ©΄ mypageλ‘ λ¦¬λ€μ΄λ νΈ
- if (userId === 1) {
- redirect('/mypage');
- }
+ // if (userId === 1) {
+ // redirect('/mypage');
+ // }
const queryClient = getQueryClient();