실제 브라우저로 '다 조립된 앱'(프론트+백엔드+DB)을 태우는 통합 테스트 6개: - 인증 가드(비로그인→로그인), 로그인 실패 안내, 학생 로그인→대시보드 - 학습 센터→강좌 열기, 퀴즈 풀기→제출→서버 채점 결과 - 멘토 로그인→멘토 화면+개인정보 접근 기록(SEC-02 감사로그까지 검증) 운영 무오염 설계: - deploy/docker-compose.e2e.yml — 별도 프로젝트(mirime2e)·네트워크·휘발성 DB(tmpfs) - deploy/Caddyfile.e2e — 평문 HTTP로 SPA+/api 프록시(운영과 동일 구조) - deploy/e2e.sh — 스택 기동→테스트 학생 API 생성→퀴즈 slug 자동조회→ Playwright(브라우저 내장 이미지) 실행→down -v(정리) - 백엔드 힙 256MB 제한(작은 인스턴스에서 운영과 공존) EC2에서 6/6 통과 확인. 러너가 운영과 같은 인스턴스라 push마다가 아닌 온디맨드(릴리스 전)로 운영 — 전용 러너 확보 시 CI 게이트로 승격. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.0 KiB
JavaScript
49 lines
2.0 KiB
JavaScript
import { test, expect } from '@playwright/test'
|
|
import { login, STUDENT, QUIZ_SLUG } from './helpers.js'
|
|
|
|
// 핵심 사용자 여정: 로그인 → 학습 센터 → 강좌 → 퀴즈 채점.
|
|
// (프론트 SPA 라우팅 + 백엔드 API + DB가 함께 도는 통합 경로를 실제로 태운다.)
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await login(page, STUDENT)
|
|
})
|
|
|
|
test('학습 센터 → 카테고리 → 강좌 열기', async ({ page }) => {
|
|
await page.goto('/learn')
|
|
await expect(page.getByRole('heading', { name: '어썸데브 학습 센터' })).toBeVisible()
|
|
|
|
// 강좌 카드(링크)가 보이고, 클릭하면 강좌 페이지로 이동한다.
|
|
const courseLink = page.locator('a[href^="/learn/"]').first()
|
|
await expect(courseLink).toBeVisible()
|
|
await courseLink.click()
|
|
await expect(page).toHaveURL(/\/learn\/[^/]+$/)
|
|
})
|
|
|
|
test('퀴즈 풀기 → 제출 → 채점 결과 표시', async ({ page }) => {
|
|
test.skip(!QUIZ_SLUG, '퀴즈가 있는 코스 slug가 주입되지 않았음(E2E_QUIZ_SLUG)')
|
|
|
|
// "안 푼 문제가 있어요" 확인창이 뜨면 자동 수락(모두 풀 것이므로 보통 안 뜬다).
|
|
page.on('dialog', (dialog) => dialog.accept())
|
|
|
|
await page.goto(`/learn/${QUIZ_SLUG}/quiz`)
|
|
|
|
// 제출 버튼이 보일 때까지 = 문항 로드 완료.
|
|
const submit = page.getByRole('button', { name: '제출하고 채점받기' })
|
|
await expect(submit).toBeVisible()
|
|
|
|
// 각 문항 카드에서 첫 번째 보기를 고른다.
|
|
// 보기는 라디오를 감싼 <label>이므로, 라벨을 클릭하면 해당 보기가 선택된다.
|
|
const questionCards = page.locator('.card').filter({ has: page.locator('input[type=radio]') })
|
|
const count = await questionCards.count()
|
|
expect(count).toBeGreaterThan(0)
|
|
for (let i = 0; i < count; i++) {
|
|
await questionCards.nth(i).locator('label').first().click()
|
|
}
|
|
|
|
await submit.click()
|
|
|
|
// 서버 채점 결과가 표시된다(점수).
|
|
await expect(page.getByText(/채점 완료/)).toBeVisible()
|
|
await expect(page.locator('.stat-num')).toContainText('/')
|
|
})
|