Showing Posts From

실패한

스크린샷과 비디오: 실패한 테스트를 디버깅하는 강력한 도구

스크린샷과 비디오: 실패한 테스트를 디버깅하는 강력한 도구

새벽 3시에 깨진 테스트 새벽에 슬랙 알림이 왔다. CI가 깨졌다고. 일어나서 노트북 켰다. 테스트 리포트 열었다. "AssertionError: Expected 'Success' but got 'Error'" 이게 뭐야. 로그만 봐서는 모르겠다. 로컬에서 돌리면 통과한다. 당연히. 이럴 때 필요한 게 스크린샷이다. 그리고 비디오. 실패 순간을 봐야 안다. 다시 잤다. 아침에 보기로.Pytest-html이 필요한 이유 출근해서 다시 봤다. 로그는 여전히 쓸모없다. 그래서 pytest-html을 쓴다. pip install pytest-html pytest --html=report.html --self-contained-html이게 전부다. 설치하고 옵션 주면 끝. 리포트 파일이 하나 나온다. 여는 순간 다르다. HTML이라 브라우저에서 본다.테스트 전체 요약 각 케이스별 실행 시간 실패한 케이스 하이라이트 스택 트레이스 접었다 폈다로그 파일 grep 하던 시절은 끝났다. 근데 이것도 한계가 있다. 텍스트일 뿐. 실패 순간 화면을 못 본다.conftest.py에 스크린샷 로직 스크린샷은 실패할 때만 찍으면 된다. pytest hook을 쓴다. conftest.py에 추가. import pytest from selenium import webdriver import os from datetime import datetime@pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item): outcome = yield report = outcome.get_result() if report.when == 'call' and report.failed: driver = item.funcargs.get('driver') if driver: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') screenshot_path = f'screenshots/failed_{item.name}_{timestamp}.png' os.makedirs('screenshots', exist_ok=True) driver.save_screenshot(screenshot_path) # HTML 리포트에 추가 extra = getattr(report, 'extra', []) extra.append(pytest_html.extras.image(screenshot_path)) report.extra = extra이제 테스트 실패하면 자동으로 찍힌다. screenshots 폴더에 쌓인다. 파일명에 타임스탬프 넣는 게 포인트. 같은 테스트 여러 번 실패하면 다 남는다. 지난주에 이거로 버그 찾았다. 로딩 스피너가 안 사라진 채로 클릭해서 실패한 거였다. 로그로는 절대 못 찾았다. 비디오 녹화는 더 강력하다 스크린샷은 한 장면만 보여준다. 비디오는 전체 흐름을 본다. Selenium 4부터 녹화가 쉬워졌다. 근데 나는 pytest-xvfb랑 ffmpeg 쓴다. import subprocess import pytestclass VideoRecorder: def __init__(self, test_name): self.test_name = test_name self.process = None def start(self): video_path = f'videos/{self.test_name}.mp4' os.makedirs('videos', exist_ok=True) # Xvfb 디스플레이에서 녹화 self.process = subprocess.Popen([ 'ffmpeg', '-video_size', '1920x1080', '-framerate', '25', '-f', 'x11grab', '-i', ':99', # Xvfb 디스플레이 '-c:v', 'libx264', video_path ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def stop(self): if self.process: self.process.terminate() self.process.wait()@pytest.fixture def video_recorder(request): recorder = VideoRecorder(request.node.name) recorder.start() yield recorder # 실패한 경우만 비디오 보관 if request.node.rep_call.failed: recorder.stop() else: recorder.stop() # 통과한 테스트는 비디오 삭제 video_path = f'videos/{request.node.name}.mp4' if os.path.exists(video_path): os.remove(video_path)이렇게 하면 실패한 테스트만 비디오가 남는다. 용량 문제 해결. 통과한 건 볼 필요 없다. 비디오로 찾은 버그가 있다. 드롭다운 메뉴가 너무 빨리 닫혀서 클릭 실패. 스크린샷으로는 타이밍 이슈 파악 못 했을 거다.원격 디버깅의 현실 재현이 안 되는 버그가 제일 짜증난다. "제 로컬에서는 되는데요?" CI 환경은 다르다. 네트워크 속도 CPU 성능 메모리 브라우저 버전 OS로컬에서 100번 돌려도 통과한다. CI에서는 10번 중 3번 실패한다. Flaky test. 이럴 때 비디오가 답이다. CI에서 녹화해서 아티팩트로 남긴다. # GitHub Actions 예시 - name: Run tests with video run: | pytest --video=on- name: Upload artifacts if: failure() uses: actions/upload-artifact@v3 with: name: test-videos path: videos/ retention-days: 7실패하면 Actions 탭에서 비디오 다운받는다. 재생해서 본다. 문제가 보인다. 지난달에 이거로 해결한 게 있다. 페이지 로드가 완료되기 전에 요소를 찾아서 실패. 비디오 보니까 로딩 인디케이터가 0.2초 늦게 사라졌다. explicit wait 추가했다. 해결. Allure Report는 더 예쁘다 pytest-html도 좋은데 Allure가 더 이쁘다. 회사 비개발자들한테 보여주기 좋다. pip install allure-pytestpytest --alluredir=allure-results allure serve allure-results브라우저가 뜬다. 대시보드가 나온다.통과/실패 비율 파이 차트 시간별 추세 그래프 카테고리별 분류 스크린샷, 비디오 임베드스크린샷 추가는 간단하다. import allure from allure_commons.types import AttachmentType@pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item): outcome = yield report = outcome.get_result() if report.when == 'call' and report.failed: driver = item.funcargs.get('driver') if driver: allure.attach( driver.get_screenshot_as_png(), name='failure_screenshot', attachment_type=AttachmentType.PNG )비디오도 비슷하게 attach 하면 된다. 리포트에서 바로 재생된다. 리드가 "QA 리포트 깔끔하네요" 했다. Allure 덕분이다. 저장 공간 관리는 필수 비디오는 용량 먹는다. Full HD 5분 테스트면 200MB다. 하루에 테스트 100개 돌린다. 10개 실패하면 2GB. 일주일이면 14GB. 스토리지 터진다. 관리가 필요하다. 첫째, 압축한다. # ffmpeg 옵션 조정 '-c:v', 'libx264', '-crf', '28', # 품질 낮춤 (18이 기본, 28이면 용량 반) '-preset', 'veryfast' # 인코딩 속도 우선화질 좀 떨어져도 디버깅에는 문제없다. 용량이 절반으로 줄었다. 둘째, 해상도 낮춘다. 1920x1080 대신 1280x720. 체감 차이 별로 없다. 셋째, 보관 기간 정한다. # 7일 지난 비디오 삭제 import os import timedef cleanup_old_videos(days=7): cutoff = time.time() - (days * 86400) for file in os.listdir('videos'): filepath = os.path.join('videos', file) if os.path.getctime(filepath) < cutoff: os.remove(filepath)매일 아침 cron으로 돌린다. 오래된 거 자동 삭제. 넷째, 실패만 저장한다. 통과한 테스트 비디오는 의미 없다. 위에 fixture에서 이미 처리했다. 지금은 한 달에 30GB 정도 쓴다. S3에 올린다. 비용 한 달에 1달러. 실제로 잡은 버그들 비디오 녹화 도입 후 3개월. 찾은 버그가 확 늘었다. 케이스 1: 무한 로딩증상: 로그인 후 홈 화면 안 뜸 로그: "Timeout waiting for element" 비디오: 로딩 스피너만 10초간 돈다 원인: 백엔드 API 타임아웃 해결: API 팀에 비디오 넘김, 3일 만에 수정케이스 2: 깜빡이는 모달증상: 테스트 5번 중 1번 실패 로그: "Element not clickable" 비디오: 모달이 뜨자마자 사라짐 (0.1초) 원인: CSS transition 버그 해결: 프론트 팀에서 즉시 패치케이스 3: 드래그 앤 드롭 실패증상: 파일 업로드 안 됨 로그: 아무 에러 없음 (가장 최악) 비디오: 드래그는 되는데 드롭 영역이 안 보임 원인: z-index 문제로 드롭존 가림 해결: CSS 수정개발자들한테 비디오 보내니까 반응이 다르다. "아 이거구나" 바로 이해한다. 말로 설명하면 10분. 비디오 보내면 10초. 팀 도입할 때 주의점 처음에 팀원들 반발이 있었다. "설정 복잡해요", "로컬에서 느려요" 천천히 설득했다. 첫째, CI에만 우선 적용. 로컬은 선택사항. 원하면 켜라. 둘째, 간단한 가이드 작성. 3줄로 시작하는 법 정리. # 간단 버전 pip install pytest-html pytest --html=report.html --self-contained-html # 끝셋째, 성공 사례 공유. "이 버그 비디오로 찾았어요" 한 달 후 다들 쓴다. 이제 비디오 없으면 못 일한다. 후배한테 pytest-html 가르쳤다. 30분이면 세팅 끝난다. 어렵지 않다. 비디오는 환경 좀 탄다. Xvfb 설치, ffmpeg 설정. 그래도 공식 문서 따라하면 된다. 내가 쓰는 최종 세팅 정리한다. 지금 내 설정. conftest.py import pytest import allure from datetime import datetime import os@pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item): outcome = yield report = outcome.get_result() if report.when == 'call' and report.failed: driver = item.funcargs.get('driver') if driver: # 스크린샷 allure.attach( driver.get_screenshot_as_png(), name=f'{item.name}_failure', attachment_type=allure.attachment_type.PNG ) # 브라우저 로그 logs = driver.get_log('browser') allure.attach( str(logs), name='browser_logs', attachment_type=allure.attachment_type.TEXT )pytest.ini [pytest] addopts = --alluredir=allure-results --html=report.html --self-contained-html -v --tb=shortCI 설정 (GitHub Actions) - name: Run tests run: pytest- name: Generate Allure report if: always() run: allure generate allure-results- name: Upload report if: always() uses: actions/upload-artifact@v3 with: name: test-report path: allure-report/이게 전부다. 복잡하지 않다. 한 번 세팅하면 끝. 테스트 돌리면 자동으로 스크린샷 찍힌다. 실패하면 비디오 남는다. 리포트는 예쁘게 나온다. 디버깅 시간이 절반으로 줄었다. 버그 재현율이 80%에서 95%로 올랐다. 투자할 가치 있다. 다음 단계 지금은 만족한다. 근데 개선할 점은 있다. 첫째, 실패 비디오 자동 분석. AI로 실패 패턴 찾기. 가능할까? 둘째, 라이브 스트리밍. 테스트 돌 때 실시간으로 보기. CI에서 무슨 일 벌어지는지. 셋째, 네트워크 트래픽 녹화. HAR 파일로 저장. API 응답까지 분석. 욕심은 많다. 시간이 없다. 지금 것만 잘 써도 충분하다.오늘도 테스트 3개 실패했다. 비디오 봤다. 10분 만에 원인 찾았다. 스크린샷 없었으면 하루 걸렸을 거다.