-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(fe): add playwright tests (#238)
* chore: add GitHub Actions workflow for PR testing * feat: add Playwright testing configuration and update scripts * fix: redirect to home instead of session on error * feat: add disabled state to Button component and update usage in SignIn/SignUp modals * feat: add Playwright tests * chore: update ESLint configuration to include playwright.config.ts * feat: install Playwright browsers in workflow * chore: update GitHub Actions workflow to use latest actions and improve dependency installation * chore: update Playwright browser installation path in GitHub Actions workflow * chore: remove redundant timeout and runs-on settings in GitHub Actions workflow
- Loading branch information
Showing
17 changed files
with
951 additions
and
22 deletions.
There are no files selected for viewing
This file contains 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,31 @@ | ||
name: PR Test | ||
|
||
on: | ||
pull_request: | ||
branches: [main] | ||
|
||
jobs: | ||
test: | ||
timeout-minutes: 60 | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: lts/* | ||
|
||
- name: Install dependencies | ||
run: npm install -g pnpm && pnpm install | ||
|
||
- name: Install Playwright Browsers | ||
run: | | ||
cd apps/client | ||
pnpm exec playwright install --with-deps | ||
cd ../.. | ||
- name: Create .env file | ||
run: echo "${{ secrets.FRONT_END_ENV }}" > apps/client/.env | ||
|
||
- name: Run tests | ||
run: pnpm run test |
This file contains 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 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 |
---|---|---|
|
@@ -23,4 +23,9 @@ dist-ssr | |
*.sln | ||
*.sw? | ||
|
||
.env | ||
.env | ||
|
||
/test-results/ | ||
/playwright-report/ | ||
/blob-report/ | ||
/playwright/.cache/ |
This file contains 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 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,76 @@ | ||
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') }); | ||
|
||
/** | ||
* See https://playwright.dev/docs/test-configuration. | ||
*/ | ||
export default defineConfig({ | ||
testDir: './tests', | ||
/* Run tests in files in parallel */ | ||
fullyParallel: true, | ||
/* Fail the build on CI if you accidentally left test.only in the source code. */ | ||
forbidOnly: !!process.env.CI, | ||
/* Retry on CI only */ | ||
retries: process.env.CI ? 2 : 0, | ||
/* Opt out of parallel tests on CI. */ | ||
workers: process.env.CI ? 1 : 1, | ||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ | ||
reporter: process.env.CI ? 'html' : 'line', | ||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ | ||
webServer: { | ||
command: 'pnpm run dev', | ||
url: 'http://localhost:5173', | ||
reuseExistingServer: !process.env.CI, | ||
stdout: 'ignore', | ||
stderr: 'pipe', | ||
}, | ||
use: { | ||
/* Base URL to use in actions like `await page.goto('/')`. */ | ||
baseURL: 'http://127.0.0.1:5173', | ||
|
||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ | ||
trace: 'on-first-retry', | ||
}, | ||
|
||
/* Configure projects for major browsers */ | ||
projects: [ | ||
{ | ||
name: 'chromium', | ||
use: { ...devices['Desktop Chrome'] }, | ||
}, | ||
|
||
/* Test against mobile viewports. */ | ||
// { | ||
// name: 'Mobile Chrome', | ||
// use: { ...devices['Pixel 5'] }, | ||
// }, | ||
// { | ||
// name: 'Mobile Safari', | ||
// use: { ...devices['iPhone 12'] }, | ||
// }, | ||
|
||
/* Test against branded browsers. */ | ||
// { | ||
// name: 'Microsoft Edge', | ||
// use: { ...devices['Desktop Edge'], channel: 'msedge' }, | ||
// }, | ||
// { | ||
// name: 'Google Chrome', | ||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' }, | ||
// }, | ||
], | ||
|
||
/* Run your local dev server before starting the tests */ | ||
// webServer: { | ||
// command: 'npm run start', | ||
// url: 'http://127.0.0.1:3000', | ||
// reuseExistingServer: !process.env.CI, | ||
// }, | ||
}); |
This file contains 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 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 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 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 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,177 @@ | ||
import { expect, test } from '@playwright/test'; | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await page.goto('/', { waitUntil: 'domcontentloaded' }); | ||
}); | ||
|
||
test('헤더와 설명 텍스트가 올바르게 표시되는지 확인', async ({ page }) => { | ||
await expect(page.getByText('질문과 답변을 넘어,')).toBeVisible(); | ||
await expect(page.getByText('함께 만드는 인사이트')).toBeVisible(); | ||
await expect( | ||
page.getByText('실시간 Q&A와 소통을 위한 최적의 플랫폼'), | ||
).toBeVisible(); | ||
}); | ||
|
||
test('기능 카드들이 모두 표시되는지 확인', async ({ page }) => { | ||
const features = [ | ||
{ title: '실시간 Q&A', desc: '연사자와 익명의 청중의 실시간 응답' }, | ||
{ title: '채팅', desc: '실시간 채팅으로 즉각적인 소통' }, | ||
{ | ||
title: '권한 관리', | ||
desc: '연사자와 참가자를 위한 세분화된 권한 시스템', | ||
}, | ||
{ title: '아카이빙', desc: '세션 내용 보존과 효율적인 자료화' }, | ||
]; | ||
|
||
await Promise.all( | ||
features.map(async (feature) => { | ||
await expect( | ||
page.locator(`text=${feature.title} >> .. >> text=${feature.desc}`), | ||
).toBeVisible(); | ||
}), | ||
); | ||
}); | ||
|
||
test('회원가입 플로우 전체 테스트', async ({ page }) => { | ||
await page.click('text=회원가입'); | ||
|
||
const signUpButton = page.locator('text=회원 가입'); | ||
await expect(signUpButton).toBeDisabled(); | ||
|
||
await page.route('**/api/users/emails/**', async (route) => { | ||
await route.fulfill({ | ||
status: 200, | ||
contentType: 'application/json', | ||
body: JSON.stringify({ exists: false }), | ||
}); | ||
}); | ||
|
||
await page.route('**/api/users/nicknames/**', async (route) => { | ||
await route.fulfill({ | ||
status: 200, | ||
contentType: 'application/json', | ||
body: JSON.stringify({ exists: false }), | ||
}); | ||
}); | ||
|
||
await page.route('**/api/users', async (route) => { | ||
await route.fulfill({ | ||
status: 201, | ||
contentType: 'application/json', | ||
}); | ||
}); | ||
|
||
await page.fill('input[placeholder="[email protected]"]', '[email protected]'); | ||
await page.waitForResponse('**/api/users/emails/**'); | ||
|
||
await page.fill('input[placeholder="닉네임을 입력해주세요"]', 'testUser'); | ||
await page.waitForResponse('**/api/users/nicknames/**'); | ||
|
||
await page.fill( | ||
'input[placeholder="비밀번호를 입력해주세요"]', | ||
'Password123!', | ||
); | ||
|
||
await expect(signUpButton).toBeEnabled(); | ||
|
||
const response = page.waitForResponse('**/api/users'); | ||
await signUpButton.click(); | ||
expect((await response).status()).toBe(201); | ||
|
||
await expect(page.locator('text=회원가입 되었습니다.')).toBeVisible(); | ||
}); | ||
|
||
test('회원 가입이 이미 중복된 이메일이 있어서 실패하는 경우', async ({ | ||
page, | ||
}) => { | ||
await page.click('text=회원가입'); | ||
|
||
const signUpButton = page.locator('text=회원 가입'); | ||
await expect(signUpButton).toBeDisabled(); | ||
|
||
await page.route('**/api/users/emails/**', async (route) => { | ||
await route.fulfill({ | ||
status: 200, | ||
contentType: 'application/json', | ||
body: JSON.stringify({ exists: true }), | ||
}); | ||
}); | ||
|
||
await page.fill( | ||
'input[placeholder="[email protected]"]', | ||
'[email protected]', | ||
); | ||
await page.waitForResponse('**/api/users/emails/**'); | ||
|
||
await expect(page.locator('text=이미 사용 중인 이메일입니다.')).toBeVisible(); | ||
await expect(signUpButton).toBeDisabled(); | ||
}); | ||
|
||
test('회원 가입이 이미 중복된 닉네임이 있어서 실패하는 경우', async ({ | ||
page, | ||
}) => { | ||
await page.click('text=회원가입'); | ||
|
||
const signUpButton = page.locator('text=회원 가입'); | ||
await expect(signUpButton).toBeDisabled(); | ||
|
||
await page.route('**/api/users/emails/**', async (route) => { | ||
await route.fulfill({ | ||
status: 200, | ||
contentType: 'application/json', | ||
body: JSON.stringify({ exists: false }), | ||
}); | ||
}); | ||
|
||
await page.route('**/api/users/nicknames/**', async (route) => { | ||
await route.fulfill({ | ||
status: 200, | ||
contentType: 'application/json', | ||
body: JSON.stringify({ exists: true }), | ||
}); | ||
}); | ||
|
||
await page.fill( | ||
'input[placeholder="[email protected]"]', | ||
'[email protected]', | ||
); | ||
await page.waitForResponse('**/api/users/emails/**'); | ||
await expect(page.locator('text=사용 가능한 이메일입니다.')).toBeVisible(); | ||
|
||
await page.fill('input[placeholder="닉네임을 입력해주세요"]', 'testUser'); | ||
await page.waitForResponse('**/api/users/nicknames/**'); | ||
await expect(page.locator('text=이미 사용 중인 닉네임입니다.')).toBeVisible(); | ||
|
||
await expect(signUpButton).toBeDisabled(); | ||
}); | ||
|
||
test('로그인 / 로그아웃 플로우 전체 테스트', async ({ page }) => { | ||
await page.click('text=로그인'); | ||
|
||
const loginButton = page.locator('text=로그인').nth(1); | ||
|
||
await page.fill('input[placeholder="[email protected]"]', '[email protected]'); | ||
await page.fill( | ||
'input[placeholder="비밀번호를 입력해주세요"]', | ||
'Password123!', | ||
); | ||
|
||
await expect(loginButton).toBeEnabled(); | ||
|
||
await page.route('**/api/auth/login', async (route) => { | ||
route.fulfill({ | ||
status: 200, | ||
contentType: 'application/json', | ||
body: JSON.stringify({ accessToken: 'fake-jwt-token' }), | ||
}); | ||
}); | ||
|
||
const response = page.waitForResponse('**/api/auth/login'); | ||
await loginButton.click(); | ||
expect((await response).status()).toBe(200); | ||
|
||
await expect(page.locator('text=로그인 되었습니다.')).toBeVisible(); | ||
|
||
await page.click('text=로그아웃'); | ||
await expect(page.locator('text=로그아웃 되었습니다.')).toBeVisible(); | ||
}); |
Oops, something went wrong.