Python August 14, 2026 16 min read

Playwright Python Tutorial 2026: Setup, Pytest & Your First Test

A complete, hands-on guide to using Playwright with Python and pytest. From pip install to cross-browser testing, tracing, Page Object Model, and CI/CD — everything a Python developer needs to start writing reliable end-to-end tests today.

Playwright for Python is an official Microsoft library that brings the full power of Playwright to the Python ecosystem. With 1.4 million+ weekly PyPI downloads in 2026, it's the fastest-growing Python test automation library — and for good reason: auto-waiting, built-in tracing, cross-browser support, and first-class pytest integration make it dramatically better than Selenium for new Python projects.

This tutorial assumes you know basic Python. No prior test automation experience needed.


Why Playwright + Python?

What you get with Playwright Python

  • Cross-browser: Chromium, Firefox, WebKit
  • Auto-waiting on every action (no sleeps)
  • Built-in pytest fixtures (page, browser, context)
  • Trace Viewer for visual debugging
  • Network interception & mocking
  • Screenshot & video on failure
  • Sync and async API variants
  • Parallel test execution via pytest-xdist

If you're coming from Selenium Python, Playwright eliminates the need for WebDriverWait, ChromeDriverManager, and most explicit waits. Every Playwright action automatically waits for the element to be actionable before interacting with it.

Step 1: Install Playwright for Python

Two commands and you're ready:

Terminal
# Install Playwright + pytest plugin
pip install pytest-playwright

# Download browser binaries (Chromium, Firefox, WebKit)
playwright install

Virtual environment recommended: Use python -m venv .venv and activate it before installing. This keeps Playwright isolated from your system Python.

Verify the installation:

Terminal
# Check Playwright version
playwright --version
# Version 1.48.0

# Check pytest recognizes the plugin
pytest --co
# Should list available fixtures including 'page', 'browser', 'context'

Step 2: Project Structure

Create a clean project layout:

Project structure
my-playwright-project/
├── tests/
│   ├── __init__.py
│   ├── test_login.py
│   └── test_homepage.py
├── pages/                  # Page Object Model (optional)
│   ├── __init__.py
│   └── login_page.py
├── conftest.py             # Shared fixtures
├── pytest.ini              # pytest config
└── requirements.txt
pytest.ini
[pytest]
# Run tests in headed mode during development
addopts = --headed --slowmo 500

# For CI, remove --headed and --slowmo:
# addopts = --browser chromium

Step 3: Write Your First Test

The pytest-playwright plugin provides a page fixture automatically — no browser setup code needed:

tests/test_homepage.py
import re
from playwright.sync_api import Page, expect


def test_homepage_has_title(page: Page):
    """Verify the homepage loads with the correct title."""
    page.goto("https://playwright.dev/")

    # Assert the page title contains "Playwright"
    expect(page).to_have_title(re.compile("Playwright"))


def test_get_started_link(page: Page):
    """Click 'Get Started' and verify navigation."""
    page.goto("https://playwright.dev/")

    # Click the "Get started" link
    page.get_by_role("link", name="Get started").click()

    # Verify navigation to the intro page
    expect(page.get_by_role("heading", name="Installation")).to_be_visible()

Run it:

Terminal
# Run all tests
pytest

# Run a specific file
pytest tests/test_homepage.py

# Run with verbose output
pytest -v

Step 4: Locator Strategies

Playwright Python uses the same role-based locators as the TypeScript version. Always prefer these over CSS/XPath — they're more resilient to UI changes:

Python — locator strategies (best to worst)
# ✅ BEST: Role-based locators (accessible, resilient)
page.get_by_role("button", name="Submit")
page.get_by_role("link", name="Sign In")
page.get_by_role("heading", name="Dashboard")

# ✅ GOOD: Label and placeholder
page.get_by_label("Email address")
page.get_by_placeholder("Search...")

# ✅ GOOD: Test ID (requires data-testid in HTML)
page.get_by_test_id("checkout-button")

# ✅ OK: Text content
page.get_by_text("Add to Cart")

# ⚠️ AVOID: CSS selectors (brittle)
page.locator("#submit-btn")
page.locator(".form-container > button.primary")

Step 5: Assertions

Playwright's expect API auto-waits for conditions to be true (with a default 5-second timeout):

Python — common assertions
from playwright.sync_api import expect

# Page-level assertions
expect(page).to_have_url("https://example.com/dashboard")
expect(page).to_have_title("Dashboard")

# Element visibility
expect(page.get_by_role("alert")).to_be_visible()
expect(page.get_by_text("Loading")).to_be_hidden()

# Element content
expect(page.get_by_test_id("count")).to_have_text("42")
expect(page.get_by_role("textbox")).to_have_value("user@test.com")

# Element state
expect(page.get_by_role("button", name="Submit")).to_be_enabled()
expect(page.get_by_role("checkbox")).to_be_checked()

Step 6: A Real-World Login Test

tests/test_login.py
from playwright.sync_api import Page, expect


def test_successful_login(page: Page):
    """User can log in with valid credentials."""
    page.goto("http://localhost:3000/login")

    page.get_by_label("Email").fill("user@example.com")
    page.get_by_label("Password").fill("securePass123")
    page.get_by_role("button", name="Sign In").click()

    # Verify redirect to dashboard
    expect(page).to_have_url("http://localhost:3000/dashboard")
    expect(page.get_by_role("heading", name="Dashboard")).to_be_visible()


def test_invalid_credentials_show_error(page: Page):
    """Invalid login shows an error message."""
    page.goto("http://localhost:3000/login")

    page.get_by_label("Email").fill("wrong@example.com")
    page.get_by_label("Password").fill("wrongpass")
    page.get_by_role("button", name="Sign In").click()

    # Error message should appear
    expect(page.get_by_role("alert")).to_have_text("Invalid email or password")
    # Should stay on login page
    expect(page).to_have_url("http://localhost:3000/login")

Step 7: Shared Fixtures with conftest.py

Use conftest.py to set base URL, default timeouts, and shared authentication:

conftest.py
import pytest
from playwright.sync_api import Page


@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
    """Set default viewport and locale for all tests."""
    return {
        **browser_context_args,
        "viewport": {"width": 1280, "height": 720},
        "locale": "en-US",
    }


@pytest.fixture(scope="session")
def base_url():
    """Base URL for all page.goto() calls."""
    return "http://localhost:3000"

Authentication fixture: For apps behind login, save auth state once and reuse it. Run playwright codegen --save-storage=auth.json, log in manually, then load the state in conftest: browser_context_args["storage_state"] = "auth.json".

Step 8: Page Object Model

For larger test suites, encapsulate page interactions in classes:

pages/login_page.py
from playwright.sync_api import Page, expect


class LoginPage:
    def __init__(self, page: Page):
        self.page = page
        self.email = page.get_by_label("Email")
        self.password = page.get_by_label("Password")
        self.submit = page.get_by_role("button", name="Sign In")
        self.error = page.get_by_role("alert")

    def goto(self):
        self.page.goto("/login")
        return self

    def login(self, email: str, password: str):
        self.email.fill(email)
        self.password.fill(password)
        self.submit.click()

    def expect_error(self, message: str):
        expect(self.error).to_have_text(message)
tests/test_login.py — using POM
from pages.login_page import LoginPage


def test_successful_login(page):
    login = LoginPage(page).goto()
    login.login("user@example.com", "securePass123")
    expect(page).to_have_url("/dashboard")

Step 9: Tracing & Debugging

Playwright's Trace Viewer is the most powerful debugging tool in test automation. Enable it on failure:

Terminal — run with tracing
# Record traces only on test failure
pytest --tracing retain-on-failure

# View the trace file
playwright show-trace test-results/test-login-chromium/trace.zip

The Trace Viewer shows a timeline of every action, DOM snapshots before and after each step, network requests, and console logs — making it trivial to understand why a test failed.

Step 10: Cross-Browser Testing

Terminal
# Run on all browsers
pytest --browser chromium --browser firefox --browser webkit

# Run on a specific browser
pytest --browser firefox

# Run headed (visible browser window)
pytest --headed

# Slow motion (great for demos)
pytest --headed --slowmo 1000

Step 11: Parallel Execution

Install pytest-xdist for parallel test execution:

Terminal
pip install pytest-xdist

# Run tests across 4 workers
pytest -n 4

# Auto-detect number of CPU cores
pytest -n auto

Step 12: CI/CD with GitHub Actions

.github/workflows/playwright.yml
name: Playwright Python Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install pytest-playwright
      - run: playwright install --with-deps chromium
      - run: pytest --browser chromium --tracing retain-on-failure
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-traces
          path: test-results/

Playwright Python vs Selenium Python

If you're deciding between the two for a Python project in 2026:

  • Auto-waiting: Playwright auto-waits on every action. Selenium requires explicit WebDriverWait everywhere.
  • Setup: Playwright is 2 commands (pip install + playwright install). Selenium needs browser drivers + driver managers.
  • Speed: Playwright uses direct browser protocols. Selenium goes through WebDriver HTTP bridge (slower).
  • Debugging: Playwright has Trace Viewer. Selenium has manual logging.
  • Cross-browser: Playwright bundles exact browser versions. Selenium depends on system-installed drivers.
  • AI integration: Playwright has MCP Server for Claude AI test generation. Selenium has nothing comparable.

For a full comparison, see our Playwright vs Cypress vs Selenium 2026 guide.

Frequently Asked Questions

Can I use Playwright with Python?

Yes. Playwright has an official Python library maintained by Microsoft. Install with pip install pytest-playwright and playwright install. It supports all the same features as the TypeScript version.

Is Playwright Python as good as Playwright TypeScript?

Yes — identical capabilities. Both are maintained by Microsoft. The Python API has sync and async variants. All locators, tracing, network interception, and browser support work the same way.

How do I install Playwright for Python?

Run pip install pytest-playwright then playwright install. Two commands, under 60 seconds.

Should I use pytest or unittest with Playwright?

Use pytest. The official pytest-playwright plugin provides built-in fixtures, automatic browser lifecycle management, parallel execution, and screenshot-on-failure out of the box.

Is Playwright Python better than Selenium Python?

For new projects in 2026, yes. Playwright is faster, has auto-waiting, includes tracing and video recording, and supports all modern browsers. Selenium is only better if you need IE11 or use Ruby/PHP elsewhere in your org.


Asim Noaman - Playwright and Claude AI course instructor

Asim Noaman

Senior QA Automation Engineer & AI Testing Specialist

With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.

Udemy InstructorPublished course author
Playwright + AI ExpertSpecialized in AI-powered QA
Production ExperienceEnterprise-grade frameworks
Connect on LinkedIn