Playwright로 E2E 테스트 자동화를 도입한 QA 엔지니어가 실제로 경험한 「실패담」을 7선으로 해설합니다. auto-waiting 과신·언어 선택 실수·CI 환경 차이·팀 도입 저항 등, Playwright 고유의 함정과 대책을 실무 경험을 바탕으로 소개합니다.
「Playwright로 갈아타면 해결된다」——그 착각이 다음 실패의 시작이었습니다. 도구를 바꿔도 사용법을 틀리면 결과는 같습니다.
📌 이런 분께 추천합니다
- Playwright로 E2E 테스트 도입을 검토하고 있는 QA 엔지니어
- Selenium에서 Playwright로 마이그레이션을 생각하는 엔지니어
- Playwright 도입 후 「생각했던 것과 다르다」고 느끼고 있는 분
- 팀에 Playwright 도입을 추진하려는 리드·매니저
✅ 이 기사를 읽으면 얻을 수 있는 것
- Playwright 고유의 실패 패턴 7가지와 구체적인 원인을 알 수 있다
- 「구현 레벨의 함정」이 아닌 「도입·운용 레벨의 판단 실수」를 알 수 있다
- 각 실패에 대한 구체적인 회피책·개선책을 알 수 있다
👤 이 기사를 쓴 사람
QA 엔지니어·테스트 자동화 엔지니어로서 15년 이상의 실무 경험을 가진 Yoshi가 집필. Selenium·Playwright·pytest를 실무 프로젝트에서 사용하며, Playwright 도입 시 「이렇게 했으면 좋았을 텐데」라고 느낀 실패를 가감 없이 공유합니다.
📖 관련 기사와의 사용 구분
- 테스트 자동화에서 자주 발생하는 실패 5선:도구 비의존의 「자동화 전략·설계」 레벨의 실패 → 「무엇을 자동화해야 하는가」에서 실패한 분은 이쪽
- Playwright × pytest 베스트 프랙티스:코드 레벨의 구현 함정 → 「작성법을 모르겠다」는 분은 이쪽
- 이 기사:「Playwright를 선택하고·도입하고·팀에서 사용한다」는 체험 레벨의 실패담
📌 결론 (3가지 포인트)
- Playwright 도입 실패의 대부분은 「도구의 문제」가 아니라 「도입 계획·언어 선택·팀 설계」의 문제
- auto-waiting·page.route()·CI 환경은 Playwright 고유의 함정이 있으며, 사전에 알면 회피할 수 있다
- Playwright 자체는 뛰어난 도구. 실패를 알고 사용하면 장기적으로 큰 가치를 발휘한다
「Selenium에서 Flaky 지옥에 빠졌으니 Playwright로 갈아타자」——2년 전의 판단은 반은 맞고, 반은 틀렸습니다.
Playwright 자체는 확실히 뛰어난 도구입니다. 하지만 「갈아타면 만사해결」이라는 착각으로 진행했기 때문에, Playwright 고유의 새로운 실패를 양산했습니다. 이 기사에서는 그 체험담을 7가지로 정리하여 가감 없이 공유합니다.
Playwright 도입에서 실패한 이야기 7선이란?조견표
| # | 실패 내용 | 근본 원인 |
|---|---|---|
| ① | 「갈아타면 만사해결」이라는 과신 | 도구 선정의 사상 레벨의 실수 |
| ② | auto-waiting을 과신하여 타임아웃 투성이가 됐다 | Playwright 고유 기능에 대한 오해 |
| ③ | TypeScript로 시작했더니 팀 아무도 유지보수할 수 없게 됐다 | 팀 스킬을 무시한 언어 선택 |
| ④ | page.route()로 목업했다고 생각했는데 실제 API를 호출하고 있었다 | 네트워크 목업 구조에 대한 오해 |
| ⑤ | CI 환경에서 브라우저가 실행되지 않는 문제를 방치했다 | 로컬 의존의 개발 습관 |
| ⑥ | 버전 업데이트로 한꺼번에 30개 테스트가 실패했다 | 의존성 관리·업데이트 전략의 부재 |
| ⑦ | 「왜 Playwright인가」를 팀에 설명할 수 없었다 | 도구 선정 근거가 자기 머릿속에만 있었다 |
① 「Playwright로 갈아타면 만사해결」이라는 과신이란?
무슨 일이 일어났나
Selenium에서 Flaky 테스트가 늘어 한계를 느꼈을 때, 「Playwright는 auto-waiting이 있으니 Flaky가 해소된다」「모던한 API로 유지보수하기 쉽다」는 평판을 믿고, 기존 200건의 Selenium 테스트를 전부 Playwright로 재작성하는 프로젝트를 시작했습니다. 결과는 Flaky가 줄었지만 제로는 되지 않았습니다. 그리고 재작성에 3개월을 쏟은 공수는 테스트 설계 개선에는 사용되지 않았습니다.
Playwright로 개선되는 Flaky와 개선되지 않는 Flaky의 차이
| Flaky 원인 | Playwright로 개선? | 이유 |
|---|---|---|
| Selenium의 implicit wait 부족 | ✅ 개선됨 | auto-waiting으로 자동 대기 |
| ChromeDriver 버전 불일치 | ✅ 개선됨 | 바이너리를 라이브러리와 함께 관리(단, install 실행·CI 캐시 설정은 별도로 필요) |
| 애니메이션 중 조작 타이밍 | △ 부분적으로 개선 | stable 체크는 있지만 완전하지 않음 |
| 외부 API 응답이 불안정 | ❌ 개선 안 됨 | 외부 의존은 도구로 해결할 수 없다 |
| 테스트 간 DB·상태 경합 | ❌ 개선 안 됨 | 테스트 설계 문제는 도구로 해결할 수 없다 |
| CI 환경의 CPU·메모리 성능 차이 | △ 부분적으로 대응 가능 | timeout 값 조정으로 완화 가능하지만 근본 해결은 아님 |
| 네트워크 흔들림·지연 | ❌ 개선 안 됨 | 목업화로 회피하는 것이 정답 |
| 병렬 실행 시 테스트 간 경합 | ❌ 개선 안 됨 | 테스트 설계(독립성 확보)로 해결한다 |
② auto-waiting을 과신하여 타임아웃 투성이가 됐다란?
무슨 일이 일어났나
「Playwright는 auto-waiting이 있으니 명시적인 대기 처리는 불필요」——이 이해로 작성하기 시작한 테스트는, 얼마 후 알 수 없는 타임아웃 에러를 양산하기 시작했습니다.
expect()를 병용한 명시적인 대기가 필요합니다.# ❌ auto-waiting에 너무 의존한 작성법
# locator.click()은 actionability check를 실행하지만,
# 비동기 상태 변화나 커스텀 UI 컴포넌트에서는 명시적 대기가 필요한 경우가 있다
page.locator("#submit-btn").click()
# ↑ 비동기로 disabled→enabled 로 바뀌는 버튼은
# 변화 타이밍에 따라 타임아웃이 될 수 있다
# ✅ 현대적인 Locator API + expect()로 의도를 명시한다
from playwright.sync_api import expect
expect(page.locator("#submit-btn")).to_be_enabled()
page.locator("#submit-btn").click()- 비동기로 disabled→enabled 로 바뀌는 버튼:타이밍에 따라 disabled 상태 그대로 클릭을 시도하여 타임아웃.
expect(locator).to_be_enabled()로 먼저 상태를 확인한다 - API 응답 후에 표시되는 콘텐츠:DOM에 존재하지 않는 동안은 기다려주지만, API가 느린 경우 전체 타임아웃이 될 수 있다
- 애니메이션 중인 요소:stable 체크는 있지만, CSS 애니메이션 구현에 따라 예상대로 동작하지 않는 경우가 있다
- Shadow DOM의 내부 요소:셀렉터 작성 방법에 따라 attached로 올바르게 판정되지 않는 경우가 있다
expect(locator).to_be_enabled()나 expect(locator).to_be_visible()를 조합하면 「무엇을 기다리는지」가 명시되어 Flaky가 줄어듭니다. 참고로 page.wait_for_selector()는 완전 비권장이 아니며, hidden/detach 상태 대기 등 일부 용도에서는 지금도 실무 활용되고 있지만, 신규 코드는 Locator API + expect()가 공식 권장 스타일입니다.③ TypeScript로 시작했더니 팀 아무도 유지보수할 수 없게 됐다란?
무슨 일이 일어났나
「Playwright는 TypeScript/Node.js 버전의 샘플·문서량이 가장 풍부」「타입이 있는 편이 유지보수하기 쉽다」는 이유로 TypeScript를 선택했습니다. 나는 JavaScript 경험이 있어서 작성할 수 있었지만, 팀의 다른 멤버들은 Python 엔지니어뿐. 반년 후에는 테스트 코드 리뷰어가 나 혼자가 되어, 사실상의 특정인 의존이 완성됐습니다.
| 판단 축 | TypeScript | Python |
|---|---|---|
| 공식 샘플·문서의 풍부함 | ✅ 가장 정보량이 많다 | ✅ 충분한 지원 있음 |
| 타입 안전성·자동완성 | ✅ 강력 | △ 타입 힌트로 대응 가능 |
| Python 팀이 많은 현장에서의 유지보수성 | ❌ 특정인 의존 위험 높음 | ✅ 전원이 관여할 수 있다 |
| pytest 와의 통합 | ❌ 별도 에코시스템 | ✅ pytest 로 통합 가능 |
④ page.route()로 목업했다고 생각했는데 실제 API를 호출하고 있었다란?
무슨 일이 일어났나
page.route()로 API를 목업했는데, 왜인지 테스트 결과가 실제 본번 데이터에 의존하고 있었습니다. 조사해 보니, 테스트 대상 API가 JavaScript(브라우저)경유가 아니라 서버 사이드 렌더링(SSR)으로 직접 호출되고 있던 케이스였습니다.
page.route()는 Browser Context 내의 HTTP 통신만을 intercept합니다. 한편, Playwright의 API 테스트용 request fixture는 APIRequestContext라는 독립된 HTTP 클라이언트로 동작하기 때문에, 통신 레이어가 완전히 다릅니다. 「Playwright니까 page.route()로 전부 목업할 수 있다」는 오해가 함정의 근본 원인입니다.# page.route() 는 브라우저의 HTTP 통신만을 인터셉트
await page.route("**/api/users", lambda route: route.fulfill(
status=200,
body='{"users": []}'
))
# ↑ SSR이나 Next.js의 getServerSideProps 내에서의 fetch는 가로챌 수 없다
# ↑ Playwright API Testing(request fixture)의 HTTP 통신도 가로챌 수 없다- SSR/SSG:Next.js·Nuxt 등의 서버 사이드에서의 fetch(Node.js 측 통신이기 때문에). 이 경우는 upstream API의 목업이나 Node 측 목업 대응이 필요
- Service Worker:PWA나 MSW(Mock Service Worker)도입 환경에서 SW가 통신을 가로채는 케이스. fetch가 SW 경유가 되면 page.route()가 닿지 않는다
- WebSocket:WebSocket 통신은 별도로
page.on("websocket")으로 대응 - Playwright API Testing(request fixture):Node.js 측의 HTTP 클라이언트 통신
route.fulfill()의 응답이 실제로 돌아오고 있는지를 네트워크 탭에서 확인합니다. SSR 환경이나 Service Worker 환경에서는 msw·Nock·WireMock 등의 서버 측 또는 SW 측 목업과 조합하는 접근법이 유효합니다. 「page.route()로 전부 목업할 수 있다」는 전제로 설계하면 환경에 따라 하마리게 됩니다.⑤ CI 환경에서 브라우저가 실행되지 않는 문제를 방치했다란?
무슨 일이 일어났나
로컬에서는 완벽하게 동작하는데, GitHub Actions에 올리면 「브라우저가 실행되지 않는다」「의존 라이브러리가 부족하다」는 에러가 속출했습니다. 원인은 알고 있었지만 미루다 보니, 「로컬에서 실행하여 수동으로 CI에 결과를 보고하는」 본말전도 운용을 2주 동안 계속해버렸습니다.
CI 환경에서 자주 발생하는 Playwright 실행 에러
| 에러 종류 | 원인 | 대책 |
|---|---|---|
BrowserType.launch: Executable doesn't exist | 브라우저 바이너리 미설치 | npx playwright install --with-deps 실행 |
error while loading shared libraries | Linux 의존 라이브러리 부족 | --with-deps 옵션으로 해결 |
| 타임아웃이 로컬보다 빈번하게 발생 | CI 머신의 CPU·메모리가 로컬보다 낮다 | timeout을 2〜3배로 설정 |
| 헤드리스 모드에서 동작이 다르다 | viewport 차이·GPU 없음·hover 미동작·animation timing 차이·Intersection Observer 동작 차이 등 | --headed로 확인·설계를 재검토 |
⑥ 버전 업데이트로 한꺼번에 30개 테스트가 실패했다란?
무슨 일이 일어났나
pip install playwright --upgrade로 버전을 올렸더니, 다음날 CI에서 30건의 테스트가 한꺼번에 실패했습니다. 원인은 버전 간의 API 변경과 브라우저 바이너리의 버전 불일치입니다.
# ❌ 버전 고정 없음(업데이트할 때마다 깨질 위험)
playwright
# ✅ 버전을 고정(requirements.txt)
playwright==1.44.0
# 브라우저 바이너리도 반드시 재설치
# pip install playwright==1.44.0 후에 반드시 실행
# playwright install chromiumrequirements.txt의 버전 고정은 필수입니다. Renovate·Dependabot으로 버전 업데이트 PR을 자동 생성하는 구조를 넣으면 관리가 편해집니다.⑦ 「왜 Playwright인가」를 팀에 설명할 수 없었다란?
무슨 일이 일어났나
「Playwright가 더 좋으니까」라고 혼자 결정하고 팀에 전개하려 했을 때, 선배 엔지니어로부터 「왜 Selenium은 안 돼요?」라는 질문을 받고 제대로 대답할 수 없었습니다. 「왠지 모던한 느낌이라서」「소셜미디어에서 평판이 좋아서」——이것으로는 기술적인 의사결정으로는 너무 약합니다. 결과적으로 「일단 지금의 Selenium으로 계속하자」가 되어, 마이그레이션 프로젝트가 반년간 허공에 떠버렸습니다.
팀을 설득할 수 있는 「왜 Playwright인가」의 근거
| 비교 축 | Selenium | Playwright |
|---|---|---|
| auto-waiting | 수동으로 time.sleep/wait가 필요 | 조작 시 actionability check를 자동 실행 |
| ChromeDriver 관리 | Chrome 버전과 수동으로 맞출 필요 | 브라우저 바이너리를 라이브러리와 함께 관리 |
| API 테스트와의 통합 | 별도 라이브러리(requests 등)가 필요 | request fixture로 E2E+API를 통합 |
| 네트워크 목업 | 별도 목업 서버가 필요 | page.route()로 브라우저 통신을 목업 |
| HTML Report / Trace | 별도 Allure 등의 설정이 필요 | 내장 HTML 리포트·Trace Viewer |
FAQ
Q. Selenium에서 Playwright로 마이그레이션할 가치가 있나요?
있습니다. 단 「마이그레이션하기만 하면 해결된다」는 과신은 금물입니다. ChromeDriver 관리 폐지·auto-waiting·HTML 리포트 내장 등, 운용 비용을 낮추는 요소가 많은 것은 사실입니다. 「지금의 Selenium에서 무엇이 불편한가」를 정리하고 나서 판단하면, 마이그레이션 효과를 최대화할 수 있습니다.
Q. Playwright 도입은 어디서부터 시작하는 것이 정답인가요?
「새로운 테스트를 1〜2건만 Playwright로 작성하여, CI에 올리는 곳까지 완수한다」는 것이 최초 스텝으로 최선입니다. 기존 테스트의 전체 마이그레이션부터 시작하면 공수와 학습 비용으로 막혀버립니다. 작게 동작시켜 성공 체험을 만들고 나서 점진적으로 범위를 넓혀가는 것이 실패하기 어려운 접근법입니다.
Q. Python 버전과 TypeScript 버전, 어느 쪽을 선택해야 하나요?
팀의 주요 언어에 맞추는 것이 최선입니다. Python 엔지니어 팀이라면 Python 버전(playwright-pytest)이 유지보수하기 쉽고, 기존 pytest 스택과도 통합할 수 있습니다. TypeScript는 문서·샘플이 가장 충실하지만, 「아무도 유지보수할 수 없는」 테스트 코드는 최악의 상태입니다. 팀 전원이 읽고 쓸 수 있는 언어를 선택하세요.
Q. auto-waiting이 있는데 왜 Flaky 테스트가 발생하나요?
auto-waiting은 조작 시에 actionability check를 실행하는 기능이며, 「비동기 상태 변화를 추적하는」 것은 아닙니다. 「API 응답이 돌아올 때까지」「버튼이 enabled 상태가 될 때까지」는 expect()를 사용한 명시적인 대기가 별도로 필요합니다. Flaky가 많은 경우는 auto-waiting의 대상 외 케이스가 포함되어 있지 않은지 확인하세요.
Q. Playwright의 버전은 어떻게 관리하면 되나요?
requirements.txt로 버전을 고정하고, 업데이트는 전용 브랜치에서 전체 테스트 통과를 확인하고 나서 main에 머지하는 것이 기본입니다. Playwright는 라이브러리와 브라우저 바이너리의 버전이 연동되어 있기 때문에, 라이브러리를 업데이트했으면 반드시 playwright install로 브라우저도 업데이트하세요. Renovate·Dependabot으로 업데이트 PR을 자동 생성하면 관리가 편해집니다.
📖 관련 기사(이 기사와 함께 읽으면 이해가 깊어집니다)
【Playwright 구현】
【자동화 전략·판단 기준】
- 테스트 자동화에서 자주 발생하는 실패 5선|전략·설계 레벨의 실패와 개선책
- 자동화하지 않는 것이 좋은 테스트 케이스 7선|Flaky가 되기 쉬운 테스트 구분법
- 자동화해야 할 테스트와 해서는 안 되는 테스트의 구분 방법
- 테스트 자동화의 ROI(비용 대비 효과)사고방식
【로드맵】
Playwright 자체는 정말 뛰어난 도구입니다. 이 기사에서 소개한 실패는 모두 「Playwright의 문제」가 아니라 「사용법·도입 계획·팀 설계」의 문제였습니다. 실패 패턴을 알고 시작하면, 같은 전철을 밟지 않아도 됩니다. 작게 시작하여, CI에 올려, 팀에서 사용할 수 있는 상태를 만든다——이 순서로 진행하는 것이 Playwright 도입을 성공시키는 가장 효율적인 방법입니다.
📋 이 기사의 정리
- 「Playwright로 마이그레이션하면 해결된다」는 반은 맞다. 테스트 설계 문제는 도구를 바꿔도 해결되지 않는다
- auto-waiting은 조작 시 actionability check를 실행하는 기능. 비동기 상태 변화는 expect()로 명시적으로 대기가 필요
- 언어 선택은 팀의 다수가 유지보수할 수 있는 것을 우선. 「문서 양」보다 「팀이 사용할 수 있다」가 중요
- page.route()는 Browser Context 내의 통신만 목업. SSR·Service Worker·request fixture에는 효과가 없다
- CI 대응은 도입 첫날에 완수한다. 「나중에」가 최대의 시간 낭비가 된다
- 버전 업데이트는 라이브러리와 브라우저 바이너리를 세트로 관리. 고정 버전으로 운용한다
- 팀 도입은 「현재 과제→해결책→마이그레이션 비용 시산」의 3점 세트로 설득력을 갖게 한다
