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_roleandget_by_test_id - You can auto-generate test code with Codegen
- What Is Playwright? How Does It Compare to Selenium?
- ① Install Playwright
- ② Run Something First (Script Style)
- ③ Write Tests with pytest
- ④ Writing Assertions with expect
- ⑤ Organize Tests with conftest.py
- ⑥ Auto-Generate Test Code with Codegen
- ⑦ Screenshots and Trace Viewer
- 7 Common Pitfalls When Starting with Playwright
- Frequently Asked Questions (FAQ)
- Summary
What Is Playwright? How Does It Compare to Selenium?
| Aspect | Selenium | Playwright |
|---|---|---|
| Driver setup | Required separately (webdriver-manager, etc.) | ✅ Bundled during installation |
| Auto-waiting | Must write explicit waits (WebDriverWait) yourself | ✅ Waits automatically before each action (built in) |
| Locator API | CSS, XPath, ID, etc. | ✅ Semantic APIs like get_by_role also available |
| Supported browsers | Chrome / Firefox / Safari / Edge | Chromium (incl. Chrome & Edge) / Firefox / WebKit |
| Codegen (recording) | Not built in (IDE plugins available separately) | ✅ Built in |
| Trace Viewer | Not 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 |
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.
| Reason | What it means in practice |
|---|---|
| Built-in auto-waiting | No need to write WebDriverWait for every action — Flaky Tests are far less common |
| Works well with modern SPAs | React, Vue, and Angular apps can be tested reliably |
| Codegen & Trace Viewer | Debugging and initial code creation are dramatically easier |
| No separate driver needed | playwright install gets you browsers and everything else in one step |
| CI-friendly | Official GitHub Actions support makes CI integration straightforward |
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
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.
| Assertion | What 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
--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.
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
With
pytest-playwright, options like --screenshot=only-on-failure save screenshots only on failure — making CI pipelines much easier to debug.- 🔗 Page Object Model (POM) — A Design Pattern for Maintainable E2E Tests (for when tests start to grow)
- 🔗 GitHub Actions + Playwright — Building a CI/CD Pipeline (run tests automatically on every push)
- 🔗 CSS Selectors & XPath — Understanding Locators in Selenium & Playwright
- 🔗 Common Playwright Failures — Pitfalls and Solutions
- 🔗 Playwright × pytest Best Practices
- 🔗 Python Setup Guide for Test Automation
7 Common Pitfalls When Starting with Playwright
⚠️ Watch out for these
- Forgetting playwright install
pip install playwrightalone won’t work. You must runplaywright installto download the browser executables. If you see “Executable doesn’t exist” in your error message, this is the cause. - Not realizing tests run headless by default
The default mode is headless (no visible window). During development, add--headedto see the browser and spot issues visually. - Mixing sync and async APIs
from playwright.sync_api import sync_playwright(synchronous) andfrom playwright.async_api import async_playwright(asynchronous) cannot be mixed. For use with pytest, the sync API is easier to work with. - 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. - Browser process left running after forgetting the with block
Not usingsync_playwright()in awithblock leaves the browser process running indefinitely. With pytest-playwright, thepagefixture handles cleanup automatically. - Not running playwright install in CI
CI environments like GitHub Actions also needplaywright install(orplaywright install --with-deps). The--with-depsflag installs system-level dependencies as well. - 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 withexpect(locator).to_be_visible(timeout=10000).
Frequently Asked Questions (FAQ)
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.
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.
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.
Yes — it’s completely free and open source. Maintained by Microsoft and released under the MIT license. Free for commercial projects as well.
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.
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.
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.
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.
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.
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.
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
| Step | What to do |
|---|---|
| ① Install | pip install playwright pytest-playwright → playwright install |
| ② Smoke test | Open a page with sync_playwright() and take a screenshot |
| ③ pytest tests | Use the page fixture with get_by_role, fill, click, and expect |
| ④ Organize | Centralize common setup in conftest.py. Configure browser in pytest.ini |
| ⑤ Debug | Use 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.
