The idea
Much data only appears after you do something: search for a term, pick a filter, accept a banner, click "Show more". Playwright's actions (fill, click, press, check, select_option) behave like a real user, including auto-waiting until the element is visible, enabled and not covered by something else.
Filling and submitting a search form
Some data only appears after you interact with the page, like the results of a search. A scraper can type into the search box and submit it, exactly as a person would:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/")
search_box = page.get_by_placeholder("Search products")
search_box.fill("galaxy")
search_box.press("Enter")
print(page.url)
print(page.locator(".result-count").inner_text())
print(page.locator(".product-card .title").all_inner_texts())
browser.close()
https://nusantara-gadget.test/search?q=galaxy
5 results
['Galaxy A55 5G', 'Galaxy S24', 'Galaxy Buds FE', 'Galaxy Watch6', 'Galaxy Fit3']
fill(text)clears the field and types the text in one go.press("Enter")presses a key while the field is focused, which submits the form, as for a person.- Clicking the submit button works too:
page.get_by_role("button", name="Search").click().
Notice the URL: /search?q=galaxy. The form is a plain GET form, so the search term ends up in the URL. Once you've seen that, you could goto("/search?q=...") directly for every term, skipping the form entirely. Always look for shortcuts like this.
fill sets the value instantly. Some sites react to individual keystrokes (autocomplete boxes); for those, press_sequentially("galaxy", delay=100) types one character at a time.
When something is in the way
The deals page opens with a cookie banner covering the page. A click on a deal link fails, because a real user couldn't click it either:
from playwright.sync_api import TimeoutError as PlaywrightTimeout
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/deals")
try:
page.locator(".deal-name").first.click(timeout=3000)
except PlaywrightTimeout as error:
print(str(error).splitlines()[-2].strip())
page.get_by_role("button", name="Reject").click() # dismiss the banner (choosing the privacy-friendly option)
page.locator(".deal-name").first.click()
print(page.url)
print(page.locator(".product-title").inner_text())
browser.close()
- <div id="cookie-banner"> intercepts pointer events
https://nusantara-gadget.test/product/2
Redmi Note 13 Pro
Playwright's error log names the culprit: the cookie-banner element intercepts pointer events. Dismiss it first, then carry on. (click(force=True) skips this check, but that's like clicking through a wall: it hides real problems. Dismiss overlays instead.)
The deal link click also waited the 2.5 seconds for the deals to load: auto-waiting applies to clicks too.
Buttons that change the page
Not every click navigates. "Add to cart" updates the page in place:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://nusantara-gadget.test/product/1")
add = page.get_by_role("button", name="Add to cart")
add.click()
add.click()
print("cart:", page.locator("#cart-count").inner_text())
page.goto("https://nusantara-gadget.test/product/2")
button = page.get_by_role("button", name="Add to cart")
print("enabled:", button.is_enabled(), "|", page.locator(".stock").inner_text())
browser.close()
cart: 2
enabled: False | Sold out
Product 2 is sold out, so its button is disabled. Clicking a disabled button would wait for it to become enabled, then time out. Check is_enabled() (or is_visible(), is_checked()) first when a control may legitimately be unavailable; here, a disabled button is itself useful data: "out of stock".
Interact like a user: fill + press("Enter") or click the button; dismiss overlays before clicking what's behind them; check is_enabled() when a control may be disabled. And look at the URL afterwards: forms often reveal a URL pattern you can use directly.
Other controls
Forms have more than text boxes. Each kind of control has its own method, and they all work the same way, on a locator:
page.get_by_label("Remember me").check() # checkboxes and radio buttons
page.get_by_label("Sort by").select_option("price-asc") # <select> dropdowns, by value or label
page.get_by_label("Quantity").fill("3") # number inputs are filled with strings
page.keyboard.press("Escape") # keys without a specific element
All of them auto-wait and work with any locator.
Why this matters for clean code
Interaction steps are where scrapers get long and repetitive. Wrapping each one in a small, named function (search(page, term), dismiss_cookies(page), add_to_cart(page)) turns the main script into a readable list of steps, and gives you one place to fix each step when the site changes.
Recap
fill(text),press("Enter"),click();press_sequentiallyfor keystroke-by-keystroke typing.- GET forms put the inputs in the URL: often you can skip the form.
- Overlays like cookie banners block clicks; dismiss them (prefer "Reject" when you're asked about cookies), don't
force=True. - Disabled controls make clicks time out; check
is_enabled()first. check(),select_option(),page.keyboard.press()for other controls.