Skip to content

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:

python
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 ​

python
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:

MethodPurpose
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 ​

python
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 ​

python
# 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:

python
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:

python
# 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;
  • fill clears then inputs; press is for Enter and other keys;
  • Three screenshot modes: viewport / full_page=True / element-level;
  • headless=False + slow_mo is 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 ​

  1. Write a script that visits https://playwright.dev/python, then takes screenshots of the header element and the full page; compare the two.
  2. Open two contexts simultaneously browsing different sites, alternating prints of each side's title to experience "parallel sessions."
  3. Use both new_context() and launch_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.