React에서 Playwright로 "검색 중..." 상태 모달을 테스트하기

kiwon kim·2024년 10월 30일

Frontend

목록 보기
14/30
post-thumbnail

React 애플리케이션에서 빠르게 나타났다 사라지는 로딩 모달을 테스트할 때 발생하는 타이밍 이슈와 이를 Promise.all을 통해 해결하는 방법을 알아보겠습니다.

테스트 대상 컴포넌트

먼저 테스트할 검색 컴포넌트의 예시 코드입니다:

// SearchComponent.tsx
import React, { useState } from 'react';
import LoadingModal from './LoadingModal';

interface SearchResult {
  id: number;
  name: string;
}

export const SearchComponent: React.FC = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [results, setResults] = useState<SearchResult[]>([]);

  const handleSearch = async () => {
    setIsLoading(true);
    try {
      const response = await fetch('/api/search');
      const data = await response.json();
      setResults(data);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleSearch}>검색</button>
      {isLoading && <LoadingModal data-testid="loading-modal" />}
      <ul>
        {results.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};
// LoadingModal.tsx
import React from 'react';

interface LoadingModalProps {
  'data-testid'?: string;
}

const LoadingModal: React.FC<LoadingModalProps> = (props) => {
  return (
    <div 
      className="modal-overlay"
      {...props}
    >
      <div className="modal-content">
        <div className="spinner" />
        <p>검색 중...</p>
      </div>
    </div>
  );
};

export default LoadingModal;

테스트의 문제점과 해결 방안

일반적인 순차 실행의 한계

순차적 실행 방식은 다음과 같은 문제점이 있습니다:

// 불안정한 테스트 코드
test('순차 실행 방식의 모달 테스트', async ({ page }) => {
  await page.getByRole('button', { name: '검색' }).click();
  await expect(page.getByTestId('loading-modal')).toBeVisible();
});

이 방식의 문제점:

  • 클릭 이벤트와 모달 표시 사이의 시간 차이를 고려하지 않습니다
  • 빠른 상태 변화를 놓칠 수 있어 테스트가 불안정합니다
  • 네트워크 지연이나 시스템 부하 상황에서 실패할 가능성이 높습니다

Promise.all을 활용한 개선된 테스트

Promise.all을 사용하면 이벤트와 상태 변화를 동시에 감지할 수 있습니다:

test('Promise.all을 활용한 안정적인 모달 테스트', async ({ mount, page }) => {
  // 컴포넌트 마운트
  await mount(<SearchComponent />);

  // API 응답 모킹
  await page.route('/api/search', async route => {
    await new Promise(resolve => setTimeout(resolve, 1000));
    await route.fulfill({
      status: 200,
      body: JSON.stringify([
        { id: 1, name: '결과 1' },
        { id: 2, name: '결과 2' }
      ])
    });
  });

  // API 응답 감시 설정
  const responsePromise = page.waitForResponse(res => 
    res.url().includes('/api/search')
  );

  // 클릭과 모달 표시를 동시에 감지
  await Promise.all([
    page.getByTestId('loading-modal').waitFor({ state: 'visible' }),
    page.getByRole('button', { name: '검색' }).click()
  ]);

  // API 응답 대기
  await responsePromise;

  // 모달 숨김 및 결과 표시 확인
  await expect(page.getByTestId('loading-modal')).toBeHidden();
  await expect(page.getByText('결과 1')).toBeVisible();
  await expect(page.getByText('결과 2')).toBeVisible();
});

Promise.all의 주요 이점

동시성 처리

  • 여러 비동기 작업을 병렬로 처리하여 테스트 실행 시간을 단축합니다
  • 이벤트 발생과 상태 변화를 놓치지 않고 정확하게 감지합니다

레이스 컨디션 방지

  • 클릭 이벤트와 모달 상태 변화를 동시에 감시하여 타이밍 이슈를 해결합니다
  • 네트워크 지연이나 시스템 부하와 관계없이 안정적으로 동작합니다

테스트 안정성 향상을 위한 추가 팁

타임아웃 설정

await Promise.all([
  page.getByTestId('loading-modal').waitFor({ 
    state: 'visible',
    timeout: 5000 
  }),
  page.getByRole('button', { name: '검색' }).click()
]);

여러 상태 변화 감지

await Promise.all([
  page.getByTestId('loading-modal').waitFor({ state: 'visible' }),
  page.getByTestId('results').waitFor({ state: 'hidden' }),
  page.getByRole('button', { name: '검색' }).click()
]);

테스트 설정

Playwright 컴포넌트 테스트를 위한 설정 파일입니다:

// playwright-ct.config.ts
import { defineConfig, devices } from '@playwright/experimental-ct-react';

export default defineConfig({
  testDir: './',
  timeout: 10000,
  use: {
    ctPort: 3100,
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    }
  ],
});

Promise.all을 활용한 테스트는 특히 모달이나 토스트 메시지처럼 빠르게 상태가 변하는 UI 요소를 테스트할 때 매우 효과적입니다. 이를 통해 테스트의 안정성과 신뢰성을 크게 향상시킬 수 있으며, 특히 CI/CD 환경에서 더욱 안정적인 테스트 실행이 가능해집니다.

이러한 방식으로 테스트를 작성하면 깜빡이는 모달과 같은 까다로운 UI 요소도 안정적으로 테스트할 수 있습니다. 특히 Promise.all을 활용한 동시성 처리가 테스트의 신뢰성을 높이는 핵심 요소가 됩니다.

profile
FOR_THE_BEST_DEVELOPER

0개의 댓글