FR EN
Datasets flowing through a funnel and producing a series of passing tests
Playwright with Python: from first script to CI | AutomationDataCamp
October 2, 2026 ADC Team 11 min read

Playwright with Python and pytest: a complete, tested tutorial

Playwright for Python installs with two commands, pip install pytest-playwright then playwright install, and runs with pytest through the page fixture. This tutorial goes from a first script to GitHub Actions: locators, auto-retrying assertions, Codegen, Trace Viewer, API tests and a simple Page Object. The examples were run with Playwright 1.63 and pytest-playwright 0.9 before publication.

Key takeaways
  • Install: pip install pytest-playwright, then playwright install to download Chromium, Firefox and WebKit
  • Tests: a test_... function that receives the page fixture, and expect(...) assertions that retry until the timeout
  • Locators: prefer get_by_role, get_by_label, get_by_text and get_by_test_id over CSS selectors
  • Debugging: --headed to watch the browser, --tracing on then playwright show-trace to replay a test step by step
  • Python or TypeScript: TypeScript has the most complete runner; Python suits teams that already work in Python

What is Playwright for Python?

Playwright is an open-source library from Microsoft that automates Chromium, Firefox and WebKit with a single API. The Python version is official, maintained by Microsoft like the TypeScript one, and gets the same browser features. It offers two styles: a synchronous API (the simplest for tests) and an asynchronous API based on asyncio.

What sets Playwright apart from older tools: it waits automatically for an element to be visible, stable and enabled before acting, which removes most sleep calls and flaky tests.

Step 1: install Playwright for Python

Work in a virtual environment to isolate the project dependencies:

python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install pytest-playwright
playwright install

pytest-playwright installs both the playwright library and the pytest plugin. playwright install downloads the browsers (playwright install chromium for just one). On a fresh Linux machine or in CI, add --with-deps to install the system libraries the browsers need.

Step 2: a first script with the sync API

Before writing tests, a few lines are enough to check that everything works. This script opens the Playwright docs, prints the page title and saves a screenshot:

first_script.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()          # headless by default
    page = browser.new_page()
    page.goto("https://playwright.dev/python/")
    print(page.title())
    page.screenshot(path="home.png")
    browser.close()

Run python first_script.py: the title is printed and home.png appears in the folder. The browser runs without a window (headless); use p.chromium.launch(headless=False) to watch it. The async equivalent lives in playwright.async_api.

Step 3: your first pytest test

For tests, let pytest-playwright manage the browser. The plugin gives each test a fresh page in an isolated browser context and closes everything afterwards. Create a tests/ folder and a file whose name starts with test_:

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


def test_page_title(page: Page):
    page.goto("https://playwright.dev/python/")
    expect(page).to_have_title(re.compile("Playwright"))


def test_get_started_link(page: Page):
    page.goto("https://playwright.dev/python/")
    page.get_by_role("link", name="Get started").click()
    expect(page.get_by_role("heading", name="Installation")).to_be_visible()

Run pytest at the project root: both tests run in headless Chromium and pytest prints 2 passed. No browser.close() to write: the fixture cleans up after each test.

Step 4: pick locators users can see

A locator describes how to find an element. Playwright recommends targeting what the user sees (role, label, text) rather than the HTML structure, which changes often. Tests written this way survive redesigns.

LocatorTargetsExample
get_by_roleAccessibility role and nameget_by_role("button", name="Send")
get_by_labelForm field by its labelget_by_label("Email address")
get_by_textVisible textget_by_text("Thank you")
get_by_placeholderInput placeholderget_by_placeholder("Search")
get_by_test_iddata-testid attributeget_by_test_id("submit")
locatorCSS or XPath selector (last resort)locator("#cart .total")

The attribute read by get_by_test_id is data-testid by default. If your application uses another name, set it once, for example in a fixture:

playwright.selectors.set_test_id_attribute("data-qa")

Step 5: web-first assertions

Playwright expect assertions retry until the condition is true or the timeout expires (5 seconds by default). That is the key difference with a plain Python assert, which checks once and fails if the page has not finished updating.

  • page: expect(page).to_have_title(...), expect(page).to_have_url(...)
  • element state: to_be_visible(), to_be_enabled(), to_be_checked()
  • content: to_have_text(...), to_contain_text(...), to_have_value(...), to_have_count(...)
  • negation: expect(locator).not_to_be_visible()

Keep assert for values you already have, such as the JSON body of an API response.

Step 6: useful command-line options

pytest-playwright adds options to pytest. The ones you will use every day:

OptionEffect
--headedShow the browser while tests run
--browser firefoxPick the browser (chromium, firefox, webkit); repeat it to run several
--slowmo 500Slow each action down by 500 ms to follow it on screen
--base-url URLLets you write page.goto("/path")
--tracing onRecord a trace per test (retain-on-failure keeps only failures)
--screenshot only-on-failureScreenshot of failing tests
--video retain-on-failureVideo of failing tests
--output DIRFolder for traces, screenshots and videos (test-results by default)

To avoid retyping them, put them in a pytest.ini file at the root:

pytest.ini
[pytest]
pythonpath = .
addopts = --base-url https://playwright.dev --tracing retain-on-failure

Step 7: generate code with Codegen

Codegen opens a browser, records your clicks and typing, and writes the matching Playwright code live:

playwright codegen --target python-pytest https://playwright.dev

--target python-pytest produces a pytest test function directly; -o test_generated.py saves it to a file. Codegen favours role and text locators. Treat the result as a draft: rename, add the assertions that matter to the business and delete the noise.

Step 8: understand a failure with the Trace Viewer

A trace holds, for every action, a DOM snapshot before and after, the console, the network requests and the source code. Run the tests with --tracing on (or retain-on-failure), then open the trace:

pytest --tracing on
playwright show-trace test-results/<test-folder>/trace.zip

Each test gets its own subfolder in test-results/. The Trace Viewer lets you step back in time action by action: it is the most effective way to understand a test that only fails in CI. For live debugging, PWDEBUG=1 pytest -s opens the Playwright Inspector and pauses at each step.

Step 9: test an API with Playwright for Python

Playwright also sends HTTP requests without a browser, through APIRequestContext. Create the context once per session in a fixture, then use it in your tests:

tests/test_api.py
from typing import Generator
import pytest
from playwright.sync_api import Playwright, APIRequestContext, expect


@pytest.fixture(scope="session")
def api(playwright: Playwright) -> Generator[APIRequestContext, None, None]:
    context = playwright.request.new_context(base_url="https://api.github.com")
    yield context
    context.dispose()


def test_playwright_python_repo(api: APIRequestContext):
    response = api.get("/repos/microsoft/playwright-python")
    expect(response).to_be_ok()
    assert response.json()["name"] == "playwright-python"

expect(response).to_be_ok() checks a status between 200 and 299. Inside a UI test, page.request gives you the same client sharing the browser cookies: handy to create data through the API before checking the screen. To compare API testing tools, read API testing: Selenium vs REST Assured vs Postman.

Step 10: organise the project (conftest.py and Page Object)

Beyond a handful of tests, group the locators and actions of each page in a class: the Page Object Model. Tests become readable and a UI change is fixed in one place. A simple layout:

my-project/
├── pages/
│   ├── __init__.py
│   └── home.py
├── tests/
│   ├── conftest.py
│   ├── test_home.py
│   └── test_journey.py
├── pytest.ini
└── requirements.txt
pages/home.py
from playwright.sync_api import Page


class HomePage:
    def __init__(self, page: Page):
        self.page = page
        self.get_started = page.get_by_role("link", name="Get started")

    def open(self):
        self.page.goto("/python/")

    def go_to_installation(self):
        self.get_started.click()

pytest loads conftest.py automatically: declare shared fixtures there, here one that builds the home page from the plugin's page fixture.

tests/conftest.py
import pytest
from playwright.sync_api import Page
from pages.home import HomePage


@pytest.fixture
def home(page: Page) -> HomePage:
    return HomePage(page)
tests/test_journey.py
from playwright.sync_api import expect
from pages.home import HomePage


def test_installation_journey(home: HomePage):
    home.open()
    home.go_to_installation()
    expect(home.page.get_by_role("heading", name="Installation")).to_be_visible()

The pythonpath = . line in pytest.ini makes the pages package importable from the tests, and --base-url allows relative paths in goto. To go further, see Page Object Model and test design patterns, and to vary the data of a single test, data-driven testing.

Step 11: run the tests in GitHub Actions

One workflow file is enough to run the tests on every push and keep the traces of failures:

.github/workflows/playwright.yml
name: Playwright tests
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  tests:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Install browsers
        run: python -m playwright install --with-deps
      - name: Run tests
        run: pytest --tracing retain-on-failure
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-traces
          path: test-results/

requirements.txt contains at least pytest-playwright; pin the version so CI and every laptop use the same browsers. When a run fails, download the playwright-traces artifact and open the trace with playwright show-trace. CI principles in general are covered in integrating automation into CI/CD.

Playwright in Python or TypeScript?

Both versions drive the same browsers with the same concepts. The difference is mostly the tooling around them:

CriterionPython (pytest-playwright)TypeScript (@playwright/test)
Test runnerpytest and its plugin ecosystemRunner built into Playwright
Parallel runsThrough the pytest-xdist pluginBuilt in (workers)
HTML reportThrough a pytest pluginBuilt in
UI mode, test agentsNoYes
Typical teamPython, data and Django or FastAPI back-end teamsFront-end and full-stack JavaScript teams

In practice: if your team and your application are already in Python, stay in Python and share tools and fixtures. If you start from scratch or aim at QA automation jobs, TypeScript gives access to the most complete tooling (runner, report, UI mode). Our Playwright courses use TypeScript; everything you learn here (locators, web-first assertions, traces, Page Object) transfers directly.

Frequently asked questions

Does Playwright work with Python?

Yes. Microsoft maintains an official Playwright library for Python, with a synchronous and an asynchronous API, plus a pytest plugin (pytest-playwright) that provides the page, context and browser fixtures.

Do I need pytest to use Playwright in Python?

No, a plain script is enough to automate a browser. For tests, the official pytest-playwright plugin is recommended: it launches the browsers, isolates each test and adds options such as --headed, --browser and --tracing.

Is Playwright for Python free?

Yes. Playwright is an open-source project published by Microsoft under the Apache 2.0 licence, free for personal and commercial use.

Playwright Python or Selenium: which should I choose?

For a new project, Playwright brings auto-waiting, retrying assertions, the Trace Viewer and API testing in a single tool. Selenium remains relevant when a team already has a large Selenium suite or depends on Selenium Grid.

How do I run Playwright Python tests in parallel?

pytest-playwright does not parallelise on its own: install the pytest-xdist plugin and run pytest --numprocesses auto. Each worker process starts its own browser.

Written with AI assistance; the code was executed before publication (Playwright 1.63, pytest-playwright 0.9). Also available in French: Playwright Python : tutoriel complet.

From this tutorial to a real project

Our certified Playwright course (taught in French, in TypeScript) applies these concepts to a full project: locators, Page Object, API tests, GitHub Actions and test reporting. To browse all our programmes, see the course catalogue.

View our courses Playwright course (in French)

Related articles

Illustration for the article: API Testing

API Testing: Selenium vs REST Assured vs Postman

Which tool to test REST APIs, and where Playwright fits.

Read more : API Testing