PHPackages                             soyhuce/laravel-embuscade - 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. soyhuce/laravel-embuscade

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

soyhuce/laravel-embuscade
=========================

Test Laravel views in isolation, inspired by nunomaduro/laravel-mojito

1.1.1(1y ago)2735[7 PRs](https://github.com/Soyhuce/laravel-embuscade/pulls)MITPHPPHP ^8.4CI passing

Since Mar 7Pushed 1mo ago3 watchersCompare

[ Source](https://github.com/Soyhuce/laravel-embuscade)[ Packagist](https://packagist.org/packages/soyhuce/laravel-embuscade)[ Docs](https://github.com/soyhuce/laravel-embuscade)[ GitHub Sponsors](https://github.com/SoyHuCe)[ RSS](/packages/soyhuce-laravel-embuscade/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (15)Versions (13)Used By (0)

Test Laravel views in isolation
===============================

[](#test-laravel-views-in-isolation)

Inspired by [nunomaduro/laravel-mojito](https://github.com/nunomaduro/laravel-mojito)

[![Latest Version on Packagist](https://camo.githubusercontent.com/c56feaf31b1e0ea945defae715cd6bc68987c8f82ce10cec7f7c416dea925411/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736f79687563652f6c61726176656c2d656d627573636164652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/soyhuce/laravel-embuscade)[![GitHub Tests Action Status](https://camo.githubusercontent.com/631c7745c56ab7aa2b119bb413bb4746c1a76249d1a6b5a3f75a092bb4354ff1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f79687563652f6c61726176656c2d656d627573636164652f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/soyhuce/laravel-embuscade/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/4007406aa5539530760f56a38c74726f604f2510642f34469ffbeb861591d267/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f79687563652f6c61726176656c2d656d627573636164652f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/soyhuce/laravel-embuscade/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![GitHub PHPStan Action Status](https://camo.githubusercontent.com/0e3a3a2236df289244d0f7f5cd381821813c83b45c6837b284e137d6ee2a7492/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f79687563652f6c61726176656c2d656d627573636164652f7068707374616e2e796d6c3f6272616e63683d6d61696e266c6162656c3d7068707374616e)](https://github.com/soyhuce/laravel-embuscade/actions?query=workflow%3APHPStan+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/3c66f09405aa47b1caeedd310d98de7b4daa36a94bdf6863fcbd20ffbb5c50d4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f736f79687563652f6c61726176656c2d656d627573636164652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/soyhuce/laravel-embuscade)

Test your Laravel views in isolation, interacting directly with the HTML.

```
$this->get('/')->expectView()->toContain('Laravel');

$this->view('menu', ['links' => $links])
    ->expectView()
    ->in('.links')
    ->first('a')
    ->toHave('href', 'https://laravel.com/docs')
    ->toHaveClass('btn')
    ->toHaveText('Documentation');
```

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

[](#installation)

You can install the package via composer:

```
composer require --dev soyhuce/laravel-embuscade
```

Usage
-----

[](#usage)

### Accessing view expectations

[](#accessing-view-expectations)

The most basic way to access the ViewExpect is to create it with an HTML string :

```
use Soyhuce\LaravelEmbuscade\ViewExpect;

new ViewExpect($html);
```

As this is not the most convenient way, you can create the ViewExpect from various objects:

```
// From a TestResponse
$expect = $this->get('/')->expectView();

// From a TestView
$expect = $this->view('home', ['links' => $links])->expectView();

// From a TestComponent
$expect = $this->component(Home::class, ['links' => $links])->expectView();
```

If you use Livewire, you can also create a ViewExpect from a Livewire test component:

```
$expect = Livewire::test(HomePage::class, ['links' => $links])->expectView();
```

### Navigating the view

[](#navigating-the-view)

Once the ViewExpect is created, you can navigate the view using the following methods:

```
 // Selects all elements matching the CSS selector
$expect->in($cssSeclector);
 // Selects the nth element matching the CSS selector, index starts at 0 !
$expect->at($cssSelector, $index);
 // Selects the first element matching the CSS selector
$expect->first($cssSelector);
 // Selects the last element matching the CSS selector
$expect->last($cssSeclector);
 // Selects the only element matching the CSS selector
$expect->sole($cssSelector);
```

`$cssSelector` must be any valid CSS selector, like `.class`, `#id`, `tag`, `tag.class`, `tag#id`, `tag[attr=value]`, etc.

> Note : Some pseudo-classes and pseudo-elements are not supported, like `:hover`, `:before`, `:after`, `:has`, etc.

#### Embuscade selectors

[](#embuscade-selectors)

You can also use Embuscade selectors to navigate the view as navigating CSS selectors can be cumbersome:

```
Login
```

```
$expect->sole('@login-button')
    ->...
```

You can also use the `@embuscade` directive to generate Embuscade selectors in your blade views:

```
Login
```

The `data-embuscade` attribute will be added to the element, only on testing environment or is debug mode is enabled.

> Note : Because @embuscade is not really a blade directive, it requires use of single quotes `'` to work and won't have access to execution context. `@embuscade("login-button")` will not work.
>
> ```
> @foreach ($array as $key => $value)
>     {{ $value}}
> @endforeach
> ```
>
>
>
> will not work either.

You can customize the HTML attribute Embuscade will use for the selectors using `selectorHtmlAttribute` method:

```
use Soyhuce\LaravelEmbuscade\Embuscade;

Embuscade::selectorHtmlAttribute('data-test'); // or 'dusk' if you use Dusk and want to leverage existing Dusk selectors.
```

#### In production

[](#in-production)

If you run in production without dev-dependencies installed, you will need an extra setup in order to remove `@embuscade` directives from your views.

In your `AppServiceProvider::boot` method, you can add the following code:

```
if (!class_exists(EmbuscadeServiceProvider::class)) {
    $this->app->get('blade.compiler')->prepareStringsForCompilationUsing(
        fn (string $input) => preg_replace( '/@embuscade\\(\'([^)]+)\'\\)/x', '', $input)
    );
}
```

There won't be any overhead here as cached views won't contain any `@embuscade` directive.

> Note : if you run in production with your dev-dependencies installed, you should definitively consider removing them.

### Expectations

[](#expectations)

#### Expectations on entire view

[](#expectations-on-entire-view)

Some expectations will be applied to the entire view:

```
// Expects the view to contain at least an element matching the CSS selector
$expect->toHave('.links a');
// Expects the view to contain exactly n elements matching the CSS selector
$expect->toHave('.links a', 2);
// Expects the view to contain at least one element pointing to $link
$expect->toHaveLink('https://laravel.com/docs');
// Expect the view contains a meta tag with the given attributes in head section
$expect->toHaveMeta(['property' => 'og:title', 'content' => 'Laravel']);
// Expect the view text equals given text
$expect->toHaveText('Laravel');
// Expect the view text contains given text
$expect->toContainText('Documentation');
// Expect the view text is empty
$expect->toBeEmpty();
// Expect the view html contains given content
$expect->toContain('Documentation');
```

#### Expectations on current element

[](#expectations-on-current-element)

Other expectation will only look at current root:

```
// Expect the element to have the given attribute
$expect->toHaveAttribute('disabled');
// Expect the element to have the given attribute with the given value
$expect->toHaveAttribute('href', 'https://laravel.com/docs');
// Expect the element to have the given attribute containing the given value
$expect->toHaveAttributeContaining('class', 'btn');
```

Some helpers are also available for you:

```
$expect->toAccept($value); // same as toHaveAttribute('accept', $value)
$expect->toBeDisabled(); // same as toHaveAttribute('disabled')
$expect->toHaveAlt($value); // same as toHaveAttribute('alt', $value)
$expect->toHaveClass($value); // same as toHaveAttributeContaining('class', $value)
$expect->toHaveHref($value); // same as toHaveAttribute('href', $value)
$expect->toHaveSrc($value); // same as toHaveAttribute('src', $value)
$expect->toHaveValue($value); // same as toHaveAttribute('value', $value)
```

#### Negating expectation

[](#negating-expectation)

You can negate any expectation by calling `not` before the expectation:

```
$expect->not->toHave('.links a');
$expect->not->toBeDisabled();
```

The negation will only apply to the next expectation.

```
$this->view('menu', ['links' => $links])
    ->expectView()
    ->in('.links')
    ->first('a')
    ->toHave('href', 'https://laravel.com/docs')
    ->not->toHaveAttribute('target')
    ->toHaveClass('btn')
    ->toHaveText('Documentation');
```

### Navigating and expectations on elements

[](#navigating-and-expectations-on-elements)

You can navigate and apply expectations on elements in a single chain, in order to not loose focus on the current element: Given the following HTML:

```

  Disabled fieldset

      Name:  Regular

    Number:

```

you can test it with the following code:

```
use Soyhuce\LaravelEmbuscade\ViewExpect;

$this->view('test')
    ->expectView()
    ->toBeDisabled()
    ->sole('legend', fn(ViewExpect $expect) => $expect->toHaveText('Disabled fieldset'))
    ->at('p input', 0, fn(ViewExpect $expect) => $expect->toHaveAttribute('type', 'radio'))
    ->at('p input', 1, fn(ViewExpect $expect) => $expect->toHaveAttribute('type', 'number'));
```

Every selection method will allow you to pass a closure that will receive a new ViewExpect, focused on the selected element.

### Customization

[](#customization)

The `ViewExpect` class is macroable, so you can add your own expectations:

```
ViewExpect::macro('toHaveCharset', function (string $charset) {
        return $this->in('head')->first('meta')->toHaveAttribute('charset', $charset);
    });
});

$this->view('home')->expectView()->toHaveCharset('utf-8');
```

### Debugging

[](#debugging)

You can dump the current state of the ViewExpect using the `dump` or `dd` methods:

```
$this->view('home')->expectView()->in('a')->dump();
```

It will dump the current HTML node.

WTF is Embuscade ?
------------------

[](#wtf-is-embuscade-)

Embuscade is a French word meaning ambush. It makes reference to the original package name, Laravel Mojito, as "une embuscade" is also a famous local cocktail from [Caen](https://fr.wikipedia.org/wiki/Caen).

Each bar has its own recipe, but it could be something like:

- 20 cl of blond beer
- 12 cl of white wine
- 8 cl of calvados (cider brandy, 40% alcohol)
- 4 cl of blackcurrant syrup
- 4 cl of lemon syrup

Easy to drink but quite strong, be careful not to fall into the ambush!

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Bastien Philippe](https://github.com/bastien-phi)
- [Nuno Maduro](https://github.com/nonumaduro) for the inspiration with [laravel-mojito](https://github.com/nunomaduro/laravel-mojito)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance71

Regular maintenance activity

Popularity19

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 87.9% 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 ~5 days

Total

3

Last Release

422d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/206cfbf866a463f7e7d1e86946d59b82f4191b9c89f9981fb03eeb264d77af79?d=identicon)[SoyHuCe](/maintainers/SoyHuCe)

---

Top Contributors

[![bastien-phi](https://avatars.githubusercontent.com/u/10199039?v=4)](https://github.com/bastien-phi "bastien-phi (29 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (2 commits)")

---

Tags

laraveltestsviewssoyhuce

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/soyhuce-laravel-embuscade/health.svg)

```
[![Health](https://phpackages.com/badges/soyhuce-laravel-embuscade/health.svg)](https://phpackages.com/packages/soyhuce-laravel-embuscade)
```

###  Alternatives

[spatie/laravel-visit

Quickly visit any route of your Laravel app

15614.6k](/packages/spatie-laravel-visit)[mpociot/laravel-test-factory-helper

Generate Laravel test factories from your existing models

933635.2k2](/packages/mpociot-laravel-test-factory-helper)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[laracraft-tech/laravel-useful-additions

A collection of useful Laravel additions!

58109.4k](/packages/laracraft-tech-laravel-useful-additions)[michiruf/laravel-http-automock

Automatically mock http requests when testing

161.0k](/packages/michiruf-laravel-http-automock)

PHPackages © 2026

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