Laravel Dusk and Preview Environments: Testing PRs, Not Just Localhost
Environments as a ServiceSeptember 18, 20265 min read

Laravel Dusk and Preview Environments: Testing PRs, Not Just Localhost

Laravel Dusk is a solid, framework-native browser testing tool — most teams running it point it at localhost during development and a shared staging server in CI. Both work until they don't: localhost misses integration issues, and shared staging means Dusk's results depend on whatever else happens to be deployed there. Pointing Dusk at an isolated environment per pull request fixes both, without touching a single test.

What Dusk Does Well

Dusk is Laravel's own answer to E2E browser testing — built on ChromeDriver, with an API that feels like the rest of Laravel's testing tools. $browser->visit('/login')->type('email', $email)->press('Login')->assertSee('Dashboard') reads like Laravel, not like a general-purpose testing framework bolted onto a PHP app. For teams that are Laravel through and through, that framework-native feel is a real reason to pick Dusk over Playwright, Cypress, or Selenium, which are all capable tools but not Laravel-specific.

Where the Base URL Matters More Than the Framework

Dusk, like every browser-based E2E tool, doesn't care what's running at the URL it's pointed at — it just needs a base URL, set via APP_URL or Dusk's base() method. That single fact is why the framework choice and the environment choice are two separate decisions, not one.

Testing against localhost catches UI-level bugs but misses anything that depends on real service interaction — a queue job that behaves differently against a real Redis instance, a webhook that only fires correctly when a second service actually receives it, database constraints that a local SQLite test database doesn't enforce the same way production MySQL does.

Testing against shared staging in CI is closer to real conditions, but introduces a different problem: staging reflects whatever was deployed there most recently, which might not be the PR under test. A Dusk run that passes against staging can be passing against a mix of the current PR and three other unmerged changes, or failing because of someone else's half-deployed feature — not because of anything the PR itself did.

Point your existing Dusk suite at a real environment per PR.

Same tests, same syntax — the base URL is the only thing that changes.

What Changes With a Per-PR Environment

Elena maintained a Dusk suite for a 6-person Laravel team building an internal ops tool. Their CI ran Dusk against a shared staging server, and roughly once every couple of weeks a Dusk run failed for reasons that turned out to be unrelated to the PR being tested — another branch's half-finished migration, a queue worker stuck processing someone else's test data. Each one cost an afternoon of "is this my bug or not" before someone traced it back to staging contention.

Switching Dusk's CI job to point at a per-PR environment — provisioned automatically on pull_request: opened, torn down on close — didn't change a single test. It changed what APP_URL resolved to in CI, from a shared staging domain to that PR's unique environment URL. The false-failure pattern stopped, because there was no longer another PR's state to collide with. This mirrors what regression testing in PR preview environments covers for the broader regression-suite case — Dusk fits the same pattern specifically for browser-based Laravel tests.

Setting This Up

The pipeline shape is the same one covered in adding E2E testing to your CI/CD pipeline, applied to Dusk specifically:

  1. PR opens, triggering the CI workflow
  2. A provisioning step creates a full-stack environment from the existing app definition — Laravel, the database, Redis, a queue worker, whatever the app actually needs
  3. The environment's URL gets passed into the CI job as APP_URL
  4. php artisan dusk runs against that URL exactly as it would against localhost
  5. Results post back to the PR; the environment tears down on merge or close

Nothing about steps 1-5 requires touching a single Dusk test file. The --without-tty and headless Chrome setup Dusk already needs in CI doesn't change either — only where the browser points.

Handling the Laravel-Specific Details

Database seeding matters more for Dusk than for API-only testing, since browser tests typically need realistic data on screen (DatabaseMigrations or DatabaseTruncation traits still apply the same way inside a preview environment as they would locally).

Mixed-stack apps — Laravel plus a Node.js service, a separate frontend, or a background worker in another language — need the whole stack provisioned together for a Dusk test to mean anything if the tested flow touches more than the Laravel service. This is where a Docker Compose or Helm-based environment definition, like the one in Bunnyshell's Laravel preview environment guide, earns its keep over a Laravel-only deployment target — see how this compares to Laravel Forge and Laravel Cloud for that same mixed-stack case.

✅ Key Takeaways

  • Dusk needs only a base URL — the framework and the environment it tests against are independent decisions
  • Localhost misses real service-interaction bugs; shared staging introduces false failures from other PRs' state
  • Pointing Dusk at a per-PR environment fixes both without changing a single test file
  • The CI pipeline shape is the same one used for any E2E framework — provision, wait, test, report, teardown
  • Mixed-stack Laravel apps need the whole stack provisioned together for Dusk results to be meaningful

FAQ

What is Laravel Dusk used for?

Laravel Dusk is Laravel's own browser automation and E2E testing tool, built on ChromeDriver. It lets you write expressive, Laravel-native tests that click through an actual browser — login flows, form submissions, JavaScript-dependent UI — the same category of testing Playwright or Cypress handle for other frameworks.

Can Laravel Dusk run against a preview environment instead of localhost?

Yes. Dusk just needs a base URL to point its browser at, set via the APP_URL environment variable or Dusk's base() method. Pointing it at a freshly provisioned per-PR environment instead of localhost or shared staging is a configuration change, not a test rewrite.

Why test a Laravel PR against a real environment instead of locally with Dusk?

Local Dusk runs test your machine's state — your database, your queue worker, your file storage. A PR that changes how the app interacts with a queue, another service, or the database can pass locally and fail once deployed alongside real dependencies. Testing against an environment that mirrors production catches that gap before merge.

Does Laravel Dusk work well in CI?

Yes, Dusk runs headless Chrome and integrates cleanly with GitHub Actions, GitLab CI, or any CI system that can run PHP. The setup that most affects reliability is not Dusk itself but what environment the CI job points the browser at — a fresh, isolated environment per PR reduces the flakiness that comes from testing against a shared, possibly-stale staging server.

Should I use Laravel Dusk or Playwright for a Laravel app?

Dusk is the natural choice if your team is Laravel-only and wants tests written in familiar PHP/Laravel syntax with tight framework integration. Playwright is worth considering if the app has a substantial separate JavaScript frontend, or if the team already has Playwright experience from other projects. Both work fine against the same kind of per-PR preview environment.

Try Bunnyshell free.

Give your existing Dusk suite an isolated environment for every pull request — no test rewrites.