Python으로 API 테스트를 작성할 때 「requests와 Playwright API, 어느 쪽을 써야 할까?」라고 고민하는 QA 엔지니어가 많다. requests는 심플하고 학습 비용이 낮으며, Playwright API는 E2E 테스트와의 통합에 강하다. 이 글에서는 15년 이상의 QA 실무 경험을 바탕으로, 같은 API 테스트를 두 도구로 작성해 비교하면서 실무 목선에서 사용 구분의 판단 기준을 해설한다.
requests는 「API 테스트 전용 경량 도구」, Playwright API는 「E2E+API를 하나의 코드베이스로 관리하는 도구」 — 목적이 다르기 때문에 어느 쪽이 더 낫다는 이야기가 아니다.
📌 이런 분께 추천합니다
- Python으로 API 테스트를 시작하고 싶은데 어떤 도구를 선택해야 할지 모르는 분
- requests로 API 테스트를 작성 중이고 Playwright API로 이전을 검토 중인 분
- E2E 테스트에 Playwright를 사용하고 있으며 API 테스트도 통합하고 싶은 분
- 두 도구의 코드 작성 방식 차이를 실제로 보고 비교하고 싶은 분
✅ 이 글을 읽으면
- requests와 Playwright API의 차이를 비교표로 정리할 수 있다
- 같은 API 테스트를 두 도구로 작성한 코드를 보고 차이를 체감할 수 있다
- 「어느 쪽을 선택해야 할지」의 판단 기준과 체크리스트를 얻을 수 있다
👤 이 글을 쓴 사람
QA 엔지니어·테스트 자동화 엔지니어로 15년 이상의 실무 경험 보유. requests는 API 테스트 초기부터, Playwright API는 릴리즈 초기부터 실무에서 사용. 두 도구 모두 실제 프로젝트에서 운용해온 경험을 바탕으로 쓴다.
⚡ 고민된다면 이것으로 결정
| API 테스트만 필요하다 | → requests를 선택 |
| E2E 테스트도 Playwright로 작성 중이다 | → Playwright API로 통합 |
| 우선 학습 비용을 줄이고 싶다 | → requests부터 시작 |
이 글의 목표는 「어느 쪽이 더 낫다」를 결론내리는 것이 아니라, 「내 팀·운용에 어느 쪽이 맞는가」를 실무 베이스로 정리하는 것이다.
playwright.request.new_context()로 이용하는 APIRequestContext 기능을 가리킨다. Playwright Test(Node.js 버전)그 자체가 아닌, Python 버전 Playwright가 제공하는 API Testing 기능이다.📌 이 글의 결론
- API 테스트만이라면 requests + pytest가 가장 심플하고 합리적
- E2E 테스트에 Playwright를 사용 중이라면 API 테스트도 Playwright API로 통합하는 가치가 높다
- 두 도구는 경쟁 관계가 아니라 용도에 따라 구분해서 사용하는 것이 정답
requests vs Playwright API|기본 비교
| 항목 | 📦 requests | 🎭 Playwright API |
|---|---|---|
| 용도 | HTTP 클라이언트 / API 테스트 | E2E + API 테스트 통합 |
| 학습 비용 | ◎ 매우 낮음 | △ API 단독이라면 도입 가능, E2E 통합까지 사용하면 학습 범위가 넓어짐 |
| 코드량 | ◎ 적고 읽기 쉬움 | ○ 다소 많음 |
| E2E와의 통합 | ✕ 불가 | ◎ 동일 코드로 통합 가능 |
| CI/CD 친화성 | ◎ pytest 경유로 간단 | ○ pytest 단독으로도 이용 가능. fixture 활용 시 pytest-playwright가 편리 |
| 네트워크 가로채기·모킹 | △ 별도 라이브러리 필요 | ◎ E2E 테스트에서는 page.route()로 브라우저 통신 가로채기·모킹을 표준 기능으로 구현 가능 |
| 설치 무게 | ◎ 경량(pip 한 줄) | △ 일반적인 Playwright 설정에서는 브라우저 바이너리 설치를 수반하여 환경 구축이 무거워지기 쉬움 |
| 브라우저 불필요 | ◎ 완전 불필요 | ○ API 테스트만이라면 브라우저 불필요로 실행 가능 |
| 비동기 대응 | △ requests 자체는 동기 전용(비동기는 httpx 등이 필요) | ○ async API를 정식 제공(QA 현장에서는 sync 채용이 비교적 많다) |
| 접속 재이용·Cookie 공유 | ◎ requests.Session()으로 간단하게 실현 | ○ APIRequestContext로 Cookie·헤더 공유 가능(실무에서는 fixture화 추천) |
| Python API 테스트 실적 | ◎ pytest + requests 구성이 아직도 QA 팀에서 널리 채택됨 | △ 빠르게 증가 중 |
httpx를 채택하는 팀도 늘고 있다. 2026년 현재 QA 현장에서는 requests + pytest 구성이 아직도 널리 채택되고 있지만, 비동기 API 테스트가 필요한 경우 httpx + pytest-asyncio도 유력한 선택지다.코드로 보는 차이|같은 테스트를 두 도구로 비교
① GET 요청·기본 어설션
# ✅ requests + pytest
import requests
def test_유저_목록을_가져올_수_있다():
response = requests.get(
"https://jsonplaceholder.typicode.com/users",
timeout=(3, 10)
)
assert response.status_code == 200
users = response.json()
assert len(users) > 0
assert "name" in users[0]# 🎭 Playwright API + pytest
# ※ playwright fixture를 사용하는 경우 pytest-playwright 플러그인이 필요
from playwright.sync_api import Playwright
def test_유저_목록을_가져올_수_있다(playwright: Playwright):
api = playwright.request.new_context(
base_url="https://jsonplaceholder.typicode.com"
)
response = api.get(
"/users",
timeout=10_000 # ms 지정(10초)
# Playwright에는 기본 30초 타임아웃이 있음. CI 안정화를 위해 명시 지정 권장
)
assert response.status == 200
users = response.json()
assert len(users) > 0
assert "name" in users[0]
# 샘플에서는 간략화를 위해 직접 dispose. 실무에서는 fixture화하여 공통 관리
api.dispose()requests.get(url)라고만 쓰면 timeout이 기본 무제한이 된다. 네트워크 장애 시 CI가 수십 분 행하는 위험이 있으므로, 실무에서는 반드시 timeout=(접속초, 읽기초)를 명시하자.단순한 GET이라면
requests 쪽이 코드가 짧고 심플하다. 실무 팁으로, requests에서는 response.raise_for_status()를 사용해 4xx/5xx를 예외화할 수 있다. Playwright API에는 동등한 메서드가 없으므로 통상 assert response.status == 200으로 명시적으로 검증한다. 두 방법 모두 실무에서 널리 사용된다.response.json()을 직접 호출하면 JSONDecodeError가 발생할 수 있다. 본번 테스트에서는 Content-Type 확인이나 실패 시 로그 출력 등을 검토하자.② POST 요청·JSON 바디 전송
# ✅ requests + pytest
import requests
def test_게시글을_생성할_수_있다():
payload = {
"title": "자동화 테스트 게시글",
"body": "Playwright API와 requests 비교",
"userId": 1
}
response = requests.post(
"https://jsonplaceholder.typicode.com/posts",
json=payload,
timeout=(3, 10)
)
assert response.status_code == 201
data = response.json()
assert data["title"] == payload["title"]
assert "id" in data# 🎭 Playwright API + pytest
def test_게시글을_생성할_수_있다(playwright: Playwright):
api = playwright.request.new_context(
base_url="https://jsonplaceholder.typicode.com"
)
payload = {
"title": "자동화 테스트 게시글",
"body": "Playwright API와 requests 비교",
"userId": 1
}
response = api.post(
"/posts",
json=payload,
timeout=10_000 # ms 지정(10초). 기본 30초 있음, 실무에서는 명시 지정 권장
)
assert response.status == 201
data = response.json()
assert data["title"] == payload["title"]
assert "id" in data
# 샘플에서는 간략화를 위해 직접 dispose. 실무에서는 fixture화하여 공통 관리
api.dispose()requests는
json=payload, Playwright API도 json=payload를 사용하면 Content-Type이 자동으로 application/json으로 설정된다. 두 도구 모두 JSON 전송에는 json=을 사용하는 것이 안전하고 권장되는 방법이다.③ Cookie 인증 요청
# ✅ requests + pytest(Cookie 인증)
import requests
import pytest
BASE_URL = "https://restful-booker.herokuapp.com"
@pytest.fixture(scope="session")
def auth_token() -> str:
response = requests.post(
f"{BASE_URL}/auth",
json={"username": "admin", "password": "password123"},
timeout=(3, 10)
)
response.raise_for_status() # HTTP 에러를 즉시 감지
data = response.json()
assert "token" in data, "인증 응답에 token이 없음"
return data["token"]
def test_예약을_삭제할_수_있다(auth_token: str):
headers = {"Cookie": f"token={auth_token}"}
response = requests.delete(
f"{BASE_URL}/booking/1",
headers=headers,
timeout=(3, 10)
)
assert response.status_code in (200, 201)# 🎭 Playwright API + pytest(Cookie 인증)
import pytest
from playwright.sync_api import Playwright, APIRequestContext
@pytest.fixture(scope="session")
def api_context(playwright: Playwright) -> APIRequestContext:
# 실무에서는 conftest.py에서 fixture화하여 전체 테스트에서 재사용
api = playwright.request.new_context(
base_url="https://restful-booker.herokuapp.com"
)
yield api
api.dispose()
@pytest.fixture(scope="session")
def auth_token(api_context: APIRequestContext) -> str:
response = api_context.post(
"/auth",
json={"username": "admin", "password": "password123"},
timeout=10_000 # ms 지정(10초)
)
assert response.status == 200
data = response.json()
assert "token" in data, "인증 응답에 token이 없음"
return data["token"]
def test_예약을_삭제할_수_있다(api_context: APIRequestContext, auth_token: str):
response = api_context.delete(
"/booking/1",
headers={"Cookie": f"token={auth_token}"},
timeout=10_000
)
assert response.status in (200, 201)두 도구 모두
scope="session"으로 인증 토큰을 재사용할 수 있다. Playwright API는 api_context fixture를 session 스코프로 공유함으로써 Cookie·헤더·접속을 재이용할 수 있다. 이는 requests.Session()에 가까운 운용 이미지로, 대규모 테스트에서는 fixture 공유에 의한 관리 메리트가 크다. 단, requests.Session()으로도 동일한 접속 최적화가 가능하다.④ Playwright API의 진가|E2E+API 통합 테스트
# 🎭 Playwright:E2E 테스트와 API 테스트를 하나의 테스트로 통합
from playwright.sync_api import Page, expect
def test_유저_등록_후_API로도_확인할_수_있다(page: Page, playwright):
# ① UI로 유저 등록(E2E 테스트)
page.goto("https://example.com/register")
page.fill("#email", "test@example.com")
page.fill("#password", "SecurePass123")
page.click("#register-btn")
expect(page.get_by_text("등록 완료")).to_be_visible()
# ② API로 유저가 생성됐는지 확인(API 테스트)
api = playwright.request.new_context(
base_url="https://example.com"
)
response = api.get("/api/users?email=test@example.com")
assert response.status == 200
users = response.json()
assert len(users) == 1
assert users[0]["email"] == "test@example.com"
api.dispose()
# requests 단독으로는 위의 UI 조작을 다룰 수 없으므로
# 이런 E2E+API 통합은 Playwright와의 조합이 자연스럽다🔑 Playwright API의 큰 강점
「UI로 조작한 결과를 API로 검증하는」 통합 테스트를 동일 fixture·동일 코드베이스에서 자연스럽게 통합하기 쉬운 것이 Playwright API의 강점이다. requests도 Selenium 등과 조합하면 실현은 가능하지만 구성이 분리되기 쉬워진다. E2E와 API를 일원화하고 싶을 때 Playwright API는 매우 합리적인 선택이다.
Playwright에서 UI 요소의 대기 포함 어설션에는
expect(locator)를 사용하고, API 응답의 확인에는 통상의 assert를 사용하는 구성이 실무에서 일반적이다. expect()는 Auto-wait가 작동하므로 요소 표시를 기다리면서 검증할 수 있다. API의 response.status나 response.json()에는 assert로 충분하다.requests의 강점과 약점
| ✅ 강점 | ⚠️ 약점 |
|---|---|
|
|
Playwright API의 강점과 약점
| ✅ 강점 | ⚠️ 약점 |
|---|---|
|
|
어느 쪽을 선택할까|판단 흐름
▶ E2E 테스트에 Playwright를 사용하고 있나요? |
YES ↓ | NO ↓ |
✅ Playwright API로 통합 추천 | ▶ API 테스트만이 대상인가요? |
YES ↓ | NO ↓ |
✅ requests 추천 | ⚡ 양쪽 병용 검토 |
이전 판단 체크리스트|requests → Playwright API
| 체크 항목 | 점수 |
|---|---|
| E2E 테스트에 Playwright를 이미 사용하고 있다 | YES → +3점 |
| 「UI로 조작한 결과를 API로 검증하는」 통합 테스트를 작성하고 싶다 | YES → +3점 |
| 네트워크 가로채기·API 모킹을 테스트에 사용하고 싶다 | YES → +2점 |
| 테스트 레포트를 E2E와 API에서 통일하고 싶다(Allure 등) | YES → +1점 |
| 팀이 Playwright를 이미 습득했다 | YES → +1점 |
| API 테스트만이고 E2E 통합 예정이 없다 | YES → −2점 |
🔑 판단 기준
- 4점 이상:Playwright API로의 이전을 적극 검토해야 한다
- 2~3점:새 테스트는 Playwright API로 작성하고 기존 requests는 유지하는 부분 이전을 검토
- 1점 이하:requests + pytest를 계속하는 것이 가장 합리적
💡 실무에서 자주 보이는 병용 패턴
「전부 Playwright API로 이전」이 아니라, 「심플한 API 테스트는 requests, E2E 통합이 필요한 것은 Playwright API」라는 병용이 유지보수 비용과 운용 효율의 균형이 좋다. 두 도구는 경쟁 관계가 아니라 상호 보완 관계다.
⚠️ Playwright API가 맞지 않는 케이스
- 대규모 병렬 API 부하 테스트:Playwright API는 부하 테스트 도구가 아니다. 수백~수천 요청의 병렬 전송에는 Locust·k6 등의 전용 도구가 합리적이다
- asyncio 기반의 비동기 API 테스트:동기 버전 Playwright가 주류이므로, async/await 전제 설계에는
httpx + pytest-asyncio가 더 자연스럽다 - E2E를 사용하지 않는 순수 API 테스트 전담 팀:Playwright 전체의 도입 비용이 맞지 않는 경우가 있다. requests로 충분하다면 무리하게 이전할 필요는 없다
- CI 환경에서 설치 용량을 최소화하고 싶은 경우:Playwright는 브라우저 바이너리를 수반하므로, 경량 API 테스트용 Docker 이미지에서는 requests가 유리하다
실무 추천 구성|Session·fixture·conftest.py
requests.Session()을 사용해야 할 때
import pytest
import requests
BASE_URL = "https://restful-booker.herokuapp.com"
@pytest.fixture(scope="session")
def auth_session() -> requests.Session:
session = requests.Session()
# 공통 헤더를 한 번에 설정
session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
})
# 인증하여 Cookie 취득
response = session.post(
f"{BASE_URL}/auth",
json={"username": "admin", "password": "password123"},
timeout=(3, 10)
)
response.raise_for_status() # HTTP 에러를 즉시 감지
data = response.json()
assert "token" in data, "인증 응답에 token이 없음"
token = data["token"]
session.cookies.set("token", token)
yield session # yield로 후처리가 실행되도록
session.close() # 테스트 종료 후 세션을 확실하게 해제
def test_세션으로_예약_목록_취득(auth_session: requests.Session):
response = auth_session.get(f"{BASE_URL}/booking", timeout=(3, 10))
assert response.status_code == 200
assert len(response.json()) > 0conftest.py를 사용한 추천 디렉터리 구성
tests/
├── conftest.py # 공통 fixture(auth_session, api_context 등)
├── api/
│ ├── test_booking.py # requests 기반 API 테스트
│ └── test_auth.py
└── e2e/
├── test_login.py # Playwright 기반 E2E 테스트
└── test_booking_ui.pyconftest.py에 auth_session(requests용)과 api_context(Playwright용)의 양쪽 fixture를 정의해두면 테스트 파일 측은 import 없이 사용할 수 있다. E2E에서만 쓸 수 있는 부분은 Playwright, 심플한 API 확인은 requests로 구분하는 구성이 유지보수 비용과 운용 효율의 균형이 좋다.📖 관련 글
FAQ|자주 묻는 질문
Q. pytest-playwright가 없어도 Playwright API를 사용할 수 있나요?
사용할 수 있다. sync_playwright()를 사용하면 플러그인 없이 API 테스트를 작성할 수 있다. 다음이 최소 구성 예다.
# pytest-playwright를 사용하지 않는 최소 구성
from playwright.sync_api import sync_playwright
def test_유저_목록_취득():
with sync_playwright() as p:
api = p.request.new_context(
base_url="https://jsonplaceholder.typicode.com"
)
response = api.get("/users", timeout=10_000)
assert response.status == 200
api.dispose()playwright fixture를 fixture 인수로 사용하는 경우(본 글의 메인 코드 예)에는 pytest-playwright가 있으면 편리하지만 필수는 아니다.
Q. api.dispose()를 하지 않으면 어떻게 되나요?
dispose()를 호출하지 않으면 APIRequestContext가 보유한 네트워크 접속과 Cookie가 해제되지 않는다. 테스트 1건 정도라면 문제가 되기 어렵지만, 대량의 테스트를 실행할 경우 리소스 리크의 원인이 될 수 있다. 따라서 실무에서는 fixture화하여 yield 후의 teardown에서 dispose()를 호출하는 설계가 권장된다. try/finally로 감싸면 assert 실패 시에도 확실하게 해제할 수 있다.
Q. requests.Session()은 사용해야 하나요?
복수의 API 요청을 묶어서 보내는 경우에는 적극적으로 사용해야 한다. requests.Session()은 접속을 재이용하고 Cookie·인증 헤더를 자동으로 이어받으므로, 로그인 후 복수의 API를 호출하는 시나리오에서 특히 편리하다. scope="session"의 pytest fixture와 조합하면 테스트 스위트 전체에서 1회 인증으로 재사용할 수 있다.
Q. requests와 Playwright API를 동시에 사용해도 되나요?
물론 문제없다. 「심플한 REST API 확인은 requests, E2E와 조합하는 부분은 Playwright API」처럼 하나의 프로젝트 안에서 병용하는 팀도 많다. 어느 한쪽으로 통일할 필요는 없으며, 테스트의 목적에 따라 사용 구분하는 것이 실무적인 정답이다.
Q. requestsのtimeout은 어떻게 설정해야 하나요?
timeout=(3, 10)의 튜플 형식이 권장된다. 처음 3초가 접속 타임아웃, 10초가 읽기 타임아웃을 의미한다. 타임아웃 없이 테스트를 실행하면 네트워크 장애 시에 테스트가 무한히 대기하는 위험이 있다. timeout=None은 본번 테스트에서는 사용하지 않도록 하자.
Q. httpx는 왜 비교 대상에 포함하지 않나요?
httpx는 requests와 호환성이 높은 API를 가지면서 비동기(asyncio)에도 대응한 모던한 HTTP 클라이언트다. 2026년 현재 FastAPI나 asyncio 기반 프로젝트와의 친화성이 높아 주목받고 있다. 이 글에서는 가장 널리 사용되는 requests와의 비교에 집중했지만, 비동기 API 테스트가 필요한 경우 httpx + pytest-asyncio 조합도 유력한 선택지다.
Q. requests는 앞으로 사용되지 않게 될까요?
그렇지 않다. requests는 PyPI 주간 다운로드 수에서도 상위권을 유지하고 있으며 개발·유지보수도 계속되고 있다. httpx의 보급이 진행되고 있는 것은 사실이지만, requests의 동기 API 테스트에 특화된 심플함은 앞으로도 강점으로 남는다. 「requests에서 반드시 이전해야 하는」 상황은 기본적으로 없다.
Q. Playwright API만으로 백엔드 API 테스트를 완결할 수 있나요?
기술적으로는 가능하다. GET/POST/PUT/DELETE·인증·JSON 어설션까지 일통으로 다룰 수 있다. 하지만 순수한 API 테스트 전용이라면 requests 쪽이 경량으로 코드도 심플해진다. Playwright API가 진가를 발휘하는 것은 E2E와 API를 동일 코드베이스로 통합할 때다. 팀이 E2E에 Playwright를 사용하지 않는데 API 테스트만을 위해 Playwright를 도입하는 것은 과잉 투자가 되기 쉽다.
Q. Python API 테스트의 학습 로드맵은 어떻게 하면 좋을까요?
Python QA 엔지니어로서 가장 실무적인 순서는 「requests + pytest(기초)→ Playwright API(E2E 통합)」다. requests로 HTTP의 기초(GET/POST/인증/어설션)를 익힌 후 Playwright API로 이동하면, new_context()의 의미나 dispose의 필요성을 체감 수준으로 이해할 수 있다.
마치며
📋 이 글의 정리
- API 테스트만이라면 requests + pytest가 가장 심플하고 학습 비용이 낮다
- E2E 테스트에 Playwright를 사용하고 있다면 Playwright API에 통합하는 가치가 높다
- 「UI로 조작한 결과를 API로 검증하는」 통합 테스트를 동일 코드베이스에서 자연스럽게 통합하기 쉬운 것은 Playwright API의 큰 강점이다
- 두 도구는 경쟁 관계가 아니라 목적에 따라 구분해서·병용하는 것이 정답
- 학습 순서는 「requests → Playwright API」가 체감에서의 이해로 이어진다
「어느 쪽이 더 낫다」가 아닌 「내 팀에 무엇이 필요한가」로 선택하는 것이 중요하다. 고민될 때는 이 글의 체크리스트와 판단 흐름을 참고해주시기 바란다.

