Pest Browser Testing with Laravel Sail on WSL2

· By Chrysostomos Zampetakis · Category : Laravel · 7 min read

Running browser tests in headed mode (with a visible browser window) is invaluable for debugging, but getting it to work with Laravel Sail on WSL2 requires some specific configuration. This guide walks you through the complete setup.

The Challenge

When working with Laravel Sail on WSL2, browser tests run inside Docker containers. By default, these containers don’t have access to your display, making it impossible to see the browser window during headed test runs. This becomes particularly frustrating when you want to debug failing tests or use methods like $page->debug() or $page->waitForKey().

Prerequisites

Before starting, ensure you have:

  • WSL2 installed and running
  • WSLg configured (verify with echo $DISPLAY – should return :0)
  • Laravel Sail project set up
  • Pest and Pest Browser plugin installed

If echo $DISPLAY doesn’t return anything, you’ll need to set up WSLg first (comes by default with recent Windows 11 updates).

Initial Setup

1. Install Pest Browser Plugin

First, install the necessary packages:

sail composer require pestphp/pest-plugin-browser --dev
sail npm install playwright@latest

2. Install Playwright Browsers

Run this inside your Sail container:

sail shell
npx playwright install --with-deps
exit

3. Configure .gitignore

Add the screenshots directory to avoid committing test artifacts:

echo "tests/Browser/Screenshots" >> .gitignore

Enabling Headed Mode in Docker

The key to running headed tests is configuring Docker to share your display with the container.

Modify docker-compose.yml

Add the following to your laravel.test service in docker-compose.yml:

services:
  laravel.test:
    build:
      context: ./vendor/laravel/sail/runtimes/8.4
      dockerfile: Dockerfile
      args:
        WWWGROUP: '${WWWGROUP}'
    image: sail-8.4/app
    extra_hosts:
      - 'host.docker.internal:host-gateway'
    ports:
      - '${APP_PORT:-80}:80'
      - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
    environment:
      WWWUSER: '${WWWUSER}'
      LARAVEL_SAIL: 1
      XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
      XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
      IGNITION_LOCAL_SITES_PATH: '${PWD}'
      DISPLAY: ':0'  # Add this line
    volumes:
      - '.:/var/www/html'
      - '/tmp/.X11-unix:/tmp/.X11-unix:rw'  # Add this line
    networks:
      - sail
    depends_on:
      - mysql
      - redis
      - meilisearch
      - mailpit
      - selenium

What these lines do:

  • DISPLAY: ':0' – Tells the container which display to use
  • '/tmp/.X11-unix:/tmp/.X11-unix:rw' – Shares the X11 Unix socket between WSL and Docker

Apply Changes

Restart your containers to apply the configuration:

sail down
sail up -d

Running Headed Tests

Now you can run your browser tests in headed mode:

# Run all browser tests in headed mode
sail pest --headed

# Run specific test file
sail pest tests/Feature/MyBrowserTest.php --headed

# Run specific test with filter
sail pest --filter "user can login" --headed

# Debug mode (pauses on failure)
sail pest --debug

When you run these commands, you’ll see browser windows appear on your WSL2 desktop!

Working with VSCode PHPUnit Test Explorer

If you’re using the PHPUnit Test Explorer extension for VSCode, you need to understand its limitations with headed tests.

The Problem

The VSCode extension runs tests through its output panel, which cannot receive keyboard input. When a test pauses (using $page->debug() or $page->waitForKey()), you won’t be able to press a key to continue, causing the test to hang.

Use a hybrid approach:

For Regular Development:

  • Use VSCode Test Explorer for quick, headless test runs
  • Fast iteration and feedback during development

For Visual Debugging:

  • Use the terminal with sail pest --headed
  • Better control and ability to interact with paused tests
# Quick headless run (VSCode extension or terminal)
sail pest

# Visual debugging (terminal only)
sail pest tests/Feature/Auth/LoginTest.php --headed

Writing Tests

Here’s an example of a browser test that benefits from headed mode:

<?php

use function Pest\Laravel\{get, actingAs};

it('allows user to book an appointment', function () {
    $user = User::factory()->create();
    
    $page = visit('/booking')
        ->assertSee('Available Times')
        ->click('9:00 AM')
        ->type('name', 'John Doe')
        ->type('email', 'john@example.com')
        ->click('Book Appointment')
        ->assertSee('Booking Confirmed');
        
    // Use this for debugging only when running with --headed
    // $page->debug(); // Pauses execution and opens browser
});

Debugging Techniques

1. Visual Inspection with –headed

sail pest tests/Feature/BookingTest.php --headed

Watch the browser interact with your application in real-time.

2. Pause on Failure with –debug

sail pest --debug

Automatically opens the browser and pauses when a test fails.

3. Take Screenshots

// Takes screenshot with test name as filename
$page->screenshot();

// Custom filename
$page->screenshot(filename: 'booking-step-2');

// Full page screenshot
$page->screenshot(fullPage: true);

// Screenshot specific element
$page->screenshotElement('#booking-form');

Screenshots are saved to tests/Browser/Screenshots/.

4. Interactive Debugging with Tinker

// Open Tinker session in browser context
$page->tinker();

5. Wait and Inspect

// Pause and wait for manual inspection (terminal only!)
$page->waitForKey();

Common Issues and Solutions

Issue: Browser window doesn’t appear

Check WSLg:

echo $DISPLAY
# Should output: :0

Verify Docker volumes:

docker compose exec laravel.test bash
echo $DISPLAY
# Should also output: :0

Issue: "Executable doesn’t exist" error

Reinstall Playwright browsers:

sail shell
npx playwright install --with-deps

Issue: Tests hang with $page->debug()

Don’t run debug tests through VSCode extension. Use terminal instead:

sail pest tests/Feature/MyTest.php --headed

Issue: Permission denied on /tmp/.X11-unix

Ensure the volume mount has :rw (read-write) permissions:

volumes:
  - '/tmp/.X11-unix:/tmp/.X11-unix:rw'

Best Practices

  1. Use headed mode sparingly – Run most tests headless for speed, use headed mode only when debugging
  2. Don’t commit with debug() calls – Remove $page->debug() and $page->waitForKey() before committing
  3. Create test-specific aliases – Make running headed tests easier:
    alias sail-test-headed='sail pest --headed'
    alias sail-test-debug='sail pest --debug'
    
  4. Use screenshots for CI – In CI environments where headed mode isn’t available, rely on screenshots:
    if (! app()->environment('local')) {
        $page->screenshot();
    }
    
  5. Combine with conditional debugging:
    if (env('TEST_DEBUG', false)) {
        $page->debug();
    }
    

Then run:

TEST_DEBUG=true sail pest tests/Feature/MyTest.php --headed

Running in CI (GitHub Actions)

For CI environments, you’ll need to install Playwright differently:

- uses: actions/setup-node@v4
  with:
    node-version: lts/*

- name: Install dependencies
  run: npm ci

- name: Install Playwright Browsers
  run: npx playwright install --with-deps

- name: Run Browser Tests
  run: ./vendor/bin/pest

Note: Don’t use --headed in CI. Use screenshots for debugging failed CI tests.

Conclusion

Running Pest browser tests in headed mode with Laravel Sail on WSL2 requires proper Docker configuration to share your display. Once set up, you get a powerful debugging workflow:

  • VSCode Test Explorer for fast, headless development iterations
  • Terminal with –headed for visual debugging when needed
  • Screenshots and tinker() for inspecting state

The key is understanding when to use each tool and avoiding the pitfalls of running interactive tests through VSCode’s output panel.

Happy testing! 🧪✨


Quick Reference Commands:

# Initial setup
sail composer require pestphp/pest-plugin-browser --dev
sail npm install playwright@latest
sail shell && npx playwright install --with-deps

# Run tests
sail pest                                    # Headless (fast)
sail pest --headed                          # Headed (visual)
sail pest --debug                           # Pause on failure
sail pest <file> --filter "test name" --headed

# Stop hung tests
docker compose exec laravel.test pkill -9 pest

# Verify WSLg
echo $DISPLAY  # Should show :0