PHPackages                             valcuandrei/pest-e2e - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. valcuandrei/pest-e2e

ActiveLibrary[Testing &amp; Quality](/categories/testing)

valcuandrei/pest-e2e
====================

A Laravel-first package that runs JS-owned E2E and component tests from Pest without introducing a PHP browser DSL.

v1.1.0(1mo ago)036↓41.7%MITPHPPHP ^8.4CI passing

Since Mar 20Pushed 1mo agoCompare

[ Source](https://github.com/valcuandrei/pestE2E)[ Packagist](https://packagist.org/packages/valcuandrei/pest-e2e)[ RSS](/packages/valcuandrei-pest-e2e/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (17)Versions (3)Used By (0)

pestE2E
=======

[](#peste2e)

**Laravel-first E2E orchestration for JavaScript-native browser testing.**

Run your existing JS E2E suite (Playwright by default) from Pest — without introducing a PHP browser DSL.

---

What This Is
------------

[](#what-this-is)

**pestE2E** is a Laravel-native orchestration layer that runs JavaScript-owned browser tests and maps structured results back into Pest output.

Conceptually, this is an Inertia-style bridge for E2E testing:

- Laravel owns test intent, state, authentication, and data
- JavaScript owns browser execution
- A stable contract connects the two

Pest orchestrates JS execution, passes context (environment, params, auth), and consumes structured JSON reports.

This package does **not** wrap Playwright in PHP. It orchestrates your existing JS test suite from Laravel.

---

Key Features
------------

[](#key-features)

- JS test filtering via `only()` and `runTest()`
- Laravel authentication using one-time auth tickets
- Runner agnostic (Playwright by default, extensible via worker contracts)
- Managed testing server (no manual `php artisan serve`)
- Isolated testing environment
- Stable JSON reporting contract (`pest-e2e.v1`)
- Fully type-safe (PHPStan compliant)

---

What This Is NOT
----------------

[](#what-this-is-not)

- Not a browser abstraction
- Not a PHP wrapper around Playwright
- Not Dusk
- Not Selenium
- No `visit()`, `click()`, or `type()` in PHP — ever

All browser logic lives in JavaScript.

---

Status
------

[](#status)

**Stable v1**

The public PHP API, authentication contract, and JSON report schema are locked. Internal runner adapters may evolve.

### Parallel test execution

[](#parallel-test-execution)

Browser tests that call `e2e()->…->run()` can run with Pest / Laravel parallel testing:

```
php artisan test --parallel --processes=4
```

#### Required `.env.testing` setup

[](#required-envtesting-setup)

Use a real test database (MySQL or PostgreSQL), not SQLite in-memory. Laravel parallel testing creates one database per worker (`testing_test_1`, `testing_test_2`, …). Recommended overrides:

```
SESSION_DRIVER=array
CACHE_STORE=array
QUEUE_CONNECTION=sync
DB_DATABASE=testing
```

Run with `--recreate-databases` when bootstrapping worker databases for the first time.

#### Per-worker isolation

[](#per-worker-isolation)

Each Pest worker process gets:

- its own **managed Laravel server** on a dedicated port
- **`APP_URL` / `baseUrl`** passed to Playwright matching that port
- **worker-scoped auth ticket cache keys** (no cross-worker ticket bleed)
- the same **`DB_*` / `CACHE_PREFIX` env** as the PHP test worker (including `testing_test_{TEST_TOKEN}`)

Default port formula when `server.parallel_port_offset` is enabled:

```
server.port + TEST_TOKEN

```

Example with base port `8800`: worker `1` → `8801`, worker `4` → `8804`. Serial (non-parallel) runs keep using an ephemeral free port.

Configure the base port in `config/pest-e2e.php` under `server.port` (or legacy `parallel.base_port`) or via `PEST_E2E_SERVER_PORT` / `PEST_E2E_PARALLEL_BASE_PORT` in `.env.testing`. Set `server.host` / `PEST_E2E_SERVER_HOST` when the app must bind to a specific interface.

---

Installation
============

[](#installation)

Install the package:

```
composer require valcuandrei/pest-e2e --dev
```

Then run:

```
php artisan pest-e2e:install
```

The installer can:

- Update your `pest.php` to include `E2ETestCase`
- Publish `config/pest-e2e.php`
- Publish the base E2E test case
- Publish the JS harness
- Publish the Playwright integration
- Install `@playwright/test` and download Playwright **browser binaries** (`playwright install`)
- Create or update `.env.testing` with parallel-safe E2E overrides
- Create `database/testing.sqlite` for projects that intentionally use SQLite
- Configure `phpunit.xml` to let `.env.testing` control DB/cache (comment out overrides)
- Ensure `phpunit.xml` defines a **Browser** testsuite for `tests/Browser` (when `phpunit.xml` exists; idempotent on every successful install)
- When **Laravel Sail** is present (`laravel/sail` in `composer.json` or `vendor/laravel/sail`, plus a `laravel.test` service), offer to merge the **Headed Mode in Sail** block into your Docker Compose file — the file is chosen in the same order Docker Compose does: `compose.yaml`, `compose.yml`, `docker-compose.yaml`, then `docker-compose.yml`

Each step is skipped if already done. Use explicit flags to force: `--setup-env-testing`, `--update-testing-env`, `--setup-testing-database`, `--configure-phpunit`.

When publishing `E2ETestCase`, the installer sets the default JS package manager from tools **found on your PATH** (`pnpm`, `yarn`, `bun`, `npm`). The same resolved manager is used for the **Playwright dev dependency install** (so it no longer follows auto-detection / `PEST_E2E_PACKAGE_MANAGER` during that step). Pass **`--package-manager=pnpm`** (etc.) to force the stub value and skip detection. If more than one is available, you are prompted to pick one in interactive installs; with `--no-interaction` / `--yes`, it prefers a manager that matches an existing **lockfile**, otherwise the first in priority order (pnpm → yarn → bun → npm). If none are on PATH, it falls back to lockfile-only detection (same order), then `npm`.

### Unattended / CI mode

[](#unattended--ci-mode)

```
php artisan pest-e2e:install --yes
```

Alias:

```
php artisan pest-e2e:install --unattended
```

### Options

[](#options)

OptionDescription`--yes`Answer yes to all questions (performs full setup: update-pest, publish-config, publish-base-test-case, publish-js-harness, publish-js-playwright, add-csrf-exclusion, setup-env-testing/update-testing-env, setup-testing-database, configure-phpunit, sail-wslg-headed when Sail is detected, install-playwright)`--no`Answer no to all questions`--force`Overwrite existing files when publishing`--update-pest`Update Pest config to include E2ETestCase`--setup-env-testing`Create `.env.testing` with parallel-safe E2E overrides`--update-testing-env`Patch an existing `.env.testing` with parallel-safe session/cache/queue settings`--setup-testing-database`Create `database/testing.sqlite` for projects that intentionally use SQLite`--configure-phpunit`Comment out DB/cache env in `phpunit.xml` so `.env.testing` controls them`--sail-wslg-headed`Merge WSLg display/volume settings into the Sail `laravel.test` service of the resolved Compose file (see **Headed Mode in Sail** below)`--add-csrf-exclusion`Add pest-e2e auth route to CSRF exclusion (required for Herd/Windows)`--publish-config`Publish config`--publish-base-test-case`Publish E2ETestCase`--publish-js-harness`Publish JS harness`--publish-js-playwright`Publish Playwright adapter`--publish-browser-tests`Publish browser tests`--publish-playwright-tests`Publish Playwright tests`--install-playwright`Install `@playwright/test` and run `playwright install` (browser binaries)`--package-manager=`Force the value embedded in `E2ETestCase` and used for Playwright install (`npm`, `yarn`, `pnpm`, `bun`); skips PATH / lockfile detection and interactive choice---

Testing Environment (Important)
===============================

[](#testing-environment-important)

pestE2E starts a managed Laravel server using:

```
--env=testing

```

If a `.env.testing` file exists, Laravel automatically loads it.

**The installer can create this for you** with `--setup-env-testing` (included in `--yes`). It prefers Sail-compatible MySQL defaults because Laravel parallel testing creates per-worker databases from the base database name:

```
APP_ENV=testing
APP_URL=http://127.0.0.1

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=sail
DB_PASSWORD=password

SESSION_DRIVER=array
CACHE_STORE=array
QUEUE_CONNECTION=sync

PEST_E2E_AUTH_ROUTE_ENABLED=true
```

For existing `.env.testing` files, use `--update-testing-env` or `--yes`. The installer preserves existing MySQL/PostgreSQL database credentials, updates `SESSION_DRIVER=array`, `CACHE_STORE=array`, and `QUEUE_CONNECTION=sync`, and warns before replacing SQLite with Sail MySQL defaults unless `--yes`, `--unattended`, or `--force` already implies consent.

**Manual setup:** If you prefer to configure yourself, create `.env.testing` with a real isolated test database:

```
APP_ENV=testing
APP_DEBUG=true

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=sail
DB_PASSWORD=password

SESSION_DRIVER=array
CACHE_STORE=array
QUEUE_CONNECTION=sync

PEST_E2E_AUTH_ROUTE_ENABLED=true
```

This ensures:

- Your development database is not modified
- Auth routes are enabled only during testing
- The Pest process and the managed server use the same database
- Feature tests and browser tests do not share database-backed session/cache/queue state across workers

For `php artisan test --parallel`, Laravel creates per-worker databases such as `testing_test_1`, `testing_test_2`, etc. SQLite is not recommended for parallel browser testing because it does not provide the same per-worker database isolation model.

Your `phpunit.xml` must not override `DB_CONNECTION`, `DB_DATABASE`, `CACHE_STORE`, or `SESSION_DRIVER` — let `.env.testing` control them. The installer can comment these out for you with `--configure-phpunit`.

The managed server is started in isolation and inherits no development state beyond explicitly provided environment variables.

---

Quick Start
===========

[](#quick-start)

Configure a target inside the setUp() method of tests/E2ETestCase.php:

```
e2e()->target('frontend', fn ($p) => $p
    ->dir('resources/js/e2e')
    ->env(['APP_URL' => 'http://localhost'])
    ->params(['baseUrl' => 'http://localhost'])
);
```

> Register targets in your base E2E test case (`E2ETestCase::setUp()`), not inside individual test functions. For `--parallel`, ensure per-worker databases (see [Parallel test execution](#parallel-test-execution) under [Status](#status)).

Run all tests:

```
e2e('frontend')->run();
```

Run a specific test:

```
e2e('frontend')->runTest('UserProfile can update their profile');
```

---

Example: Complex Frontend Flow
==============================

[](#example-complex-frontend-flow)

PHP (Pest)
----------

[](#php-pest)

```
use App\Models\User;

test('that a user can update their profile', function () {
    $user = User::factory()->create();

    e2e('frontend')
        ->actingAs($user)  // or ->loginAs($user)
        ->withParams([
            'name' => 'Test User',
            'email' => 'test@example.com',
        ])
        ->runTest('UserProfile can update their profile');

    expect($user->fresh()->name)->toBe('Test User');
    expect($user->fresh()->email)->toBe('test@example.com');
});
```

JavaScript (Playwright)
-----------------------

[](#javascript-playwright)

```
import { test, expect } from '@playwright/test';
import { readParams } from '../pest-e2e/core.mjs';

test('UserProfile can update their profile', async ({ page }) => {
    const { name, email } = await readParams();

    await page.goto('/settings/profile');
    await page.locator('#name').fill(name);
    await page.locator('#email').fill(email);
    await page.getByTestId('update-profile-button').click();

    await expect(page.getByText('Saved.')).toBeVisible();
});
```

Laravel controls state and authentication. JavaScript controls the browser.

---

Why Not Use Pest’s Native Browser Testing?
==========================================

[](#why-not-use-pests-native-browser-testing)

Pest’s built-in browser testing (Dusk-style) is excellent for:

- Form submissions
- CRUD flows
- Traditional backend-driven pages
- Simple UI assertions

However, for advanced frontend systems such as:

- Drag-and-drop page builders
- Resizable layout systems
- CSS box-model assertions (width, height, margin, padding)
- Transform-based positioning
- Vue / Pinia state inspection
- DOM measurement and layout calculations

You need full native Playwright running in its own JavaScript environment.

pestE2E orchestrates your JS suite — it does not abstract it.

---

Managed Testing Server
======================

[](#managed-testing-server)

When you call:

```
e2e('frontend')->run();
```

pestE2E automatically:

1. Boots a temporary Laravel HTTP server
2. Forces it into `APP_ENV=testing`
3. Binds it to `127.0.0.1` on a free port
4. Executes your JS runner against that server
5. Collects the JSON report
6. Shuts the server down

No manual `php artisan serve` required. No environment leakage into development.

---

Running Tests
=============

[](#running-tests)

Local:

```
php artisan test
```

Sail:

```
sail artisan test
```

**E2E / Browser tests:** parallel runs are supported when each worker has an isolated database (see [Parallel test execution](#parallel-test-execution) under [Status](#status)).

```
php artisan test --parallel --processes=4
```

Successful E2E detail output is shown in normal test runs. In `--compact` and `--parallel` runs, passed E2E details are suppressed so Pest output stays readable; failed E2E runs still print their details.

Agent / PAO output
------------------

[](#agent--pao-output)

For AI agents and CI parsers, enable compact JSON (one line per `e2e()->run()`):

```
PEST_E2E_AGENT_OUTPUT=1 php artisan test ./tests/Browser
php artisan test ./tests/Browser --pest-e2e-agent-output
php artisan test ./tests/Browser --parallel --pest-e2e-agent-output
```

Also auto-detected when `laravel/agent-detector` is installed or common agent env vars are set (e.g. `CURSOR_AGENT`). Configure via `PEST_E2E_AGENT_OUTPUT` / `PAO_FORCE` in `.env.testing`, or `agent_output` in `config/pest-e2e.php`.

Disable with `PEST_E2E_AGENT_OUTPUT_DISABLE=1` or `PAO_DISABLE=1`.

In agent mode, human-readable Pest output is suppressed. Failed runs include `php_test`, `failures` (JS name, file, message, stack), and `report_dir`. See `.docs/API.md` for the full JSON contract.

---

Debug &amp; Headed Mode
=======================

[](#debug--headed-mode)

```
php artisan test --browse
php artisan test --debug
php artisan test --run-using=yarn
```

- `--browse` / `--headed` → runs browser in headed mode
- `--debug` → enables debug mode and implies headed mode
- `--run-using=npm|yarn|pnpm|bun` → use a specific package manager for E2E runs (default is set in `E2ETestCase::$e2ePackageManager` during install)

Timing Instrumentation
----------------------

[](#timing-instrumentation)

Enable baseline timing markers:

```
PEST_E2E_TIMING=true
```

Markers are emitted to `stderr` with prefix:

```
[pest-e2e:timing]

```

Each marker is JSON payload with `phase`, `atMs`, and optional `durationMs`.

Headed Mode in Sail (WSL2 + WSLg)
---------------------------------

[](#headed-mode-in-sail-wsl2--wslg)

If you run Pest inside Sail on Windows (WSL2) and want headed mode, forward WSLg into the container by adding this to your `laravel.test` service:

```
environment:
  DISPLAY: ${DISPLAY}
  WAYLAND_DISPLAY: ${WAYLAND_DISPLAY}
  XDG_RUNTIME_DIR: ${XDG_RUNTIME_DIR}
  PULSE_SERVER: ${PULSE_SERVER}

volumes:
  - /mnt/wslg:/mnt/wslg
  - /tmp/.X11-unix:/tmp/.X11-unix
```

This is only required for headed browser mode inside Docker on WSL2. Headless mode works without additional configuration.

---

Authentication Contract
=======================

[](#authentication-contract)

Default auth route:

```
/pest-e2e/auth/login

```

Configurable via:

```
config('pest-e2e.auth.route');
```

Security:

- Disabled by default
- Requires header (default: `X-Pest-E2E: 1`)
- Tickets are single-use and short-lived

---

Reports
=======

[](#reports)

The package does **not** store JSON reports on disk. Playwright emits its JSON report to stdout; the PHP side parses it in memory and maps it to the canonical `pest-e2e.v1` schema.

Playwright artifacts are written to a run-scoped directory:

```
{reports.base_dir}/{target}/{runId}

```

Configure the base directory globally with `config('pest-e2e.reports.base_dir')`. The default is `storage/framework/testing/pest-e2e`.

Old run directories are pruned according to `reports.prune`. Only directories marked as pestE2E runs are deleted, and the current run directory is never pruned.

---

Configuration
=============

[](#configuration)

Key config keys in `config/pest-e2e.php`:

KeyDescription`auth.route`Auth endpoint path (default: `/pest-e2e/auth/login`)`auth.route_enabled`Enable auth route (default: `false`, set via `PEST_E2E_AUTH_ROUTE_ENABLED`)`auth.ttl_seconds`Auth ticket TTL (default: 60)`auth.header.name` / `auth.header.value`Header required for auth requests (default: `X-Pest-E2E: 1`)`server.driver`Server runner: `artisan` or `php_builtin` (default: `php_builtin`)`server.host`Bind address for the managed server (default: `127.0.0.1`)`server.port`Base HTTP port for parallel workers (default: `8800`)`server.parallel_port_offset`When true, parallel workers use `server.port + TEST_TOKEN` (default: `true`)`reports.base_dir`Base directory for Playwright artifacts (default: `storage/framework/testing/pest-e2e`)`reports.prune.enabled`Enable old run pruning (default: `true`)`reports.prune.keep_runs`Number of most recent marked runs to keep (default: 50)`reports.prune.keep_days`Age window for marked runs to keep (default: 7)`timing.enabled`Enable timing instrumentation (default: `false`, set via `PEST_E2E_TIMING`)`js_runner.driver`JS runner (default: `playwright`)`js_runner.mode`Runner mode: `cold` or `warm` (default: `cold`)`package_manager`Package manager for E2E runs: `npm`, `yarn`, `pnpm`, or `bun` (default: set in E2ETestCase during install, overridable via `--run-using`)`parallel.base_port`Deprecated alias for `server.port``agent_output`Force agent JSON output (default: from `PEST_E2E_AGENT_OUTPUT` / `PAO_FORCE` env)`bindings`Contract-to-implementation map for swapping the JS runner. Keys: `JsWorkerContract::class`, `JsonParserContract::class`. Default: Playwright. Override to use Cypress, Puppeteer, etc.---

Final Positioning
=================

[](#final-positioning)

pestE2E is not browser testing for Laravel.

It is a **contract-driven bridge** between Laravel and JS-native E2E systems.

If you are building advanced frontend applications — page builders, editors, complex layouts — and you want Laravel to orchestrate while JavaScript owns the browser, this package is for you.

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance93

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~59 days

Total

2

Last Release

36d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/36803234?v=4)[webcityro](/maintainers/webcityro)[@webcityro](https://github.com/webcityro)

---

Top Contributors

[![webcityro](https://avatars.githubusercontent.com/u/36803234?v=4)](https://github.com/webcityro "webcityro (89 commits)")

---

Tags

phptestingunittestpestlaravelpackage

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/valcuandrei-pest-e2e/health.svg)

```
[![Health](https://phpackages.com/badges/valcuandrei-pest-e2e/health.svg)](https://phpackages.com/packages/valcuandrei-pest-e2e)
```

###  Alternatives

[pestphp/pest

The elegant PHP Testing Framework.

11.5k67.7M18.9k](/packages/pestphp-pest)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.6k](/packages/larastan-larastan)[orchestra/testbench

Laravel Testing Helper for Packages Development

2.2k41.3M38.7k](/packages/orchestra-testbench)[pestphp/pest-plugin-laravel

The Pest Laravel Plugin

22650.9M10.7k](/packages/pestphp-pest-plugin-laravel)[laravel/ai

The official AI SDK for Laravel.

9782.1M161](/packages/laravel-ai)[defstudio/pest-plugin-laravel-expectations

A plugin to add laravel tailored expectations to Pest

98585.1k7](/packages/defstudio-pest-plugin-laravel-expectations)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
