TIL

Playwright Python: Tutorial #5 - API Testing

아람2 2025. 2. 28. 17:17
반응형

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'>

 

 

 

 

 

반응형