๐ ์ด๋ฐ ๋ถ๊ป ์ถ์ฒํฉ๋๋ค
- Playwright์ APIRequestContext๋ฅผ ์ฌ์ฉํ API ํ ์คํธ๋ฅผ ์์ํ๊ณ ์ถ์ ๋ถ
- ์ธ์ฆ ํ๋ก์ฐ๏ผํ ํฐ ์ทจ๋ยท์ ๋ฌยท๊ฑฐ๋ถ๏ผ์ ํ ์คํธ ๊ตฌํ์ ๊ด์ฌ ์๋ ๋ถ
- pytest์ fixture๋ฅผ ํ์ฉํ์ฌ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ถ์ ๋ถ
- REST API์ CRUD ์กฐ์๏ผPOSTยทPUTยทDELETE๏ผ์ ์๋ ํ ์คํธํ๊ณ ์ถ์ ๋ถ
โ ์ด ๊ธ์ ์ฝ์ผ๋ฉด ์ ์ ์๋ ๊ฒ
- Playwright์ APIRequestContext๋ก API ํ ์คํธ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ
- ์ธ์ฆ ํ ํฐ์ fixture๋ก ๊ด๋ฆฌํ์ฌ ํ ์คํธ ๊ฐ์ ๊ณต์ ํ๋ ํจํด
- GET / POST / PUT / DELETE ๊ฐ HTTP ๋ฉ์๋์ ํ ์คํธ ๊ตฌํ
- ์ธ์ฆ ์๋ ์ ๊ทผ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฑฐ๋ถํ๋ ๊ฒ์ ์๋ ํ ์คํธํ๋ ๋ฐฉ๋ฒ
API ํ ์คํธ๋ผ๊ณ ํ๋ฉด ใrequests ๋ผ์ด๋ธ๋ฌ๋ฆฌ + pytestใ์ ์กฐํฉ์ด ์ ๋ช ํ์ง๋ง, ์ฌ์ค Playwright์๋ APIRequestContext๋ผ๋ ๊ฐ๋ ฅํ API ํ ์คํธ ๊ธฐ๋ฅ์ด ํ์ฌ๋์ด ์์ต๋๋ค.
์ด ๊ธ์์๋ ํธํ ์์ฝ API ์ฐ์ต ์ฌ์ดํธ Restful Booker๋ฅผ ์ฌ์ฉํ์ฌ, ์ธ์ฆ ํ๋ก์ฐ๏ผ๋ก๊ทธ์ธโํ ํฐ ์ทจ๋โ์ธ์ฆ ํฌํจ ๋ฆฌํ์คํธ๏ผ๋ฅผ 6๊ฐ์ง ํ ์คํธ ์ผ์ด์ค๋ก ์๋ํํ๋ ๊ตฌํ ์์๋ฅผ ํด์คํฉ๋๋ค.
- ๋์ APIใปํ ์คํธ ๊ตฌ์ฑ
- ํ๊ฒฝ ๊ตฌ์ถ
- Playwright์ API ํ ์คํธ ๊ธฐ๋ณธ ํจํด
- fixture ์ค๊ณ๏ผํ ํฐ๊ณผ ์์ฝ ID๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค
- ํ
์คํธ ์ฝ๋ ์ ๋ฌธ
- TC01๏ผ์ ์ ๋ก๊ทธ์ธ โ ํ ํฐ ์ทจ๋
- TC02๏ผ๋น์ ์ ๋ก๊ทธ์ธ๏ผ์ค๋ฅ ๋น๋ฐ๋ฒํธ๏ผ
- TC03๏ผํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์์ฑ๏ผPOST๏ผ
- TC04๏ผํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์ ๋ฐ์ดํธ๏ผPUT๏ผ
- TC05๏ผํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์ญ์ ๏ผDELETE๏ผ
- TC06๏ผํ ํฐ ์์ด ๋ณดํธ ์๋ํฌ์ธํธ์ ์ ๊ทผ๏ผ๊ฑฐ๋ถ ํ์ธ๏ผ
- ์คํ ๋ฐฉ๋ฒ๊ณผ ๊ฒฐ๊ณผ
- ์ค๊ณ ํฌ์ธํธ๏ผ์ ์ด ๊ตฌ์กฐ๋ก ํ๋๊ฐ
- ์ ๋ฆฌ
๋์ APIใปํ ์คํธ ๊ตฌ์ฑ
์ฌ์ฉํ๋ API
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋์ ์ฌ์ดํธ | Restful Booker๏ผํธํ ์์ฝ API ์ฐ์ต ์ฌ์ดํธ๏ผ |
| BASE URL | restful-booker.herokuapp.com |
| ํ๋ ์์ํฌ | Playwright๏ผPython๏ผ+ pytest |
| ์ธ์ฆ ๋ฐฉ์ | ํ ํฐ ์ธ์ฆ๏ผCookie: token=xxx๏ผ |
6๊ฐ์ง ํ ์คํธ ์ผ์ด์ค
| TC | HTTP ๋ฉ์๋ | ๋ด์ฉ | ๊ธฐ๋ ์ํ |
|---|---|---|---|
| TC01 | POST /auth | ์ ์ ๋ก๊ทธ์ธ โ ํ ํฐ ์ทจ๋ | 200 |
| TC02 | POST /auth | ๋น์ ์ ๋ก๊ทธ์ธ๏ผ์ค๋ฅ ๋น๋ฐ๋ฒํธ๏ผโ ์๋ฌ ํ์ธ | 200 + reason |
| TC03 | POST /booking | ํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์์ฑ | 200 |
| TC04 | PUT /booking/{id} | ํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์ ๋ฐ์ดํธ | 200 |
| TC05 | DELETE /booking/{id} | ํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์ญ์ | 201 |
| TC06 | DELETE /booking/{id} | ํ ํฐ ์์ด ์ญ์ โ ๊ฑฐ๋ถ ํ์ธ | 403 |
ํ๊ฒฝ ๊ตฌ์ถ
ํ์ํ ํจํค์ง ์ค์น
# Playwright + pytest ์ค์น
pip install playwright pytest pytest-playwright pytest-html
# Playwright ๋ธ๋ผ์ฐ์ ์ค์น๏ผAPI ํ
์คํธ๋ง์ด๋ผ๋ฉด ์๋ต ๊ฐ๋ฅ๏ผ
playwright install
playwright install์ ์๋ตํ ์ ์์ง๋ง, ํฅํ E2E ํ
์คํธ์ ์กฐํฉํ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ์ฌ ์ค์นํด ๋๋ฉด ํธ๋ฆฌํฉ๋๋ค.
pytest.ini๏ผHTML ๋ฆฌํฌํธ๋ฅผ ์๋ ์์ฑํ๋ค
ํ๋ก์ ํธ ๋ฃจํธ์ pytest.ini๋ฅผ ๋๋ฉด, ํ
์คํธ ์คํ๋ง๋ค HTML ๋ฆฌํฌํธ๊ฐ ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค. ํ
์คํธ ๊ฒฐ๊ณผ๋ฅผ ๋ธ๋ผ์ฐ์ ์์ ํ์ธํ ์ ์๊ฒ ๋์ด, ์๋น๋์ค๋ก๋ ํ์ฉํ ์ ์์ต๋๋ค.
โผ ํด๋ ๊ตฌ์ฑ
project/
โโโ pytest.ini # โ ์ฌ๊ธฐ์ ๋๋ค
โโโ test_auth_flow.py
โโโ report.html # โ ์คํ ํ ์๋ ์์ฑ๋๋ค
โผ pytest.ini ๋ด์ฉ
[pytest]
addopts = --html=report.html --self-contained-html
โผ ๊ฐ ์ต์ ์ ์๋ฏธ
| ์ต์ | ์๋ฏธ |
|---|---|
--html=report.html |
report.html์ด๋ผ๋ ์ด๋ฆ์ผ๋ก HTML ๋ฆฌํฌํธ๋ฅผ ์์ฑํ๋ค |
--self-contained-html |
CSS๋ ์ด๋ฏธ์ง๋ฅผ HTML์ ๋ด์ฅ. ํ์ผ 1๊ฐ๋ก ์๊ฒฐ๋์ด ๊ณต์ ํ๊ธฐ ์ฝ๋ค |
# pytest.ini๊ฐ ์์ผ๋ฉด ์ถ๊ฐ ์ต์
๋ถํ์ โ ์๋์ผ๋ก HTML ๋ฆฌํฌํธ๊ฐ ์์ฑ๋๋ค
pytest test_auth_flow.py -v -s
--html=report.html์ ์
๋ ฅํ ํ์๊ฐ ์์ด์ง๋๋ค. ํ ์ ์ฒด์์ ์ค์ ์ด ํต์ผ๋ฉ๋๋ค.
report.html์ด ์ ๋๋ก ํ์๋์ง ์์ ๋์ ๋์ฒ๋ฒ
report.html์ ๋๋ธํด๋ฆญ์ผ๋ก ์ง์ ๋ธ๋ผ์ฐ์ ์์ ์ด๋ฉด ๋ณด์ ์ ํ๏ผCORS๏ผ์ผ๋ก ์คํ์ผ์ด ๋ฌด๋์ง๊ฑฐ๋ ๋ด์ฉ์ด ํ์๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ๊ทธ๋ฐ ๊ฒฝ์ฐ Python ๋ด์ฅ ์๋ฒ๋ฅผ ์ฌ์ฉํ์ฌ ๋ก์ปฌ์์ ํธ์คํ
ํ๋ ๋ฐฉ๋ฒ์ด ์ ํจํฉ๋๋ค.
โผ ์์
| STEP | ์กฐ์ ๋ด์ฉ |
| 1 | report.html์ด ์๋ ํด๋์์ ํฐ๋ฏธ๋์ ์ฐ๋ค |
| 2 | ์๋ ์ปค๋งจ๋๋ฅผ ์คํํ์ฌ ๋ก์ปฌ ์๋ฒ๋ฅผ ๊ธฐ๋ํ๋ค |
| 3 | ๋ธ๋ผ์ฐ์ ์์ localhost:8080/report.html์ ์ฐ๋ค๏ผURL ๋ฐ์ ์ง์ ์
๋ ฅ๏ผ |
# STEP 2๏ผ๋ก์ปฌ ์๋ฒ๋ฅผ ๊ธฐ๋๏ผreport.html์ด ์๋ ํด๋์์ ์คํ๏ผ
python -m http.server 8080
# ๊ธฐ๋ ํ ํฐ๋ฏธ๋์ ๋ค์ ๋ฉ์์ง๊ฐ ํ์๋๋ค
# Serving HTTP on 0.0.0.0 port 8080 ...
# STEP 3๏ผ๋ธ๋ผ์ฐ์ ์์ ์ด URL์ ์ฐ๋ค๏ผURL ๋ฐ์ ์ง์ ์
๋ ฅ๏ผ
localhost:8080/report.html
print()๋ก ์ถ๋ ฅํ ๋ด์ฉ๏ผ-s ์ต์
์ฌ์ฉ ์๏ผ
Ctrl + C๋ฅผ ๋๋ฅด์ธ์. python -m http.server๋ Python์ ํ์ค ํ์ฌ๋์ด ์์ด ์ถ๊ฐ ์ค์น๊ฐ ํ์ ์์ต๋๋ค.
Playwright์ API ํ ์คํธ ๊ธฐ๋ณธ ํจํด
์ค์ ์ฝ๋๋ฅผ ๋ณด๊ธฐ ์ ์, Playwright์ API ํ ์คํธ์์ ํ์ ํด ๋์ด์ผ ํ 4๊ฐ์ง ๊ธฐ๋ณธ ํจํด์ ์ ๋ฆฌํฉ๋๋ค.
โ APIRequestContext๏ผHTTP ํด๋ผ์ด์ธํธ ์์ฑ
Playwright์์ API ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ด๋ ค๋ฉด ๋จผ์ APIRequestContext๋ฅผ ์์ฑํฉ๋๋ค. base_url์ ์ง์ ํจ์ผ๋ก์จ ์ดํ์ ๋ฆฌํ์คํธ์์๋ ํจ์ค๋ง ์ฐ๋ฉด ๋ฉ๋๋ค.
# base_url์ ์ง์ ํ์ฌ APIRequestContext๋ฅผ ์์ฑ
request_context = playwright.request.new_context(
base_url="https://restful-booker.herokuapp.com"
)
# ์ดํ์๋ ํจ์ค๋ง์ผ๋ก ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ผ ์ ์๋ค
response = request_context.get("/booking")
โก ํ ํฐ ์ทจ๋๏ผ์ธ์ฆ ์๋ํฌ์ธํธ๋ก์ POST
์ธ์ฆ API์ POST ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ด๊ณ , ๋ฆฌ์คํฐ์ค์ JSON์์ ํ ํฐ์ ๊บผ๋ด๋ ๊ธฐ๋ณธ ํจํด์ ๋๋ค.
response = request_context.post(
"/auth",
data={"username": "admin", "password": "password123"}
)
# ๋ฆฌ์คํฐ์ค JSON์์ ํ ํฐ์ ์ทจ๋
token = response.json()["token"]
print(token) # โ "abc123xyz..."
โข ์ธ์ฆ ํค๋ ์ ๋ฌ ๋ฐฉ๋ฒ๏ผCookie ๋ฐฉ์ vs Bearer ๋ฐฉ์
์ทจ๋ํ ํ ํฐ์ API์ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ API์ ์ฌ์์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค. ์ด๋ฒ์ ์ฌ์ฉํ๋ Restful Booker๋ Cookie ๋ฐฉ์์ ๋๋ค. ์ผ๋ฐ์ ์ธ REST API์์๋ Bearer ๋ฐฉ์์ด ๋ง์ผ๋ฏ๋ก, ์์ชฝ ๋ค ์ตํ๋๋ฉด ๋ค๋ฅธ API์๋ ์์ฉํ ์ ์์ต๋๋ค.
| ๋ฐฉ์ | ํค๋ ์์ฑ๋ฒ | ์ฃผ์ ์ฉ๋ |
|---|---|---|
| Cookie ๋ฐฉ์ โ ์ด๋ฒ ์ฌ์ฉ | "Cookie": f"token={token}" |
Restful Booker ๋ฑ ์ผ๋ถ API |
| Bearer ๋ฐฉ์ | "Authorization": f"Bearer {token}" |
JWT ์ธ์ฆ ๋ฑ ์ผ๋ฐ์ ์ธ REST API |
# โ
Cookie ๋ฐฉ์๏ผ์ด๋ฒ Restful Booker์์ ์ฌ์ฉ๏ผ
headers = {"Cookie": f"token={token}"}
# Bearer ๋ฐฉ์๏ผ์ผ๋ฐ์ ์ธ REST API์์ ๋ง๋ค๏ผ
# headers = {"Authorization": f"Bearer {token}"}
403 Forbidden์ด ๋ฐํ๋ฉ๋๋ค.
โฃ API ํ ์คํธ ์คํ๏ผ๋ฆฌํ์คํธ ์ก์ ๊ณผ ๊ฒ์ฆ
ํค๋๋ฅผ ์กฐํฉํ์ผ๋ฉด ๋ฆฌํ์คํธ๋ฅผ ์ก์ ํ๊ณ , ์ํ ์ฝ๋์ ๋ฆฌ์คํฐ์ค ๋ฐ๋๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
# GET ๋ฆฌํ์คํธ๏ผ์ธ์ฆ ํค๋ ํฌํจ๏ผ
response = request_context.get(
"/booking/1",
headers={"Cookie": f"token={token}"}
)
# ์ํ ์ฝ๋ ๊ฒ์ฆ
assert response.status == 200, f"๊ธฐ๋๊ฐ 200์ ๋ํด {response.status}๊ฐ ๋ฐํ๋์ต๋๋ค"
# ๋ฆฌ์คํฐ์ค ๋ฐ๋ ๊ฒ์ฆ
body = response.json()
assert body["firstname"] == "Taro", "firstname์ด ๊ธฐ๋๊ฐ๊ณผ ๋ค๋ฆ
๋๋ค"
fixture ์ค๊ณ๏ผํ ํฐ๊ณผ ์์ฝ ID๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค
์ด ํ ์คํธ์์๋ pytest์ fixture๋ฅผ 3์ธต ๊ตฌ์กฐ๋ก ์ค๊ณํ๊ณ ์์ต๋๋ค. ์ธ์ฆ ํ ํฐ๊ณผ ์์ฝ ID๋ฅผ ์ธ์ ์ ์ฒด์์ ๊ณต์ ํจ์ผ๋ก์จ, ํ ์คํธ๋ง๋ค ๋ก๊ทธ์ธ์ ๋ค์ ํ๋ ๋ญ๋น๋ฅผ ์ค์ด๊ณ ์์ต๋๋ค.
โผ fixture ์์กด ๊ด๊ณ
|
api_request_context ์ธ์ ์ ์ฒด์์ 1๊ฐ |
โ |
auth_token ํ ๋ฒ ์ทจ๋ํ์ฌ ์ ์ฒด ๊ณต์ |
โ |
booking_id TC03์์ ์์ฑโTC04/05์์ ์ฌ์ฉ |
fixtureโ ๏ผAPIRequestContext
@pytest.fixture(scope="session")
def api_request_context(playwright: Playwright) -> APIRequestContext:
context = playwright.request.new_context(base_url=BASE_URL)
yield context
context.dispose() # ํ
์คํธ ์ข
๋ฃ ํ ์๋์ผ๋ก ๋ฆฌ์์ค๋ฅผ ํด๋ฐฉ
scope="session"์ ์ง์ ํจ์ผ๋ก์จ ํ
์คํธ ์ธ์
์ ์ฒด์์ 1๊ฐ์ ์ปจํ
์คํธ๋ฅผ ์ฌ์ฌ์ฉํฉ๋๋ค. ๋งค ํ
์คํธ๋ง๋ค ์ ์์ ๋ค์ ํ์ง ์์ ๊ณ ์์
๋๋ค.
fixtureโก๏ผ์ธ์ฆ ํ ํฐ
@pytest.fixture(scope="session")
def auth_token(api_request_context: APIRequestContext) -> str:
response = api_request_context.post(
"/auth",
data={"username": USERNAME, "password": PASSWORD},
)
assert response.status == 200, "ํ ํฐ ์ทจ๋์ ์คํจํ์ต๋๋ค"
token = response.json().get("token")
assert token, "๋ฆฌ์คํฐ์ค์ token์ด ํฌํจ๋์ด ์์ง ์์ต๋๋ค"
return token
fixtureโข๏ผํ ์คํธ์ฉ ์์ฝ ID
@pytest.fixture(scope="session")
def booking_id(api_request_context: APIRequestContext, auth_token: str) -> int:
response = api_request_context.post(
"/booking",
headers={"Content-Type": "application/json", "Accept": "application/json"},
data="""{
"firstname": "Taro",
"lastname": "Yamada",
"totalprice": 12000,
"depositpaid": true,
"bookingdates": {
"checkin": "2025-01-01",
"checkout": "2025-01-05"
},
"additionalneeds": "Breakfast"
}""",
)
assert response.status == 200
booking_id = response.json().get("bookingid")
assert booking_id, "์์ฝ ID๋ฅผ ์ทจ๋ํ ์ ์์์ต๋๋ค"
return booking_id
ํ ์คํธ ์ฝ๋ ์ ๋ฌธ
TC01๏ผ์ ์ ๋ก๊ทธ์ธ โ ํ ํฐ ์ทจ๋
def test_tc01_login_success(api_request_context: APIRequestContext):
"""TC01: ์ฌ๋ฐ๋ฅธ ์ธ์ฆ ์ ๋ณด๋ก ํ ํฐ์ด ์ทจ๋๋ ๊ฒ"""
response = api_request_context.post(
"/auth",
data={"username": USERNAME, "password": PASSWORD},
)
assert response.status == 200, f"์ํ ์ฝ๋๊ฐ 200์ด ์๋๋๋ค: {response.status}"
body = response.json()
assert "token" in body, "๋ฆฌ์คํฐ์ค์ token์ด ํฌํจ๋์ด ์์ง ์์ต๋๋ค"
assert len(body["token"]) > 0, "ํ ํฐ์ด ๋น์ด ์์ต๋๋ค"
print(f"\nโ
TC01 PASS | Token ์ทจ๋ ์ฑ๊ณต: {body['token']}")
token ํค๊ฐ ์กด์ฌ / ํ ํฐ์ด ๋น์ด์์ง ์์ ๊ฒ
TC02๏ผ๋น์ ์ ๋ก๊ทธ์ธ๏ผ์ค๋ฅ ๋น๋ฐ๋ฒํธ๏ผ
def test_tc02_login_failure(api_request_context: APIRequestContext):
"""TC02: ์๋ชป๋ ๋น๋ฐ๋ฒํธ๋ก ํ ํฐ์ด ๋ฐ๊ธ๋์ง ์์ ๊ฒ"""
response = api_request_context.post(
"/auth",
data={"username": USERNAME, "password": "wrongpassword"},
)
assert response.status == 200, f"์ํ ์ฝ๋๊ฐ 200์ด ์๋๋๋ค: {response.status}"
body = response.json()
assert "token" not in body, "์๋ชป๋ ๋น๋ฐ๋ฒํธ์ธ๋ฐ ํ ํฐ์ด ๋ฐํ๋์ต๋๋ค"
assert body.get("reason") == "Bad credentials", (
f"์๋ฌ ๋ฉ์์ง๊ฐ ๊ธฐ๋๊ฐ๊ณผ ๋ค๋ฆ
๋๋ค: {body.get('reason')}"
)
print(f"\nโ
TC02 PASS | ์ธ์ฆ ์คํจ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฐ์ง: {body}")
reason: "Bad credentials"์ผ๋ก ์๋ฌ๋ฅผ ํ์ ํฉ๋๋ค. ์ค์ API์์๋ ์ํ 401์ด ์ผ๋ฐ์ ์ด์ง๋ง, API์ ๋ฐ๋ผ ์ฌ์์ด ๋ค๋ฆ
๋๋ค.
TC03๏ผํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์์ฑ๏ผPOST๏ผ
def test_tc03_create_booking_with_token(
api_request_context: APIRequestContext, auth_token: str, booking_id: int
):
"""TC03: ์ธ์ฆ๋ ํ ํฐ์ผ๋ก ์์ฝ์ด ์์ฑ๋ ๊ฒ"""
assert booking_id > 0, "์์ฝ ID๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค"
print(f"\nโ
TC03 PASS | ์์ฝ ์์ฑ ์ฑ๊ณต (BookingID: {booking_id})")
booking_id fixture ์์์ ์ด๋ฃจ์ด์ง๋๋ค. TC03์ fixture๊ฐ ์ ์์ผ๋ก ์คํ๋์์ ํ์ธํ๋ ์ฌํํ ํ
์คํธ์
๋๋ค.
TC04๏ผํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์ ๋ฐ์ดํธ๏ผPUT๏ผ
def test_tc04_update_booking_with_token(
api_request_context: APIRequestContext, auth_token: str, booking_id: int
):
"""TC04: ์ธ์ฆ๋ ํ ํฐ์ผ๋ก ์์ฝ์ด ์
๋ฐ์ดํธ๋ ๊ฒ"""
response = api_request_context.put(
f"/booking/{booking_id}",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Cookie": f"token={auth_token}", # ํ ํฐ์ Cookie ํค๋๋ก ์ ๋ฌ
},
data="""{
"firstname": "Hanako",
"lastname": "Yamada",
"totalprice": 15000,
"depositpaid": false,
"bookingdates": {
"checkin": "2025-02-01",
"checkout": "2025-02-07"
},
"additionalneeds": "Dinner"
}""",
)
assert response.status == 200, f"์
๋ฐ์ดํธ์ ์คํจํ์ต๋๋ค: {response.status}"
body = response.json()
assert body.get("firstname") == "Hanako", "firstname์ด ์
๋ฐ์ดํธ๋์ง ์์์ต๋๋ค"
assert body.get("totalprice") == 15000, "totalprice๊ฐ ์
๋ฐ์ดํธ๋์ง ์์์ต๋๋ค"
print(f"\nโ
TC04 PASS | ์์ฝ ์
๋ฐ์ดํธ ์ฑ๊ณต: {body}")
TC05๏ผํ ํฐ์ ์ฌ์ฉํ์ฌ ์์ฝ ์ญ์ ๏ผDELETE๏ผ
def test_tc05_delete_booking_with_token(
api_request_context: APIRequestContext, auth_token: str, booking_id: int
):
"""TC05: ์ธ์ฆ๋ ํ ํฐ์ผ๋ก ์์ฝ์ด ์ญ์ ๋ ๊ฒ"""
response = api_request_context.delete(
f"/booking/{booking_id}",
headers={"Cookie": f"token={auth_token}"},
)
assert response.status == 201, f"์ญ์ ์ ์คํจํ์ต๋๋ค: {response.status}"
print(f"\nโ
TC05 PASS | ์์ฝ ์ญ์ ์ฑ๊ณต (BookingID: {booking_id})")
TC06๏ผํ ํฐ ์์ด ๋ณดํธ ์๋ํฌ์ธํธ์ ์ ๊ทผ๏ผ๊ฑฐ๋ถ ํ์ธ๏ผ
def test_tc06_delete_booking_without_token(
api_request_context: APIRequestContext, booking_id: int
):
"""TC06: ํ ํฐ ์์ด ์ญ์ ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ด๋ฉด ๊ฑฐ๋ถ๋ ๊ฒ"""
# TC05์์ ์ญ์ ์๋ฃ โ ๊ฐ์ ID๋ก ์ฌ์๋ํ์ฌ ์ธ์ฆ ์๋ฌ๋ฅผ ํ์ธ
response = api_request_context.delete(
f"/booking/{booking_id}",
headers={}, # Cookie ์์๏ผ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ๋ฌํ์ง ์์๏ผ
)
assert response.status == 403, (
f"ํ ํฐ ์์ด ์ญ์ ํ ์ ์์์ต๋๋ค: {response.status}"
)
print(f"\nโ
TC06 PASS | ๋ฏธ์ธ์ฆ ์ ๊ทผ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฑฐ๋ถ: status={response.status}")
์คํ ๋ฐฉ๋ฒ๊ณผ ๊ฒฐ๊ณผ
ํ ์คํธ ์คํ
# ํต์ ์คํ
pytest test_auth_flow.py -v
# print ์ถ๋ ฅ๋ ํ์ํ๋ ๊ฒฝ์ฐ
pytest test_auth_flow.py -v -s
์คํ ๊ฒฐ๊ณผ๏ผํฐ๋ฏธ๋๏ผ
์ค์ ๋ก ์คํํ ๊ฒฐ๊ณผ์ ๋๋ค. 6ํ ์คํธ ์ ๋ถ PASSED๏ผํฉ๊ณ 5.07์ด๋ก ์๋ฃ๋์ต๋๋ค.
โฒ ํฐ๋ฏธ๋์์์ ์คํ ๊ฒฐ๊ณผ โ TC01ใTC06 ์ ๋ถ PASSED
HTML ๋ฆฌํฌํธ๏ผreport.html๏ผ
pytest.ini์ ์ค์ ์ผ๋ก ์๋ ์์ฑ๋ report.html์ ๋ธ๋ผ์ฐ์ ์์ ์ฐ ํ๋ฉด์
๋๋ค.
โฒ report.html ํ์ ๊ฒฐ๊ณผ โ 0 Failed / 6 Passed / ํฉ๊ณ 5์ด
์ค๊ณ ํฌ์ธํธ๏ผ์ ์ด ๊ตฌ์กฐ๋ก ํ๋๊ฐ
๋งค ํ ์คํธ๋ง๋ค ๋ก๊ทธ์ธ์ ๋ค์ ํ์ง ์๊ณ , ์ธ์ ์์ 1๋ฒ๋ง ์ทจ๋. DRY ์์น์ ์ค์ฒ.
booking_id fixture๊ฐ auth_token์ ์์กดํ๋ ์ค๊ณ๋ก ์ ์ ์กฐ๊ฑด์ ์๋ ๋ณด์ฆ.
TC06์์ ใ๊ฑฐ๋ถ๋ ๊ฒใ์ ๊ฒ์ฆ. ์ ์๊ณ๋ฟ๋ง ์๋๋ผ ์ด์๊ณยท๋ณด์๋ ์๋ํ.
APIRequestContext๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ๊ธฐ๋ํ์ง ์์ 6ํ ์คํธ๊ฐ ์ฝ 5์ด์ ์๋ฃ.
๐ ๊ด๋ จ ๊ธฐ์ฌ
์ ๋ฆฌ
์ด ๊ธ์์๋ Playwright์ APIRequestContext์ pytest๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์ฆ ํ๋ก์ฐ๋ฅผ 6๊ฐ์ง ํ ์คํธ ์ผ์ด์ค๋ก ์๋ํํ๋ ๋ฐฉ๋ฒ์ ํด์คํ์ต๋๋ค.
๐ ์ด ๊ธ์ ์ ๋ฆฌ
- Playwright์ APIRequestContext๋ ๋ธ๋ผ์ฐ์ ๋ถํ์๋ก ๊ณ ์์ธ API ํ ์คํธ๋ฅผ ์์ฑํ ์ ์๋ค
- ์ธ์ฆ ํ ํฐ์
scope="session"์ fixture๋ก ๊ด๋ฆฌํจ์ผ๋ก์จ ์ ์ฒด ํ ์คํธ์์ ๊ณต์ ํ ์ ์๋ค - POSTยทPUTยทDELETE์ ๊ฐ HTTP ๋ฉ์๋์ ๊ตฌํ ํจํด์ ์ต๋ํ๋ค
- ์ด์๊ณ๏ผ์ค๋ฅ ๋น๋ฐ๋ฒํธ๏ผยท๋ณด์๏ผํ ํฐ ์์ ๊ฑฐ๋ถ๏ผ์ ํ ์คํธ๋ ์๋ํํ ์ ์๋ค
- API์ ๋ฐ๋ผ ์ํ ์ฝ๋์ ์ฌ์์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์, ๋ฐ๋์ API ๋ฌธ์๋ฅผ ํ์ธํ๋ค
Playwright๋ E2E ํ ์คํธ๋ฟ๋ง ์๋๋ผ API ํ ์คํธ์๋ ๋์ํ๊ณ ์์ต๋๋ค. E2E์ API ํ ์คํธ๋ฅผ ๊ฐ์ ํ๋ ์์ํฌ๋ก ํต์ผํ ์ ์๋ ๊ฒ๋ ํฐ ๋ฉ๋ฆฌํธ์ ๋๋ค.

