pestphp / pestphp/pest

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

Open
#1,605 3 comments 2 reactions 0 assignees View on GitHub

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:

  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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.