Getting Started with Playwright | Browser Automation with Python โ€” A Beginner’s Guide

Playwright is a browser automation library developed by Microsoft. Compared to Selenium, it requires less configuration, ships with auto-waiting built in, and offers a modern API. This guide walks you through installation to running your first test โ€” one command at a time.

๐Ÿ“Œ Who This Article Is For

โœ… Anyone using Playwright for the first time
โœ… Selenium users who want to understand how Playwright differs
โœ… Anyone who wants to automate browser interactions and start writing E2E tests
โœ… Those who want to try Codegen (test recording) or Trace Viewer

๐Ÿ“– What You’ll Be Able to Do After This Guide

โœ” Install Playwright and launch a browser
โœ” Open pages, click elements, fill forms, and write assertions
โœ” Structure tests using pytest-playwright
โœ” Record interactions with Codegen and auto-generate test code
โœ” Debug failures with screenshots and Trace Viewer

I have worked on E2E tests using Playwright across multiple real projects, including migrations from Selenium to Playwright.
This guide targets “a working test by the end of day one” and proactively addresses the points where beginners typically get stuck.

โœ… Goal of This Guide

  • Playwright is installed and you can run tests with pytest
  • You can interact with forms using locators like get_by_role and get_by_test_id
  • You can auto-generate test code with Codegen

What Is Playwright? How Does It Compare to Selenium?

AspectSeleniumPlaywright
Driver setupRequired separately (webdriver-manager, etc.)โœ… Bundled during installation
Auto-waitingMust write explicit waits (WebDriverWait) yourselfโœ… Waits automatically before each action (built in)
Locator APICSS, XPath, ID, etc.โœ… Semantic APIs like get_by_role also available
Supported browsersChrome / Firefox / Safari / EdgeChromium (incl. Chrome & Edge) / Firefox / WebKit
Codegen (recording)Not built in (IDE plugins available separately)โœ… Built in
Trace ViewerNot availableโœ… Visual inspection of test failures
Ease of learningโ—‹ Rich documentation and community resourcesโ—Ž Less setup, easier to get started
Job market presenceโ—Ž More listings (longer history)โ—‹ Growing rapidly
๐Ÿ’ก Selenium or Playwright โ€” which should I learn?
For new projects, Playwright is easier to write and produces more stable tests. That said, Selenium still has more job listings. Learning Selenium basics first and then moving to Playwright lets you understand the strengths of both.

Why Playwright Has Grown So Quickly

Since around 2020, Playwright has gained rapid adoption as an E2E testing tool. Here’s why.

ReasonWhat it means in practice
Built-in auto-waitingNo need to write WebDriverWait for every action โ€” Flaky Tests are far less common
Works well with modern SPAsReact, Vue, and Angular apps can be tested reliably
Codegen & Trace ViewerDebugging and initial code creation are dramatically easier
No separate driver neededplaywright install gets you browsers and everything else in one step
CI-friendlyOfficial GitHub Actions support makes CI integration straightforward
๐Ÿ“ From real migration experience
The biggest change when I migrated a project from Selenium to Playwright was how rarely I needed to write explicit waits. With Selenium, writing a wait condition before every element access was simply the norm. With Playwright, action methods wait automatically โ€” the test code got noticeably cleaner. The frequency of Flaky Tests dropped too.

โ‘  Install Playwright

First, make sure you have a Python environment ready (see the Python Setup Guide).
Run the following with your virtual environment activated.

# Install Playwright and the pytest plugin
pip install playwright pytest-playwright

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

Verify the installation:

playwright --version
# Should print: Playwright 1.x.x
๐Ÿ’ก playwright install is not optional
pip install playwright alone is not enough. You must run playwright install to download the actual browser executables. The same applies in CI environments.

โ‘ก Run Something First (Script Style)

Before using pytest, let’s confirm Playwright works with a simple standalone script.

# first_playwright.py
from playwright.sync_api import sync_playwright


def main() -> None:
    with sync_playwright() as p:
        # Launch browser (headless=False shows the browser window)
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()

        # Open a page
        page.goto("https://example.com")  # sample URL

        # Print the page title
        print(page.title())

        # Take a screenshot
        page.screenshot(path="screenshot.png")

        browser.close()


if __name__ == "__main__":
    main()
python first_playwright.py

If a browser window opens and the page loads, it’s working. screenshot.png will also be saved.

โ‘ข Write Tests with pytest

In real projects, tests are managed with pytest. With pytest-playwright installed, the page fixture is available automatically.

Your First Test File

# test_first.py
from playwright.sync_api import Page, expect


def test_page_title(page: Page) -> None:
    page.goto("https://example.com")  # sample URL
    assert "Example" in page.title()
    # The Playwright-idiomatic way uses expect:
    # expect(page).to_have_title("Example Domain")


def test_heading_visible(page: Page) -> None:
    page.goto("https://example.com")  # sample URL
    heading = page.get_by_role("heading", name="Example Domain")
    expect(heading).to_be_visible()  # Playwright-idiomatic assertion
pytest test_first.py -v

Expected output:

========================= test session starts ==========================
collected 2 items

test_first.py::test_page_title    PASSED    [ 50%]
test_first.py::test_heading_visible PASSED  [100%]

========================== 2 passed in 2.34s ==========================

Login Form Test (Practical Example)

# test_login.py
from playwright.sync_api import Page, expect


def test_login_success(page: Page) -> None:
    page.goto("https://example.com/login")  # sample URL

    # Fill in email
    page.get_by_label("Email").fill("user@example.com")

    # Fill in password
    page.get_by_label("Password").fill("password123")

    # Click the login button
    page.get_by_role("button", name="Login").click()

    # Verify redirect to dashboard
    expect(page).to_have_url("https://example.com/dashboard")  # sample URL


def test_login_invalid_password(page: Page) -> None:
    page.goto("https://example.com/login")  # sample URL

    page.get_by_label("Email").fill("user@example.com")
    page.get_by_label("Password").fill("wrong-password")
    page.get_by_role("button", name="Login").click()

    # Verify error message is shown
    expect(page.get_by_test_id("error-message")).to_be_visible()
    expect(page.get_by_test_id("error-message")).to_contain_text("Incorrect password")

โ‘ฃ Writing Assertions with expect

Playwright’s expect is an assertion library tailored for web pages that automatically retries until the condition is met.

AssertionWhat it checks
expect(locator).to_be_visible()Element is visible
expect(locator).to_be_hidden()Element is hidden
expect(locator).to_have_text("...")Element’s text matches the given value
expect(locator).to_contain_text("...")Element’s text contains the given string
expect(locator).to_have_value("...")Input’s value matches the given value
expect(locator).to_be_enabled()Element is enabled (not disabled)
expect(locator).to_be_checked()Checkbox is checked
expect(page).to_have_url("...")Page URL matches the given value
expect(page).to_have_title("...")Page title matches the given value

โ‘ค Organize Tests with conftest.py

Putting base URL and browser configuration in conftest.py keeps individual test files clean and focused.

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


BASE_URL = "https://example.com"  # sample URL


@pytest.fixture
def logged_in_page(page: Page) -> Page:
    """Returns a page fixture already logged in."""
    page.goto(f"{BASE_URL}/login")
    page.get_by_label("Email").fill("user@example.com")
    page.get_by_label("Password").fill("password123")
    page.get_by_role("button", name="Login").click()
    page.wait_for_url(f"{BASE_URL}/dashboard")
    return page
# test_dashboard.py
from playwright.sync_api import Page, expect


def test_dashboard_shows_username(logged_in_page: Page) -> None:
    expect(logged_in_page.get_by_test_id("user-name")).to_contain_text("Test User")

Configure Browser and Headless Mode in pytest.ini

# pytest.ini
[pytest]
addopts =
    --browser chromium
    --headed
๐Ÿ’ก Use –headless in CI environments
--headed is for local development and debugging. In CI environments like GitHub Actions, there’s no display available, so use --headless (or remove --headed from addopts). Consider separating your local and CI configurations, or switching via an environment variable.
# Can also be passed on the command line
pytest --browser chromium              # use Chromium
pytest --browser firefox               # use Firefox
pytest --headed                        # show browser window
pytest --headless                      # headless mode (for CI)
pytest --slowmo 500                    # slow down actions by 500ms for easier debugging

โ‘ฅ Auto-Generate Test Code with Codegen

Codegen records your browser interactions and converts them into test code in real time.
It’s a great starting point when you’re unsure how to write a locator.

# Launch Codegen (replace URL with your target page)
playwright codegen https://example.com  # sample URL

A browser opens; as you interact with it, the code appears in a side panel.

โš ๏ธ Use Codegen as a starting point, not a final answer
Codegen is handy for locator discovery and initial code creation, but committing the generated code as-is tends to produce verbose, hard-to-maintain tests. Use the output as a reference, rewrite locators to use data-testid, and organize tests into fixtures and Page Object Model (POM) as they grow.

โ‘ฆ Screenshots and Trace Viewer

Screenshots

# Full-page screenshot
page.screenshot(path="screenshots/result.png", full_page=True)

# Screenshot of a specific element
page.get_by_test_id("error-message").screenshot(path="screenshots/error.png")

Trace Viewer โ€” Inspect Test Failures Visually

Trace Viewer records every step of a test run so you can inspect exactly what the page looked like at each point when a test fails.

# Save a trace automatically when a test fails
pytest --tracing=retain-on-failure

# Open the trace to inspect it
playwright show-trace trace.zip
๐Ÿ’ก Useful pytest-playwright settings
With pytest-playwright, options like --screenshot=only-on-failure save screenshots only on failure โ€” making CI pipelines much easier to debug.

7 Common Pitfalls When Starting with Playwright

โš ๏ธ Watch out for these

  1. Forgetting playwright install
    pip install playwright alone won’t work. You must run playwright install to download the browser executables. If you see “Executable doesn’t exist” in your error message, this is the cause.
  2. Not realizing tests run headless by default
    The default mode is headless (no visible window). During development, add --headed to see the browser and spot issues visually.
  3. Mixing sync and async APIs
    from playwright.sync_api import sync_playwright (synchronous) and from playwright.async_api import async_playwright (asynchronous) cannot be mixed. For use with pytest, the sync API is easier to work with.
  4. get_by_role name doesn’t match visible text
    get_by_role("button", name="Login") matches exact text or ARIA labels. Differences in whitespace, line breaks, or letter case can cause it to fail. Use Codegen-generated code as a reference to verify.
  5. Browser process left running after forgetting the with block
    Not using sync_playwright() in a with block leaves the browser process running indefinitely. With pytest-playwright, the page fixture handles cleanup automatically.
  6. Not running playwright install in CI
    CI environments like GitHub Actions also need playwright install (or playwright install --with-deps). The --with-deps flag installs system-level dependencies as well.
  7. expect timeout is too short
    The default assertion timeout is typically 5 seconds (5000ms), but it’s configurable at the project level. In slow CI environments, consider extending it โ€” either globally in your config or per-assertion with expect(locator).to_be_visible(timeout=10000).

Frequently Asked Questions (FAQ)

Q. What’s the difference between Playwright Test and pytest-playwright?

Playwright Test is Playwright’s own test runner for JavaScript/TypeScript, distributed as the @playwright/test package. pytest-playwright is a Playwright plugin for Python’s pytest framework. For Python test automation, use pytest-playwright. While Playwright Test has deeper integration on the JS side, pytest-playwright is fully capable for Python-based workflows.

Q. Is it worth migrating from Selenium to Playwright?

If your existing Selenium tests are stable and working well, there’s no urgent need to migrate. But for new tests, choosing Playwright is well worth it. Auto-waiting, Codegen, and Trace Viewer are built in, and the design naturally produces fewer Flaky Tests. In practice, many teams keep Selenium for existing coverage while starting new flows in Playwright.

Q. Do I need to know JavaScript to use Playwright?

No โ€” Python alone is enough. Playwright’s Python API uses standard Python idioms, and no JavaScript knowledge is required. That said, knowing JavaScript fundamentals (DOM, async behavior) is useful when testing JavaScript-heavy web apps, as it helps with test design and debugging.

Q. Is Playwright free?

Yes โ€” it’s completely free and open source. Maintained by Microsoft and released under the MIT license. Free for commercial projects as well.

Q. Playwright or Cypress โ€” which should I use?

If you’re using Python, Playwright is the clear choice. Cypress is JavaScript/TypeScript-only and can’t be used with Python tests. If you’re open to JavaScript, Playwright offers broader multi-browser and multi-language support, and handles complex scenarios like iframes, new tabs, and auth flows more easily.

Q. Does Playwright work with languages other than Python?

Yes. Playwright supports Python, JavaScript/TypeScript, Java, and C#. QA engineers tend to choose Python for its strong pytest integration and the ability to leverage existing Selenium knowledge.

Q. Is pytest-playwright required?

It’s not strictly required. You can use sync_playwright() directly, or manage fixtures yourself with pytest.fixture. That said, pytest-playwright provides page, browser, and context fixtures out of the box, and handles browser setup and teardown automatically โ€” recommended for real projects.

Q. How do I run the same tests on multiple browsers?

Pass multiple --browser flags: pytest --browser chromium --browser firefox. In CI you can test across browsers in one run. That said, running all browsers at once more than doubles test time โ€” a good approach is to stabilize on Chromium first, then expand coverage.

Q. Should I use Playwright’s expect or Python’s assert?

Use Playwright’s expect for checking web element state. expect retries internally until the condition is met. Python’s assert evaluates instantly โ€” using it for async UI state leads to Flaky Tests.

Q. When does page.goto() return?

By default it waits for the load event (DOM and most resources loaded). For SPAs rendered by JavaScript, you may need to wait for element visibility after goto(). While wait_until="networkidle" can help in some cases, Playwright’s own documentation cautions against relying on it. Prefer expect(), page.wait_for_url(), or locator.wait_for() for stable waiting. Apps with persistent connections like WebSockets can make networkidle unreliable.

Q. Do I need time.sleep() when interacting with pages?

Generally, no. Playwright’s action methods (click(), fill()) automatically wait for the element to be actionable. When explicit waiting is necessary, use page.wait_for_selector(), page.wait_for_url(), or expect.

Summary

StepWhat to do
โ‘  Installpip install playwright pytest-playwright โ†’ playwright install
โ‘ก Smoke testOpen a page with sync_playwright() and take a screenshot
โ‘ข pytest testsUse the page fixture with get_by_role, fill, click, and expect
โ‘ฃ OrganizeCentralize common setup in conftest.py. Configure browser in pytest.ini
โ‘ค DebugUse Codegen to explore locators; use --tracing=retain-on-failure to visualize failures

Playwright ships with auto-waiting, Trace Viewer, and Codegen as standard โ€” less setup than Selenium and accessible even to those new to browser automation.
Just running playwright codegen against your app gives you a feel for the whole workflow.

As your test suite grows, organize it with Page Object Model (POM), or take the next step and run tests automatically in GitHub Actions on every push.

Copied title and URL