2025, Oct 05 11:00

How to set the same value on multiple HTML select elements in Playwright Python using reliable waits

Learn how to set the same option on multiple HTML select elements in Playwright Python. Use query_selector_all, locators and wait_for to avoid timing errors.

Setting the same value across multiple HTML select elements is a trivial-sounding task that can turn frustrating when you bump into API mismatches or timing issues. If you are using Playwright’s Python sync API and need to flip every select on the page to a specific option, here’s how to do it without surprises.

Reproducing the issue

The page contains multiple select elements, for example:

<select class="sc-fzXfMv sc-fzXfMw dRuVWn">
  <option value="json">JSON</option>
  <option value="text">Text</option>
  <option value="raw">Hex</option>
</select>

The initial attempts to iterate and set the option ran into errors like missing attributes and non-iterable locators:

# Attempt 1
pg.locator("select").all()    # AttributeError: 'Locator' object has no attribute 'all'

# Attempt 2
pg.query_selector_all("select")    # AttributeError: 'Page' object has no attribute 'query_selector_all'

# Attempt 3
for ddl in pg.locator("select"):
    ddl.select_option("raw")        # TypeError: 'Locator' object is not iterable

# Attempt 4
sels = pg.locator("select")
for s in sels:
    s.select_option("raw")          # same error, not iterable

What’s really going on

The core of the problem is twofold. First, a Locator is not something you can loop over directly; it represents a query that can match many elements, but it isn’t an iterable itself. Second, timing matters. If the selects are not yet present or visible, operations that require them to exist or be interactable will fail. That is why it’s important to either fetch a concrete list of elements up front or wait appropriately before expanding a locator into a list.

Two working approaches

The first approach is to fetch a concrete list of select elements and iterate over it. This uses query_selector_all and selects the desired option on each element:

dropdown_nodes = pg.query_selector_all("select")
for node in dropdown_nodes:
    node.select_option("raw")

An alternative is to stay with locators and convert them to a list only after waiting. Waiting ensures the elements are ready before you iterate over them. The following uses wait_for followed by all:

menus = pg.locator("select")
menus.first.wait_for(state="attached")
for item in menus.all():
    item.select_option("raw")

It can also be practical to rely on the default visible wait predicate. This variation waits for the first matching select to become visible before enumerating all of them:

lists = pg.locator("select")
lists.first.wait_for()
for dd in lists.all():
    dd.select_option("raw")

If your issue turned out to be that the elements weren’t visible yet, the wait step is what unlocks reliability.

Why this matters

Playwright’s sync API gives you powerful auto-waiting semantics, but you still need to pick the right level of abstraction. Iterating a Locator directly won’t work, and trying to rush a selection before the DOM is ready leads to avoidable errors. By either enumerating a concrete list with query_selector_all or waiting on a Locator and then using all, you keep the code explicit and robust. If multiple elements match, using first for wait_for also avoids trouble in strict scenarios.

Conclusion and practical advice

When you need to set the same value across several select elements, use one of the two safe patterns. Either grab a list of elements with query_selector_all and loop through it, or wait with a Locator and then expand it via all before iterating. If the page builds content asynchronously, add wait_for ahead of enumeration, and consider relying on the default visible predicate to ensure the elements are ready for interaction. These choices keep the code concise and predictable while staying aligned with Playwright’s intended usage.

The article is based on a question from StackOverflow by Dawg and an answer by 0ro2.