First Playwright Test

Learn Playwright testing from your first browser launch to professional automation frameworks using practical, real-time examples.

Beginner → Intermediate → Advanced → Professional
🚀 1. What Is Your First Playwright Test?

Start Small, Then Build Like a Professional

Your first Playwright test does not need to be complicated. Start by launching a browser, opening a page, performing an action and validating the result. As you become comfortable, introduce fixtures, reusable page objects, test data, API calls, authentication and CI/CD execution.

🌐

Browser

Launch Chromium, Firefox or WebKit.

📄

Page

Navigate to a web application and interact with it.

🎯

Locator

Find buttons, fields, links and other elements.

✅

Assertion

Verify that the application behaves as expected.

🔄 2. Basic Playwright Test Flow
Start Playwright
→
Launch Browser
→
Create Page
→
Navigate
→
Interact
→
Assert
🟢 3. Beginner Level — Your First Browser Test
Beginner Example 1 — Open a Website

The simplest Playwright program launches a browser, opens a website and prints its title.

first_test.py
from playwright.sync_api import sync_playwright


with sync_playwright() as p:

    browser = p.chromium.launch(headless=False)

    page = browser.new_page()

    page.goto("https://example.com")

    print(page.title())

    browser.close()
$ python first_test.py
What happens?

Chromium opens, navigates to example.com, prints the page title and then closes.

Beginner Example 2 — Write a pytest Test

In a real automation project, tests are commonly organized using pytest.

test_homepage.py
from playwright.sync_api import sync_playwright


def test_homepage():

    with sync_playwright() as p:

        browser = p.chromium.launch()

        page = browser.new_page()

        page.goto("https://example.com")

        assert "Example" in page.title()

        browser.close()
$ pytest -v
🎯 4. Beginner Level — Locators and Actions

Real applications contain text boxes, buttons, links, checkboxes and dropdowns. Playwright uses locators to interact with these elements.

Common Actions
# Fill text field
page.get_by_label("Username").fill("john")

# Fill password
page.get_by_label("Password").fill("secret123")

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

# Click link
page.get_by_role("link", name="Products").click()

# Check checkbox
page.get_by_label("Remember me").check()

# Select dropdown option
page.get_by_label("Country").select_option("India")
Beginner Tip:

Prefer meaningful locators such as get_by_role() and get_by_label() instead of relying heavily on fragile CSS or XPath selectors.

✅ 5. Beginner Level — Your First Assertion

A test becomes useful when it verifies expected behavior.

Assertions
from playwright.sync_api import expect

expect(page).to_have_title("My Application")

expect(
    page.get_by_role("heading", name="Dashboard")
).to_be_visible()

expect(
    page.get_by_text("Login successful")
).to_be_visible()
Think like a QA engineer:

Do not only perform an action. Ask: "What should happen after this action?" That expected behavior becomes your assertion.

🔐 6. Real-Time Example — Login Test
Login Page
→
Username
→
Password
→
Login
→
Dashboard
test_login.py
from playwright.sync_api import sync_playwright, expect


def test_login():

    with sync_playwright() as p:

        browser = p.chromium.launch(headless=False)

        page = browser.new_page()

        page.goto("https://your-app.example/login")

        page.get_by_label("Username").fill("testuser")

        page.get_by_label("Password").fill("Password123")

        page.get_by_role("button", name="Login").click()

        expect(
            page.get_by_role("heading", name="Dashboard")
        ).to_be_visible()

        browser.close()
Security:

Use test accounts and environment variables for credentials. Avoid committing real passwords into source control.

🟣 7. Intermediate Level — Playwright Fixtures
Why Fixtures?

Opening a browser manually inside every test creates duplicated code. Playwright's pytest integration provides fixtures such as page.

test_home.py
from playwright.sync_api import Page, expect


def test_homepage(page: Page):

    page.goto("https://example.com")

    expect(page).to_have_title("Example Domain")
$ pip install pytest-playwright

$ playwright install

$ pytest -v
Intermediate Example — Form Validation
test_registration.py
from playwright.sync_api import Page, expect


def test_registration_validation(page: Page):

    page.goto("https://your-app.example/register")

    page.get_by_label("Email").fill("invalid-email")

    page.get_by_role(
        "button",
        name="Create Account"
    ).click()

    expect(
        page.get_by_text("Enter a valid email")
    ).to_be_visible()
🌐 8. Intermediate Level — Multi-Page Workflow

Real business flows often span multiple pages.

Login
→
Products
→
Cart
→
Checkout
→
Confirmation
E-Commerce Test
def test_purchase_flow(page):

    page.goto("https://shop.example")

    page.get_by_role(
        "link",
        name="Products"
    ).click()

    page.get_by_text("Laptop").click()

    page.get_by_role(
        "button",
        name="Add to Cart"
    ).click()

    page.get_by_role(
        "link",
        name="Cart"
    ).click()

    expect(
        page.get_by_text("Laptop")
    ).to_be_visible()
🏗️ 9. Advanced Level — Page Object Model

When a test suite grows, page objects help keep locators and application interactions reusable.

pages/login_page.py
from playwright.sync_api import Page


class LoginPage:

    def __init__(self, page: Page):

        self.page = page

        self.username = page.get_by_label("Username")

        self.password = page.get_by_label("Password")

        self.login_button = page.get_by_role(
            "button",
            name="Login"
        )


    def open(self):

        self.page.goto(
            "https://your-app.example/login"
        )


    def login(self, username, password):

        self.username.fill(username)

        self.password.fill(password)

        self.login_button.click()
tests/test_login.py
from playwright.sync_api import expect

from pages.login_page import LoginPage


def test_login(page):

    login_page = LoginPage(page)

    login_page.open()

    login_page.login(
        "testuser",
        "Password123"
    )

    expect(
        page.get_by_role(
            "heading",
            name="Dashboard"
        )
    ).to_be_visible()
Professional benefit:

If the login button changes later, you can update the locator in the page object instead of modifying every test that uses login.

📊 10. Advanced Level — Data-Driven Testing

Professional test suites frequently execute the same scenario against multiple data sets.

pytest parameterization
import pytest

from playwright.sync_api import Page, expect


@pytest.mark.parametrize(
    "username,password",
    [
        ("user1", "Password1"),
        ("user2", "Password2"),
        ("user3", "Password3")
    ]
)
def test_login_users(
    page: Page,
    username,
    password
):

    page.goto(
        "https://your-app.example/login"
    )

    page.get_by_label(
        "Username"
    ).fill(username)

    page.get_by_label(
        "Password"
    ).fill(password)

    page.get_by_role(
        "button",
        name="Login"
    ).click()

    expect(
        page.get_by_role(
            "heading",
            name="Dashboard"
        )
    ).to_be_visible()
🔑 11. Advanced Level — Reuse Authentication

Logging in before every test can make a large suite repetitive. Playwright supports saving authentication state so tests can start from an already authenticated session.

Authentication Setup
from playwright.sync_api import sync_playwright


with sync_playwright() as p:

    browser = p.chromium.launch()

    context = browser.new_context()

    page = context.new_page()

    page.goto(
        "https://your-app.example/login"
    )

    page.get_by_label("Username").fill("testuser")

    page.get_by_label("Password").fill("Password123")

    page.get_by_role(
        "button",
        name="Login"
    ).click()

    page.wait_for_url("**/dashboard")

    context.storage_state(
        path="auth.json"
    )

    browser.close()
Use case:

Later tests can create a browser context using the saved authentication state instead of repeating the complete login flow.

👥 12. Advanced Level — Multiple Users in One Test

A real application may require testing interactions between two different users, such as an administrator and a customer.

Admin Context
↔
Application
↔
Customer Context
Multi-User Test
def test_admin_customer_workflow(browser):

    admin_context = browser.new_context()

    customer_context = browser.new_context()

    admin_page = admin_context.new_page()

    customer_page = customer_context.new_page()

    admin_page.goto(
        "https://your-app.example/admin"
    )

    customer_page.goto(
        "https://your-app.example"
    )

    # Admin performs an operation

    # Customer verifies the result

    admin_context.close()

    customer_context.close()
🔌 13. Professional Level — API + UI Testing

Professional automation can combine API setup with UI validation. For example, create test data through an API and then verify that the same data appears in the UI.

API + UI Example
def test_api_data_visible_in_ui(
    page,
    request
):

    response = request.post(
        "https://api.example.test/users",
        data={
            "name": "John Doe",
            "email": "john@example.test"
        }
    )

    assert response.ok

    page.goto(
        "https://your-app.example/users"
    )

    expect(
        page.get_by_text("John Doe")
    ).to_be_visible()
Why use this approach?

API calls can often prepare test data faster than navigating through many UI screens, while the UI test verifies the user-facing behavior.

🌐 14. Professional Level — Network Mocking

Playwright can intercept requests and provide controlled responses. This is useful when a test needs predictable backend data.

API Mock Example
def test_mock_products(page):

    page.route(
        "**/api/products",
        lambda route: route.fulfill(
            status=200,
            content_type="application/json",
            body='''{
                "products": [
                    {
                        "id": 1,
                        "name": "Test Laptop",
                        "price": 999
                    }
                ]
            }'''
        )
    )

    page.goto(
        "https://your-app.example/products"
    )

    expect(
        page.get_by_text("Test Laptop")
    ).to_be_visible()
📸 15. Professional Level — Screenshots and Debugging
Screenshot
page.screenshot(
    path="screenshots/homepage.png",
    full_page=True
)
Save HTML
html = page.content()

with open(
    "page-source.html",
    "w",
    encoding="utf-8"
) as file:

    file.write(html)
Professional debugging approach:

When a test fails, capture useful evidence such as screenshots, traces, logs and relevant application state rather than simply rerunning the test.

⚡ 16. Professional Level — Parallel Test Execution

Large test suites can take considerable time when every test runs sequentially. pytest-based Playwright projects can use parallel execution tools such as pytest-xdist.

Install xdist
pip install pytest-xdist
Run Tests in Parallel
pytest -n 4
Important:

Parallel tests should be designed so that they do not unintentionally depend on shared mutable data or the state created by another test.

🌀 17. Professional Level — Async Playwright

Python Playwright provides both synchronous and asynchronous APIs. The async API can be useful when your application or test architecture already uses asyncio.

async_test.py
import asyncio

from playwright.async_api import async_playwright


async def main():

    async with async_playwright() as p:

        browser = await p.chromium.launch()

        page = await browser.new_page()

        await page.goto(
            "https://example.com"
        )

        print(await page.title())

        await browser.close()


asyncio.run(main())
🏆 18. Complete Professional Test Example

The following example combines fixtures, page objects, locators and assertions into a maintainable test flow.

pages/login_page.py
from playwright.sync_api import Page


class LoginPage:

    def __init__(self, page: Page):

        self.page = page

        self.username = page.get_by_label(
            "Username"
        )

        self.password = page.get_by_label(
            "Password"
        )

        self.login_button = page.get_by_role(
            "button",
            name="Login"
        )

        self.dashboard = page.get_by_role(
            "heading",
            name="Dashboard"
        )


    def open(self):

        self.page.goto(
            "https://your-app.example/login"
        )


    def login(self, username, password):

        self.username.fill(username)

        self.password.fill(password)

        self.login_button.click()
tests/test_login.py
from playwright.sync_api import Page, expect

from pages.login_page import LoginPage


def test_valid_login(page: Page):

    login = LoginPage(page)

    login.open()

    login.login(
        "testuser",
        "Password123"
    )

    expect(
        login.dashboard
    ).to_be_visible()
What makes this professional?

The test focuses on business behavior while the page object owns the UI locators and interactions. This separation makes the suite easier to maintain as the application grows.

📈 19. From Beginner to Professional
Level Focus Typical Skills Example
Beginner Browser Basics Browser, page, navigation Open website
Beginner+ Locators Role, label, text Login form
Intermediate Assertions Visibility, text, URL Form validation
Intermediate+ Fixtures pytest fixtures Reusable page
Advanced Architecture Page Object Model Enterprise framework
Advanced+ Integration API + UI, auth, network Business workflows
Professional Scale Parallelism, CI/CD, reporting Production automation
📁 20. Professional Playwright Project Structure
Enterprise-style Structure
playwright-project/
│
├── .venv/
│
├── tests/
│   ├── test_login.py
│   ├── test_registration.py
│   ├── test_search.py
│   └── test_checkout.py
│
├── pages/
│   ├── login_page.py
│   ├── home_page.py
│   ├── product_page.py
│   └── checkout_page.py
│
├── fixtures/
│   └── test_fixtures.py
│
├── test-data/
│   ├── users.json
│   └── products.json
│
├── utils/
│   ├── api_helper.py
│   └── data_helper.py
│
├── screenshots/
│
├── reports/
│
├── requirements.txt
│
└── README.md
⭐ 21. Professional Best Practices
✓ Use meaningful test names
✓ Prefer stable user-facing locators
✓ Use web-first assertions
✓ Avoid unnecessary hard waits
✓ Keep test data separate
✓ Use fixtures for reusable setup
✓ Use Page Object Model where appropriate
✓ Keep credentials outside source code
✓ Capture useful failure evidence
✓ Design tests for safe parallel execution
🎯 22. Final Learning Path
Browser Launch
→
Locators
→
Assertions
→
Fixtures
→
Page Objects
→
API + UI
→
CI/CD

Your First Test Is the Beginning

A professional Playwright engineer does not simply know how to click a button with automation. The goal is to build reliable, readable, maintainable and scalable tests that validate real business behavior.

Recommended next lesson:

Playwright Locators & Selectors — learn get_by_role(), get_by_label(), get_by_text(), get_by_placeholder(), CSS selectors, XPath, locator chaining and strategies for choosing stable locators.