Allra Fintech
Convention

E2E 테스트 규칙

올라핀테크 프론트엔드 프로젝트 내에서 지켜야하는 E2E 테스트 규칙을 서술합니다.

E2E 테스팅

1. 디렉터리 구조

e2e/
├── core/                          # 테스트 인프라
│   ├── api-mock-handlers/        # API Mock 시스템
│   │   ├── index.ts              # Mock 핸들러 메인
│   │   └── mocks/                # 도메인별 Mock 데이터
│   │       ├── contract.ts
│   │       ├── factoring.ts
│   │       ├── product-margin.ts
│   │       ├── remote-config.ts
│   │       └── // ...
│   ├── sequences/                # 재사용 가능한 시퀀스
│   │   ├── user-login-success-sequence.ts
│   │   └── user-login-failed-sequence.ts
│   └── utils/                    # 유틸리티
│       ├── go-to-page.ts
│       └── url-matchers.ts

└── tests/                        # 테스트 케이스
    ├── auth/
    │   ├── sign-in/
    │   │   └── flow/
    │   │       └── sign-in.flow.spec.ts
    │   └── // ...
    ├── contract/
    │   └── flow/
    │       └── contract-flow.spec.ts
    └── factoring/
        ├── flow/
        └── ui/

2. 테스트 작성 패턴

플로우 함수로 구조화

test.describe('계약 플로우', () => {
  /**
   * @시나리오
   * - 로그인 후 마이페이지로 이동
   * - 계약이 필요한 상태
   * @조건
   * - 로그인 상태여야 함
   * - 계약이 필요한 상태여야 함
   * - 조회 성공 응답이 있어야 함
   * @검증
   * - 단계 별 인증을 검증함
   * - 최종 계약 완료 확인
   */
  test('성공 플로우', async ({ page, next }) => {
    apiMockHandlers(next, [
      {
        match: signatureMatch(API_SIGNATURE.CONTRACT.CHECK_CONDITION),
        responseBody: CHECK_CONTRACT_CONDITION_RESPONSE_FIXTURE.SUCCESS.data,
      },
      {
        match: signatureMatch(API_SIGNATURE.MY_CONTRACT.INFO),
        responseBody:
          GET_CONTRACT_INFO_FIXTURE.SUCCESS_보증보험동의계약필요.data,
      },
    ])
    await 유저_로그인_성공_플로우(page)
    await 계약_조회_플로우(page)
    await 계약_인증_플로우(page)
    await 계약_서명_플로우(page)
    await 계약_완료_플로우(page)
  })
})

플로우 함수 패턴 장점:

  • 테스트 단계별로 명확한 구조
  • 재사용 가능
  • 한글 함수명으로 가독성 향상
  • 유지보수 용이

3. API Mock 시스템

Mock 사용 예시

  • 모든 happy path가 갖춰진 apiMockHandlers를 사용하고, 필요한 API를 테스트별로 오버라이딩 하여 사용
test('로그인 성공', async ({ page, next }) => {
  apiMockHandlers(next, [
    {
      match: signatureMatch(API_SIGNATURE.AUTH.SIGN_IN),
      responseBody: SIGN_IN_FIXTURE.SUCCESS.data,
    },
  ])
  await ensureCredentials(page)
  await 유저_로그인_성공_플로우(page)
})

test('서버 오류로 로그인 실패', async ({ page, next }) => {
  apiMockHandlers(next, [
    {
      match: signatureMatch(API_SIGNATURE.AUTH.SIGN_IN),
      responseBody: SIGN_IN_FIXTURE.SERVER_ERROR,
      serverError: true, // 500 에러 시뮬레이션
    },
  ])
  await ensureCredentials(page)
  await 유저_로그인_성공_플로우(page)
})

4. 재사용 가능한 Sequence

  • 로그인 등 일반적인 플로우는 재사용 가능한 플로우 함수로 구현
export const 유저_로그인_성공_플로우 = async (page: Page) => {
  await goToPage(page, Routes.signIn())
  const idInput = page.getByPlaceholder('-제외 10자리 입력')
  const passwordInput = page.getByPlaceholder('비밀번호를 입력해주세요')
  await expect(idInput).toBeEditable()
  await expect(passwordInput).toBeEditable()
  // ...
}

5. 선택자 베스트 프랙티스

// ✅ 1순위: Test ID
page.getByTestId('user-info-menu')

// ✅ 2순위: Role 기반
page.getByRole('button', { name: '로그인' })
page.getByRole('textbox', { name: '비밀번호' })

// ⚠️ 3순위: Label
page.getByLabel('짝짝짝 👏🏻계약이 완료 되었어요!')

// ⚠️ 4순위: Text (변경 가능성)
page.locator('text="로그인 정보가 일치하지 않아요"')

// ❌ 지양: CSS/XPath (취약)
page.locator('.login-button')

6. 대기 패턴

// ✅ 명시적 대기
await page.getByTestId('user-info-menu').waitFor({ state: 'visible' })

// ✅ expect 타임아웃
await expect(page.locator('text="로그인 성공"')).toBeVisible({
  timeout: 50000,
})

// ✅ URL 대기
await page.waitForURL(Routes.contractInfo())

// ❌ 암시적 대기 (지양)
await page.waitForTimeout(5000)

// ❌ 네트워크 상태 대기 (지양)
await page.waitForLoadState('networkidle')
await page.waitForLoadState('domcontentloaded')
await page.waitForLoadState('load')