๐
Bridge the Gap
Test frontends running on different ports or servers โ all from your Laravel test suite using Pest.
Learn more โ
Write browser tests in PHP for Vue, React, Nuxt, Next.js โ no JavaScript test code required
Modern apps use decoupled architectures:
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Laravel API โ โโโโโโโบ โ Vue/React/Nuxt โ
โ localhost:8000 โ API โ localhost:3000 โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โฒ โฒ
โ โ
โโโโ Where tests run โโโโ What we need to testtest('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');
});describe() blocks. Each block targets a different URL. // 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// 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');
});--headed or pause mid-test with debug(). # Run with visible browser
./vendor/bin/pest tests/Browser --headedtest('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');
});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');
});// 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.
./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.89sTests/Browser/Screenshots/
โโโ checkout_flow_1703847293.png โ See exactly where it failed