DataLens

Clicking, typing, and filling forms

Interact with pages like a user, filling and submitting search forms, clicking buttons, dismissing cookie banners that block clicks, and checking whether controls are enabled.

Level: Intermediate · 7 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

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']

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".

Key idea

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

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Handling pagination and infinite scroll →