Python Playwright Web Automation Guide

Written by

in

Looking to super‑charge your web testing or data‑scraping projects with a modern, reliable tool? Python Playwright offers a fast, cross‑browser automation library that works with Chromium, Firefox, and WebKit—all from a clean, Pythonic API. In this guide we’ll walk through everything you need to know to get started, write robust scripts, and integrate Playwright into larger test suites. Whether you’re a beginner or an experienced QA engineer, you’ll find practical examples, best‑practice tips, and SEO‑friendly keywords to help your content rank higher in search results.

What Is Playwright and Why Choose It for Python?

Playwright was created by the same team behind Microsoft Edge and quickly became a favorite for developers who need:

  • Cross‑browser support: One script runs on Chromium, Firefox, and WebKit.
  • Auto‑waiting: Built‑in smart waits eliminate flaky tests.
  • Rich selector engine: CSS, text, XPath, and custom selectors.
  • Headless and headed modes: Perfect for CI pipelines or interactive debugging.

Compared to Selenium, Playwright offers faster execution, better handling of modern web features (like Shadow DOM), and a more intuitive async API—making it a top choice for Python developers.

Installing Playwright for Python

Step‑by‑step installation

pip install playwright
# Install the browsers (Chromium, Firefox, WebKit)
python -m playwright install

For projects that use asyncio, you’ll also want the async version of the library:

pip install playwright[async]

If you prefer a virtual environment (highly recommended), set it up before installing:

python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install --upgrade pip

Writing Your First Playwright Script

Synchronous example

from playwright.sync_api import sync_playwright

def run():
    with sync_playwright() as p:
        # Launch Chromium in headless mode
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        # Navigate to a website
        page.goto("https://example.com")
        # Capture a screenshot
        page.screenshot(path="example.png")
        # Extract the page title
        print("Page title:", page.title())
        browser.close()

if __name__ == "__main__":
    run()

Asynchronous version

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.firefox.launch(headless=False)
        page = await browser.new_page()
        await page.goto("https://news.ycombinator.com")
        await page.screenshot(path="hn.png")
        print("Title:", await page.title())
        await browser.close()

asyncio.run(main())

Both snippets demonstrate the core workflow: launch → new_page → goto → actions → close. The auto‑waiting feature means you rarely need explicit time.sleep() calls.

Understanding Selectors in Playwright

Playwright’s selector engine is flexible. Here are the most common patterns:

  • page.click("button#submit") – CSS selector.
  • page.fill("input[name='q']", "Playwright") – Fill a text field.
  • page.click("text=Login") – Text selector (matches visible text).
  • page.locator("xpath=//div[@class='price']") – XPath.
  • page.locator("data-test-id=nav-item") – Custom attribute selector.

When dealing with Shadow DOM or iframes, use the shadow and frame helpers:

# Click a button inside a shadow root
page.locator("css=custom-element").shadow_root().click("button")
# Switch to an iframe
frame = page.frame(name="payment-frame")
await frame.fill("input#card-number", "4242424242424242")

Smart Waiting and Assertions

One of Playwright’s biggest advantages is its built‑in waiting mechanism. Instead of manually adding delays, you can rely on:

  • page.wait_for_selector() – Waits until an element appears.
  • page.wait_for_load_state() – Waits for load, domcontentloaded, or networkidle.
  • expect(page).to_have_url() – Assertion that automatically waits.
from playwright.sync_api import expect

page.goto("https://example.com/login")
page.fill("input#email", "user@example.com")
page.click("button#next")
# Wait until the password field is visible
page.wait_for_selector("input#password")
page.fill("input#password", "Secret123")
page.click("button#login")
# Verify successful navigation
expect(page).to_have_url("**/dashboard")

These helpers dramatically reduce flaky test failures caused by timing issues.

Capturing Screenshots, PDFs, and Video

Visual artifacts are essential for debugging and reporting. Playwright supports:

  • Screenshots – Full page or element specific.
  • PDF generation – Ideal for invoices or reports.
  • Video recording – Capture the entire test run.
# Full‑page screenshot
page.screenshot(path="full.png", full_page=True)

# Element screenshot
logo = page.locator("img[alt='Logo']")
logo.screenshot(path="logo.png")

# Save page as PDF (Chromium only)
page.pdf(path="report.pdf", format="A4")

# Record video (needs context option)
context = browser.new_context(record_video_dir="videos/")
page = context.new_page()
page.goto("https://example.com")
page.wait_for_timeout(2000)  # Let the page settle
context.close()  # Video saved automatically

Handling Multiple Pages, Tabs, and Pop‑ups

Modern web apps frequently open new tabs or windows. Playwright makes it easy to manage them:

# Listen for a new page (tab) event
def on_new_page(page):
    print("New page URL:", page.url)

context = browser.new_context()
context.on("page", on_new_page)

page = context.new_page()
page.goto("https://example.com")
page.click("a[target='_blank']")  # Opens a new tab
# The callback prints the URL of the newly opened page

For pop‑ups like alerts, confirmations, or prompts, use the page.on("dialog") handler:

def handle_dialog(dialog):
    print("Dialog message:", dialog.message)
    dialog.accept()  # Or dialog.dismiss()

page.on("dialog", handle_dialog)
page.click("button#delete")  # Triggers a confirm dialog

Integrating Playwright with Test Frameworks

Most teams combine Playwright with pytest for a clean test runner experience. Below is a minimal conftest.py that provides a browser fixture:

# conftest.py
import pytest
from playwright.sync_api import sync_playwright

@pytest.fixture(scope="session")
def browser():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        yield browser
        browser.close()
# test_login.py
def test_successful_login(browser):
    page = browser.new_page()
    page.goto("https://example.com/login")
    page.fill("input#email", "user@example.com")
    page.fill("input#password", "Secret123")
    page.click("button#login")
    expect(page).to_have_url("**/dashboard")
    page.close()

For async projects, replace sync_playwright with async_playwright and use pytest-asyncio to handle the event loop.

Best Practices and Performance Tips

  • Reuse browser contexts instead of launching a new browser for each test. Contexts are lightweight and share the same executable.
  • Enable tracing for detailed logs: browser.start_tracing(...) and browser.stop_tracing(path="trace.zip").
  • Parallel execution: Use pytest-xdist or Playwright’s built‑in test runner to run multiple workers.
  • Keep selectors stable: Prefer data attributes (e.g., data-test-id) over volatile CSS classes.
  • Limit network traffic in CI by mocking or blocking unnecessary resources with page.route().
# Block images and fonts to speed up tests
page.route("**/*.{png,jpg,svg,woff,woff2}", lambda route: route.abort())

Debugging Playwright Scripts

If a test fails, these tools can help you pinpoint the issue:

  1. Playwright Inspector: Run pwdebug or set PWDEBUG=1 to launch an interactive UI.
  2. Trace Viewer: Open the .zip

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *