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

Dusk Laravel Package

duncan3dc/dusk

A collection of helpers and utilities for Laravel Dusk that makes browser-based end-to-end testing more convenient. It reduces boilerplate around common page interactions and assertions, helping you write clearer, more maintainable UI tests while keeping close to Dusk's familiar API.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install (and pin, since the package is archived)

composer require --dev duncan3dc/dusk

Make sure a chromedriver binary is available and its major version matches your installed Chrome/Chromium — this is the single most common reason the package "doesn't work" on a fresh machine. Set the path explicitly if it isn't on PATH:

export CHROME_DRIVER=/usr/local/bin/chromedriver

2. Where to look first

  • README.md — the only authoritative usage example; skim it before anything.
  • src/Browser.php — almost certainly a thin subclass/wrapper over Laravel\Dusk\Browser. Everything you already know about visit(), type(), click(), assertSee(), waitFor() applies verbatim.
  • src/ driver/session classes — the part that actually differs from stock Dusk. This is where Chrome/Chromedriver startup, headless flags, and teardown live.
  • composer.json — confirms which laravel/dusk and php-webdriver/webdriver versions you're actually running; the wrapper is only as capable as those.

3. First use case: a standalone smoke script

The whole point of the package is that you can drive a real browser from a plain PHP file with no artisan, no DuskTestCase, and no Laravel bootstrap:

<?php
require __DIR__ . '/vendor/autoload.php';

use duncan3dc\Dusk\Browser;

$browser = new Browser();
$browser->visit('https://example.com')
        ->assertSee('Example Domain');
$browser->quit();

Run it once by hand to prove the driver spins up. Only after that green run should you wire it into PHPUnit or a queue worker.

4. First realistic use case: a PHPUnit test

use PHPUnit\Framework\TestCase;
use duncan3dc\Dusk\Browser;

final class CheckoutTest extends TestCase
{
    private Browser $browser;

    protected function setUp(): void
    {
        $this->browser = new Browser();
    }

    protected function tearDown(): void
    {
        $this->browser->quit();
    }

    public function test_guest_checkout(): void
    {
        $this->browser
            ->visit('https://shop.test/cart')
            ->waitFor('.cart-total')
            ->click('button.checkout')
            ->assertPathIs('/checkout');
    }
}

Use absolute URLs everywhere — there is no APP_URL/config('app.url') fallback without a Laravel app.

Implementation Patterns

Browser-as-a-service for other tools. The wrapper just gives you a Laravel\Dusk\Browser; you can hand it to anything that accepts a Dusk browser. A common pattern is an "actor" class that composes the browser and exposes domain methods, so tests read like user journeys and the setup cost (driver launch) is paid once:

final class Shopper
{
    public function __construct(private Browser $browser) {}

    public function login(string $email, string $password): self
    {
        $this->browser->visit('https://shop.test/login')
            ->type('email', $email)
            ->type('password', $password)
            ->press('Sign in')
            ->waitForLocation('/dashboard');
        return $this;
    }
}

Reuse one browser across a suite, but reset state per test. Launching ChromeDriver per test is slow. Keep a single browser for the class (or process) and clear cookies/navigate away between tests rather than reconstructing:

protected function setUp(): void
{
    $this->browser ??= new Browser();
    $this->browser->visit('about:blank');   // cheap state reset
}

Explicit waits beat sleeps. Dusk's waitFor, waitForText, waitUntil, and waitForLocation are how you tame SPA/JS-driven apps. Set a sane global timeout once (via the browser's default wait, or a helper) and never use usleep() — flaky suites in standalone setups almost always trace back to fixed sleeps.

Headless in CI, headed locally. Drive the mode from an environment variable in your test bootstrap rather than editing source:

putenv('HEADLESS=' . (getenv('CI') ? '1' : '0'));
$browser = new Browser();

If the wrapper exposes driver options, pass ['--headless=new', '--no-sandbox', '--disable-gpu', '--window-size=1400,900'] for CI containers.

Screenshots and HTML on failure. Since there's no artisan dusk command to auto-capture, wire it yourself in tearDown:

if ($this->hasFailed()) {
    $this->browser->screenshot('failure-' . $this->name());
    file_put_contents("failed.html", $this->browser->driver->getPageSource());
}

Integration with existing tooling. Because it's just PHP, you can call it from:

  • An artisan command in another Laravel project that only needs duncan3dc/dusk (not full Dusk) — useful when you want browser checks from a non-test context (e.g. weekly smoke tests, content scraping of your own app).
  • GitHub Actions with browser-actions/setup-chrome and nanasess/setup-chromedriver, then CHROME_DRIVER exported from the action output.
  • A queue job that captures a PDF/screenshot of a page for reporting.

Macros for repeated assertions. Dusk supports Browser::macro(); register your app-specific ones (e.g. assertLoggedInAs) in a bootstrap file and use them everywhere.

Gotchas and Tips

  • Archived package. No upstream fixes. Treat it as pinned infrastructure: commit a composer.lock, vendor a known-good chromedriver, and be ready to replace it with symfony/panther, chrome-php/chrome, or a small custom bootstrap around laravel/dusk if you need modern Chrome features or security patches.
  • Chromedriver/Chrome version skew is the #1 failure. Symptom: SessionNotCreatedException or the Only local connections are allowed message followed by a hang. Fix version parity before debugging anything else; log chromedriver --version and google-chrome --version in CI.
  • No .env, no config. Anything stock Dusk reads from Laravel (APP_URL, DUSK_*, filesystem paths) isn't available. Use absolute URLs and putenv()/$_SERVER in your bootstrap.
  • Driver leaks. If a test throws before quit(), you'll accumulate chromedriver and Chrome processes and eventually run out of memory/ports in CI. Guard teardown with try/finally, or register a shutdown function that calls quit().
  • No artisan dusk, so no built-in test isolation. You are responsible for cookie clearing, DB resets (use a transaction or truncate in setUp), and per-test navigation. A "logged in from the previous test" surprise is normal until you add explicit resets.
  • assertSee matches page source, not rendered text, in some versions — for JS-rendered content prefer waitForText/assertSeeIn with a specific selector, otherwise you may pass on hidden markup or fail on content the user actually sees.
  • Headless vs headed differences. Sticky headers can intercept clicks in headed mode but not headless (or vice versa). Add --window-size and use scrollIntoView/click with a selector rather than coordinates.
  • Debugging a hang: enable ChromeDriver verbose logging (--verbose --log-path=chromedriver.log) and check whether the session was created before the hang. If the log shows no "Starting ChromeDriver" the wrapper never launched the process; if it shows a session ID, you're failing inside a waitFor* and need a longer timeout or a selector fix.
  • Extension points: subclass the package's Browser to add app-wide helpers (login, seeding, custom assertions), and subclass/compose the driver class to add Chrome flags or swap binaries. Keep those subclasses in your test support namespace so the vendor package stays unmodified.
  • Timeouts are per-call, not global, unless the wrapper sets one. Centralise a waitForX(..., 10) convention to avoid 30-second default waits inflating suite time.
  • Parallelism: do not share a single ChromeDriver across parallel PHPUnit processes. If you use paratest, give each worker its own driver instance and a distinct --user-data-dir, or you'll get port/collision errors that look like flakiness.
  • PHP 8.1+ deprecation noise from the older php-webdriver constraint this package likely pins. Silence it in phpunit.xml (convertDeprecationsToExceptions="false") rather than chasing upstream fixes that no longer exist.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin