Getting Started with Playwright for Python
Install Playwright for Python, run your first test or automation script, choose reliable locators, debug failures, and capture screenshots.
Playwright for Python lets you automate Chromium, Firefox, and WebKit for end-to-end tests, browser scripts, and screenshots. For a test suite, the recommended starting point is the pytest plugin:
pip install pytest-playwright
playwright install
Create a test_*.py file, use the supplied page fixture, and run pytest. For a standalone automation script, install the library directly and use either its synchronous or asynchronous API.
This guide walks through both paths, browser installation, reliable locators and waits, debugging, browser configuration, failure handling, and screenshot options.
1. Choose the right Playwright workflow
| Goal | Start with | Why |
|---|---|---|
| Repeatable end-to-end tests | pytest-playwright |
Fixtures, assertions, test discovery, browser selection, and artifacts are integrated. |
| One-off browser automation | playwright library |
A direct Python script controls the browser without a test runner. |
| Existing asyncio application | async_playwright |
Operations fit an asynchronous event loop. |
| Simple sequential script | sync_playwright |
The code is straightforward to read from top to bottom. |
Playwright supports synchronous and asynchronous APIs. Pick the style that matches your application; neither is universally better.
2. Install Playwright and its browsers
Recommended pytest installation
python -m pip install pytest-playwright
playwright install
The Python package and browser binaries are separate installations. The second command installs Playwright’s supported browser versions. The official installation guide is at playwright.dev/python/docs/intro.
Standalone library installation
python -m pip install playwright
playwright install
Install one browser or operating-system dependencies
playwright install chromium
playwright install webkit
playwright install --with-deps chromium
Use playwright install-deps when your Linux environment needs system libraries. Playwright can also use branded Chrome or Edge channels, but those browsers are not installed by default. Consult the browser installation documentation for current platform requirements and supported channels.
After upgrading Playwright, run the install command again when necessary. Each Playwright release expects specific browser binary versions.
3. Run your first Python test with pytest
Create test_example.py:
from playwright.sync_api import Page, expect
def test_playwright_homepage(page: Page) -> None:
page.goto("https://playwright.dev/")
expect(page).to_have_title("Playwright")
page.get_by_role("link", name="Get started").click()
expect(page.get_by_role("heading", name="Installation")).to_be_visible()
Run it from the project directory:
pytest
The plugin runs headless Chromium by default. Pytest discovers files beginning with test_ and functions beginning with test_. The page fixture creates a page for the test and the expect assertion retries until the condition is met or the timeout expires.
A minimal project layout
project/
├── test_example.py
├── requirements.txt
└── pytest.ini
A requirements file can pin the Python package version used by your project:
pytest-playwright
Pin versions in production according to your normal dependency policy, then reinstall browsers whenever the Playwright version changes.
4. Write a standalone synchronous script
Use this route for automation that is not organized as tests:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://playwright.dev/")
print(page.title())
browser.close()
Save it as script.py and run:
python script.py
Always close the browser, preferably with a context manager as shown. A browser process left running can consume memory and keep CI jobs alive.
5. Use the asynchronous API
Choose the async API when the surrounding application already uses asyncio:
import asyncio
from playwright.async_api import async_playwright
async def main() -> None:
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto("https://playwright.dev/")
print(await page.title())
await browser.close()
asyncio.run(main())
Do not call asyncio.run() inside an environment that already owns an event loop, such as some notebooks or async web handlers. In those environments, await main() from the existing loop.
6. Locate elements reliably
Locators are central to Playwright’s auto-waiting and retry behavior. Prefer user-facing locators in this order when they describe the page accurately:
get_by_role()for buttons, links, headings, checkboxes, and other accessible roles.get_by_label()for form controls with labels.get_by_text()for visible text.get_by_placeholder(),get_by_alt_text(), andget_by_title()when those attributes are meaningful.- A configured test ID when the application provides stable test hooks.
page.get_by_role("button", name="Sign in").click()
page.get_by_label("Email").fill("dev@example.com")
page.get_by_placeholder("Search").fill("Playwright")
page.get_by_text("Installation").click()
CSS and XPath are available for cases where semantic locators do not fit, but they are usually more coupled to implementation details:
page.locator("#checkout").click()
page.locator("xpath=//button[@data-action='save']").click()
If a locator matches multiple elements, narrow it with a role name, text, or a chained locator. Playwright actions expect the target to resolve to one element.
7. Rely on auto-waiting instead of fixed sleeps
Before clicking, Playwright checks that the element resolves uniquely, is visible, stable, able to receive events, and enabled. Web-first assertions retry until they pass or time out.
from playwright.sync_api import Page, expect
def test_dashboard(page: Page) -> None:
page.goto("https://example.com/dashboard")
page.get_by_role("button", name="Load report").click()
expect(page.get_by_role("heading", name="Report ready")).to_be_visible()
expect(page.get_by_test_id("result-count")).to_have_text("42")
Avoid routine time.sleep() calls. Fixed delays can be slower than the real operation and can still finish before the page is ready. Wait for a user-visible state, a specific selector, or a documented application event instead.
page.locator("[data-status='ready']").wait_for()
expect(page.get_by_role("status")).to_contain_text("Saved")
8. Control browser contexts and pages
A browser context is an isolated session with its own cookies, local storage, permissions, viewport, and other settings. Create separate contexts when tests must not share state.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
viewport={"width": 1440, "height": 900},
locale="en-US",
timezone_id="UTC",
)
page = context.new_page()
page.goto("https://example.com/")
context.close()
browser.close()
For authenticated tests, save and reuse storage state instead of logging in through every test:
context = browser.new_context(storage_state="state.json")
Keep that file out of source control when it contains credentials or session cookies.
9. Run headed, cross-browser, and device tests
The pytest plugin exposes browser and artifact options:
pytest --headed
pytest --browser chromium --browser firefox --browser webkit
pytest --browser-channel chrome
pytest --device="iPhone 13"
pytest --tracing=retain-on-failure
pytest --video=retain-on-failure
pytest --screenshot=only-on-failure
Use headed mode when you need to see the browser. Add Firefox or WebKit when compatibility matters. Device presets configure a browser context with mobile-like viewport and input settings; they do not replace testing on every real device.
Equivalent context settings can be supplied directly in a script:
context = browser.new_context(
viewport={"width": 390, "height": 844},
device_scale_factor=3,
is_mobile=True,
has_touch=True,
)
10. Debug a failing test
Open the Playwright Inspector and pause execution with:
PWDEBUG=1 pytest -s -k test_playwright_homepage
The Inspector shows locator details, action steps, and the live page. You can also use a normal Python debugger, such as the VS Code Python extension.
Capture diagnostic artifacts on failures:
pytest --tracing=retain-on-failure \
--video=retain-on-failure \
--screenshot=only-on-failure
When an action times out, inspect the trace or screenshot before increasing the timeout. A larger timeout can hide a broken locator or a page that never reaches the expected state.
11. Configure timeouts, navigation, and screenshots
Set a deliberate default timeout, then override individual operations when needed:
page.set_default_timeout(10_000)
page.set_default_navigation_timeout(30_000)
page.goto("https://example.com/", wait_until="domcontentloaded")
page.screenshot(path="example.png", full_page=True)
Common navigation milestones include domcontentloaded and load. Use a specific application-ready locator for pages whose data arrives after navigation.
Screenshot options include full-page capture, a selected element, and image output:
page.screenshot(path="viewport.png")
page.screenshot(path="full.png", full_page=True)
page.get_by_role("main").screenshot(path="main.png")
For deterministic visual comparisons, fix the viewport, browser, locale, timezone, fonts, and test data. Disable animations in test CSS when motion causes unstable pixels.
12. Reliability and performance practices
- Use one isolated context per test or test group that needs clean state.
- Reuse a browser process when running many tests, while keeping contexts isolated.
- Prefer semantic locators and web-first assertions over arbitrary sleeps.
- Wait for the smallest meaningful readiness condition instead of waiting for every network request.
- Keep test data deterministic and avoid depending on third-party pages you do not control.
- Run only the browsers required by your compatibility policy; each additional browser increases runtime and artifact volume.
- Store traces, videos, and screenshots only when useful, especially in large CI runs.
- Install browser binaries during image or environment setup so test jobs do not download them repeatedly.
Playwright browser binaries and your Python dependencies both affect reproducibility. Pin the dependency set and reinstall matching browsers after upgrades.
13. Troubleshooting common errors
| Error or symptom | Likely cause | Fix |
|---|---|---|
Executable doesn't exist |
The Python package is installed but browsers are not. | Run playwright install, or install the specific browser you launch. |
| Browser fails to start on Linux | Missing system libraries or sandbox configuration. | Use playwright install --with-deps chromium in a supported environment and review the browser guide. |
| Tests pass locally but fail in CI | Different browser version, viewport, fonts, timing, or environment variables. | Install browsers in CI, pin dependencies, set an explicit viewport, and retain traces on failure. |
TimeoutError on click |
The locator is ambiguous, hidden, disabled, covered, or the page is not ready. | Inspect the locator, use a semantic role and name, wait for the visible ready state, and inspect a trace. |
strict mode violation |
The locator matches more than one element. | Make it unique with a role name, chained locator, or an intentional first/nth choice. |
| Assertion fails intermittently | The assertion checks an intermediate state or shared test data. | Use a web-first assertion on the final state and isolate or reset test data. |
| Async script reports an event-loop error | asyncio.run() was called inside an already-running loop. |
Await the coroutine from the existing loop instead. |
| Browser version mismatch after upgrade | The package was updated without updating its binaries. | Run playwright install again for the new Playwright version. |
14. Or skip the browser setup
If your goal is simply to produce a clean website screenshot, ScreenshotNeo provides a hosted screenshot API at ScreenshotNeo. It removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status.
One GET request is enough. See the ScreenshotNeo API documentation for all options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://playwright.dev -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://playwright.dev"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://playwright.dev' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
ScreenshotNeo also offers full-page capture with lazy images loaded, element capture by CSS selector, dark mode, device presets, custom viewports, retina scale, PDF output, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching, signed links, async jobs, webhooks, bulk capture, usage reporting, and an MCP server with take_screenshot, get_page_info, and capture_pdf.
Plans include 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.
15. FAQ
Do I need pytest to use Playwright for Python?
No. Install the playwright package for standalone synchronous or asynchronous scripts. Use pytest-playwright when you want fixtures, test discovery, and test-runner options.
Which browsers does Playwright support?
Playwright supports Chromium, Firefox, and WebKit, plus selected branded browser channels. Install the binaries that match your Playwright version.
Should I use sync or async Playwright?
Use the synchronous API for a straightforward sequential script. Use the asynchronous API when your application already uses asyncio.
Why does Playwright wait without sleep calls?
Locator actions perform actionability checks, and web-first assertions retry. Waiting for a meaningful page state is generally more reliable than fixed delays.
Can Playwright capture PDFs?
Chromium supports PDF generation through the browser API. For a hosted screenshot or PDF endpoint without managing browser binaries, use ScreenshotNeo’s API.


