Playwright Python: Tutorial #5 - API Testing
Playwright Python: Tutorial #5 - API Testing
Tutorial #1 https://helloahram.tistory.com/221
Tutorial #2 https://helloahram.tistory.com/222
Tutorial #3 https://helloahram.tistory.com/223
Tutorial # 4 https://helloahram.tistory.com/224
API Testing
Playwright 공식 문서 https://playwright.dev/python/docs/api-testing
API Testing 으로 Server API 를 시험하고 Server Side Post Condition 을 Validate 할 수 있다
APIRequestContext
Playwright 공식 문서 https://playwright.dev/python/docs/api/class-apirequestcontext
각 Playwright Browser Context 는 APIRequestContext 와 연관되어 있으며, 이를 통해 API 요청을 보낼 수 있다
Browser Context 는 실제 브라우저 세션을 의미하며, 페이지를 렌더링하고 상호작용하는 환경을 제공한다
APIRequestContext 는 브라우저 없이 API 요청을 처리할 수 있도록 도와주는 컨텍스트이다
browser_context.request 또는 page.request 를 통해 기존의 APIRequestContext 를 호출할 수 있으며,
이 때 API 요청은 Browser Context 에서 관리되는 쿠키 값을 공유하게 된다 (동일한 쿠키 저장소 공유)
즉, 브라우저에서 로그인한 상태를 API 요청에 반영할 수 있다
api_request.new_context() 를 호출하면 새로운 APIRequestContext 를 생성할 수 있다
이 때 생성되는 새로운 APIRequestContext 는 별도의 격리된 쿠키 저장소를 사용하게 된다
즉, 해당 객체는 별도의 격리된 쿠키 저장소를 사용하므로 기존 브라우저의 쿠키에 영향을 주지 않는다
이 방법은 테스트 간 쿠키나 상태의 격리가 필요할 때 유용하게 사용된다
API 요청에서 반환된 APIRequestContext 는 Browser Context 와 쿠키 저장소를 공유하므로
로그인 상태나 세션 정보가 API Test 에서 자동으로 반영된다
브라우저에서 수행된 요청은 자동으로 해당 쿠키 값을 포함하여 전송되고 ,
API 응답에 Set-Cookie 헤더가 포함되면, 이 쿠키가 자동으로 Browser Context 에 Update 되어
후속 페이지 요청에서 사용할 수 있다, 이를 통해 API 와 브라우저 간 상태를 동기화하고
로그인 후 페이지에서 API 호출을 계속 사용할 수 있다
이를 통해 API 를 호출하고 해당 API 의 로그인 상태나 데이터를 사용할 수 있으며
이와 반대로 로그인 후에 페이지에서 요청을 수행하는 테스트도 가능하다
api_request.new_context() 를 사용하여 테스트 간에 쿠키나 상태의 격리를 필요로 하는 경우 유용하게 활용할 수 있다
ex. 하나의 테스트에서 로그인 상태를 유지하면서 다른 테스트는 로그인 상태와 별개로 진행 가능
3줄 요약하면,
1. Browser Context 는 브라우저 세션을 관리하고, APIRequestContext 는 브라우저 없이 API 요청을 처리한다
2. browser_context.request 나 page.request 를 통해 API 요청 시 쿠키가 공유되며,
api_request.new_context() 로 격리된 상태를 만들 수 있다
3. API 요청과 브라우저 간 상태가 동기화되어 로그인 상태를 API 와 페이지에서 모두 사용할 수 있으며, 테스트 간 상태 격리가 가능하다
# https://youtu.be/2eNKNx8Va5s?si=OiDr41Ub6aehQHs7
# https://github.com/JoanEsquivel/playwright-python-test-framework/blob/master/api/pytest/api-testing.py
# ----- Documentation -----
# - Run this test using: pytest todo-app-test.py
# - APIRequestContext -> https://playwright.dev/python/docs/api/class-apirequestcontext#api-request-context-post
# - Python F Strings -> https://realpython.com/python-f-strings/https://realpython.com/python-f-strings/
# - Pytest Fixtures -> https://playwright.dev/python/docs/test-runners#fixtures
# - Python Function Annotations -> https://peps.python.org/pep-3107/
# - Typing: https://docs.python.org/3/library/typing.html
# -------------------------
# This module provides runtime support for type hints
from typing import Generator
# This module imports pytest
import pytest
# This module import instancs and modules required by playwright
from playwright.sync_api import Playwright, Page, APIRequestContext, expect
# In testing, a fixture provides a defined, reliable and conststent context for the tests
# We may configure "FUCTIONS" scope & "SESSION" scope
@pytest.fixture(scope="session")
def api_request_context(
playwright: Playwright,
# class typing.Generator
# It means that you need to declare types of variables, parameters, and return values_
# of a function upfront. The predefined types allow the compilers to check the code before
# compiling and running the program
) -> Generator[APIRequestContext, None, None]:
request_context = playwright.request.new_context(
base_url="http://localhost:3000"
)
yield request_context
# This method discards all stored responses
request_context.dispose()
def test_post_doto(api_request_context: APIRequestContext) -> None:
# https://www.w3schools.com/python/python_dictionaries.asp
data = {
"completed" : False,
"title" : "Helloahram",
"id" : "180107",
}
new_todo = api_request_context.post(
f"/todos", data=data
)
assert new_todo.ok
todos_response = new_todo.json()
# In order to check the logs, add -s as part of the command
print("")
print(f"todo Var: {new_todo}")
print(f"todo_response Var: {todos_response}")
코드 설명
api_request_context Fixture:
@pytest.fixture(scope="session") 데코레이터를 사용하여 이 함수가 세션 범위로 실행되도록 설정한다
테스트 세션 전체에서 한 번만 실행되고, 그 결과는 모든 테스트에서 사용할 수 있도록 한다
Generator[APIRequestContext, None, None] 는 함수가 Generator 타입을 반환한다고 명시하는 것으로,
yield 를 사용하여 request_context 를 반환하고 테스트 후에 dispose() 메서드를 호출하여 리소스를 정리한다
test_post_doto 테스트 함수:
api_request_context 를 매개변수로 받아, 해당 객체를 사용하여 HTTP POST 요청을 보낸다
요청 본문(data)에는 completed, title, id와 같은 데이터를 포함시켜 todos API에 새로운 항목을 추가한다
assert new_todo.ok 는 응답이 정상적으로 왔는지 확인한다
테스트 후에 응답 데이터를 todos_response 변수에 저장하고, print 를 통해 요청과 응답을 출력한다
pytest 로 코드를 돌리면, 내가 추가한 항목이 todos Database 에 잘 쌓이는 것을 확인할 수 있다
Youtube 에서는 pytest 만 돌려도 print todos Var 가 잘 출력되는데 나는 -s 를 해야 출력된다, 뭔 차이지,..
pytest -s 11_API_testing_test.py
========================= test session starts =========================
platform darwin -- Python 3.11.11, pytest-8.3.4, pluggy-1.5.0
baseurl: https://www.saucedemo.com
rootdir: /Users/ahram/Desktop/Playwright
configfile: pytest.ini
plugins: playwright-0.7.0, base-url-2.1.0
collected 1 item
08_API_testing_test.py
todo Var: <APIResponse url='http://localhost:3000/todos' status=201 status_text='Created'>
todo_response Var: {'completed': False, 'title': 'Helloahram', 'id': '180107'}
.
========================== 1 passed in 0.33s ==========================
Async API Testing
# https://youtu.be/2eNKNx8Va5s?si=jZA1xlA3PfBxmJF9
#Extracted from: https://playwright.dev/python/docs/api/class-apirequestcontext#api-request-context-get
import asyncio
from playwright.async_api import async_playwright, Playwright
async def run(playwright: Playwright):
# This will launch a new browser, create a context and page. When making HTTP
# requests with the internal APIRequestContext (e.g. `context.request` or `page.request`)
# it will automatically set the cookies to the browser page and vise versa.
# browser = await playwright.chromium.launch()
# context = await browser.new_context(base_url="https://api.github.com")
# api_request_context = context.request
# page = await context.new_page()
# Alternatively you can create a APIRequestContext manually without having a browser context attached:
api_request_context = await playwright.request.new_context(base_url="http://localhost:3000")
data = {
"completed": False,
"title": "HelloJS",
"id": "221015",
}
# Create a repository.
response = await api_request_context.post(
"/todos",
data=data,
)
assert response.ok
print(f"todo Var: {response}")
async def main():
async with async_playwright() as playwright:
await run(playwright)
asyncio.run(main())
코드 설명
playwright.request.new_context() 를 사용하여 API 요청 컨텍스트 APIRequestContext 를 생성한다
요청을 보낼 때 사용할 base_url 을 지정하며, 여기서는 http://localhost:3000 이 설정했다
data 변수는 요청에 포함할 JSON 데이터를 정의한다
api_request_context.post() 로 /todos 엔드포인트에 POST 요청을 보낸다
assert response.ok 는 응답이 성공적인지 확인하고, 응답이 정상적으로 돌아오면, 응답을 출력한다
main() 에서는 async_playwright()를 사용하여 Playwright의 실행을 비동기적으로 시작하고,
run() 함수를 호출하여 실제 API 요청을 처리한다
asyncio.run(main()) 에서 Python 의 asyncio.run() 을 통해 비동기 함수를 실행하여 전체 비동기 작업이 실행되도록 한다
실행 결과
python 12_API_async_test.py
todo Var: <APIResponse url='http://localhost:3000/todos' status=201 status_text='Created'>