I’m migrating TypeScript Playwright tests to Python and noticed that three simple async tests that navigate to playwright.dev take ~50 seconds when using:
from playwright.async_api import async_playwright
def test_has_title():
with async_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context()
page = context.new_page()
page.goto("https://playwright.dev/")
print(page.title())
Even using sync_playwright with a page object reduces it to ~30 seconds. I tried using an async Page object with pytest-asyncio like this:
import pytest
from playwright.async_api import Page
@pytest.mark.asyncio
async def test_has_title(page: Page):
await page.goto("https://playwright.dev/")
print(await page.title())
but I can’t seem to share the page object efficiently among tests. How can I reuse the page object asynchronously to speed up test execution?
The main reason your async tests are slow is that each test creates a new browser and context, which is expensive. You can speed things up by sharing a single browser/page across tests using a pytest-asyncio fixture:
import pytest
from playwright.async_api import async_playwright, Page, Browser, BrowserContext
@pytest.fixture(scope="session")
async def browser_context():
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context()
yield context
await browser.close()
@pytest.fixture
async def page(browser_context: BrowserContext) -> Page:
page = await browser_context.new_page()
yield page
await page.close()
@pytest.mark.asyncio
async def test_has_title(page: Page):
await page.goto("https://playwright.dev/")
print(await page.title())
@pytest.mark.asyncio
async def test_another(page: Page):
await page.goto("https://playwright.dev/docs/intro")
print(await page.title())
This way, the browser and context are created once per session, and each test gets a fresh page. Tests become much faster.
If your tests don’t need a completely isolated page per test, you can reuse the same page instance:
@pytest.fixture(scope="session")
async def page():
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context()
page = await context.new_page()
yield page
await browser.close()
@pytest.mark.asyncio
async def test_one(page):
await page.goto("https://playwright.dev/")
print(await page.title())
@pytest.mark.asyncio
async def test_two(page):
await page.goto("https://playwright.dev/docs/intro")
print(await page.title())
This eliminates the overhead of opening a new page or context for each test, making tests much faster.
Another approach is to use an autouse fixture to automatically provide a shared page for all async tests:
@pytest.fixture(scope="session", autouse=True)
async def shared_page(request):
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context()
page = await context.new_page()
request.cls.page = page
yield
await browser.close()
@pytest.mark.asyncio
class TestPlaywright:
async def test_home(self):
await self.page.goto("https://playwright.dev/")
print(await self.page.title())
async def test_docs(self):
await self.page.goto("https://playwright.dev/docs/intro")
print(await self.page.title())
This pattern is handy when you have many tests in a class and want to reuse the same page instance without passing it explicitly to every test.