PHPackages                             vielhuber/cassette - 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. vielhuber/cassette

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

vielhuber/cassette
==================

Record real HTTP flows once, replay them deterministically as regression tests.

1.2.9(1mo ago)113↓83.6%MITPHPPHP &gt;=8.3

Since Mar 27Pushed 1mo agoCompare

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

READMEChangelogDependencies (2)Versions (31)Used By (0)

[![GitHub Tag](https://camo.githubusercontent.com/525bc43e94953158d59df669618543ca20c379a1c098117524426ae4ef0f7764/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f7461672f7669656c68756265722f6361737365747465)](https://github.com/vielhuber/cassette/tags)[![Code Style](https://camo.githubusercontent.com/1540f8ce219727155ab62506c77b818b720421c22c4cf0b18a5f160942132e2d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64655f7374796c652d7073722d2d31322d6666363962342e737667)](https://www.php-fig.org/psr/psr-12/)[![License](https://camo.githubusercontent.com/a0e60ec0458a9346a6b5a5ce2adacffd749932418ae22b23943886e1e5f947ae/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f7669656c68756265722f6361737365747465)](https://github.com/vielhuber/cassette/blob/main/LICENSE.md)[![Last Commit](https://camo.githubusercontent.com/a87d0338a91464d0367267306614fb395da0d6e5f12f860abaf0a026094b8537/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f7669656c68756265722f6361737365747465)](https://github.com/vielhuber/cassette/commits)[![PHP Version Support](https://camo.githubusercontent.com/5ba2837b2c8d70f7ef769ad5e39e4de9f8039b04be506932bde96aabd8eba4ce/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f7669656c68756265722f6361737365747465)](https://packagist.org/packages/vielhuber/cassette)[![Packagist Downloads](https://camo.githubusercontent.com/92601d376ad0a66dcb1a0fbe54a30cb6d145fdfec8ef7ba927f1547b8e75d7e5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7669656c68756265722f6361737365747465)](https://packagist.org/packages/vielhuber/cassette)

📼 cassette 📼
============

[](#-cassette-)

cassette hooks into your PHP application at the lowest level — intercepting database calls and outgoing HTTP requests via [uopz](https://www.php.net/manual/en/book.uopz.php) and serialises every return value to a JSON tape. On replay, the server runs normally but all external I/O is served from that tape, making each test completely self-contained: no database, no network, no side effects. Visual regression is layered on top via Playwright, which navigates through the recorded request sequence and compares screenshots against committed baselines.

Installation
------------

[](#installation)

### Production

[](#production)

```
cd /var/www/my-project
composer require --dev vielhuber/cassette`
./vendor/bin/...
```

### Development

[](#development)

```
cd /var/www/cassette
export CASSETTE_ROOT=/var/www/my-project
```

### Usage

[](#usage)

```
./cassette up --php=8.5
./cassette record run_001
./cassette replay run_001 --http-only --screenshot-only
./cassette delete run_001
./cassette list
./cassette down --php=8.5
```

Notes**Requirements:**

- PHP ≥ 8.3 with the [uopz](https://www.php.net/manual/en/book.uopz.php) extension
- Node.js (for visual screenshot regression — installed automatically on first use)

**Project root resolution.** Every command resolves the project root in this order:

1. `--root=` flag
2. `CASSETTE_ROOT` environment variable
3. Current working directory (default)

So `cd /var/www/my-project && vendor/bin/cassette record run_001` just works without any flags.

**Toggle uopz and inject the bootstrap line** — `up`/`down` enable/disable uopz (incl. the PHP 8.5 patch) and write/remove the `require_once` hook in `wp-config.php` (WordPress) or `public/index.php` (Laravel):

```
./cassette up   --php=8.5 --root=/var/www/my-project
./cassette down --php=8.5 --root=/var/www/my-project
```

`.cassette/config.json` is created automatically on the first `vendor/bin/cassette record` call.

Usage
-----

[](#usage-1)

CommandDescription`vendor/bin/cassette up [--php=]`Enable uopz, install LD\_PRELOAD shim (PHP 8.5), inject bootstrap line`vendor/bin/cassette down [--php=]`Reverse `up` — disable uopz and remove bootstrap line`vendor/bin/cassette record `Switch to record mode and clear old data — then click through the flow in the browser`vendor/bin/cassette stop `Stop recording — further requests are no longer captured`vendor/bin/cassette replay  [--refresh] [--base-url=] [--http-only|--screenshot-only]`HTTP diff + visual screenshot comparison; `--refresh` recreates baselines`vendor/bin/cassette accept `Interactively accept HTTP diffs as new baseline`vendor/bin/cassette delete `Delete a run including all its data and screenshots`vendor/bin/cassette delete --all`Delete all runs`vendor/bin/cassette list`List all recorded runs with request count and screenshotsAll commands accept `--root=`. If omitted, `CASSETTE_ROOT` (env) is used, falling back to the current working directory.

Exit code `0` = all green, `1` = deviations found. CI-compatible.

All run data is stored in `.cassette/runs//`. The directory is created on the first `vendor/bin/cassette` invocation with a self-contained `.cassette/.gitignore` that excludes everything (`*`), so recordings stay out of git without touching the project's own `.gitignore`. To track screenshot baselines, replace that file's contents with negation patterns:

```
*
!runs/*/screenshots/
!runs/*/screenshots/*
!.gitignore

```

Development workflow (working on cassette itself)
-------------------------------------------------

[](#development-workflow-working-on-cassette-itself)

When developing cassette alongside a project, you can run the CLI directly from the cassette source directory and point it at the target project — either via `CASSETTE_ROOT` or via `--root`:

```
cd /var/www/cassette
export CASSETTE_ROOT=/var/www/my-project
./cassette record run_001
./cassette stop   run_001
./cassette replay run_001
```

Or with an explicit flag:

```
./cassette record run_001 --root=/var/www/my-project
```

This way the target project's `composer.json` stays completely untouched — no path-repository, no `minimum-stability`, no symlinks. All cassette data is read from and written to `/var/www/my-project/.cassette/` as usual.

Portability
-----------

[](#portability)

Recordings are captured on one host (e.g. `https://custom-tld.dev`) but can be replayed anywhere — CI, localhost, staging — without re-recording:

```
# Replay against a different host than the one used during recording
vendor/bin/cassette replay run_001 --base-url=http://localhost
```

The `--base-url` flag replaces the host for both the HTTP diff and the Playwright screenshots. The recorded `base_url` in `http.json` is never modified.

For GitHub Actions, pass the URL as a secret:

```
- name: Replay cassettes
  run: vendor/bin/cassette replay run_001 --base-url=${{ secrets.APP_URL }}
```

### No database required for replay

[](#no-database-required-for-replay)

During replay, all database queries and outgoing HTTP calls are intercepted by [uopz](https://www.php.net/manual/en/book.uopz.php) and served from the recorded cassette data. The server must be running and reachable, but:

- **no real database connection is needed** — the DB state at replay time is completely irrelevant
- **no test fixtures or seed data** are required
- external APIs and curl calls are mocked the same way

This makes cassette replays safe to run on CI, on a fresh machine, or against a server whose database is empty, stale, or even offline.

### curl interception

[](#curl-interception)

To intercept `__::curl()` calls, add [`vielhuber/stringhelper`](https://github.com/vielhuber/stringhelper) to your project:

```
composer require vielhuber/stringhelper
```

Without it, cassette still records and replays all database calls — curl interception is simply skipped.

Configuration
-------------

[](#configuration)

Create `.cassette/config.json` to customise recording and screenshot behaviour per project:

```
{
    "ignoreUrls": [],
    "screenshot": {
        "headless": true,
        "zoom": 0.7,
        "maxDiffPixelRatio": 0.01,
        "maskSelectors": [],
        "maskDates": true,
        "timeout": 60000,
        "waitAfterGoto": 2000
    }
}
```

KeyDefaultDescription`ignoreUrls``[]`List of URI substrings — any HTTP request whose path contains one of these strings is silently skipped during recording (not written to `http.json`)`screenshot.headless``true`Run Playwright in headless mode`screenshot.zoom``0.7`CSS zoom applied to `` before each screenshot`screenshot.maxDiffPixelRatio``0.01`Maximum allowed pixel difference ratio (0–1)`screenshot.maskSelectors``[]`CSS selectors whose elements are hidden before each screenshot (uses direct DOM manipulation, so `position: fixed` elements are reliably hidden)`screenshot.maskDates``true`Automatically hide all date and time values in the page (ISO dates `2026-03-29`, German dates `29.03.2026`, times `12:34` / `12:34:56`) including `` values and plain text nodes`screenshot.waitAfterGoto``0`Extra milliseconds to wait after `networkidle` before taking the screenshot. Used as a fixed wait by default, or as a ceiling when `domStableMs > 0``screenshot.domStableMs``0`Opt-in early-exit. When set &gt; 0, the wait exits early once the DOM signature (HTML length + scrollHeight) has been stable for this many consecutive milliseconds, capped at `waitAfterGoto`. Skips signature-only changes (CSS animations, image decoding) — leave at `0` for pages with rich client-side interactivity

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance93

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity60

Established project with proven stability

 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 ~2 days

Total

30

Last Release

35d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3183737?v=4)[David Vielhuber](/maintainers/vielhuber)[@vielhuber](https://github.com/vielhuber)

---

Top Contributors

[![vielhuber](https://avatars.githubusercontent.com/u/3183737?v=4)](https://github.com/vielhuber "vielhuber (30 commits)")

### Embed Badge

![Health badge](/badges/vielhuber-cassette/health.svg)

```
[![Health](https://phpackages.com/badges/vielhuber-cassette/health.svg)](https://phpackages.com/packages/vielhuber-cassette)
```

###  Alternatives

[dms/phpunit-arraysubset-asserts

This package provides ArraySubset and related asserts once deprecated in PHPUnit 8

14228.7M340](/packages/dms-phpunit-arraysubset-asserts)[orchestra/workbench

Workbench Companion for Laravel Packages Development

8219.1M69](/packages/orchestra-workbench)[phpbenchmark/phpbenchmark

Easy to use benchmark toolkit for your PHP-application. This library contains classes for comparing algorithms as well as benchmarking application responses

8011.5k2](/packages/phpbenchmark-phpbenchmark)

PHPackages © 2026

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