Chapter 2 · First Script and Sync API
Chapter goals: Master the sync API object model (Playwright → Browser → Context → Page), learn basic operations like goto/click/fill and screenshots, understand the headless and slow_mo debugging switches.
2.1 Sync API Object Model
The entry object returned by sync_playwright() has three layers of core objects hanging off it — understanding their responsibilities is the foundation for writing correct scripts:
from playwright.sync_api import sync_playwright
with sync_playwright() as p: # p: Playwright entry
browser = p.chromium.launch() # Browser: one browser process
context = browser.new_context() # Context: isolated "browser profile"
page = context.new_page() # Page: one tab
page.goto("https://example.com")
browser.close()- Browser: each
launch()call creates one real process with high startup cost; - BrowserContext: equivalent to a brand-new incognito profile (independent Cookie/cache/storage), with very low creation cost. This is the cornerstone of the official test isolation strategy — the pytest plugin in Chapter 6 creates "one new context per test";
- Page: one tab within a context, the primary entry point for daily operations.
How many contexts can you open?
Dozens or even hundreds — a context is just a lightweight isolation unit within the same process. To simulate "multiple users online simultaneously", open multiple contexts rather than multiple browsers.
2.2 The Three-Step Basic Operation
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# 1) Navigation: waits for load state by default
page.goto("https://playwright.dev/python")
# 2) Fill search box (fill clears first, then inputs)
search = page.get_by_role("button", name="Search")
search.click()
page.get_by_placeholder("Search docs").fill("locators")
# 3) Press Enter to trigger search and wait for results
page.get_by_placeholder("Search docs").press("Enter")
page.wait_for_load_state()
print(page.title())
browser.close()Quick reference for common interaction methods:
| Method | Purpose |
|---|---|
page.goto(url) | Navigate, default wait for load |
locator.click() | Click |
locator.fill(text) | Clear and type text (input/textarea) |
locator.press(key) | Key press, e.g. "Enter", "Control+a" |
locator.check() / uncheck() | Check/uncheck a checkbox |
locator.select_option("value") | Dropdown selection |
2.3 Screenshots: Full Page, Element, and Viewport
page.goto("https://playwright.dev/python")
# Viewport screenshot (default)
page.screenshot(path="viewport.png")
# Full-page scroll screenshot
page.screenshot(path="full.png", full_page=True)
# Screenshot only a specific element
page.locator("header").screenshot(path="header.png")Screenshots are the first piece of evidence when debugging failures; Chapter 16 shows how to attach them as CI failure artifacts.
2.4 headless vs headed
# Headless: no window, fast, CI standard
browser = p.chromium.launch()
# Headed: real window pops up, intuitive for local debugging
browser = p.chromium.launch(headless=False)The debugging gem slow_mo: forces a delay (in ms) after every operation so your eyes can keep up with what's happening on the page:
browser = p.chromium.launch(headless=False, slow_mo=1000)Another option often confused is launch_persistent_context — it merges user-data-dir and browser launch into one step, with Cookies that persist across runs. This is great for scraper login-state reuse; plain new_context() gives you a clean incognito profile every time:
# Persistent context: data written to local dir, survives restart
context = p.chromium.launch_persistent_context(
user_data_dir="./my-profile",
headless=False,
)Don't use persistent context in tests
Persistent contexts leak login state between test runs, polluting each other. E2E tests should always use clean new_context().
2.5 Chapter Summary
- Object hierarchy: Playwright → Browser (process) → Context (isolated profile) → Page (tab);
- Contexts are lightweight and fully isolated — the foundation for multi-user simulation and test isolation;
fillclears then inputs;pressis for Enter and other keys;- Three screenshot modes: viewport /
full_page=True/ element-level; headless=False + slow_mois the golden combo for local debugging; never use persistent context in tests.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. To simulate "two users each logged into their own account", the correct approach is?
2. What is the difference between page.screenshot(path="a.png") and full_page=True?
3. Why is launch_persistent_context discouraged in E2E tests?
4. What does the slow_mo=1000 parameter do?
🛠️ Hands-On Exercises
- Write a script that visits
https://playwright.dev/python, then takes screenshots of the header element and the full page; compare the two. - Open two contexts simultaneously browsing different sites, alternating prints of each side's title to experience "parallel sessions."
- Use both
new_context()andlaunch_persistent_context(user_data_dir="./t")to visit any site, set localStorage, close and reopen, then read the value back — observe the persistence difference.
With the object model understood, move on to Chapter 3: Locators and Strict Mode.