[Bug]: Browser plugin - Navigation causes silent PHP crash with no output
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 11.7k
- Forks
- 538
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
In our org we're looking into migrating from Dusk browser tests to Pest v4 - with the browser plugin. We're experiencing a situation that could be caused by a bug, where the test just stops generating any output, and silently crashes.
Pest Browser Version
v4.x (latest)
PHP Version
8.4
Laravel Version
11.x
Description
Any navigation event (redirect, navigate(), goto(), or JS navigation like window.location.href) causes the PHP process to crash silently when subsequent operations are performed on the page. There is no error message, no exception, no output - the process simply terminates.
This is related to but distinct from #1511 (AwaitableWebpage::click times out) - that issue describes timeouts with "Execution context was destroyed" errors. Our issue is a complete silent crash with zero output.
Root Cause Analysis
Through extensive testing, we've identified:
- Each
$this->visit()creates a NEW browser context - contexts don't share cookies/sessions - After navigation,
PendingAwaitablePagebecomes corrupted - Calling ANY method on a corrupted page crashes PHP silently
What Works vs What Crashes
| Scenario | Result |
|---|---|
| Basic visit without navigation | ✅ PASS |
| Login WITHOUT server redirect | ✅ PASS |
JS fetch() after login |
✅ PASS (returns 200) |
Login then navigate() |
❌ CRASHES |
Login then page()->goto() |
❌ CRASHES |
Login then JS window.location.href = ... |
❌ CRASHES |
| Login WITH server-side redirect | ❌ CRASHES |
Two independent visit() calls |
❌ CRASHES |
waitForURL() after navigation |
❌ CRASHES |
waitForLoadState() after navigation |
❌ CRASHES |
Key Finding
Authentication WORKS. The issue is NAVIGATION, not authentication.
We can prove this with a fetch test:
// Login without redirect
$page = $this->visit(route('dusk.auth.login', ['userId' => $user->getKey()]));
$page->assertSee('Logged in');
// Fetch dashboard content via JS - uses session cookies
$result = $page->script("
(async () => {
const response = await fetch('/dashboard', {credentials: 'same-origin'});
return {status: response.status, url: response.url};
})()
");
// Returns: {"status": 200, "url": "http://127.0.0.1:PORT/dashboard"}
// Auth IS working - fetch succeeds with 200!
Steps To Reproduce
it('crashes on navigation', function () {
$user = User::factory()->create();
// Step 1: Login (works fine)
$page = $this->visit(route('dusk.auth.login', ['userId' => $user->getKey()]));
$page->assertSee('Logged in');
// Step 2: Any of these cause silent crash:
// Option A: navigate() method
$page->navigate('/dashboard'); // CRASH - no output
// Option B: Playwright goto
$page->page()->goto('/dashboard'); // CRASH - "Broken pipe"
// Option C: JS navigation
$page->script("window.location.href = '/dashboard'");
$page->waitForLoadState('load'); // CRASH - no output
// Option D: Fresh visit
$this->visit('/dashboard'); // CRASH - no output
// None of these lines are reached
$page->assertSee('Dashboard');
});
Workaround
We've developed a fetch+inject pattern that works for static content tests:
// Step 1: Login without redirect
$page = $this->visit(route('dusk.auth.login', ['userId' => $user->getKey()]));
// Step 2: Fetch page HTML via JS (uses session cookies, no navigation)
$html = $page->script("
(async () => {
const response = await fetch('/dashboard', {credentials: 'same-origin'});
return await response.text();
})()
");
// Step 3: Inject into current DOM
$page->script("document.body.innerHTML = " . json_encode($html));
// Step 4: Use JS assertions (assertSee can also crash!)
$found = $page->script("document.body.innerText.includes('Dashboard')");
expect($found)->toBeTrue();
Limitations: This workaround cannot test real navigation, interactive features, form submissions, or SPA navigation.
Environment
- OS: macOS (also reproduced on Linux CI)
- Playwright: Latest
- Running via
php artisan pest:browser
Additional Context
We have ~140 browser tests that all crash silently due to this issue. The diagnostic test file we used to isolate this bug is available if helpful.
This appears to be a fundamental issue with how PendingAwaitablePage handles navigation events. The page object becomes corrupted after any URL change, and subsequent method calls cause a PHP crash rather than throwing a catchable exception.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reproduction in the issue and the PendingAwaitablePage entry point; run it through php artisan pest:browser to observe the silent termination after navigation. Done means the listed navigation scenarios no longer crash PHP and subsequent page operations produce usable results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- laravel, php, playwright
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100