PHPackages                             thelemon2020/pest-plugin-pom - 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. thelemon2020/pest-plugin-pom

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

thelemon2020/pest-plugin-pom
============================

A Pest plugin for writing browser tests using the Page Object Model.

0.8(2w ago)27MITPHPPHP ^8.3CI passing

Since May 5Pushed 1w agoCompare

[ Source](https://github.com/thelemon2020/pest-plugin-pom)[ Packagist](https://packagist.org/packages/thelemon2020/pest-plugin-pom)[ RSS](/packages/thelemon2020-pest-plugin-pom/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (8)Dependencies (3)Versions (9)Used By (0)

Pest Plugin POM
===============

[](#pest-plugin-pom)

A [Pest](https://pestphp.com) plugin for writing browser tests using the Page Object Model pattern. Integrates with [pest-plugin-browser](https://github.com/pestphp/pest-plugin-browser).

> **Early Release:** Pre-v1, bugs are expected. Please [open an issue](https://github.com/thelemon2020/pest-plugin-pom/issues) if you find one.

Requirements
------------

[](#requirements)

- PHP ^8.3
- Pest ^4.0
- pest-plugin-browser ^4.0

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

[](#installation)

```
composer require thelemon2020/pest-plugin-pom --dev
```

### Config

[](#config)

```
php artisan vendor:publish --tag=pest-plugin-pom-config
```

Creates `config/pest-plugin-pom.php`:

```
return [
    'path' => 'tests/Browser/Pages',
];
```

---

Page Objects
------------

[](#page-objects)

Extend `Page`, define a `url()`, and add interaction methods:

```
namespace Tests\Browser\Pages;

use Thelemon2020\PestPom\Page;
use Thelemon2020\PestPom\Concerns\InteractsWithForms;

class LoginPage extends Page
{
    use InteractsWithForms;

    public static function url(): string
    {
        return '/login';
    }

    public function loginAs(string $email, string $password): static
    {
        return $this
            ->fillForm(['Email' => $email, 'Password' => $password])
            ->submitForm('Log in');
    }
}
```

Navigate to a page with `page()` or `::open()`:

```
$page = page(LoginPage::class);
// or
$page = LoginPage::open();
```

### Parameterized URLs

[](#parameterized-urls)

```
class ProductPage extends Page
{
    public static function url(): string
    {
        return '/products/{id}';
    }
}

ProductPage::open(['id' => 42]);
$page->navigateTo(ProductPage::class, ['id' => 42]);
```

### Navigating Between Pages

[](#navigating-between-pages)

`navigateTo()` sends the browser to a page's URL. `nowOn()` re-wraps the current session as a different page type without reloading — useful after a server-side redirect. `nowOn()` also verifies the current URL matches the destination.

```
// explicit navigation
$page->navigateTo(DashboardPage::class);

// after a redirect
$page->submitForm('Log in')->nowOn(DashboardPage::class);
```

`navigateTo()``nowOn()`Navigates the browserYesNoVerifies current URLNoYesAccepts `{param}` valuesYesPattern-matched---

Tests
-----

[](#tests)

Tests using Page Objects live in `tests/Browser/` — Pest's browser plugin handles the rest.

```
it('allows a user to log in', function () {
    page(LoginPage::class)
        ->loginAs('jane@example.com', 'password')
        ->assertSee('Dashboard');
});
```

---

Components
----------

[](#components)

Components encapsulate a reusable piece of UI. They work like pages but have no URL and are always obtained through a page instance.

```
php artisan pest:component SearchBar
```

```
namespace Tests\Browser\Components;

use Thelemon2020\PestPom\Component;

class SearchBarComponent extends Component
{
    public static function selector(): string
    {
        return '#search-bar';
    }

    public function search(string $query): static
    {
        return $this
            ->fill('input[name=query]', $query)
            ->click('button[type=submit]');
    }
}
```

```
page(DashboardPage::class)
    ->component(SearchBarComponent::class)
    ->search('pest php')
    ->assertSee('pest-plugin-browser');
```

Selectors passed to interaction methods are scoped to the component's root element automatically.

### Typed Accessors

[](#typed-accessors)

```
class DashboardPage extends Page
{
    public function searchBar(): SearchBarComponent
    {
        return $this->component(SearchBarComponent::class);
    }
}
```

### Multiple Instances

[](#multiple-instances)

Use `components()->item(n)` (1-based) to target a specific occurrence:

```
$page->components(CardComponent::class)->assertTotal(3);
$page->components(CardComponent::class)->item(2)->assertTitle('Advanced Usage');
```

### Sub-Components

[](#sub-components)

Call `component()` on a component to create a child scoped within the parent:

```
// resolves to: #nav .user-menu
$page->header()->userMenu()->assertSee('Jane Doe');
```

### Scoped Assertions

[](#scoped-assertions)

MethodDescription`assertSee(string $text)`Text appears within the component`assertDontSee(string $text)`Text does not appear within the component`assertVisible()`Root element is visible`assertPresent()`Root element is in the DOM`assertMissing()`Root element is absent from the DOM`assertCount(string $selector, int $expected)`Count of child elements matching selector`assertSeeIn(string $selector, string $text)`Text appears within a child element`assertDontSeeIn(string $selector, string $text)`Text does not appear within a child element`assertTotal(int $expected)`Number of elements matching this component's selector### Scoped Interactions

[](#scoped-interactions)

MethodDescription`click(string $selector)`Click an element`rightClick(string $selector)`Right-click an element`type(string $field, string $value)`Type into a field`typeSlowly(string $field, string $value, int $delay = 100)`Type slowly into a field`fill(string $field, string $value)`Fill a field`append(string $field, string $value)`Append text to a field`clear(string $field)`Clear a field`hover(string $selector)`Hover over an element`select(string $field, array|string|int $option)`Select a dropdown option`radio(string $field, string $value)`Select a radio button`check(string $field, ?string $value = null)`Check a checkbox`uncheck(string $field, ?string $value = null)`Uncheck a checkbox`attach(string $field, string $path)`Attach a file`keys(string $selector, array|string $keys)`Send keyboard input`drag(string $from, string $to)`Drag one element to another`text(string $selector): ?string`Return element text content`attribute(string $selector, string $attribute): ?string`Return element attribute value`press()`, `pressAndWaitFor()`, and `withKeyDown()` are not scoped — they pass through via `__call`.

---

Generators
----------

[](#generators)

```
php artisan pest:page Login
php artisan pest:page Register --concerns=forms,alerts

php artisan pest:component SearchBar
php artisan pest:component DataTable --concerns=navigation,modals
```

Available concerns: `forms`, `alerts`, `modals`, `navigation`.

The `Page`/`Component` suffix is optional and won't be doubled.

---

Concerns
--------

[](#concerns)

### `InteractsWithForms`

[](#interactswithforms)

MethodDescription`fillForm(array $fields)`Fill multiple fields, keyed by label`submitForm(string $button = 'Submit')`Click a submit button by label`checkBox(string $label)`Check a checkbox by label`choose(string $field, array|string|int $option)`Select a dropdown option by label### `InteractsWithAlerts`

[](#interactswithalerts)

MethodDescription`assertSuccessMessage(string $message)`Assert a success alert is visible`assertErrorMessage(string $message)`Assert an error alert is visible`assertFieldError(string $field, string $message)`Assert a validation error for a field### `InteractsWithModals`

[](#interactswithmodals)

MethodDescription`openModal(string $trigger)`Click the element that opens the modal`confirmModal(string $button = 'Confirm')`Click the confirm button`dismissModal(string $button = 'Cancel')`Click the cancel button`closeModal(string $button = 'Close')`Close without confirming### `InteractsWithNavigation`

[](#interactswithnavigation)

MethodDescription`clickLink(string $label)`Click a link by visible text`goBack()`Navigate back`goForward()`Navigate forward`refresh()`Reload the page---

Expectations
------------

[](#expectations)

```
expect($page)->toBeOnPage(DashboardPage::class);
expect($page)->toSee('Welcome back');
```

---

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance98

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity44

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

Total

8

Last Release

14d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8b6e5c4ff8a4d599b9a8f888c6e4b243dbf7166837591ed401da4f2d58f4a3cf?d=identicon)[thelemon2020](/maintainers/thelemon2020)

---

Top Contributors

[![thelemon2020](https://avatars.githubusercontent.com/u/53379286?v=4)](https://github.com/thelemon2020 "thelemon2020 (48 commits)")

---

Tags

plugintestingpestbrowserpage-object-model

### Embed Badge

![Health badge](/badges/thelemon2020-pest-plugin-pom/health.svg)

```
[![Health](https://phpackages.com/badges/thelemon2020-pest-plugin-pom/health.svg)](https://phpackages.com/packages/thelemon2020-pest-plugin-pom)
```

###  Alternatives

[spatie/pest-plugin-snapshots

A package for snapshot testing in Pest

401.8M203](/packages/spatie-pest-plugin-snapshots)[defstudio/pest-plugin-laravel-expectations

A plugin to add laravel tailored expectations to Pest

97585.1k7](/packages/defstudio-pest-plugin-laravel-expectations)[jonpurvis/lawman

A PestPHP Plugin to help with architecture testing SaloonPHP integrations

4134.4k9](/packages/jonpurvis-lawman)

PHPackages © 2026

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