ddp_utils.browser.facade.waits

import ddp_utils.browser.facade.waits

Synchronous backend-aware wait primitives.

class ddp_utils.browser.facade.waits.BrowserWait(browser: Browser)

Bases: WaitScopeMixin

Wait for arbitrary and browser-specific conditions synchronously.

Parameters

Name

Type

Description

browser

Browser

Owning browser facade.

Examples

Wait for any project predicate without backend branching:

value = browser.wait.until(lambda _: read_ready_value(), timeout=15)

Bind wait operations to one browser session.

Parameters

Name

Type

Description

browser

Browser

Owning browser facade.

Examples

Browser constructs one stable wait helper:

wait = BrowserWait(browser)
until(predicate: Callable[['Browser'], T], *, timeout: float, interval: float = 0.1, message: str | None = None) → T

Wait until a callable returns a truthy value.

Selenium sessions use WebDriverWait.until. Playwright sessions use their native event-loop-aware timeout sleep between observations.

Parameters

Name

Type

Description

predicate

Callable[['Browser'], T]

Callable receiving the browser and returning a truthy final value when satisfied.

timeout

float

Seconds to wait. Zero performs one immediate observation.

interval

float

Poll interval in seconds for repeated observations.

message

str | None

Optional timeout explanation.

Returns

Type

Description

T

The truthy value returned by predicate.

Raises

Exception

Description

WaitTimeoutError

The condition remains false.

BrowserConfigurationError

Timeout or interval is negative.

Examples

Wait for a project state transition:

record = browser.wait.until(load_record, timeout=30)
until_not(predicate: Callable[['Browser'], Any], *, timeout: float, interval: float = 0.1, message: str | None = None) → bool

Wait until a callable returns a false value.

Parameters

Name

Type

Description

predicate

Callable[['Browser'], Any]

Callable receiving the browser.

timeout

float

Seconds to wait.

interval

float

Poll interval in seconds.

message

str | None

Optional timeout explanation.

Returns

Type

Description

bool

True when the callable becomes false.

Raises

Exception

Description

WaitTimeoutError

The condition remains true.

BrowserConfigurationError

Timeout or interval is negative.

Examples

Wait for a loading marker to disappear:

browser.wait.until_not(lambda b: b.find("div", attrs={"class": "loading"}))
first_of(conditions: Mapping[str, Callable[['Browser'], Any]], *, timeout: float, interval: float = 0.1, required: bool = True, message: str | None = None) → WaitMatch | None

Wait until the first ordered condition returns a truthy value.

Conditions are evaluated in mapping insertion order on every observation. They must only inspect state; perform clicks, navigation, CAPTCHA solving, and other side effects after this method returns.

Parameters

Name

Type

Description

conditions

Mapping[str, Callable[['Browser'], Any]]

Non-empty ordered mapping of names to callables receiving the active browser. A false value means not ready; a truthy value becomes WaitMatch.value.

timeout

float

Shared deadline in seconds for all conditions.

interval

float

Delay between complete observation cycles.

required

bool

Raise on timeout when True; return None otherwise.

message

str | None

Optional timeout explanation.

Returns

Type

Description

WaitMatch | None

Winning condition metadata, or None after an optional timeout.

Raises

Exception

Description

BrowserConfigurationError

Conditions are empty, unnamed, not callable, or timing values are negative.

WaitTimeoutError

No condition wins and required is True.

Exception

Any condition exception is propagated unchanged.

Examples

Return the first available navigation choice:

match = browser.wait.first_of(
    {
        "search": lambda b: b.find("a", text="Search", timeout=0),
        "login": lambda b: b.find("a", text="Login", timeout=0),
    },
    timeout=30,
)

Treat timeout as an ordinary missing state:

match = browser.wait.first_of(checks, timeout=5, required=False)
if match is None:
    return False

Pass existing bound methods without a lambda:

match = browser.wait.first_of(
    {"captcha": self.detect_captcha},
    timeout=20,
)
cycle(*, timeout: float, interval: float = 0.1, max_total: float | None = None) → WaitCycle

Create an extendable deadline for repeated state decisions.

Parameters

Name

Type

Description

timeout

float

Initial soft deadline in seconds.

interval

float

Default polling interval for WaitCycle.first_of().

max_total

float | None

Optional hard lifetime cap measured from cycle creation.

Returns

Type

Description

WaitCycle

A cycle bound to this browser wait facade.

Raises

Exception

Description

BrowserConfigurationError

A duration is negative or max_total is shorter than the initial timeout.

Examples

Extend the same decision cycle after servicing a checkpoint:

cycle = browser.wait.cycle(timeout=30, max_total=180)
match = cycle.first_of(checks)
if match.name == "captcha":
    solve(match.value)
    cycle.extend(60)
class ddp_utils.browser.facade.waits.WaitCycle(wait: BrowserWait, *, timeout: float, interval: float = 0.1, max_total: float | None = None)

Bases: object

Track one reusable browser decision deadline and state occurrences.

Parameters

Name

Type

Description

wait

BrowserWait

Browser wait facade that performs all observations.

timeout

float

Initial soft deadline in seconds.

interval

float

Default polling interval between observation cycles.

max_total

float | None

Optional non-extendable lifetime cap in seconds.

Examples

Continue after checkpoints and return on a business state:

cycle = browser.wait.cycle(timeout=30, max_total=180)
while (match := cycle.first_of(checks, required=False)) is not None:
    if match.name == "confirmation":
        match.value.click()
        cycle.extend(10)
        continue
    return match.value

Initialize monotonic soft and optional hard deadlines.

Parameters

Name

Type

Description

wait

BrowserWait

Browser wait facade used for observations.

timeout

float

Initial soft deadline in seconds.

interval

float

Default polling interval in seconds.

max_total

float | None

Optional hard lifetime cap in seconds.

Raises

Exception

Description

BrowserConfigurationError

Timing values are invalid.

Examples

Construct a bounded cycle through its public factory:

cycle = browser.wait.cycle(timeout=30, max_total=180)
property elapsed: float

Return monotonic seconds since cycle creation.

Returns

Non-negative elapsed seconds.

Examples

diagnostics.record("wait.elapsed", cycle.elapsed).

property remaining: float

Return seconds remaining before the effective deadline.

Returns

Non-negative remaining seconds, clamped by max_total.

Examples

if cycle.remaining < 5: cycle.extend(10).

property expired: bool

Return whether no effective wait time remains.

Returns

True when the soft or hard deadline is exhausted.

Examples

if cycle.expired: return False.

property attempts: int

Return cumulative successful decision observation attempts.

Returns

Attempts reported by matches returned during this cycle.

Examples

print(cycle.attempts).

count(name: str) → int

Return how often one named condition won this cycle.

Parameters

Name

Type

Description

name

str

Condition name supplied to first_of().

Returns

Type

Description

int

Zero or a positive occurrence count.

Examples

Stop repeated CAPTCHA checkpoints:

if cycle.count("captcha") >= 3:
    raise RuntimeError("CAPTCHA retry limit reached")
extend(seconds: float) → WaitCycle

Add seconds to the soft deadline without exceeding max_total.

Parameters

Name

Type

Description

seconds

float

Non-negative extension duration.

Returns

Type

Description

WaitCycle

This cycle for optional chaining.

Raises

Exception

Description

BrowserConfigurationError

seconds is negative.

Examples

Grant CAPTCHA solving one additional minute:

cycle.extend(60)
reset(timeout: float | None = None) → WaitCycle

Start a fresh soft deadline from now, respecting max_total.

Parameters

Name

Type

Description

timeout

float | None

New duration, or None to reuse the initial timeout.

Returns

Type

Description

WaitCycle

This cycle for optional chaining.

Raises

Exception

Description

BrowserConfigurationError

The requested duration is negative.

Examples

Give the page a fresh thirty seconds after confirmation:

cycle.reset(30)

Reuse the original cycle timeout:

cycle.reset()
first_of(conditions: Mapping[str, Callable[['Browser'], Any]], *, required: bool = True, message: str | None = None) → WaitMatch | None

Wait for one state using the cycle’s remaining budget.

Parameters

Name

Type

Description

conditions

Mapping[str, Callable[['Browser'], Any]]

Ordered named state probes accepted by BrowserWait.first_of().

required

bool

Raise when the cycle expires; return None otherwise.

message

str | None

Optional timeout explanation.

Returns

Type

Description

WaitMatch | None

Winning state with cycle-wide timing and occurrence metadata, or None after an optional timeout.

Raises

Exception

Description

WaitTimeoutError

No time remains or no condition wins while required is True.

BrowserConfigurationError

A condition contract is invalid.

Exception

Any condition exception is propagated unchanged.

Examples

Service a checkpoint and continue the same cycle:

match = cycle.first_of(checks, required=False)
if match and match.name == "confirmation":
    match.value.click()
    cycle.extend(10)

Stop normally when the shared budget expires:

if cycle.first_of(checks, required=False) is None:
    return False
class ddp_utils.browser.facade.waits.WaitMatch(name: str, value: Any, elapsed: float, attempts: int, occurrence: int = 1)

Bases: object

Describe the first condition accepted by a browser wait.

Parameters

Name

Type

Description

name

str

Stable caller-defined condition name.

value

Any

Truthy value returned by the winning condition.

elapsed

float

Monotonic seconds elapsed in the owning wait or cycle.

attempts

int

Observation cycles completed before the match.

occurrence

int

Number of times this named state won in the cycle.

Examples

Branch on the detected state and use its returned value:

if match.name == "confirmation":
    match.value.click()