The Most Important Playwright Relationship
Playwright automation is easier to understand when you visualize the hierarchy: Browser → BrowserContext → Page. A browser represents the browser process, a context provides an isolated browser session, and a page represents a tab within that context.
Browser
Controls the browser process, such as Chromium, Firefox or WebKit.
BrowserContext
Provides an isolated session with its own cookies, local storage and session state.
Page
Represents a tab where you navigate, locate elements, interact and validate the UI.
Start with the simplest possible Playwright example: launch a browser and create a page.
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()
The Browser object represents the browser process launched by Playwright.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
chromium = p.chromium.launch()
firefox = p.firefox.launch()
webkit = p.webkit.launch()
chromium.close()
firefox.close()
webkit.close()
You normally do not need to launch all three browsers inside one test. A test suite can be configured to execute scenarios against different browser engines.
A BrowserContext is an isolated browser session. Think of it like a fresh browser profile created inside the same browser process.
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://example.com")
print(page.title())
context.close()
browser.close()
The context owns the session state, while the page is where your web interactions happen.
A Page represents a browser tab. Most day-to-day Playwright automation happens through the Page object.
page.goto("https://example.com")
print(page.url)
print(page.title())
page.reload()
page.go_back()
page.go_forward()
page.screenshot(
path="homepage.png"
)
One BrowserContext can contain multiple Page objects. This is useful when your application opens multiple tabs.
browser = p.chromium.launch()
context = browser.new_context()
page1 = context.new_page()
page2 = context.new_page()
page1.goto("https://example.com")
page2.goto("https://example.org")
print(page1.title())
print(page2.title())
context.close()
browser.close()
Suppose clicking a button opens a new tab. Playwright can wait for the new Page and then interact with it.
with page.expect_popup() as popup_info:
page.get_by_role(
"link",
name="Open Report"
).click()
new_page = popup_info.value
new_page.wait_for_load_state()
print(new_page.title())
new_page.close()
This pattern can be useful for payment pages, reports, external authentication pages, documentation links or other workflows that open a separate tab.
One of the most useful BrowserContext features is session isolation. You can create separate contexts for different users.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False
)
# Admin session
admin_context = browser.new_context()
admin_page = admin_context.new_page()
# Customer session
customer_context = browser.new_context()
customer_page = customer_context.new_page()
admin_page.goto(
"https://your-app.example/admin"
)
customer_page.goto(
"https://your-app.example"
)
# Each context has separate session state.
admin_context.close()
customer_context.close()
browser.close()
The admin and customer sessions can maintain different cookies, storage and authentication state without requiring two separate browser processes.
Cookies belong to the browser context. This makes contexts useful for testing separate authenticated sessions.
context = browser.new_context()
page = context.new_page()
page.goto(
"https://your-app.example"
)
cookies = context.cookies()
print(cookies)
context.add_cookies([
{
"name": "test_cookie",
"value": "12345",
"domain": "your-app.example",
"path": "/"
}
])
When you create a completely new BrowserContext, it starts with independent session state unless you explicitly provide state such as saved authentication information.
BrowserContext isolation also helps when applications store client-side data such as local storage.
context = browser.new_context()
page = context.new_page()
page.goto(
"https://your-app.example"
)
page.evaluate("""
localStorage.setItem(
'userRole',
'admin'
)
""")
value = page.evaluate("""
localStorage.getItem('userRole')
""")
print(value)
You can save authentication state from a context and reuse it when creating another context.
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"
)
context.close()
Reuse the Authentication State
context = browser.new_context(
storage_state="auth.json"
)
page = context.new_page()
page.goto(
"https://your-app.example/dashboard"
)
Authentication state can reduce repeated login setup when many tests need an already authenticated session.
A context can be configured with options that describe the environment your test should simulate.
context = browser.new_context(
viewport={
"width": 1280,
"height": 720
},
locale="en-IN",
timezone_id="Asia/Kolkata",
color_scheme="light",
user_agent="Playwright-Test-Agent"
)
page = context.new_page()
page.goto(
"https://your-app.example"
)
📐 Viewport
Test the application at a specific browser viewport size.
🌍 Locale
Simulate a specific locale for localization testing.
🕐 Timezone
Test date and time behavior using a selected timezone.
🎨 Color Scheme
Test light or dark color-scheme behavior.
Playwright can create browser contexts with device characteristics from its device descriptors.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
mobile = p.devices["iPhone 13"]
context = browser.new_context(
**mobile
)
page = context.new_page()
page.goto(
"https://your-app.example"
)
print(page.title())
context.close()
browser.close()
A QA team can use device contexts to check responsive layouts and mobile-oriented browser behavior without physically changing the development machine.
Consider a business application where a customer submits a request and an administrator approves it.
def test_customer_admin_workflow(browser):
# -----------------------------
# Customer Context
# -----------------------------
customer_context = browser.new_context()
customer_page = customer_context.new_page()
customer_page.goto(
"https://your-app.example/login"
)
customer_page.get_by_label(
"Username"
).fill("customer")
customer_page.get_by_label(
"Password"
).fill("CustomerPass"
)
customer_page.get_by_role(
"button",
name="Login"
).click()
# Customer submits request
customer_page.get_by_role(
"button",
name="New Request"
).click()
customer_page.get_by_label(
"Request"
).fill("Leave Request")
customer_page.get_by_role(
"button",
name="Submit"
).click()
# -----------------------------
# Admin Context
# -----------------------------
admin_context = browser.new_context()
admin_page = admin_context.new_page()
admin_page.goto(
"https://your-app.example/login"
)
admin_page.get_by_label(
"Username"
).fill("admin")
admin_page.get_by_label(
"Password"
).fill("AdminPass"
)
admin_page.get_by_role(
"button",
name="Login"
).click()
# Admin approves request
admin_page.get_by_text(
"Leave Request"
).click()
admin_page.get_by_role(
"button",
name="Approve"
).click()
# -----------------------------
# Customer verifies
# -----------------------------
customer_page.reload()
customer_page.get_by_text(
"Approved"
).is_visible()
customer_context.close()
admin_context.close()
For production test suites, credentials should be supplied securely rather than stored directly in the test source.
| Object | Represents | Main Responsibility | Typical Methods |
|---|---|---|---|
| Browser | Browser process | Launch and manage browser | new_context(), close() |
| BrowserContext | Isolated session | Cookies, storage, session configuration | new_page(), cookies(), storage_state() |
| Page | Browser tab | Navigation and UI interaction | goto(), click(), fill(), screenshot() |
📄 Multiple Pages
Use multiple pages when you need multiple tabs sharing the same browser session.
context = browser.new_context()
page1 = context.new_page()
page2 = context.new_page()
🔐 Multiple Contexts
Use multiple contexts when you need independent browser sessions.
context1 = browser.new_context()
context2 = browser.new_context()
New tab? Think Page.
New independent user session?
Think BrowserContext.
Screenshots belong to the Page because the Page represents the visible browser tab.
context = browser.new_context(
viewport={
"width": 1440,
"height": 900
}
)
page = context.new_page()
page.goto(
"https://your-app.example"
)
page.screenshot(
path="homepage.png",
full_page=True
)
context.close()
from playwright.sync_api import (
sync_playwright,
expect
)
def test_browser_context_page():
with sync_playwright() as p:
# =================================
# 1. Browser
# =================================
browser = p.chromium.launch(
headless=False
)
# =================================
# 2. Browser Context
# =================================
context = browser.new_context(
viewport={
"width": 1280,
"height": 720
}
)
# =================================
# 3. Page
# =================================
page = context.new_page()
# =================================
# 4. Navigate
# =================================
page.goto(
"https://example.com"
)
# =================================
# 5. Validate
# =================================
expect(page).to_have_title(
"Example Domain"
)
# =================================
# 6. Screenshot
# =================================
page.screenshot(
path="example.png"
)
# =================================
# 7. Cleanup
# =================================
context.close()
browser.close()
Browser starts the browser process → Context creates an isolated session → Page represents the tab → Page methods perform navigation and UI actions → Assertions validate the result.
Remember This Rule
Browser = the browser process.
BrowserContext = an isolated browser
session.
Page = a tab inside that session.
Playwright Locators & Selectors — learn get_by_role(), get_by_label(), get_by_text(), get_by_placeholder(), CSS selectors, XPath, locator chaining and stable locator strategies.