Test external frontends from Laravel — write browser tests in PHP for Vue, React, Nuxt, Next.js.
Documentation · Getting Started
You have a headless Laravel API and a separate frontend (Vue, Nuxt, React, Next.js). Two apps, two ports, two problems:
- Your tests can't reach the frontend
- Your frontend can't find the API during tests
Pest Bridge Plugin solves both directions:
// test → frontend: bridge() visits the external frontend
$this->bridge('/login')->assertSee('Welcome');
// frontend → API: automatic environment variable injection
// VITE_API_URL, NUXT_PUBLIC_API_BASE, NEXT_PUBLIC_API_URL, etc.composer require testflowlabs/pest-plugin-bridge --devRequires pestphp/pest-plugin-browser
// tests/Pest.php
use TestFlowLabs\PestPluginBridge\Bridge;
Bridge::add('http://localhost:3000')
->serve('npm run dev', cwd: '../frontend');// tests/Browser/LoginTest.php
test('user can login', function () {
$user = User::factory()->create(['email' => 'test@example.com']);
$this->bridge('/login')
->typeSlowly('[data-testid="email"]', 'test@example.com')
->typeSlowly('[data-testid="password"]', 'password')
->click('[data-testid="submit"]')
->waitForEvent('networkidle')
->assertPathContains('/dashboard');
});Frontend starts on first bridge() call, stops when tests complete. No manual server management.
Bridge::add('http://localhost:3000')
->serve('npm run dev', cwd: '../frontend')
->readyWhen('VITE.*ready');Test multiple frontends in one suite. Child frontends share the parent's server process.
Bridge::add('http://localhost:3000'); // Default
Bridge::add('http://localhost:3001', 'admin')
->child('/analytics', 'analytics') // Same server, /analytics path
->serve('npm run dev', cwd: '../admin');$this->bridge('/products'); // Default frontend
$this->bridge('/users', 'admin'); // Admin frontend
$this->bridge('/', 'analytics'); // Child of adminGitHub Actions checks out both repos side-by-side. Works with private repos.
- uses: actions/checkout@v4
with: { path: backend }
- uses: actions/checkout@v4
with: { repository: your-org/frontend, path: frontend }typeSlowly() triggers real keyboard events that Vue v-model and React hooks respond to.
// fill() sets DOM directly — Vue won't see it
// typeSlowly() fires keydown/input/keyup events
->typeSlowly('[data-testid="email"]', 'test@example.com')For complete guides, examples, and CI/CD workflows:
- Introduction — How it works
- Configuration — All options
- CI/CD Setup — GitHub Actions
- Troubleshooting — Common issues
composer install
composer testMIT License. See LICENSE for details.