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.0.0(1mo ago)01↑2900%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 1mo ago

READMEChangelog (1)Dependencies (8)Versions (2)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 is not supported

[](#️-parallel-test-execution-is-not-supported)

**Do not use Pest’s `--parallel` option (or other parallel PHPUnit / process splitting) for suites that call `e2e()->…->run()`.** E2E tests must run **one process at a time**: they rely on a managed app server, auth tickets, shared testing DB/session configuration, and coordinated Playwright processes. Running Browser/E2E tests in parallel is **unsupported** and may fail unpredictably (lost targets, port conflicts, flaky auth). Run those tests **sequentially** (default for a single `pest` / `phpunit` process without `--parallel`). Parallel support may be added in a future release.

---

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 `.env.testing` from `.env` with E2E-appropriate overrides
- Create `database/testing.sqlite` for SQLite tests
- 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`, `--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, 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` from `.env` with E2E overrides`--setup-testing-database`Create `database/testing.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 copies `.env` and applies E2E overrides:

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

DB_CONNECTION=sqlite
DB_DATABASE=testing

CACHE_STORE=database
SESSION_DRIVER=database

PEST_E2E_AUTH_ROUTE_ENABLED=true
```

For SQLite, the installer can also create `database/testing.sqlite` (`--setup-testing-database`) and configure `phpunit.xml` to omit DB/cache env vars so `.env.testing` controls them (`--configure-phpunit`).

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

```
APP_ENV=testing
APP_DEBUG=true

DB_CONNECTION=sqlite
DB_DATABASE=testing

CACHE_STORE=database
SESSION_DRIVER=database

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
- Cache and session are shared (required for auth ticket exchange)

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. Do not run those tests with `--parallel` (see **Parallel test execution is not supported** under [Status](#status) above).

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:** keep the default **sequential** run. Avoid `pest --parallel` / `php artisan test --parallel` for directories or projects that include `e2e()->…->run()` — parallel execution is **not supported** at this time (see warning under [Status](#status)).

---

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.

---

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`)`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`)`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

39

—

LowBetter than 86% of packages

Maintenance90

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

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

Unknown

Total

1

Last Release

50d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/53c7aa2cc22d7bdc3f2815d061744cc07ea64062ecf87ecd2e8c4763539a9677?d=identicon)[webcityro](/maintainers/webcityro)

---

Top Contributors

[![webcityro](https://avatars.githubusercontent.com/u/36803234?v=4)](https://github.com/webcityro "webcityro (78 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.4k59.5M14.2k](/packages/pestphp-pest)[larastan/larastan

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

6.4k43.5M5.2k](/packages/larastan-larastan)[pestphp/pest-plugin-laravel

The Pest Laravel Plugin

22044.1M7.9k](/packages/pestphp-pest-plugin-laravel)[defstudio/pest-plugin-laravel-expectations

A plugin to add laravel tailored expectations to Pest

98548.9k4](/packages/defstudio-pest-plugin-laravel-expectations)[pestphp/pest-plugin-drift

The Pest Drift Plugin

734.0M74](/packages/pestphp-pest-plugin-drift)[orchestra/workbench

Workbench Companion for Laravel Packages Development

8017.0M43](/packages/orchestra-workbench)

PHPackages © 2026

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