Skip to content

[Bug]: Browser plugin - Navigation causes silent PHP crash with no output #1605

@zetxek

Description

@zetxek

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:

  1. Each $this->visit() creates a NEW browser context - contexts don't share cookies/sessions
  2. After navigation, PendingAwaitablePage becomes corrupted
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions