← Larascraper 11 / 17

Conditional flow

Conditional flow

The action chain is a small query builder for the page. Besides the linear actions, you can branch and loop, and the condition is evaluated by Puppeteer against the live page at runtime.

PHP is not inside the browser, so you do not write the check as a closure over the DOM. You describe what to check with the Condition helper, and Node does the checking.

when

Runs a branch only if a condition holds. The closure receives a sub-builder you chain actions on, exactly like Laravel's $query->when():

use EduLazaro\Larascraper\Support\Condition;

protected function handle(string $url): ScraperResponse
{
    return $this->scrape($url)
        ->when(
            Condition::selectorExists('#cookie-banner'),
            fn ($b) => $b->click('#accept-cookies'),
        )
        ->crawl(ProductCrawler::class)
        ->run();
}

The else branch is optional, and rarely needed, since usually you just continue the main chain afterwards:

->when(
    Condition::textContains('No results', '.notice'),
    fn ($b) => $b->click('#clear-filters'),       // then
    fn ($b) => $b->waitForSelector('.product'),   // else
)

Guarding an action with when() is what keeps an optional step from breaking a run. A click('#accept-cookies') on a page that has no banner is a failed action and therefore a failed fetch.

repeatUntil

Repeats a branch until a condition holds, for "retry until it works" flows such as solving a captcha or paginating.

It is always bounded. max defaults to 5 and is clamped to at least 1; there is no unbounded mode. delay throttles the time between iterations so you do not hammer a server:

->repeatUntil(
    Condition::selectorMissing('#captcha-img'),   // stop once the captcha is gone
    fn ($b) => $b
        ->solveCaptcha('#captcha-img', '#captcha-input')
        ->clickAndWait('#verify'),
    max: 6,
    delay: 1500,
)

A failed attempt is not a failed run

If the body throws mid-iteration, a transient page where an expected element is missing, or a gotoAttr() that finds no PDF this pass, that counts as one failed attempt. The loop re-checks the condition and retries on the next pass, bounded by max, instead of aborting the whole fetch. Only when every attempt is exhausted and the condition still never holds is the last error surfaced.

That is what makes loops resilient when each pass navigates to freshly generated state: a new captcha image, a re-issued session, a token that expires between attempts.

The conditions

True when
Condition::selectorExists($selector) an element matching the selector exists
Condition::selectorMissing($selector) no element matching the selector exists
Condition::textContains($text, $selector = null) the text is found, in $selector or in the whole page
Condition::urlContains($text) the current URL contains the substring
Condition::captured() a file or binary has been captured, to pair with capture() in a loop

Choosing the right condition

Write the condition as the outcome you want, not as the step you just performed. selectorMissing('#captcha-img') says "the captcha is gone", which is true exactly when the attempt worked. Something like "the submit button was clicked" is true whether or not anything came of it, and the loop exits on the first pass having achieved nothing.

The same applies to captured(): it becomes true the moment a file is grabbed, so a download loop stops on success and gives up after max attempts, with no bookkeeping of your own.