Chapter 18 · Async API and FastAPI Integration Testing
Chapter goals: Master Playwright's async API (
async_api), learn to write async E2E tests under pytest-asyncio, and combine browser tests with FastAPI apps into a complete end-to-end integration test plan.
18.1 Sync vs Async: Two Parallel APIs
Playwright Python is one of the few automation libraries offering two complete parallel APIs:
- Sync API:
from playwright.sync_api import sync_playwright— used throughout Chapters 1–17 of this course; - Async API:
from playwright.async_api import async_playwright— every method returns a coroutine, requiresawait.
Both share the exact same underlying driver (both communicate with the browser process via pipe); features are one-to-one mapped. The only selection criterion is: is your app and test stack sync or async?
# Sync version (what we've been using)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com")
print(page.title())
browser.close()# Async version: same flow
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto("https://example.com")
print(await page.title())
await browser.close()
asyncio.run(main())Naming convention
Async API class and method names are almost identical to the sync API; the differences are only three: entry point changes from sync_playwright() to async_playwright(); every I/O method gets await; assertions switch from playwright.sync_api.expect to playwright.async_api.expect (the async expect accepts coroutines and auto-waits).
18.2 async def Tests and pytest-asyncio
pytest itself does not support coroutine test functions — you need the pytest-asyncio plugin. And pytest-playwright's default page fixture is sync — for async scenarios use Playwright's built-in async fixture system:
pip install pytest-playwright pytest-asyncio# test_async_demo.py
import pytest
from playwright.async_api import Page, expect
@pytest.mark.asyncio
async def test_title_is_async(page: Page):
# pytest-playwright provides an async page fixture
# when the asyncio marker is present
await page.goto("https://playwright.dev/")
await expect(page).to_have_title("Fast and reliable end-to-end testing")Key points:
- Under async fixtures,
page.goto(),locator.click(), etc. all require await; expect(page)fromplaywright.async_api— itsto_have_titleand other assertions are also auto-retrying Web-First assertions;- If you forget await, you often get not an error but an unconsumed coroutine object — the operation never executes, and subsequent assertions fail with "element not found". This is the most common async E2E pitfall.
18.3 Async API Difference Checklist
Beyond "await everywhere", a few坑-prone differences:
| Scenario | Sync | Async |
|---|---|---|
| Wait for navigation | page.click("...") | await page.click("...") |
| Read text | page.locator("h1").inner_text() | await page.locator("h1").inner_text() |
| Screenshot | page.screenshot() | await page.screenshot() |
| Expect assertion | expect(loc).to_be_visible() | await expect(loc).to_be_visible() |
The most frequent mistake: mixing sync and async imports. Import expect from playwright.sync_api but call it in an async def — you'll get a coroutine that never runs. Always import from playwright.async_api in async tests.
18.4 Integrating with FastAPI
FastAPI applications are async-native (built on Starlette/uvicorn). Combining Playwright async API with a running FastAPI dev server gives a clean integration test setup:
# tests/test_fastapi_e2e.py
import pytest
from playwright.async_api import Page, expect
@pytest.mark.asyncio
async def test_checkout_flow(page: Page, fastapi_app_url: str):
await page.goto(f"{fastapi_app_url}/cart")
await page.get_by_role("button", name="Checkout").click()
await expect(page.get_by_text("Order confirmed")).to_be_visible()The fastapi_app_url fixture starts a test server (e.g., via uvicorn) before the test suite and tears it down after — covered in detail in the FastAPI course's testing chapter.
18.5 Chapter Summary
- Async API mirrors sync API exactly; difference is
awaiton every I/O method; - Use
pytest-asyncio+@pytest.mark.asynciofor async test functions; - Import
expectfromplaywright.async_apiin async tests — mixing imports is the #1坑; - Async Playwright + async FastAPI = natural integration test pairing.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. What happens if you forget await on page.goto() in an async test?
2. Where should you import expect from in an async test?
3. Why is Playwright async API a natural fit for FastAPI integration testing?
4. What plugin is required to run async def test functions in pytest?
🛠️ Hands-On Exercises
- Convert the Chapter 2 sync script to async style; run with
pytest --asyncio-mode=auto; verify both styles produce the same result. - Start a local FastAPI dev server; write an async E2E test that hits the app via Playwright async API and asserts the response page.
- Deliberately omit one
awaitin an async test; observe the "unconsumed coroutine" failure mode and fix it.
Proceed to Chapter 19: Debugging Techniques and Flaky Test Resolution.