protonemedia/inertiajs-events-laravel-dusk

Proposal for a method without any special JavaScript code

Opened this issue · 3 comments

Hi! I just wanted to propose a method for this package that does not require any special JavaScript code to work. This method uses the WebDriver's executeAsyncScript function to attach an actual event listener.

This is the basic function:

use Facebook\WebDriver\Exception\ScriptTimeoutException;
use Facebook\WebDriver\Exception\TimeoutException;
use Laravel\Dusk\Browser;

function (string $name, ?int $seconds = null): Browser {
    /** @var \Laravel\Dusk\Browser $this */
    $seconds = is_null($seconds) ? $this::$waitSeconds : $seconds;

    try {
        $this->driver->manage()
            ->timeouts()
            ->setScriptTimeout($seconds);

        $this->driver->executeAsyncScript("
            const done = () => arguments[0]();
            document.addEventListener('inertia:{$name}', done, { once: true });
        ");
    } catch (ScriptTimeoutException $e) {
        throw new TimeoutException("Waited {$seconds} seconds for an Inertia '{$name}' event.");
    }

    return $this;
}

I created a mixin class that can be used with Browser::mixin():

<?php

use Closure;
use Facebook\WebDriver\Exception\ScriptTimeoutException;
use Facebook\WebDriver\Exception\TimeoutException;
use Laravel\Dusk\Browser;

/** @mixin \Laravel\Dusk\Browser */
class InertiaEventsMixin
{
    public function waitForInertia(?int $seconds = null): Closure
    {
        return fn (?int $seconds = null) => $this->waitForInertiaEvent('navigate', $seconds);
    }

    public function waitForInertiaError(?int $seconds = null): Closure
    {
        return fn (?int $seconds = null) => $this->waitForInertiaEvent('error', $seconds);
    }

    public function waitForInertiaFinish(?int $seconds = null): Closure
    {
        return fn (?int $seconds = null) => $this->waitForInertiaEvent('finish', $seconds);
    }

    public function waitForInertiaInvalid(?int $seconds = null): Closure
    {
        return fn (?int $seconds = null) => $this->waitForInertiaEvent('invalid', $seconds);
    }

    public function waitForInertiaSuccess(): Closure
    {
        return fn (?int $seconds = null) => $this->waitForInertiaEvent('success', $seconds);
    }

    public function waitForInertiaEvent(): Closure
    {
        return function (string $name, ?int $seconds = null): Browser {
            /** @var \Laravel\Dusk\Browser $this */
            $seconds = is_null($seconds) ? $this::$waitSeconds : $seconds;

            try {
                $this->driver->manage()
                    ->timeouts()
                    ->setScriptTimeout($seconds);

                $this->driver->executeAsyncScript("
                    const done = () => arguments[0]();
                    document.addEventListener('inertia:{$name}', done, { once: true });
                ");
            } catch (ScriptTimeoutException $e) {
                throw new TimeoutException("Waited {$seconds} seconds for an Inertia '{$name}' event.");
            }

            return $this;
        };
    }
}

Then in my testing service provider:

\Laravel\Dusk\Browser::mixin(new InertiaEventsMixin);

I'd be happy to provide a pull request if you are interested.

Anyways, thanks for maintaining this package!

Cheers from Germany,
Michael

Hi Michael, this looks great! This would eliminate some configuration setup while still providing the same functionality. If you would create PR, I'd definitely merge it. Thanks!

Hey Pascal, thanks for the reply! In the meantime I contributed the waitForEvent() method to Dusk, which makes the process even easier (but would require Dusk >= v6.23.0).

Personally I switched from using a mixin to extending the Dusk Browser class with a custom InertiaBrowser class, in order to get IDE support while writing tests. In your BrowserTestCase base class you would use a ProvidesInertiaBrowser trait, which implements the newBrowser() method returning an InertiaBrowser instance.

I created a Gist to show what I mean: https://gist.github.com/michaelhue/6d62cd0470ecac2b21d296151e25fc76

What do you think? Would you prefer the inheritance or mixin approach?

Requiring Dusk >= v6.23.0 is fine! I agree with you on IDE support. It's something I find frustrating with macros as well. Maybe you could implement the methods in a trait, so people can create their own custom browser and use the trait.