Playwright는 Microsoft가 개발한 브라우저 자동화 라이브러리입니다. Selenium에 비해 설정이 적고 자동 대기가 표준 내장되어 있으며 모던한 작성법을 사용할 수 있습니다. 이 글에서는 설치부터 첫 번째 테스트 실행까지, 명령어 하나씩 차근차근 해설합니다.
📌 이 글의 대상 독자
| ✅ Playwright를 처음 사용하는 분 |
| ✅ Selenium은 알지만 Playwright와의 차이를 알고 싶은 분 |
| ✅ 브라우저 조작을 자동화해서 E2E 테스트를 시작하고 싶은 분 |
| ✅ Codegen(테스트 자동 녹화)이나 Trace Viewer를 사용해보고 싶은 분 |
📖 이 글을 읽으면 할 수 있는 것
| ✔ Playwright를 설치하고 브라우저를 실행할 수 있다 |
| ✔ 페이지를 열고 클릭·입력·어서션을 할 수 있다 |
| ✔ pytest-playwright을 사용해 테스트를 구조화할 수 있다 |
| ✔ Codegen으로 조작을 녹화해서 테스트 코드를 자동 생성할 수 있다 |
| ✔ 스크린샷·Trace Viewer로 디버깅할 수 있다 |
필자는 여러 프로젝트에서 Playwright를 사용한 E2E 테스트를 실천해 왔습니다.
Selenium에서 Playwright로 마이그레이션한 프로젝트도 있어 양측의 차이를 실무 관점에서 비교할 수 있습니다.
이 글은 “첫날에 동작하는 테스트 작성”을 목표로 막히기 쉬운 포인트를 미리 짚어서 해설합니다.
✅ 이 글의 목표
- Playwright를 설치하고
pytest로 테스트를 실행할 수 있는 상태 get_by_role·get_by_test_id등의 로케이터를 사용해 폼을 조작할 수 있는 상태- Codegen으로 테스트 코드를 자동 생성할 수 있는 상태
Playwright란? Selenium과의 차이
| 비교 항목 | Selenium | Playwright |
|---|---|---|
| 드라이버 설정 | 별도 필요 (webdriver-manager 등) | ✅ 설치 시 일괄 취득 |
| 자동 대기 | 명시적 대기(WebDriverWait)를 직접 작성해야 함 | ✅ 조작 전 자동 대기 (표준 내장) |
| 로케이터 API | CSS·XPath·ID 등 | ✅ get_by_role 등 의미적 API도 사용 가능 |
| 대응 브라우저 | Chrome / Firefox / Safari / Edge | Chromium(Chrome·Edge 포함) / Firefox / WebKit |
| Codegen (녹화) | 없음 (IDE 플러그인은 별도) | ✅ 표준 내장 |
| Trace Viewer | 없음 | ✅ 테스트 실패 상세를 시각화 |
| 학습 난이도 | ○ (자료·사례가 풍부) | ◎ (설정이 적어 시작하기 쉬움) |
| 채용 실적 | ◎ 많음 (역사가 길다) | ○ 증가 중 |
신규 프로젝트라면 Playwright가 작성하기 쉽고 안정적입니다. 다만 채용 건수에서는 Selenium이 아직 많은 상황입니다. Selenium 기초를 먼저 배우고 Playwright로 나아가면 양쪽의 강점을 이해할 수 있습니다.
Playwright가 급속히 보급된 이유
2020년 이후 Playwright는 E2E 테스트 툴로서 급속히 보급됐습니다. 그 배경에는 다음과 같은 이유가 있습니다.
| 이유 | 내용 |
|---|---|
| 자동 대기 표준 내장 | Selenium처럼 WebDriverWait를 매번 작성할 필요가 없어 Flaky Test가 발생하기 어렵다 |
| 모던 SPA와의 궁합 | React·Vue·Angular로 만든 SPA를 안정적으로 테스트할 수 있다 |
| Codegen·Trace Viewer | 디버깅과 초기 코드 작성이 훨씬 쉽다 |
| 드라이버 불필요 | playwright install 한 번으로 브라우저도 갖춰지며 설정 비용이 낮다 |
| CI와의 친화성 | GitHub Actions와의 조합이 공식으로 지원되어 도입하기 쉽다 |
Selenium에서 Playwright로 마이그레이션한 프로젝트에서 가장 크게 달라진 것은 WebDriverWait를 작성하는 횟수가 격감한 것이었습니다. Selenium 시대에는 “요소를 취득하기 전에 매번 대기 조건을 작성하는” 것이 당연했지만, Playwright에서는 조작 메서드가 자동으로 기다려줘서 테스트 코드가 간결해졌습니다. Flaky Test 발생 빈도도 확실히 줄었습니다.
① 설치하기
먼저 Python 환경이 준비되어 있는지 확인하세요 (Python 환경 구축 가이드).
venv를 활성화한 상태에서 설치합니다.
# Playwright 본체와 pytest 플러그인 설치
pip install playwright pytest-playwright
# 브라우저 본체 설치 (Chromium·Firefox·WebKit)
playwright install
설치 확인:
playwright --version
# Playwright 1.x.x 라고 표시되면 OK
pip install만으로는 동작하지 않습니다.
playwright install로 브라우저 본체(Chromium 등)를 다운로드해야 합니다. CI 환경에서도 마찬가지입니다.② 우선 실행해보기 (스크립트 형식)
pytest를 사용하기 전에 먼저 간단한 스크립트로 Playwright의 동작을 확인합니다.
# first_playwright.py
from playwright.sync_api import sync_playwright
def main() -> None:
with sync_playwright() as p:
# 브라우저 실행 (headless=False로 브라우저 창 표시)
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 페이지 열기
page.goto("https://example.com") # 샘플 URL
# 타이틀 확인
print(page.title())
# 스크린샷 찍기
page.screenshot(path="screenshot.png")
browser.close()
if __name__ == "__main__":
main()
python first_playwright.py
브라우저가 열리고 페이지가 표시되면 성공입니다. screenshot.png도 저장됩니다.
③ pytest로 테스트 작성하기
실무에서는 pytest와 조합해서 테스트를 관리합니다. pytest-playwright를 설치했다면 page fixture를 자동으로 사용할 수 있습니다.
첫 번째 테스트 파일
# test_first.py
from playwright.sync_api import Page, expect
def test_page_title(page: Page) -> None:
page.goto("https://example.com") # 샘플 URL
assert "Example" in page.title()
# Playwright다운 작성법은 expect를 사용하는 방법도 있다
# expect(page).to_have_title("Example Domain")
def test_heading_visible(page: Page) -> None:
page.goto("https://example.com") # 샘플 URL
heading = page.get_by_role("heading", name="Example Domain")
expect(heading).to_be_visible() # Playwright다운 어서션
pytest test_first.py -v
정상 출력 예시:
========================= test session starts ==========================
collected 2 items
test_first.py::test_page_title PASSED [ 50%]
test_first.py::test_heading_visible PASSED [100%]
========================== 2 passed in 2.34s ==========================
로그인 폼 테스트 (실전 예시)
# test_login.py
from playwright.sync_api import Page, expect
def test_login_success(page: Page) -> None:
page.goto("https://example.com/login") # 샘플 URL
# 이메일 주소 입력
page.get_by_label("이메일 주소").fill("user@example.com")
# 비밀번호 입력
page.get_by_label("비밀번호").fill("password123")
# 로그인 버튼 클릭
page.get_by_role("button", name="로그인").click()
# 대시보드로 전환됐는지 확인
expect(page).to_have_url("https://example.com/dashboard") # 샘플 URL
def test_login_invalid_password(page: Page) -> None:
page.goto("https://example.com/login") # 샘플 URL
page.get_by_label("이메일 주소").fill("user@example.com")
page.get_by_label("비밀번호").fill("wrong-password")
page.get_by_role("button", name="로그인").click()
# 에러 메시지가 표시되는지 확인
expect(page.get_by_test_id("error-message")).to_be_visible()
expect(page.get_by_test_id("error-message")).to_contain_text("비밀번호가 올바르지 않습니다")
④ expect로 어서션 작성하기
Playwright의 expect는 웹 페이지 전용 어서션으로 조건이 충족될 때까지 자동으로 재시도합니다.
| 어서션 | 확인 내용 |
|---|---|
expect(locator).to_be_visible() | 요소가 표시되어 있다 |
expect(locator).to_be_hidden() | 요소가 숨겨져 있다 |
expect(locator).to_have_text("...") | 요소의 텍스트가 지정값과 일치한다 |
expect(locator).to_contain_text("...") | 요소의 텍스트에 지정 문자열이 포함된다 |
expect(locator).to_have_value("...") | input의 값이 지정값과 일치한다 |
expect(locator).to_be_enabled() | 요소가 유효 상태 (disabled가 아님) |
expect(locator).to_be_checked() | 체크박스가 ON |
expect(page).to_have_url("...") | 페이지 URL이 지정값과 일치한다 |
expect(page).to_have_title("...") | 페이지 타이틀이 지정값과 일치한다 |
⑤ conftest.py로 테스트 정리하기
베이스 URL이나 브라우저 설정은 conftest.py에 모아두면 테스트 파일을 간결하게 유지할 수 있습니다.
# conftest.py
import pytest
from playwright.sync_api import Page
BASE_URL = "https://example.com" # 샘플 URL
@pytest.fixture
def logged_in_page(page: Page) -> Page:
"""로그인된 상태로 page를 반환하는 fixture"""
page.goto(f"{BASE_URL}/login")
page.get_by_label("이메일 주소").fill("user@example.com")
page.get_by_label("비밀번호").fill("password123")
page.get_by_role("button", name="로그인").click()
page.wait_for_url(f"{BASE_URL}/dashboard")
return page
# test_dashboard.py
from playwright.sync_api import Page, expect
def test_dashboard_shows_username(logged_in_page: Page) -> None:
expect(logged_in_page.get_by_test_id("user-name")).to_contain_text("테스트 사용자")
pytest.ini로 브라우저·헤드리스 모드 설정하기
# pytest.ini
[pytest]
addopts =
--browser chromium
--headed
--headed는 로컬 개발·디버깅용입니다. GitHub Actions 등 CI 환경에서는 창을 표시할 수 없으므로 --headless(또는 addopts에서 --headed 제거)를 사용합시다. CI용과 로컬용으로 설정을 나누거나 환경 변수로 전환하는 것을 권장합니다.# 커맨드라인에서도 지정할 수 있다
pytest --browser chromium # Chromium 사용
pytest --browser firefox # Firefox 사용
pytest --headed # 브라우저 창 표시
pytest --headless # 헤드리스 모드 (CI용)
pytest --slowmo 500 # 조작을 500ms 느리게 해서 디버깅하기 쉽게
⑥ Codegen으로 테스트 코드 자동 생성하기
Codegen은 브라우저를 조작하면서 그 조작을 자동으로 테스트 코드로 변환하는 툴입니다. 로케이터 작성법을 모를 때의 출발점으로 사용할 수 있습니다.
# Codegen 실행 (URL은 테스트 대상 페이지에 맞게)
playwright codegen https://example.com # 샘플 URL
브라우저가 열리고 조작하면 옆 창에 코드가 생성됩니다.
Codegen은 로케이터 조사나 초기 코드 작성에 편리하지만, 그대로 커밋하면 장황한 코드가 되기 쉽습니다. 생성된 코드를 참고하면서
data-testid 기반 로케이터로 다시 작성하고, 테스트가 늘어나면 fixture화·POM화해서 정리하는 것을 권장합니다.⑦ 스크린샷과 Trace Viewer
스크린샷
# 페이지 전체 스크린샷
page.screenshot(path="screenshots/result.png", full_page=True)
# 특정 요소만 캡처
page.get_by_test_id("error-message").screenshot(path="screenshots/error.png")
Trace Viewer (테스트 실패 상세 확인)
Trace Viewer는 테스트 실행 과정을 단계별로 녹화할 수 있는 툴입니다. 테스트가 실패했을 때 “어떤 조작에서 어떤 상태였는지”를 시각적으로 확인할 수 있습니다.
# 테스트 실패 시 자동으로 트레이스 저장
pytest --tracing=retain-on-failure
# 트레이스를 열어서 확인
playwright show-trace trace.zip
pytest-playwright를 사용하면 --screenshot=only-on-failure로 실패 시에만 스크린샷 저장 등 CI 향 설정이 쉬워집니다.Playwright 입문에서 막히기 쉬운 포인트 7가지
⚠️ 자주 있는 함정
- playwright install을 잊었다
pip install playwright만으로는 동작하지 않습니다.playwright install로 브라우저 본체를 다운로드해야 합니다. 에러 메시지에 “Executable doesn’t exist”가 나오면 이것이 원인입니다. - 헤드리스 모드에서 동작하지 않는 것을 알아채지 못한다
기본값은 헤드리스(창 없음)입니다. 동작 확인 중에는--headed옵션을 붙여서 브라우저를 표시하면 문제를 발견하기 쉬워집니다. - sync와 async를 혼재시키고 있다
from playwright.sync_api import sync_playwright(동기)와from playwright.async_api import async_playwright(비동기)를 혼재시키면 에러가 납니다. pytest를 사용하는 경우 동기 API가 다루기 쉽습니다. - get_by_role의 name에 표시 텍스트가 일치하지 않는다
get_by_role("button", name="로그인")은 버튼의 표시 텍스트와 완전 일치 또는 ARIA 레이블로 검색합니다. 공백·줄 바꿈·대소문자 차이로 찾지 못하는 경우가 있습니다. Codegen으로 생성된 코드를 참고해서 확인합시다. - with 블록을 닫는 것을 잊어서 브라우저가 남는다
sync_playwright()를with블록으로 사용하지 않으면 브라우저 프로세스가 계속 남습니다. pytest-playwright를 사용하는 경우pagefixture가 자동으로 클린업해 줍니다. - CI에서 playwright install을 실행하지 않았다
GitHub Actions 등 CI 환경에서도playwright install(또는playwright install --with-deps)을 실행해야 합니다.--with-deps를 붙이면 시스템 의존 라이브러리도 함께 설치됩니다. - expect의 타임아웃이 너무 짧다
기본 타임아웃은 통상 5초(5000ms)이지만 프로젝트 설정에서 변경할 수 있습니다. CI 환경이 느린 경우 10초 이상으로 연장하는 케이스도 있습니다.expect(locator).to_be_visible(timeout=10000)(밀리초)로 테스트별로 개별 설정도 가능합니다.
자주 묻는 질문 (FAQ)
Playwright Test는 JavaScript/TypeScript용 Playwright 전용 테스트 러너로 @playwright/test 패키지로 제공됩니다. pytest-playwright는 Python의 pytest 프레임워크용 Playwright 플러그인입니다. Python으로 테스트 자동화를 할 경우 pytest-playwright를 사용합니다. 기능 면에서 Playwright Test 쪽이 Playwright와의 통합이 깊은 부분도 있지만, Python 에코시스템으로 통일하고 싶은 경우 pytest-playwright로 충분합니다.
기존 Selenium 테스트가 안정적으로 동작하고 있다면 무리하게 마이그레이션할 필요는 없습니다. 다만 새로운 테스트를 추가하는 경우 Playwright를 선택할 가치는 충분합니다. 자동 대기·Codegen·Trace Viewer가 표준 탑재되어 있어 Flaky Test가 줄어들기 쉬운 설계입니다. 실무에서는 Selenium을 기존 자산으로 남기면서 새로운 흐름부터 Playwright로 작성하기 시작하는 케이스도 자주 볼 수 있습니다.
네, Python만으로 사용할 수 있습니다. Playwright의 Python API는 Python 표준 작성법으로 동작하므로 JavaScript 지식은 불필요합니다. 다만 테스트 대상 웹 앱이 JavaScript로 만들어진 경우, JavaScript의 기본 개념(DOM·비동기 처리 등)을 알아두면 테스트 설계나 디버깅이 쉬워집니다.
네, 완전 무료 오픈소스입니다. Microsoft가 개발·메인테넌스하고 있으며 MIT 라이선스로 공개되어 있습니다. 상용 프로젝트에서도 무료로 이용할 수 있습니다.
Python을 사용한다면 Playwright가 유일한 선택입니다. Cypress는 JavaScript/TypeScript 전용이므로 Python 테스트에는 사용할 수 없습니다. JavaScript도 사용할 수 있는 경우 Playwright 쪽이 멀티 브라우저 대응·멀티 언어 대응으로 유연성이 높고, iFrame·새 탭·인증 플로우 등 복잡한 조작에도 대응하기 쉽습니다.
네. Playwright는 Python·JavaScript/TypeScript·Java·C#에 대응하고 있습니다. QA 엔지니어가 Python을 선택하는 것은 pytest와의 친화성과 Selenium에서 쌓은 노하우를 활용하기 쉽기 때문입니다.
필수는 아닙니다. sync_playwright()를 사용하면 pytest 없이도, 또는 pytest.fixture로 직접 관리할 수도 있습니다. 다만 pytest-playwright를 사용하면 page·browser·context의 fixture를 자동으로 사용할 수 있어 브라우저 설정·클린업을 생략할 수 있으므로 실무에서는 사용하는 것을 권장합니다.
pytest --browser chromium --browser firefox처럼 복수 지정할 수 있습니다. CI에서는 주요 브라우저를 모아서 실행할 수 있습니다. 다만 모든 브라우저에서 실행하면 테스트 시간이 2배 이상 걸리므로, 우선 Chromium으로 안정시키고 나서 추가하는 것을 권장합니다.
웹 요소의 상태 확인에는 Playwright의 expect를 사용합시다. expect는 내부에서 재시도를 반복하며 조건이 충족될 때까지 기다립니다. Python의 assert는 순간적으로 평가되므로 비동기적으로 변하는 페이지 상태 확인에 사용하면 Flaky Test가 되기 쉽습니다.
기본값으로는 load 이벤트(DOM과 일부 리소스 읽기 완료)까지 기다립니다. SPA(싱글 페이지 애플리케이션) 등 JavaScript로 렌더링되는 페이지에서는 page.goto() 후에 요소 표시 완료를 기다려야 합니다. wait_until="networkidle"이 유효한 케이스도 있지만, Playwright 공식에서는 과신을 권장하지 않으며 우선 expect()·page.wait_for_url()·locator.wait_for()를 사용한 대기를 우선하는 것을 권장합니다. WebSocket 등 영속 통신을 가진 SPA에서는 networkidle이 안정되지 않는 경우가 있습니다.
기본적으로는 불필요합니다. Playwright의 조작(click()·fill())은 요소가 조작 가능해질 때까지 자동으로 대기합니다. 어떻게 해도 대기가 필요한 경우 page.wait_for_selector()·page.wait_for_url()·expect를 사용합시다.
정리
| 단계 | 할 것 |
|---|---|
| ① 설치 | pip install playwright pytest-playwright → playwright install |
| ② 동작 확인 | sync_playwright()로 페이지를 열고 스크린샷 찍기 |
| ③ pytest 테스트 | page fixture를 사용해서 get_by_role·fill·click·expect |
| ④ 정리 | conftest.py에 공통 처리 집약. pytest.ini로 브라우저 설정 |
| ⑤ 디버깅 | Codegen으로 로케이터 확인, --tracing=retain-on-failure로 실패 시각화 |
Playwright는 자동 대기·Trace Viewer·Codegen이 표준 탑재되어 있어 Selenium보다 설정이 적고 처음 브라우저 자동화를 배우는 분에게도 임하기 쉬운 툴입니다.
우선 playwright codegen으로 어떤 코드가 생성되는지 시험해보는 것만으로도 전체 모습을 파악할 수 있습니다.
다음 단계로 테스트가 늘어나면 Page Object Model(POM)으로 코드를 정리하거나, GitHub Actions로 CI에 내장하는 방법으로 나아갑시다.
