Pest Browser Testing with Playwright on Multi-Tenant Subdomain Architecture

If you’re building a multi-tenant Laravel application that identifies tenants via subdomains (like tenant1.app.test, tenant2.app.test), you might run into issues when trying to set up Pest’s browser testing with Playwright. Here’s how to solve it.

The Problem

Pest 4 introduced powerful browser testing capabilities using Playwright. For multi-tenant apps, you’d naturally reach for the withHost() method to configure the subdomain:

// tests/Pest.php
pest()->browser()->withHost('mytenant.app.test')->inFirefox();

But when you run your tests, you get an error like:

Connection to 'mytenant.app.test:52197' failed

Why withHost() Doesn’t Work

The issue lies in how Playwright’s server binding works. When you use withHost(), it attempts to bind the Playwright server to that hostname. However, servers can only bind to IP addresses, not hostnames.

Looking at the Pest browser plugin’s ServerManager.php:

// This runs: playwright run-server --host mytenant.app.test
// Which fails because servers can't bind to hostnames

The HTTP server correctly binds to 127.0.0.1, but the Playwright server fails to start.

The Solution: Use serverVariables() Instead

The Pest browser plugin provides another mechanism for setting the Host header: the serverVariables() function. This bypasses the binding issue entirely by injecting the Host header into the request after the servers are running.

Step 1: Update tests/Pest.php

Remove withHost() and add a global serverVariables() function:

<?php

// tests/Pest.php

use Spatie\Multitenancy\Concerns\UsesMultitenancyConfig;

pest()->extend(Tests\TestCase::class)
    ->use(UsesMultitenancyConfig::class)
    ->in('Feature');

pest()->extend(Tests\TestCase::class)
    ->use(UsesMultitenancyConfig::class)
    ->in('Browser');

// Configure browser testing - no withHost()!
pest()->browser();

/**
 * Server variables for browser tests to support multi-tenant subdomain architecture.
 * This sets the Host header for Laravel's subdomain-based tenant detection.
 */
function serverVariables(): array
{
    return [
        'HTTP_HOST' => 'mytenant.app.test',
        'SERVER_NAME' => 'mytenant.app.test',
    ];
}

Step 2: Add beforeEach in Browser Tests (Optional but Recommended)

For additional clarity and flexibility, you can also set server variables per test file:

<?php

// tests/Browser/ExampleBrowserTest.php

uses()->beforeEach(function () {
    // Set up tenant context for browser tests via server variables
    $this->serverVariables = [
        'HTTP_HOST' => 'mytenant.app.test',
        'SERVER_NAME' => 'mytenant.app.test',
    ];
});

it('can visit the login page', function () {
    $page = visit('/login');

    $page->assertSee('Login')
        ->assertNoJavascriptErrors();
});

Step 3: Ensure Your Hosts File Is Configured

Make sure your tenant subdomain resolves to localhost:

# /etc/hosts
127.0.0.1 mytenant.app.test

How It Works

The Pest browser plugin’s LaravelHttpServer.php calls test()->serverVariables() to get server variables that are merged into each request. By setting HTTP_HOST and SERVER_NAME, Laravel’s subdomain-based tenant detection works correctly.

// From LaravelHttpServer.php
$serverVariables = test()->serverVariables(); // Gets from your test
// These are merged into $_SERVER for the request

Testing Different Tenants

You can easily test different tenants by parameterizing your tests:

<?php

dataset('tenants', [
    'tenant1' => ['tenant1.app.test'],
    'tenant2' => ['tenant2.app.test'],
]);

it('shows the correct tenant dashboard', function (string $host) {
    $this->serverVariables = [
        'HTTP_HOST' => $host,
        'SERVER_NAME' => $host,
    ];

    $page = visit('/dashboard');

    $page->assertSee('Dashboard')
        ->assertNoJavascriptErrors();
})->with('tenants');

Running the Tests

# Run all browser tests
./vendor/bin/pest tests/Browser

# Run with debug mode (opens browser visibly)
./vendor/bin/pest tests/Browser --debug

# Run a specific test
./vendor/bin/pest tests/Browser/ExampleBrowserTest.php

Summary

When using Pest browser testing with a multi-tenant subdomain architecture:

  1. Don’t use withHost() – it breaks Playwright server binding
  2. Do use the serverVariables() function to set HTTP_HOST and SERVER_NAME
  3. Configure your /etc/hosts file to resolve tenant subdomains to 127.0.0.1

This approach lets you enjoy the full power of Pest’s browser testing while maintaining proper tenant context in your multi-tenant Laravel application.

Leave a Reply

Your email address will not be published. Required fields are marked *