테스트 자동화 코드를 열심히 만들었습니다.
그런데 디자이너가 로그인 버튼의 ID를 바꾸거나, CSS 선택자를 수정했습니다.
그러자 로그인과 관련된 수십 개의 테스트 코드가 한 번에 모두 실패합니다.
이제 우리는 모든 테스트 파일을 열어, 하나하나 선택자 코드를 수정해야 하는 ‘유지보수의 악몽’에 빠집니다.
이런 문제를 해결하기 위해 탄생한 것이 바로 ‘페이지 객체 모델(Page Object Model, POM)’입니다.

Q. ‘페이지 객체 모델(POM)’, 정확히 무엇인가요?
‘페이지 객체 모델’은 웹사이트의 각 페이지를 하나의 독립된 ‘객체(클래스)’로 바라보는 테스트 자동화 디자인 패턴입니다.
해당 페이지의 UI 요소(버튼, 입력창 등)와, 그 요소들과 관련된 모든 동작(클릭, 글자 입력 등)을 페이지 객체 안에 모두 정의하고 캡슐화합니다.
- 핵심 아이디어:
- ‘무엇을 테스트할 것인가(테스트 로직)’와 ‘어떻게 조작할 것인가(UI 상호작용)’를 명확하게 분리하는 것입니다.
Q. 왜 ‘페이지 객체 모델(POM)’을 사용해야 하나요?
가장 큰 이유는 ‘유지보수성’을 획기적으로 높여주기 때문입니다.
- 중복 코드 제거:
- 여러 테스트 케이스에서 반복적으로 사용되는 로그인 동작 코드를, ‘LoginPage’라는 객체 안에 단 한 번만 정의하고 계속 재사용할 수 있습니다.
- 쉬운 수정:
- 로그인 버튼의 선택자(selector)가 변경되었을 때, 수십 개의 테스트 코드를 일일이 수정할 필요가 없습니다.
- 오직 ‘LoginPage’ 객체 안의 해당 선택자 정의 ‘한 줄’만 수정하면, 이 객체를 사용하는 모든 테스트 코드에 자동으로 반영됩니다.
- 가독성 향상:
- 테스트 코드 자체가 훨씬 간결하고 명확해집니다.
- 복잡한 선택자 코드가 사라지고,
login_page.login()처럼 사람이 읽기 쉬운 코드로 테스트의 흐름을 표현할 수 있습니다.
Q. ‘페이지 객체 모델(POM)’을 적용하지 않은 코드(나쁜 예시)는 어떤 모습인가요?
테스트 코드 안에 페이지의 세부적인 선택자와 동작 코드가 그대로 노출되어 섞여 있습니다.
Python
# tests/test_login.py (POM 미적용)
def test_successful_login(page):
# 페이지의 세부 정보(선택자)가 테스트 코드에 섞여 있음
page.locator("#username").fill("myuser")
page.locator("#password").fill("mypass")
page.locator("button.login-button").click()
# 검증 로직
assert page.locator(".welcome-message").is_visible()
def test_failed_login_with_wrong_password(page):
# 로그인 동작 코드가 그대로 중복됨
page.locator("#username").fill("myuser")
page.locator("#password").fill("wrongpass")
page.locator("button.login-button").click()
# 검증 로직
assert page.locator(".error-message").is_visible()
이 코드의 가장 큰 문제는, 만약 로그인 버튼의 선택자 button.login-button이 다른 것으로 바뀌면, 관련된 모든 테스트 함수를 찾아가서 직접 수정해야 한다는 점입니다.
Q. ‘페이지 객체 모델(POM)’을 적용한 코드(좋은 예시)는 어떤 모습인가요?
‘페이지를 조작하는 코드’와 ‘테스트를 진행하는 코드’를 별도의 파일로 명확하게 분리합니다.
1. 페이지 객체 파일 (페이지 조작 담당)
Python
# pages/login_page.py
class LoginPage:
def __init__(self, page):
self.page = page
# 선택자를 이 파일 한 곳에서만 관리
self.username_input = page.locator("#username")
self.password_input = page.locator("#password")
self.login_button = page.locator("button.login-button")
# 로그인과 관련된 동작을 메서드로 정의
def login(self, username, password):
self.username_input.fill(username)
self.password_input.fill(password)
self.login_button.click()
2. 테스트 파일 (테스트 로직 담당)
Python
# tests/test_login.py (POM 적용)
from pages.login_page import LoginPage
def test_successful_login(page):
login_page = LoginPage(page)
# 테스트 코드는 '무엇'을 하는지만 간결하게 표현
login_page.login("myuser", "mypass")
# 검증 로직
assert page.locator(".welcome-message").is_visible()
def test_failed_login(page):
login_page = LoginPage(page)
login_page.login("myuser", "wrongpass")
# 검증 로직
assert page.locator(".error-message").is_visible()
이제 로그인 버튼의 선택자가 바뀌면, 우리는 pages/login_page.py 파일의 단 한 줄만 수정하면 됩니다. 수십, 수백 개의 테스트 파일은 전혀 건드릴 필요가 없습니다. 이것이 바로 ‘페이지 객체 모델’의 강력한 힘입니다.
결론: 유지보수하기 좋은 코드가 진짜 자동화다
‘페이지 객체 모델(POM)’은 테스트 자동화 프로젝트를 막 시작했을 때는 조금 번거롭게 느껴질 수 있습니다.
하지만 프로젝트가 커지고 UI 변경이 잦아지는 유지보수 단계에 접어들면, ‘페이지 객체 모델’은 테스트 코드의 수명을 연장하고 관리 비용을 획기적으로 줄여주는 필수적인 디자인 패턴입니다.
성공적인 ‘테스트 자동화’는 단순히 동작하는 코드를 짜는 것이 아니라, 유지보수하기 좋은 ‘깨끗한’ 코드를 짜는 것에서 시작됩니다.