Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Inertiajs Events Laravel Dusk Laravel Package

protonemedia/inertiajs-events-laravel-dusk

Laravel Dusk helpers for testing Inertia.js events. Adds assertions and utilities so you can verify Inertia events fire as expected in browser tests, making it easier to cover Inertia-powered Laravel flows end to end.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a test-only helper. It does not ship anything to production; it exists so Dusk tests can wait on Inertia's client-side lifecycle events instead of falling back to ->pause() or guessing at selectors.

Install (dev only):

composer require --dev protonemedia/inertiajs-events-laravel-dusk

Version 1.3.0 changed the support floor — check you clear it before installing:

  • PHP 8.1+ (dropped < 8.1)
  • Laravel 9 or 10 (dropped < 9)
  • Laravel Dusk 7

If your app is still on Laravel 8 or PHP 8.0, Composer will refuse to resolve 1.3.0; pin ^1.2 instead (or skip the package and use the hand-rolled macro in Gotchas).

Laravel package discovery registers the service provider; there is nothing to publish for a basic setup. Confirm it appears in vendor/protonemedia/inertiajs-events-laravel-dusk/src/ and that your DuskTestCase is otherwise stock.

Where to look first:

  1. The README's usage block — confirm the exact macro/method names and event strings for your Inertia version. 1.3.0 was a framework/PHP support refresh, not an Inertia-facing one, so the README is still the source of truth for macro names.
  2. src/ — the service provider shows which Dusk Browser macros are registered (and therefore whether a wait is armed on page load or only when called).
  3. tests/ in the package — the clearest examples of intended usage, and now the best evidence that the macros still work against Dusk 7.
  4. composer.json — the require block is the authoritative support matrix (PHP ^8.1, laravel/framework ^9|^10, laravel/dusk ^7). Cite that, not the release notes, when debugging resolution errors.

First use case — replace a sleep after an Inertia link click:

$this->browse(function (Browser $browser) {
    $browser->visit('/dashboard')
        ->clickLink('Settings')
        ->waitForInertiaEvent('success')   // confirm exact macro name in README
        ->assertPathIs('/settings')
        ->assertSee('Account settings');
});

The win is determinism: the wait resolves when Inertia finishes the visit, not after an arbitrary timeout, so fast machines don't waste time and slow CI agents don't flake.

Implementation Patterns

Wait, then assert — always in that order. Treat the Inertia event as the barrier and put every DOM assertion after it. This is the single pattern that eliminates most Inertia/Dusk flake:

$browser->click('@save-button')
    ->waitForInertiaEvent('success')
    ->assertSee('Saved');

Distinguish the phases of a visit. Inertia emits a sequence (start/navigate, then progress, then success or error, then finish). Use:

  • success when you care that the page visited and the response was OK.
  • error when asserting a failed visit (500s, network failure).
  • finish when you only care the request settled and don't need success/failure semantics.
  • start/progress rarely — mostly for testing loading indicators.

Use it for validation round-trips too. An Inertia form POST that returns a 422 will settle as a non-success event; wait for the settle event and then assert on the validation message. This is far more reliable than waitForText('The email field is required') alone, because the wait fires even when the response is fast.

Wrap it in a trait or Page object. You will repeat this everywhere, so centralize:

trait WaitsForInertia
{
    protected function waitForVisit(Browser $browser, int $seconds = 10): Browser
    {
        return $browser->waitForInertiaEvent('success', $seconds);
    }
}
class SettingsPage extends Page
{
    public function save(): static
    {
        return $this->click('@save')->waitForVisit($this->browser);
    }
}

Pin the whole test stack, not just the package. With 1.3.0 the package now sits inside the Laravel 9/10 + Dusk 7 + PHP 8.1/8.2 window. Pin laravel/dusk ^7, phpunit and your CI PHP image to the same minors you develop on; the laravel/framework constraint moving is what will silently bump Dusk under you.

Pair with path/URL assertions. After an Inertia visit, assertPathIs() proves the navigation actually completed, while the event proves it finished. Together they catch "the click didn't register" versus "the response was a redirect back."

Parallel Dusk (dusk --parallel) works fine because each browser has its own page context; just make sure any event-armed state is per-page, not shared global config.

Gotchas and Tips

  • Upgrading from 1.2.x: the constraint change is the breaking change. There is no API change in 1.3.0, but dropping Laravel < 9 and PHP < 8.1 means a composer update can now leave you on a version your app can't install — check composer why-not protonemedia/inertiajs-events-laravel-dusk 1.3.0 before blaming the code. In a mixed monorepo, some services may need ^1.2 while others take ^1.3.

  • The race is the whole game. If the listener is armed only when you call waitForInertiaEvent(), the event may already have fired and you'll hang until the timeout. Inspect the service provider: if waits are armed lazily, arm the wait before the interaction, or pre-inject the listener yourself (see fallback below). This is the most common cause of "it works locally, times out in CI."

  • Still no Inertia version guarantee. 1.3.0 refreshed Laravel/Dusk/PHP but said nothing about Inertia v1/v2. Inertia renamed and restructured its events (the inertia:navigate family), so a macro keyed to older event names will silently never resolve — a green-looking stack that always times out. Pin your @inertiajs/vue3/react version in package.json if this package is load-bearing, or migrate off it (below).

  • Maintenance is still a question mark, not a solved problem. One release moving the support matrix forward is not a roadmap. Before you double down, check the repo's issue/PR activity and whether a Dusk 8 or Laravel 11 bump is plausible; budget the escape hatch below as a fallback rather than a hypothetical.

  • error is overloaded. Inertia can surface validation errors and network failures through the same event family. If you need to distinguish a 422 from a 500, assert on the rendered error message or the response status recorded in the event detail, not just the event name.

  • Partial reloads fire events too. A link with only: [...] or a manual router.reload() still emits start/success. Don't assume an event means a full page change; combine with assertPathIs when navigation is the thing under test.

  • Timeouts default to Dusk's 5 s. Cold CI, queue-backed responses, or a slow asset graph will blow that. Pass an explicit timeout (->waitForInertiaEvent('success', 15)) rather than sprinkling pause() calls.

  • Debug what the page actually emitted. Dusk can execute JS and return values, so dump the recorded events instead of guessing:

    dump($browser->script('return window.__inertiaEvents ?? [];'));
    

    If the array is empty, the listener was never attached or your event name is wrong — not a timing problem.

  • Extension point / escape hatch. If the package lags your stack again (Dusk 8, Laravel 11, Inertia v2), the whole mechanism is ~10 lines and you can own it:

    // In a DuskTestCase setUp() or a service provider:
    Browser::macro('armInertiaEvents', function () {
        $this->script(<<<'JS'
            window.__inertiaEvents = [];
            ['navigate','start','progress','success','error','finish','invalid']
                .forEach(name => document.addEventListener('inertia:' + name,
                    () => window.__inertiaEvents.push(name)));
        JS);
        return $this;
    });
    
    Browser::macro('waitForInertiaEvent', function (string $event, int $seconds = 5) {
        return $this->waitUntil(
            "Array.isArray(window.__inertiaEvents) && window.__inertiaEvents.includes('{$event}')",
            $seconds
        );
    });
    

    Then ->armInertiaEvents()->click(...)->waitForInertiaEvent('success') gives you the behaviour without the dependency. Note the explicit arm-then-click ordering — that's the fix for the race called out above. This path has no Laravel/Dusk/PHP ceiling, which is the actual argument for adopting it once the macro has proven itself in your suite.

  • Scope the dependency. Keep it in require-dev and out of production autoloading; if you use the hand-rolled macro instead, delete the package entirely so future Laravel/Dusk upgrades aren't gated on an unmaintained repo.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource