TIL/Playwright

[TIL][Claude] GitHub Actions 에서 Playwright E2E 매일 자동 실행하기

아람2 2026. 5. 17. 20:00
반응형

E2E Suite 가 200개를 넘었다 

make parallel   # 7-Phase 병렬 실행

나름 안정화가 돼서 CI 를 붙여봤다 

CI 구조 

매일 오전 6시에 돌고, Slack 으로 결과를 받는다 

매일 06:00 KST (cron) 
    ↓
GitHub Actions: Playwright headless 
    ↓
7-Phase 병렬 실행 
    ↓
Allure Report 생성 → gh-pages 배포 
    ↓
Slack 알림 (카테고리별 PASS/FAIL)

수동 실행도 가능하다 — Phase 5 에서 터졌으면 5 부터만 재실행 가능 

on:
  workflow_dispatch:
    inputs:
      from_phase:
        description: '시작 Phase (1-7)'
        default: '1'

CI 에서 터진 것 3가지 

로컬에서 잘 되던 게 CI 에 올리니까 바로 터졌다 

1. CSS 파일 미존재

OSError: reports/custom_style.css not found

pytest.ini 에서 --css=reports/custom_style.css 를 참조하는데 .gitignore 에 걸려서 CI 에 없다

reports/
!reports/custom_style.css   # ← 예외 추가

git add -f 로 강제 추적

2. credentials.py 미존재

ModuleNotFoundError: No module named 'test_data.credentials'

로컬에서는 .env + credentials.py 로 관리하는데 CI 에는 둘 다 없다

GitHub Secrets → .env 생성은 간단한데, credentials.py 동적 생성이 좀 까다롭다

heredoc + sed 로 하면 Python 들여쓰기가 깨진다 — textwrap.dedent 가 정답

- name: Generate credentials wrapper (gitignored)
  run: |
    python3 -c "
    import textwrap, pathlib
    code = textwrap.dedent('''
        from config.settings import Settings

        class TestCredentials:
            EMAIL = Settings.get_env('TEST_EMAIL')
            PASSWORD = Settings.get_env('TEST_PASSWORD')
            OTP = '000000'
    ''').strip() + '\n'
    pathlib.Path('test_data/credentials.py').write_text(code)
    "

3. 타임아웃

Error: The operation was canceled.

timeout-minutes: 60 으로 잡았는데 2번 연속 취소됐다

CI 환경은 로컬보다 느리다 — 180분으로 올렸다

timeout-minutes: 180

concurrency:
  group: e2e-parallel-${{ github.ref }}
  cancel-in-progress: true   # 같은 브랜치 중복 실행 시 이전 건 취소

concurrency 는 같은 워크플로우가 중복 실행될 때 이전 걸 자동 취소한다

무료 할당량 아끼는 데 필수

스피너 로그 폭발

CI 첫 실행 로그를 열었더니 수만 줄이었다

⠋ Phase 3 진행 중...
⠙ Phase 3 진행 중...
⠹ Phase 3 진행 중...
... (매 0.1초마다 새 줄)

원인: 로컬 터미널은 \r 로 같은 줄을 덮어쓰는데 GitHub Actions 는 \r 을 줄바꿈으로 처리한다

스피너 1회전 = 새 줄 1개 = 10초에 100줄

CI 감지 후 분기

IS_CI="${CI:-false}"   # GitHub Actions 는 CI=true 자동 설정

start_spinner() {
    if [ "$IS_CI" = "true" ]; then
        echo "$message"   # 한 줄만 출력
        return
    fi
    # 기존 스피너 로직
}

모니터링은 5분 간격으로 줄였다

5분인 이유 — GitHub Actions 는 10분 동안 stdout 출력이 없으면 job 을 kill 한다

최종 워크플로우

name: E2E Parallel Test (Daily)
on:
  schedule:
    - cron: '0 21 * * *'   # 매일 06:00 KST
  workflow_dispatch:         # 수동 실행

jobs:
  e2e-parallel:
    runs-on: ubuntu-latest
    timeout-minutes: 180

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python 3.11
        uses: actions/setup-python@v5

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          playwright install --with-deps chromium

      - name: Create .env from secrets
        run: |
          echo "TEST_EMAIL=${{ secrets.TEST_EMAIL }}" >> .env

      - name: Run parallel tests
        run: bash scripts/test_parallel.sh

      - name: Generate Allure Report
        if: always()
        uses: simple-elf/allure-report-action@master
        with:
          allure_results: reports/allure-results
          allure_report: reports/allure-report
          allure_history: reports/allure-history

      - name: Upload test artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ github.run_number }}
          path: |
            reports/allure-report/
            reports/screenshots/
            reports/videos/
            reports/traces/
          retention-days: 30

if: always() — 실패해도 리포트 생성 + 아티팩트 업로드는 돌아가야 한다

정리

문제 해결
매일 수동 실행 cron 자동 실행
CSS 파일 미존재 .gitignore 예외 + git add -f
credentials.py 미존재 textwrap.dedent 동적 생성
60분 타임아웃 180분 + concurrency 중복 방지
스피너 로그 폭발 CI=true 감지 → 비활성화
모니터 로그 폭발 5분 간격 출력 (10분 kill 정책 대응)

로컬에서 잘 돌아가는 스크립트를 CI 에 그대로 올리면 안 된다

\r 동작, 파일 존재, 타임아웃 — 터미널에서 당연한 것들이 CI 에서는 전부 다르다

CI 를 붙이는 타이밍도 중요하다 — 실패가 많을 때 붙이면 매일 빨간불에 알림 피로만 쌓인다

안정화 먼저, CI 는 그 다음이다

반응형