2025, Dec 21 17:00

Wait for Either Element in Playwright Python Using or_ and first to Handle Blocking and Branching UI

Learn how to handle branching UI flows in Playwright Python by waiting for either of two locators with or_ and first. Avoid blocking waits and race conditions.

When UI flows branch at runtime, a common need in Playwright is to proceed as soon as one of several elements appears. The catch: a locator in Playwright is blocking by design, so a straight check for element A will wait until it appears or times out, never giving element B a chance to “win the race.” Here’s how to express the intent “wait for A or B” cleanly in Python Playwright.

The blocking pitfall in practice

The intent is to move forward as soon as either the primary control or an alternative dialog shows up. A straightforward approach that checks one locator and then the other does not model this intent and will block on the first check.

compose_btn = page.get_by_role("button", name="New")
security_popup = page.get_by_text("Confirm security settings")

# Intended: proceed when either shows up.
# Reality: this call waits exclusively for the button,
# and will timeout if the dialog appears instead.
expect(compose_btn).to_be_visible()

# This branch would only run if the first expectation completed.
if security_popup.is_visible():
    page.get_by_role("button", name="Dismiss").click()
compose_btn.click()

Why this happens

By nature, a locator blocks and waits until the element is located or it times out. Sequential checks aren’t concurrent and don’t express “either/or” readiness. You end up waiting on the wrong branch when the UI takes the alternative path.

The precise way to wait for either element

Playwright provides or_ for exactly this use case. It builds a locator that matches elements from either of the two inputs. As the documentation notes, it was added in v1.33 and can match one or both sets of elements. If both appear, strictness may be violated; in that case, use first to narrow to a single match.

Creates a locator matching all elements that match one or both of the two locators. Note that when both locators match something, the resulting locator will have multiple matches, potentially causing a locator strictness violation.

Here’s a concise pattern that waits for the first of two alternatives, then acts based on what actually appeared:

compose_btn = page.get_by_role("button", name="New")
security_popup = page.get_by_text("Confirm security settings")

# Wait for either the button or the dialog to become visible.
expect(compose_btn.or_(security_popup).first).to_be_visible()

# If the dialog won the race, handle it and continue.
if security_popup.is_visible():
    page.get_by_role("button", name="Dismiss").click()

# Proceed with the original intent.
compose_btn.click()

Multiple alternatives

If you need more than two conditions, you can chain or_ calls. This scales to three, four, or more alternatives in a single expression. It’s also possible to rely on the CSS comma operator, although it’s not user-facing.

Why it matters

Real-world flows often diverge: a security prompt may appear before the main action, or a primary control is briefly gated by another UI. Expressing a single “either/or” wait helps tests track the product’s actual behavior without brittle timeouts or complicated control flow. Using or_ with first aligns the test with the UI’s branching nature while avoiding strictness issues when both elements show up.

Closing advice

Model race conditions directly with or_ to wait on alternative outcomes, and include first to keep strictness happy if both candidates surface. If your scenario has more than two possibilities, chain or_ as needed. This keeps your Playwright tests readable, resilient, and aligned with how the UI really behaves under varying conditions.