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.