Playwright + Python์œผ๋กœ ์ธ์ฆ ํ”Œ๋กœ์šฐ API ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๏ฝœํ† ํฐ ์ทจ๋“ใƒปCRUDใƒป๋ณด์•ˆ ๊ฒ€์ฆ

๐Ÿ“Œ ์ด๋Ÿฐ ๋ถ„๊ป˜ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค

  • Playwright์˜ APIRequestContext๋ฅผ ์‚ฌ์šฉํ•œ API ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • ์ธ์ฆ ํ”Œ๋กœ์šฐ๏ผˆํ† ํฐ ์ทจ๋“ยท์ „๋‹ฌยท๊ฑฐ๋ถ€๏ผ‰์˜ ํ…Œ์ŠคํŠธ ๊ตฌํ˜„์— ๊ด€์‹ฌ ์žˆ๋Š” ๋ถ„
  • pytest์˜ fixture๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ์€ ๋ถ„
  • REST API์˜ CRUD ์กฐ์ž‘๏ผˆPOSTยทPUTยทDELETE๏ผ‰์„ ์ž๋™ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์€ ๋ถ„

โœ… ์ด ๊ธ€์„ ์ฝ์œผ๋ฉด ์•Œ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • Playwright์˜ APIRequestContext๋กœ API ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ์ธ์ฆ ํ† ํฐ์„ fixture๋กœ ๊ด€๋ฆฌํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๊ฐ„์— ๊ณต์œ ํ•˜๋Š” ํŒจํ„ด
  • GET / POST / PUT / DELETE ๊ฐ HTTP ๋ฉ”์„œ๋“œ์˜ ํ…Œ์ŠคํŠธ ๊ตฌํ˜„
  • ์ธ์ฆ ์—†๋Š” ์ ‘๊ทผ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฑฐ๋ถ€ํ•˜๋Š” ๊ฒƒ์„ ์ž๋™ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ‘ค

๊ธ€์“ด์ด ์†Œ๊ฐœ๏ผšQA ์—”์ง€๋‹ˆ์–ด๋กœ์„œ PlaywrightยทPythonยทpytest๋ฅผ ์‚ฌ์šฉํ•œ API ํ…Œ์ŠคํŠธ ์ž๋™ํ™”๋ฅผ ์‹ค๋ฌด์—์„œ ๋‹ด๋‹น. ์ด ๊ธ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋ชจ๋‘ GitHub์—์„œ ๊ณต๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. GitHub์—์„œ ์ฝ”๋“œ ๋ณด๊ธฐ โ†’

API ํ…Œ์ŠคํŠธ๋ผ๊ณ  ํ•˜๋ฉด ใ€Œrequests ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ + pytestใ€์˜ ์กฐํ•ฉ์ด ์œ ๋ช…ํ•˜์ง€๋งŒ, ์‚ฌ์‹ค Playwright์—๋„ APIRequestContext๋ผ๋Š” ๊ฐ•๋ ฅํ•œ API ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ์ด ํƒ‘์žฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ํ˜ธํ…” ์˜ˆ์•ฝ API ์—ฐ์Šต ์‚ฌ์ดํŠธ Restful Booker๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ์ธ์ฆ ํ”Œ๋กœ์šฐ๏ผˆ๋กœ๊ทธ์ธโ†’ํ† ํฐ ์ทจ๋“โ†’์ธ์ฆ ํฌํ•จ ๋ฆฌํ€˜์ŠคํŠธ๏ผ‰๋ฅผ 6๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋กœ ์ž๋™ํ™”ํ•˜๋Š” ๊ตฌํ˜„ ์˜ˆ์‹œ๋ฅผ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.


  1. ๋Œ€์ƒ APIใƒปํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ
    1. ์‚ฌ์šฉํ•˜๋Š” API
    2. 6๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค
  2. ํ™˜๊ฒฝ ๊ตฌ์ถ•
    1. ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜
    2. pytest.ini๏ผšHTML ๋ฆฌํฌํŠธ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•œ๋‹ค
    3. report.html์ด ์ œ๋Œ€๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š์„ ๋•Œ์˜ ๋Œ€์ฒ˜๋ฒ•
  3. Playwright์˜ API ํ…Œ์ŠคํŠธ ๊ธฐ๋ณธ ํŒจํ„ด
    1. โ‘  APIRequestContext๏ผšHTTP ํด๋ผ์ด์–ธํŠธ ์ž‘์„ฑ
    2. โ‘ก ํ† ํฐ ์ทจ๋“๏ผš์ธ์ฆ ์—”๋“œํฌ์ธํŠธ๋กœ์˜ POST
    3. โ‘ข ์ธ์ฆ ํ—ค๋” ์ „๋‹ฌ ๋ฐฉ๋ฒ•๏ผšCookie ๋ฐฉ์‹ vs Bearer ๋ฐฉ์‹
    4. โ‘ฃ API ํ…Œ์ŠคํŠธ ์‹คํ–‰๏ผš๋ฆฌํ€˜์ŠคํŠธ ์†ก์‹ ๊ณผ ๊ฒ€์ฆ
  4. fixture ์„ค๊ณ„๏ผšํ† ํฐ๊ณผ ์˜ˆ์•ฝ ID๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค
    1. fixtureโ‘ ๏ผšAPIRequestContext
    2. fixtureโ‘ก๏ผš์ธ์ฆ ํ† ํฐ
    3. fixtureโ‘ข๏ผšํ…Œ์ŠคํŠธ์šฉ ์˜ˆ์•ฝ ID
  5. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ „๋ฌธ
    1. TC01๏ผš์ •์ƒ ๋กœ๊ทธ์ธ โ†’ ํ† ํฐ ์ทจ๋“
    2. TC02๏ผš๋น„์ •์ƒ ๋กœ๊ทธ์ธ๏ผˆ์˜ค๋ฅ˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๏ผ‰
    3. TC03๏ผšํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์•ฝ ์ƒ์„ฑ๏ผˆPOST๏ผ‰
    4. TC04๏ผšํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์•ฝ ์—…๋ฐ์ดํŠธ๏ผˆPUT๏ผ‰
    5. TC05๏ผšํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์•ฝ ์‚ญ์ œ๏ผˆDELETE๏ผ‰
    6. TC06๏ผšํ† ํฐ ์—†์ด ๋ณดํ˜ธ ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผ๏ผˆ๊ฑฐ๋ถ€ ํ™•์ธ๏ผ‰
  6. ์‹คํ–‰ ๋ฐฉ๋ฒ•๊ณผ ๊ฒฐ๊ณผ
    1. ํ…Œ์ŠคํŠธ ์‹คํ–‰
    2. ์‹คํ–‰ ๊ฒฐ๊ณผ๏ผˆํ„ฐ๋ฏธ๋„๏ผ‰
    3. HTML ๋ฆฌํฌํŠธ๏ผˆreport.html๏ผ‰
  7. ์„ค๊ณ„ ํฌ์ธํŠธ๏ผš์™œ ์ด ๊ตฌ์กฐ๋กœ ํ–ˆ๋Š”๊ฐ€
  8. ์ •๋ฆฌ

๋Œ€์ƒ 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์˜ APIRequestContext๋Š” ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ธฐ๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. API ํ…Œ์ŠคํŠธ๋งŒ์ด๋ผ๋ฉด 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
โœ… ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๏ผš ํ…Œ์ŠคํŠธ์˜ PASS / FAIL ์ผ๋žŒยท์‹คํ–‰ ์‹œ๊ฐ„ยท์—๋Ÿฌ ์ƒ์„ธยท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}"}
๐Ÿ’ก ํฌ์ธํŠธ๏ผš ํ…Œ์ŠคํŠธํ•˜๋Š” API์˜ ๋ฌธ์„œ์—์„œ ใ€Œ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ํ† ํฐ์„ ์ „๋‹ฌํ•˜๋Š”๊ฐ€ใ€๋ฅผ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ฉ์‹œ๋‹ค. ๋ฐฉ์‹์„ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด 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์ด ๊ธฐ๋Œ€๊ฐ’๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค"
๐Ÿ’ก ์‹ค๋ฌด Tip๏ผš ์ด 4๊ฐ€์ง€ ํŒจํ„ด๏ผˆContext ์ž‘์„ฑโ†’ํ† ํฐ ์ทจ๋“โ†’ํ—ค๋” ์„ค์ •โ†’๋ฆฌํ€˜์ŠคํŠธ๏ผ†๊ฒ€์ฆ๏ผ‰์ด ๋ชธ์— ๋ฐฐ๋ฉด ์–ด๋–ค REST API์˜ ํ…Œ์ŠคํŠธ์—๋„ ์‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ดํ›„์˜ ์ฝ”๋“œ๋Š” ๋ชจ๋‘ ์ด ํŒจํ„ด์˜ ์กฐํ•ฉ์ž…๋‹ˆ๋‹ค.

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
๐Ÿ’ก ์ธ์ฆ ํ† ํฐ์€ ์„ธ์…˜์—์„œ 1ํšŒ๋งŒ ์ทจ๋“ํ•˜์—ฌ TC03ใ€œTC06 ์ „์ฒด์—์„œ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด DRY ์›์น™์˜ ์‹ค์ฒœ์ž…๋‹ˆ๋‹ค.

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
๐Ÿ’ก ์˜ˆ์•ฝ ID๋Š” ์„ธ์…˜ ์‹œ์ž‘ ์‹œ์— 1ํšŒ๋งŒ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. TC03๏ผˆ์ž‘์„ฑ ํ™•์ธ๏ผ‰โ†’TC04๏ผˆ์—…๋ฐ์ดํŠธ๏ผ‰โ†’TC05๏ผˆ์‚ญ์ œ๏ผ‰โ†’TC06๏ผˆ์‚ญ์ œ ํ›„ ์ธ์ฆ ์—๋Ÿฌ ํ™•์ธ๏ผ‰์˜ ํ๋ฆ„์œผ๋กœ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ „๋ฌธ

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']}")
โœ… ๊ฒ€์ฆ ํฌ์ธํŠธ๏ผš ์ƒํƒœ 200 / ๋ฆฌ์Šคํฐ์Šค์— 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}")
โš ๏ธ ์ฃผ์˜ ํฌ์ธํŠธ๏ผš ์ด API๋Š” ์ธ์ฆ ์‹คํŒจ ์‹œ์—๋„ ์ƒํƒœ 200์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค๏ผˆ์„ค๊ณ„ ์‚ฌ์–‘๏ผ‰. ๋Œ€์‹  ๋ฆฌ์Šคํฐ์Šค ๋ฐ”๋””์˜ 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}")
โœ… ๊ฒ€์ฆ ํฌ์ธํŠธ๏ผš ์ƒํƒœ 200 / firstname์ด “Hanako”๋กœ ์—…๋ฐ์ดํŠธ / totalprice๊ฐ€ 15000์œผ๋กœ ์—…๋ฐ์ดํŠธ

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})")
โš ๏ธ ์ฃผ์˜ ํฌ์ธํŠธ๏ผš Restful Booker์˜ DELETE๋Š” ์„ฑ๊ณต ์‹œ์— ์ƒํƒœ 201์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ REST ์„ค๊ณ„์—์„œ๋Š” 200 ๋˜๋Š” 204๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด API์˜ ์‚ฌ์–‘์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธํ•˜๋Š” API์˜ ์‚ฌ์–‘์„œ๋ฅผ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ฉ์‹œ๋‹ค.

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}")
โœ… ๊ฒ€์ฆ ํฌ์ธํŠธ๏ผš ์ธ์ฆ ์—†๋Š” ์ ‘๊ทผ์— ๋Œ€ํ•ด ์ƒํƒœ 403์ด ๋ฐ˜ํ™˜๋  ๊ฒƒ. ๋ณด์•ˆ ํ…Œ์ŠคํŠธ์˜ ๊ธฐ๋ณธ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

์‹คํ–‰ ๋ฐฉ๋ฒ•๊ณผ ๊ฒฐ๊ณผ

ํ…Œ์ŠคํŠธ ์‹คํ–‰

# ํ†ต์ƒ ์‹คํ–‰
pytest test_auth_flow.py -v

# print ์ถœ๋ ฅ๋„ ํ‘œ์‹œํ•˜๋Š” ๊ฒฝ์šฐ
pytest test_auth_flow.py -v -s

์‹คํ–‰ ๊ฒฐ๊ณผ๏ผˆํ„ฐ๋ฏธ๋„๏ผ‰

์‹ค์ œ๋กœ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. 6ํ…Œ์ŠคํŠธ ์ „๋ถ€ PASSED๏ผŒํ•ฉ๊ณ„ 5.07์ดˆ๋กœ ์™„๋ฃŒ๋์Šต๋‹ˆ๋‹ค.

pytest ์‹คํ–‰ ๊ฒฐ๊ณผ ํ„ฐ๋ฏธ๋„ - 6 passed in 5.07s

โ–ฒ ํ„ฐ๋ฏธ๋„์—์„œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” TC01ใ€œTC06 ์ „๋ถ€ PASSED

HTML ๋ฆฌํฌํŠธ๏ผˆreport.html๏ผ‰

pytest.ini์˜ ์„ค์ •์œผ๋กœ ์ž๋™ ์ƒ์„ฑ๋œ report.html์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ฐ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

report.html - 6 Passed

โ–ฒ report.html ํ‘œ์‹œ ๊ฒฐ๊ณผ โ€” 0 Failed / 6 Passed / ํ•ฉ๊ณ„ 5์ดˆ


์„ค๊ณ„ ํฌ์ธํŠธ๏ผš์™œ ์ด ๊ตฌ์กฐ๋กœ ํ–ˆ๋Š”๊ฐ€

๐Ÿ”‘
fixture๋กœ ํ† ํฐ์„ ๊ณต์œ 

๋งค ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ๋กœ๊ทธ์ธ์„ ๋‹ค์‹œ ํ•˜์ง€ ์•Š๊ณ , ์„ธ์…˜์—์„œ 1๋ฒˆ๋งŒ ์ทจ๋“. DRY ์›์น™์˜ ์‹ค์ฒœ.

๐Ÿ—
fixture ์˜์กด ๊ด€๊ณ„์˜ ํ™œ์šฉ

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 ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ํ†ต์ผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋„ ํฐ ๋ฉ”๋ฆฌํŠธ์ž…๋‹ˆ๋‹ค.

ใ‚ฟใ‚คใƒˆใƒซใจURLใ‚’ใ‚ณใƒ”ใƒผใ—ใพใ—ใŸ