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

Behat Panther Extension Laravel Package

robertfausk/behat-panther-extension

Behat Panther Extension integrates Symfony Panther with Behat, enabling real-browser acceptance tests in Chrome or Firefox. Configure Panther sessions to run JavaScript-heavy scenarios and get reliable end-to-end coverage for PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal path from zero to a green Panther-driven Behat scenario in a Laravel app. As of v2.0.0 the platform floor is PHP 8.2+ and Symfony 5+ (through Symfony 8), so confirm your app qualifies before anything else:

  1. Check the platform first. v2.0.0 dropped PHP 7.2–8.1 and Symfony 3/4. If you are on Laravel 8 / PHP 7.4 you are pinned to the 1.x line — decide that up front rather than after wiring behat.yml.
  2. Install the packages (Panther + a driver downloader are effectively required):
    composer require --dev robertfausk/behat-panther-extension:^2.0 symfony/panther
    composer require --dev dbrekelmans/bdi
    vendor/bin/bdi detect drivers   # downloads the chromedriver matching your Chrome
    
  3. Make sure a real browser exists. Chrome/Chromium (or Firefox + geckodriver) must be on the machine or CI image. Check with google-chrome --version / chromium --version.
  4. Register the extension next to MinkExtension so Mink can pick up the panther driver. On Behat 4.x, v2.0.0 also supports a PHP config file (behat.php), which is preferable if you need env-driven config:
    # behat.yml (or behat.yml.dist)
    default:
      suites:
        default:
          contexts:
            - Behat\MinkExtension\Context\MinkContext
            - App\Tests\Behat\FeatureContext
      extensions:
        Behat\MinkExtension:
          base_url: 'http://127.0.0.1:8000'
          default_session: goutte
          javascript_session: panther
          sessions:
            goutte: ~
            panther:
              panther: ~
        RobertFausk\Behat\PantherExtension: ~
    
    // behat.php — Behat 4.x alternative, lets you read env vars
    use Behat\MinkExtension\ServiceContainer\MinkExtension;
    use RobertFausk\Behat\PantherExtension;
    
    return [
        'suites' => ['default' => ['contexts' => [
            Behat\MinkExtension\Context\MinkContext::class,
            App\Tests\Behat\FeatureContext::class,
        ]]],
        'extensions' => [
            MinkExtension::class => [
                'base_url' => getenv('BEHAT_BASE_URL') ?: 'http://127.0.0.1:8000',
                'default_session' => 'goutte',
                'javascript_session' => 'panther',
                'sessions' => ['goutte' => null, 'panther' => ['panther' => null]],
            ],
            PantherExtension::class => [],
        ],
    ];
    
    (Key names drift between minor versions — confirm against the package README for the version you pin. The Behat 4.x PHP-config path landed via an unmerged upstream PR, so verify it works on your exact Behat version before standardising on it.)
  5. Start the app under test on the configured port, e.g. php artisan serve --port=8000, in a background process or a BeforeSuite hook.
  6. Write one scenario and run it:
    @javascript
    Scenario: Dashboard renders with JavaScript
      Given I am on "/dashboard"
      Then I should see "Welcome back"
    
    vendor/bin/behat features/dashboard.feature
    

Where to look first

  • The package README.md and src/ extension class — it is small; the extension mostly wires a Mink driver factory around Symfony\Component\Panther\Client.
  • The symfony/panther README for browser options (browser, chrome_arguments, web_server, base_uri, screenshot env vars).
  • The Behat\MinkExtension docs for session selection (default_session vs javascript_session, tags).
  • Laravel's own bootstrap/app.php / Illuminate\Foundation\Application if you plan to boot the framework from a hook to seed the database.

First real use case: you have a flow that Goutte/BrowserKit cannot exercise — Livewire components, Alpine/Vue interactions, Inertia page transitions, file uploads, or anything asserted after an XHR. Tag that one feature @javascript and leave the rest on Goutte so the suite stays fast.

Implementation Patterns

Dual-session split (the highest-value pattern). Keep default_session: goutte for the 80% of scenarios that are just HTTP + HTML assertions, and route only JS-dependent scenarios to Panther via MinkExtension's javascript_session and the @javascript tag. This is the difference between a suite that takes 30 seconds and one that takes 15 minutes, because every Panther scenario spins up a real browser.

Env-driven config now that behat.php exists. With Behat 4.x + v2.0.0 you can build the config programmatically: read BEHAT_BASE_URL, toggle @javascript-only suites in CI, or register a pre-built Panther client when you already have an app server running. Keep a single behat.php as the source of truth and delete the parallel behat.yml — mixing the two is a reliable way to spend an hour debugging "why isn't my session being picked up".

A Laravel-aware base context. Subclass MinkContext (or compose it) and boot Laravel once:

final class FeatureContext extends MinkContext implements Context
{
    private static ?Application $app = null;

    /** @BeforeSuite */
    public static function bootLaravel(): void
    {
        self::$app = require __DIR__ . '/../../bootstrap/app.php';
        self::$app->make(Kernel::class)->bootstrap();
    }

    /** @BeforeScenario @database */
    public function migrateFresh(): void
    {
        Artisan::call('migrate:fresh', ['--seed' => true]);
    }

    public function getPantherClient(): Client
    {
        return $this->getSession('panther')->getDriver()->getClient();
    }
}

Use migrate:fresh per scenario or once per suite plus truncation — not DatabaseTransactions. The app under test runs in a separate PHP process (the artisan serve process), so a transaction opened inside the Behat process is invisible to it. Use a file-based SQLite DB or a dedicated MySQL/Postgres test database shared by both processes.

Auth without clicking through login. For speed, either (a) keep one login scenario that exercises the real form, and (b) for everything else generate a temporary signed URL and visit it:

$url = URL::temporarySignedRoute('test.login', now()->addMinutes(5), ['user' => $user->id]);
$this->visit($url);

Guard that route behind app()->environment('testing') and Route::has(...) checks.

Reach for the raw Panther client when Mink is too thin. Mink's API is intentionally small. For waiting, JS evaluation, screenshots, or DOM queries beyond CSS, pull the client out of the driver and use waitForElementToContain, executeScript, takeScreenshot, getCrawler, etc. Wrap these in named steps so features do not leak implementation details.

Hooks for diagnostics. A @AfterStep or @AfterScenario hook that saves a screenshot and the page HTML on failure into var/behat/ is the single biggest debugging quality-of-life win. Panther's own PANTHER_ERROR_SCREENSHOT_DIR covers some cases, but a Behat-level hook gives you the failing step name in the filename.

Page objects for stable selectors. Put data-testid attributes in Blade/Vue/Livewire components and define a small Pages\… class per screen with open(), fill(), submit(). Behat step text stays in business language; selectors stay in one place.

Custom steps for Laravel concerns. Steps like "a verified user exists", "the queue is synchronous", "the cache is cleared", "I travel to tomorrow", and "I run the scheduler" belong in a LaravelContext, not in page-object code. They run in the Behat process, so they can mutate the same database the server reads (again: file/DB-backed, not in-memory SQLite).

CI workflow. On GitHub Actions, provision Chrome, then set PANTHER_CHROME_ARGUMENTS="--no-sandbox --disable-dev-shm-usage" and PANTHER_NO_SANDBOX=1, start the app server, run npm run build (Vite's manifest must exist before @vite renders), and run Behat. Cache the drivers/ directory downloaded by BDI to avoid re-downloading per build. v2.0.0's CI additions cover PHP 8.5 — mirror that in your matrix if you chase the newest runtime, but keep at least one job on your production PHP version.

Gotchas and Tips

  • The v2 upgrade is a platform bump, not a code change. Dropping PHP 7.2–8.1 and Symfony 3/4 means the only thing that can block you is your runtime. Check composer why-not robertfausk/behat-panther-extension ^2.0 before assuming an API problem. Laravel 9/10+/PHP 8.2+ apps upgrade cleanly; anything older stays on 1.x, so record the split in your composer.json constraint (^1.0 || ^2.0 is usually wrong — pick one).
  • Chrome/driver version skew is the most common first failure ("session not created: This version of ChromeDriver only supports…"). BDI solves it, but you must re-run vendor/bin/bdi detect drivers whenever Chrome updates, and commit or cache the result.
  • --no-sandbox in containers. Chrome refuses to start as root or without a sandbox. Add --no-sandbox --disable-dev-shm-usage --disable-gpu via Panther options or env vars, otherwise you get a bare "unknown error" from the driver.
  • base_url host mismatch breaks everything. Use the exact same host in base_url, APP_URL, and the URL you actually visit, or Laravel will redirect/scope cookies to a different origin. Prefer 127.0.0.1 over localhost on Linux to avoid IPv6 surprises where the server binds IPv4 only.
  • No implicit waits. I should see fails immediately if the page is still hydrating. Add a spin/wait helper (retry the assertion for N seconds) or call Panther's waitFor before asserting. Do not paper over this with sleep().
  • Fresh browser per scenario is slow. Accept it for @javascript scenarios but aggressively push assertions into non-JS scenarios. Don't be tempted to keep one long scenario to amortize startup — you lose isolation and failure clarity.
  • Assets are a silent trap. In APP_ENV=testing with @vite, missing public/build/manifest.json produces an exception page that looks like a routing bug. Run npm run build (or point at a running Vite dev server) before the suite.
  • CSRF / 419 pages. Panther preserves cookies across visit() calls within a scenario, which is usually what you want, but a 419 after a POST typically means the session driver or APP_KEY differs between the test process and the server process, or APP_URL doesn't match. Confirm with a screenshot.
  • Debug interactively: PANTHER_NO_HEADLESS=1 to watch the browser, PANTHER_NO_DEBUG=1 to quiet console noise, and vendor/bin/behat --format=pretty --stop-on-failure --name="scenario" to run a single scenario. Add --tags='~@javascript' to skip browser work while iterating on domain logic.
  • Niche package, pin it. Still ~34 stars and a NOASSERTION license: lock the version in composer.json, read the license terms before commercial use, and be prepared to fork or swap in a hand-written Mink driver if the extension goes stale. Its surface area is small, so a fork is not a big lift — the extension point is the Mink driver factory; you can also register your own panther driver factory without touching the package.
  • Extension points: pass Panther client options (browser, chrome_arguments, web_server, read_timeout) through the extension config rather than hacking env vars everywhere; subclass the extension class if you need to inject a pre-built client or a custom factory (useful for shared test infrastructure like an already-running app server). With the PHP config file these options are plain arrays you can compute at runtime.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource