Skip to content

Pest Plugin BridgeTest External Frontends from Laravel

Write browser tests in PHP for Vue, React, Nuxt, Next.js โ€” no JavaScript test code required

The Problem โ€‹

Modern apps use decoupled architectures:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Laravel API    โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚  Vue/React/Nuxt โ”‚
โ”‚  localhost:8000 โ”‚   API   โ”‚  localhost:3000 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ–ฒ                           โ–ฒ
        โ”‚                           โ”‚
        โ””โ”€โ”€โ”€ Where tests run        โ””โ”€โ”€โ”€ What we need to test
๐ŸŒ‰bridge() โ€” The BridgeHow it works โ†’
One method bridges your Laravel tests to any external frontend. Use familiar Pest syntax.
php
test('user can login to external frontend', function () {
    // Create user in Laravel
    $user = User::factory()->create([
        'email' => 'test@example.com',
    ]);

    // Test the Vue/React/Nuxt frontend running on port 3000
    $this->bridge('/login')
        ->fill('[data-testid="email-input"]', 'test@example.com')
        ->fill('[data-testid="password-input"]', 'password')
        ->click('[data-testid="login-button"]')
        ->wait(2)
        ->assertPathContains('/dashboard')
        ->assertSee('Welcome');
});
๐Ÿ”€Multiple Frontends in One FileConfiguration โ†’
Test different frontends using describe() blocks. Each block targets a different URL.
php
// tests/Pest.php - Configure all frontends once
use TestFlowLabs\PestPluginBridge\Bridge;

Bridge::setDefault('http://localhost:3000');           // Customer portal
Bridge::frontend('admin', 'http://localhost:3001');    // Admin dashboard
php
// tests/Browser/MultiFrontendTest.php
test('customer can view products', function () {
    // Uses default frontend (localhost:3000)
    $this->bridge('/products')->assertSee('Product Catalog');
});

test('customer can add to cart', function () {
    $this->bridge('/products/1')
        ->click('[data-testid="add-to-cart"]')
        ->assertVisible('[data-testid="cart-badge"]');
});

test('admin can manage users', function () {
    // Uses admin frontend (localhost:3001)
    $this->bridge('/users', 'admin')->assertSee('User Management');
});
๐ŸŽญDebug with Headed ModeBest practices โ†’
See exactly what's happening. Run with --headed or pause mid-test with debug().
bash
# Run with visible browser
./vendor/bin/pest tests/Browser --headed
php
test('debugging a complex flow', function () {
    $this->bridge('/checkout')
        ->fill('[data-testid="card-number"]', '4242424242424242')
        ->debug()  // โ† Browser opens, test pauses here for inspection
        ->click('[data-testid="pay-button"]')
        ->assertSee('Payment successful');
});
๐ŸงชFull Pest Assertion PowerAll assertions โ†’
All Pest browser assertions work seamlessly. Chain them for expressive, readable tests.
php
test('complete checkout flow', function () {
    $this->bridge('/cart')
        // Visibility assertions
        ->assertVisible('[data-testid="cart-items"]')
        ->assertNotVisible('[data-testid="empty-cart-message"]')

        // Text assertions
        ->assertSee('Shopping Cart')
        ->assertSeeIn('[data-testid="total"]', '$99.00')

        // Form interactions
        ->fill('[data-testid="coupon-input"]', 'SAVE10')
        ->click('[data-testid="apply-coupon"]')
        ->wait(1)

        // URL assertions after navigation
        ->click('[data-testid="checkout-button"]')
        ->wait(2)
        ->assertPathContains('/checkout')
        ->assertTitle('Checkout - MyStore');
});
โš™๏ธSimple ConfigurationConfiguration โ†’
One line of code. That's all you need.
php
// Configuration with automatic server management (in tests/Pest.php)
use TestFlowLabs\PestPluginBridge\Bridge;
use TestFlowLabs\PestPluginBridge\BridgeTrait;
use Tests\TestCase;

// Simple: just URL (manual server start)
Bridge::setDefault('http://localhost:3000');

// With auto-start: servers launch automatically
uses(TestCase::class, BridgeTrait::class)
    ->beforeAll(fn () => Bridge::setDefault('http://localhost:3000')
        ->serve('npm run dev', cwd: '../frontend')
        ->readyWhen('ready|localhost'))
    ->in('Browser');

Cleanup is automatic โ€” no afterAll needed.

๐Ÿ“ธAutomatic Screenshots on FailureDebugging โ†’
When a test fails, a screenshot is automatically captured for debugging.
bash
./vendor/bin/pest tests/Browser/CheckoutTest.php

   FAIL  Tests\Browser\CheckoutTest
  โœ• complete checkout flow                                   3.2s
    Screenshot saved: Tests/Browser/Screenshots/checkout_flow_1703847293.png

  Tests:    1 failed
  Duration: 3.89s
Tests/Browser/Screenshots/
โ””โ”€โ”€ checkout_flow_1703847293.png   โ† See exactly where it failed

Released under the MIT License.