# Search Results

## Http Tests Making Requests Session / Authentication
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

### Session / Authentication

Laravel provides several helpers for interacting with the session during HTTP testing. First, you may set the session data to a given array using the `withSession` method. This is useful for loading the session with data before issuing a request to your application:

```php tab=Pest
<?php

test('interacting with the session', function () {
    $response = $this->withSession(['banned' => false])->get('/');

    //
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_the_session(): void
    {
        $response = $this->withSession(['banned' => false])->get('/');

        //
    }
}
```

Laravel's session is typically used to maintain state for the currently authenticated user. Therefore, the `actingAs` helper method provides a simple way to authenticate a given user as the current user. For example, we may use a [model factory](/docs/{{version}}/eloquent-factories) to generate and authenticate a user:

```php tab=Pest
<?php

use App\Models\User;

test('an action that requires authentication', function () {
    $user = User::factory()->create();

    $response = $this->actingAs($user)
        ->withSession(['banned' => false])
        ->get('/');

    //
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Models\User;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_an_action_that_requires_authentication(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)
            ->withSession(['banned' => false])
            ->get('/');

        //
    }
}
```

You may also specify which guard should be used to authenticate the given user by passing the guard name as the second argument to the `actingAs` method. The guard that is provided to the `actingAs` method will also become the default guard for the duration of the test:

```php
$this->actingAs($user, 'web');
```

If you would like to ensure the request is unauthenticated, you may use the `actingAsGuest` method:

```php
$this->actingAsGuest();
```

---

## Http Tests Available Assertions Response Assertions assertRedirectBack
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertRedirectBack

Assert whether the response is redirecting back to the previous page:

```php
$response->assertRedirectBack();
```

---

## Browser Testing
Browser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/browser-testing.md

---
title: Browser Testing
description:
---

# Browser Testing

Browser testing is an essential part of modern web development, allowing you to ensure that your application works correctly across different browsers and devices. Pest provides a simple and elegant way to write browser tests. Here is an example of how to write a browser test using Pest:

```php
it('may welcome the user', function () {
    $page = visit('/');

    $page->assertSee('Welcome');
});
```

This is a basic example of a browser test that checks if the homepage contains the text "Welcome". However, Pest's browser testing capabilities go beyond this simple example. You can use various methods to interact with the page, such as clicking buttons, filling out forms, and navigating between pages.

Here is an example of a more complex browser test, on Laravel, that checks if a user can sign in:

```php
it('may sign in the user', function () {
    Event::fake();

    User::factory()->create([ // assumes RefreshDatabase trait is used on Pest.php...
        'email' => 'nuno@laravel.com',
        'password' => 'password',
    ]);

    $page = visit('/')->on()->mobile()->firefox();

    $page->click('Sign In')
         ->assertUrlIs('/login')
         ->assertSee('Sign In to Your Account')
         ->fill('email', 'nuno@laravel.com')
         ->fill('password', 'password')
         ->click('Submit')
         ->assertSee('Dashboard');

    $this->assertAuthenticated();

    Event::assertDispatched(UserLoggedIn::class);
});
```

Note that, you are leveraging the full power of Laravel's testing capabilities, such as refresh database, event faking, and authentication assertions, while also actually doing browser testing.

## Getting Started

To get started with browser testing in Pest, you need to install the Pest Browser plugin. You can do this by running the following command:

```bash
composer require pestphp/pest-plugin-browser --dev

npm install playwright@latest
npx playwright install
```

Finally, add `tests/Browser/Screenshots` to your `.gitignore` file to avoid committing screenshots taken during browser tests.

### Running Browser Tests

Running browser tests is similar to running regular Pest tests:

```bash
./vendor/bin/pest
```

We recommend running tests in parallel using the `--parallel` option to speed up the execution:

```bash
./vendor/bin/pest --parallel
```

For debugging purposes, you can run the tests in a headed mode and pause the execution at the end of the failed test run:

```bash
./vendor/bin/pest --debug
```

### Navigation

The `visit()` method is used to navigate to a specific URL in your browser test. It provides various methods to interact with the page:

```php
test('example', function () {
    $page = visit('/');

    $page->assertSee('Welcome');
});
```

### Using Other Browsers

By default, the `visit()` method uses Chrome as the browser. However, if you want to use a different browser, you can specify it using the `--browser` option when running the tests:

```bash
./vendor/bin/pest --browser firefox
./vendor/bin/pest --browser safari
```

If you wish to use a different browser by default without specifying it in the command line, you can set it in your `Pest.php` configuration file:

```php
pest()->browser()->inFirefox();
pest()->browser()->inSafari();
```

### Using Other Devices

The `visit()` method uses a desktop viewport. However, you can specify a mobile viewport using the `onMobile()` method. For example:

```php
$page = visit('/')->on()->mobile();
```

If you wish to use a specific device, you can use the `on()` method and chain it with the `macbook14`, `iPhone14Pro`, etc:

```php
$page = visit('/')->on()->iPhone14Pro();
```

### Using Dark Mode

Pest enforces a light color scheme by default. However, you can specify a dark color scheme using the `inDarkMode()` method:

```php
$page = visit('/')->inDarkMode();
```

### Visiting Multiple Pages

You can visit multiple pages simultaneously by passing an array of URLs to the `visit()` method. This is useful for testing scenarios where you need to interact with multiple pages at once:

```php
$pages = visit(['/', '/about']);

$pages->assertNoSmoke()
    ->assertNoAccessibilityIssues()
    ->assertNoConsoleLogs()
    ->assertNoJavaScriptErrors();

[$homePage, $aboutPage] = $pages;

$homePage->assertSee('Welcome to our website');
$aboutPage->assertSee('About Us');
```

### Navigation

After visiting a page, you can navigate to other pages using the `navigate()` method. This method allows you to navigate to a different URL while keeping the current browser context:

```php
$page = visit('/');

$page->navigate('/about')
     ->assertSee('About Us');
```

### Locating Elements

You can locate elements in the DOM using text or CSS selectors. Pest provides a simple syntax for locating elements:

```php
// Clicks the first link with the text "Login"
$page->click('Login');

// Clicks the first element with the class "btn-primary"
$page->click('.btn-primary');

// Clicks the element with the data-test attribute "login"
$page->click('@login');

// Clicks the element with the ID "submit-button"
$page->click('#submit-button');

// etc...
```

### Configuring Timeouts

Sometimes, elements may take time to appear on the page. By default, Pest waits for `5` seconds before timing out. You can configure the default timeout for browser tests in your `Pest.php` configuration file:

```php
pest()->browser()->timeout(10000);
```

### Configuring User Agent

By default, the User Agent will default to the Browser you're running for tests such as: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/133.0.6943.16 Safari/537.36`

You may wish to override the User Agent of the browser for all of your tests, you can configure this in the `Pest.php` configuration file:

```php
pest()->browser()->userAgent('CustomUserAgent');
```

### Configuring Host

By default, the server will bind to `127.0.0.1` for all browser tests.

You may wish to override the host for subdomain applications. You can configure this in the `Pest.php` configuration file:

```php
pest()->browser()->withHost('some-subdomain.localhost');
```

### Geolocation

Sometimes, you need to define where the browser believes it is physically on the earth.  This method takes a latitude and longitude and will set the `geolocation` permission in the browser and then make the coordinates available via Javascript's getCurrentPosition API:

```php
$page = visit('/')
     ->geolocation(39.399872, -8.224454);
```

### Configuring Locale

You can set the locale for your test requests using the `withLocale` method. This is particularly useful for testing multilingual applications.

```php
$page = visit('/')->withLocale('fr-FR');

$page->assertSee('Bienvenue');
```
### Configuring Timezone

You can set the timezone for your test requests using the `withTimezone` method. This is useful for testing date and time displays in different time zones.

```php
$page = visit('/')->withTimezone('America/New_York');

$page->assertSee('EST');
```

### Configuring UserAgent

You can set the User-Agent header for your test requests using the `withUserAgent` method. This is useful for testing how your application responds to different types of clients, such as mobile browsers or bots.

```php
$page = visit('/')->withUserAgent('Googlebot');

$page->assertSee('Welcome, bot!');
```

### Configuring Host

You can set the host for your test server using the `withHost` method. This is useful for testing subdomains or where different hosts serve different content.

```php
$page = visit('/dashboard')->withHost('some-subdomain.localhost');

$page->assertSee('Welcome to Some Subdomain');
```

## Table of Contents

### Available Assertions

[assertTitle](#assert-title)
[assertTitleContains](#assert-title-contains)
[assertSee](#assert-see)
[assertDontSee](#assert-dont-see)
[assertSeeIn](#assert-see-in)
[assertDontSeeIn](#assert-dont-see-in)
[assertSeeAnythingIn](#assert-see-anything-in)
[assertSeeNothingIn](#assert-see-nothing-in)
[assertCount](#assert-count)
[assertScript](#assert-script)
[assertSourceHas](#assert-source-has)
[assertSourceMissing](#assert-source-missing)
[assertSeeLink](#assert-see-link)
[assertDontSeeLink](#assert-dont-see-link)
[assertChecked](#assert-checked)
[assertNotChecked](#assert-not-checked)
[assertIndeterminate](#assert-indeterminate)
[assertRadioSelected](#assert-radio-selected)
[assertRadioNotSelected](#assert-radio-not-selected)
[assertSelected](#assert-selected)
[assertNotSelected](#assert-not-selected)
[assertValue](#assert-value)
[assertValueIsNot](#assert-value-is-not)
[assertAttribute](#assert-attribute)
[assertAttributeMissing](#assert-attribute-missing)
[assertAttributeContains](#assert-attribute-contains)
[assertAttributeDoesntContain](#assert-attribute-doesnt-contain)
[assertAriaAttribute](#assert-aria-attribute)
[assertDataAttribute](#assert-data-attribute)
[assertVisible](#assert-visible)
[assertPresent](#assert-present)
[assertNotPresent](#assert-not-present)
[assertMissing](#assert-missing)
[assertEnabled](#assert-enabled)
[assertDisabled](#assert-disabled)
[assertButtonEnabled](#assert-button-enabled)
[assertButtonDisabled](#assert-button-disabled)
[assertUrlIs](#assert-url-is)
[assertSchemeIs](#assert-scheme-is)
[assertSchemeIsNot](#assert-scheme-is-not)
[assertHostIs](#assert-host-is)
[assertHostIsNot](#assert-host-is-not)
[assertPortIs](#assert-port-is)
[assertPortIsNot](#assert-port-is-not)
[assertPathBeginsWith](#assert-path-begins-with)
[assertPathEndsWith](#assert-path-ends-with)
[assertPathContains](#assert-path-contains)
[assertPathIs](#assert-path-is)
[assertPathIsNot](#assert-path-is-not)
[assertQueryStringHas](#assert-query-string-has)
[assertQueryStringMissing](#assert-query-string-missing)
[assertFragmentIs](#assert-fragment-is)
[assertFragmentBeginsWith](#assert-fragment-begins-with)
[assertFragmentIsNot](#assert-fragment-is-not)
[assertNoSmoke](#assert-no-smoke)
[assertNoConsoleLogs](#assert-no-console-logs)
[assertNoJavaScriptErrors](#assert-no-javascript-errors)
[assertNoAccessibilityIssues](#assert-no-accessibility-issues)
[assertScreenshotMatches](#assert-screenshot-matches)

### Element Interactions

[click](#click)
[text](#text)
[attribute](#attribute)
[keys](#keys)
[withKeyDown](#withKeyDown)
[type](#type)
[typeSlowly](#type-slowly)
[select](#select)
[append](#append)
[clear](#clear)
[radio](#radio)
[check](#check)
[uncheck](#uncheck)
[attach](#attach)
[press](#press)
[pressAndWaitFor](#press-and-wait-for)
[drag](#drag)
[hover](#hover)
[submit](#submit)
[value](#value)
[withinIframe](#within-iframe)
[resize](#resize)
[script](#script)
[content](#content)
[url](#url)
[wait](#wait)
[waitForKey](#wait-for-key)

### Debugging tests

[debug](#debug)
[screenshot](#screenshot)
[screenshotElement](#screenshot-element)
[tinker](#tinker)
[headed](#headed)

## Element Assertions

---

## Http Tests Available Assertions Response Assertions
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

### Response Assertions

Laravel's `Illuminate\Testing\TestResponse` class provides a variety of custom assertion methods that you may utilize when testing your application. These assertions may be accessed on the response that is returned by the `json`, `get`, `post`, `put`, and `delete` test methods:

[assertAccepted](#assert-accepted)
[assertBadRequest](#assert-bad-request)
[assertClientError](#assert-client-error)
[assertConflict](#assert-conflict)
[assertCookie](#assert-cookie)
[assertCookieExpired](#assert-cookie-expired)
[assertCookieNotExpired](#assert-cookie-not-expired)
[assertCookieMissing](#assert-cookie-missing)
[assertCreated](#assert-created)
[assertDontSee](#assert-dont-see)
[assertDontSeeText](#assert-dont-see-text)
[assertDownload](#assert-download)
[assertExactJson](#assert-exact-json)
[assertExactJsonStructure](#assert-exact-json-structure)
[assertFailedDependency](#assert-failed-dependency)
[assertForbidden](#assert-forbidden)
[assertFound](#assert-found)
[assertGone](#assert-gone)
[assertHeader](#assert-header)
[assertHeaderContains](#assert-header-contains)
[assertHeaderMissing](#assert-header-missing)
[assertInternalServerError](#assert-internal-server-error)
[assertJson](#assert-json)
[assertJsonCount](#assert-json-count)
[assertJsonFragment](#assert-json-fragment)
[assertJsonIsArray](#assert-json-is-array)
[assertJsonIsObject](#assert-json-is-object)
[assertJsonMissing](#assert-json-missing)
[assertJsonMissingExact](#assert-json-missing-exact)
[assertJsonMissingValidationErrors](#assert-json-missing-validation-errors)
[assertJsonPath](#assert-json-path)
[assertJsonMissingPath](#assert-json-missing-path)
[assertJsonStructure](#assert-json-structure)
[assertJsonValidationErrors](#assert-json-validation-errors)
[assertJsonValidationErrorFor](#assert-json-validation-error-for)
[assertLocation](#assert-location)
[assertMethodNotAllowed](#assert-method-not-allowed)
[assertMovedPermanently](#assert-moved-permanently)
[assertContent](#assert-content)
[assertNoContent](#assert-no-content)
[assertStreamed](#assert-streamed)
[assertStreamedContent](#assert-streamed-content)
[assertNotFound](#assert-not-found)
[assertOk](#assert-ok)
[assertPaymentRequired](#assert-payment-required)
[assertPlainCookie](#assert-plain-cookie)
[assertRedirect](#assert-redirect)
[assertRedirectBack](#assert-redirect-back)
[assertRedirectBackWithErrors](#assert-redirect-back-with-errors)
[assertRedirectBackWithoutErrors](#assert-redirect-back-without-errors)
[assertRedirectContains](#assert-redirect-contains)
[assertRedirectToRoute](#assert-redirect-to-route)
[assertRedirectToSignedRoute](#assert-redirect-to-signed-route)
[assertRequestTimeout](#assert-request-timeout)
[assertSee](#assert-see)
[assertSeeInOrder](#assert-see-in-order)
[assertSeeText](#assert-see-text)
[assertSeeTextInOrder](#assert-see-text-in-order)
[assertServerError](#assert-server-error)
[assertServiceUnavailable](#assert-service-unavailable)
[assertSessionHas](#assert-session-has)
[assertSessionHasInput](#assert-session-has-input)
[assertSessionHasAll](#assert-session-has-all)
[assertSessionHasErrors](#assert-session-has-errors)
[assertSessionHasErrorsIn](#assert-session-has-errors-in)
[assertSessionHasNoErrors](#assert-session-has-no-errors)
[assertSessionDoesntHaveErrors](#assert-session-doesnt-have-errors)
[assertSessionMissing](#assert-session-missing)
[assertStatus](#assert-status)
[assertSuccessful](#assert-successful)
[assertTooManyRequests](#assert-too-many-requests)
[assertUnauthorized](#assert-unauthorized)
[assertUnprocessable](#assert-unprocessable)
[assertUnsupportedMediaType](#assert-unsupported-media-type)
[assertValid](#assert-valid)
[assertInvalid](#assert-invalid)
[assertViewHas](#assert-view-has)
[assertViewHasAll](#assert-view-has-all)
[assertViewIs](#assert-view-is)
[assertViewMissing](#assert-view-missing)

---

## Http Tests Available Assertions Response Assertions assertRedirect
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertRedirect

Assert that the response is a redirect to the given URI:

```php
$response->assertRedirect($uri = null);
```

---

## Processes Testing
Processes | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/processes.md

## Testing

Many Laravel services provide functionality to help you easily and expressively write tests, and Laravel's process service is no exception. The `Process` facade's `fake` method allows you to instruct Laravel to return stubbed / dummy results when processes are invoked.

---

## Http Client Testing
Http Client | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-client.md

## Testing

Many Laravel services provide functionality to help you easily and expressively write tests, and Laravel's HTTP client is no exception. The `Http` facade's `fake` method allows you to instruct the HTTP client to return stubbed / dummy responses when requests are made.

---

## Http Tests Available Assertions Response Assertions assertRedirectToRoute
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertRedirectToRoute

Assert that the response is a redirect to the given [named route](/docs/{{version}}/routing#named-routes):

```php
$response->assertRedirectToRoute($name, $parameters = []);
```

---

## Processes Testing Available Assertions
Processes | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/processes.md

### Available Assertions

As [previously discussed](#faking-processes), Laravel provides several process assertions for your feature tests. We'll discuss each of these assertions below.

---

## Http Tests Available Assertions Response Assertions assertRedirectBackWithoutErrors
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertRedirectBackWithoutErrors

Assert whether the response is redirecting back to the previous page and the session does not contain any error messages:

```php
$response->assertRedirectBackWithoutErrors();
```

---

## Prompts Testing
Prompts | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/prompts.md

## Testing

Laravel provides a variety of methods for testing that your command displays the expected Prompt messages:

```php tab=Pest
test('report generation', function () {
    $this->artisan('report:generate')
        ->expectsPromptsInfo('Welcome to the application!')
        ->expectsPromptsWarning('This action cannot be undone')
        ->expectsPromptsError('Something went wrong')
        ->expectsPromptsAlert('Important notice!')
        ->expectsPromptsIntro('Starting process...')
        ->expectsPromptsOutro('Process completed!')
        ->expectsPromptsTable(
            headers: ['Name', 'Email'],
            rows: [
                ['Taylor Otwell', 'taylor@example.com'],
                ['Jason Beggs', 'jason@example.com'],
            ]
        )
        ->assertExitCode(0);
});
```

```php tab=PHPUnit
public function test_report_generation(): void
{
    $this->artisan('report:generate')
        ->expectsPromptsInfo('Welcome to the application!')
        ->expectsPromptsWarning('This action cannot be undone')
        ->expectsPromptsError('Something went wrong')
        ->expectsPromptsAlert('Important notice!')
        ->expectsPromptsIntro('Starting process...')
        ->expectsPromptsOutro('Process completed!')
        ->expectsPromptsTable(
            headers: ['Name', 'Email'],
            rows: [
                ['Taylor Otwell', 'taylor@example.com'],
                ['Jason Beggs', 'jason@example.com'],
            ]
        )
        ->assertExitCode(0);
}
```

---

## Database Testing Introduction
Database Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md

## Introduction

Laravel provides a variety of helpful tools and assertions to make it easier to test your database driven applications. In addition, Laravel model factories and seeders make it painless to create test database records using your application's Eloquent models and relationships. We'll discuss all of these powerful features in the following documentation.

---

## Http Tests Testing Views
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

## Testing Views

Laravel also allows you to render a view without making a simulated HTTP request to the application. To accomplish this, you may call the `view` method within your test. The `view` method accepts the view name and an optional array of data. The method returns an instance of `Illuminate\Testing\TestView`, which offers several methods to conveniently make assertions about the view's contents:

```php tab=Pest
<?php

test('a welcome view can be rendered', function () {
    $view = $this->view('welcome', ['name' => 'Taylor']);

    $view->assertSee('Taylor');
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_a_welcome_view_can_be_rendered(): void
    {
        $view = $this->view('welcome', ['name' => 'Taylor']);

        $view->assertSee('Taylor');
    }
}
```

The `TestView` class provides the following assertion methods: `assertSee`, `assertSeeInOrder`, `assertSeeText`, `assertSeeTextInOrder`, `assertDontSee`, and `assertDontSeeText`.

If needed, you may get the raw, rendered view contents by casting the `TestView` instance to a string:

```php
$contents = (string) $this->view('welcome');
```

---

## Database Testing Available Assertions
Database Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md

## Available Assertions

Laravel provides several database assertions for your [Pest](https://pestphp.com) or [PHPUnit](https://phpunit.de) feature tests. We'll discuss each of these assertions below.

---

## Http Tests Available Assertions Response Assertions assertRedirectToSignedRoute
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertRedirectToSignedRoute

Assert that the response is a redirect to the given [signed route](/docs/{{version}}/urls#signed-urls):

```php
$response->assertRedirectToSignedRoute($name = null, $parameters = []);
```

---

## Pest V4 Is Here Now With Browser Testing
Pest V4 Is Here Now With Browser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/pest-v4-is-here-now-with-browser-testing.md

---
title: Pest v4 Is Here — Now with Browser Testing
description: Today, we're thrilled to announce Pest v4 — our biggest release yet, featuring powerful new browser testing with parallel support and full Laravel integration.
---

> To get started with Pest v4's new features including browser testing, please refer to the upgrade guide: [Upgrade Guide →](/docs/upgrade-guide).

- [Browser Testing](#content-pest-v4-is-here-now-with-browser-testing)
- [Smoke Testing](#content-smoke-testing)
- [Visual Regression Testing](#content-visual-regression-testing)
- [Test Sharding](#content-test-sharding)
- [Type Coverage Is Much Faster](#content-type-coverage-is-much-faster)
- [Profanity Checking](#content-profanity-checking)
- [On Top of PHPUnit 12](#content-on-top-of-phpunit-12)

# Pest v4 Is Here — Now with Browser Testing

Today, we're thrilled to announce the release of **Pest v4**, bringing the biggest testing upgrade yet: powerful **[Browser Testing](/docs/browser-testing)**. Pest's new browser testing features let you run elegant, maintainable browser tests — with first-class support for Laravel's testing API and the ability to run tests in parallel. For the first time, this is browser testing that feels as good as writing unit tests.

Here is the creator of Pest, [Nuno Maduro](https://twitter.com/enunomaduro), demoing the new browser testing features in Pest v4 at Laracon US:

[![Pest v4](/assets/pest4_play.png)](https://youtu.be/f5gAgwwwwOI?si=LtPpySZe3tf8qMjz&t=52)

Here is an example of Browser Testing using [Laravel](https://laravel.com):

```php
it('may reset the password', function () {
    // access any laravel testing helpers...
    Notification::fake();

    // access to the database — using the RefreshDatabase trait (even sqlite in memory...)
    $this->actingAs(User::factory()->create());

    $page = visit('/sign-in') // visit on a real browser...
        ->on()->mobile() // or ->desktop(), ->tablet(), etc...
        ->inDarkMode(); // or ->inLightMode()

    $page->assertSee('Sign In')
         ->click('Forgot Password?')
         ->type('email', 'nuno@laravel.com')
         ->press('Send Reset Link')
         ->assertSee('We have emailed your password reset link!')
         ->assertNoJavascriptErrors(); // or ->assertNoConsoleLogs()

    Notification::assertSent(ResetPassword::class);
});
```

With Pest v4's browser testing, you can:
- Seamlessly use **Laravel features** like `Event::fake()`, `assertAuthenticated()`, and model factories
- Use `RefreshDatabase`, even with SQLite in-memory databases, to ensure a clean state for each test
- Test on **multiple browsers** (Chrome, Firefox, Safari)
- Test on **different devices** and viewports (like iPhone 14 Pro, tablets, or custom breakpoints)
- Switch **color schemes** (light/dark mode)
- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.)
- Run **parallel browser tests** for dramatically faster suites
- Take **screenshots** or pause tests for debugging
- …all with the elegance of Pest syntax
- **Playwright-based** — modern, fast, and reliable

To get started with browser testing in Pest, you need to install the Pest Browser Plugin:

```bash
composer require pestphp/pest-plugin-browser --dev

npm install playwright@latest
npx playwright install
```

After, you may use the `visit()` function anywhere. Finally, running this test is as simple as executing `./vendor/bin/pest` in your terminal. Pest will handle the rest, launching a browser, navigating to the page, and performing the actions you specified.

## Smoke Testing

Smoke testing your application in real browsers has never been easier. With Pest v4, you can literally visit all your application pages, and ensure they don't throw any JavaScript errors, and they don't log any console errors.

```php
$routes = ['/', '/about', '/contact'];

visit($routes)->assertNoSmoke();

// assertNoSmoke() is a shorthand for:
// - assertNoJavascriptErrors()
// - assertNoConsoleLogs()
```

## Visual Regression Testing

Want to ensure your pages look exactly as expected over time? Pest v4 introduces visual regression testing with the `assertScreenshotMatches()` assertion. This allows you to take screenshots of your pages and compare them against baseline images, ensuring that your UI remains consistent across changes.

```php
$pages = visit(['/', '/about', '/contact']);

$pages->assertScreenshotMatches();
```

This is just a glimpse of what Browser Testing in Pest v4 can do. Find out more about the new features below, and check out the [Browser Testing documentation](/docs/browser-testing) for a complete guide on how to get started.

## Test Sharding

Pest v4 introduces **Test Sharding**, allowing you to split your test suite into smaller, manageable chunks. This is particularly useful for large applications (or when running browser tests) where running all tests at once can be time-consuming.

This feature is particularly useful on CI platforms, where on things like GitHub actions you can no longer scale vertical, but rather horizontally. This means you can run your tests in parallel across multiple machines, significantly speeding up your test suite execution.

To get started with Test Sharding, you can use the `--shard` option when running Pest:

```bash
# GitHub Workflow One
./vendor/bin/pest --shard=1/4

# GitHub Workflow Two
./vendor/bin/pest --shard=2/4

# GitHub Workflow Three
./vendor/bin/pest --shard=3/4

# GitHub Workflow Four
./vendor/bin/pest --shard=4/4
```

You may combine this with the `--parallel` option to run your tests in parallel, and this way trully maximize your test suite execution speed:

```bash
./vendor/bin/pest --shard=1/4 --parallel
```

Finally, to easily set up sharding your CI configuration, you just have to ensure each job in your CI configuration runs a different shard of your test suite. For example, in GitHub Actions, you can use the `matrix` strategy to define multiple jobs that run different shards:

```yaml
strategy:
  matrix:
    shard: [1, 2, 3, 4]

name: Tests (Shard ${{ matrix.shard }}/4)

steps:
  - name: Run tests
    run: pest --parallel --shard ${{ matrix.shard }}/4
```

## Type Coverage Is Much Faster

Remember the days when you had to wait for your type coverage to run? Not anymore! Pest v4 introduces a new type coverage engine that is significantly faster than previous versions.

Type coverage is 2x faster on the first run and instant on subsequent runs. This means you can quickly check your type coverage without waiting for long periods, making your development workflow much more efficient.

In addition, Type Coverage now supports **Sharding**. This means you can run type coverage with the `--shard` option, just like you do with your tests.

## Profanity Checking

Pest v4 introduces a new feature that allows you to check for profanity in your test code. This is particularly useful for maintaining a clean and professional codebase, especially in collaborative environments.

You can enable profanity checking by adding the `--profanity` option when running Pest:

```bash
./vendor/bin/pest --profanity
```

To start using Pest's Profanity plugin, you need to require the plugin via Composer.

```bash
composer require pestphp/pest-plugin-profanity --dev
```

After requiring the plugin, you may utilize the `--profanity` option to generate a report of your profanity.

```bash
./vendor/bin/pest --profanity
```

If any of your files contain profanity, they will be highlighted in red and displayed using their respective line
numbers and the profane word(s) that have been found.

As an example, `pr31(f*ck)` means that the word "fuck" was found on line 31.

To learn more about the Profanity plugin and how to configure it, check out the [Profanity documentation](/docs/profanity).

### Skip Locally or On CI

Pest v4 introduces the ability to conditionally skip tests based on the environment. You can use `skipLocally()` to skip tests when running locally, or `skipOnCi` to skip tests when running on a CI server.

```php
it('does not run locally', function () {
    // This test will be skipped when running locally
})->skipLocally();

it('does not run on CI', function () {
    // This test will be skipped when running on a CI server
})->skipOnCi();
```

### Miscellaneous Improvements

- You may now use `skipLocally()` or `skipOnCi` to conditionally skip tests based on the environment.
- The `not->toHaveSuspiciousCharacters()` arch expectation has been added to help you identify potential suspicious characters in your code. This arch expectation is now enabled by default on the `php` arch preset. This expectation requires the `intl` PHP extension.
- The expectation `toBeSlug` has been added to help you validate that a string is a valid slug.

### On Top of PHPUnit 12

Pest v4 is built on top of PHPUnit 12, which means you get all the latest features and improvements from PHPUnit. As such, be sure to check out the [PHPUnit 12 release announcement](https://phpunit.de/announcements/phpunit-12.html).

### Thanks To You, Pest v4 Is Here!

There's never been a better time to dive into testing and start using Pest. If you're ready to get started with Pest v4 right away, check out our [installation guide](/docs/installation) for step-by-step instructions. And if you're currently using an earlier version of Pest, we've got you covered with detailed upgrade instructions in our [upgrade guide](/docs/upgrade-guide).

Thank you for your continued support and feedback. We can't wait to see what you build with Pest v4!

---

Thank you for reading about Pest v4's new features! Want to get started with Pest? You can find the installation guide in the next section of the documentation: [Installation →](/docs/installation)

---

## Browser Testing waitForKey
Browser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/browser-testing.md

### waitForKey

The `waitForKey` method opens the current page URL in the default web browser and waits for a key press:

```php
$page->waitForKey(); // Useful for debugging
```

## Debugging tests

Sometimes you may want to debug your browser tests. Pest provides a convenient way to do this by using the `--debug` option, which makes pest to open the browser window and pause the execution of the test when it fails. You can then inspect the page and see what went wrong.

```bash
./vendor/bin/pest --debug
```

Optionally, you can also use the `debug()` method in your test. It will limit execution to this test (like using [`only()`](filtering-tests.md#only)), pause the execution and open the browser window:

```php
$page->debug();
```

You can also take a screenshot of the current page using the `screenshot()` method. This is useful for visual debugging:

NOTE: If you don't pass the filename, it will use the test name as the filename.

```php
$page->screenshot();
$page->screenshot(fullPage: true);
$page->screenshot(filename: 'custom-name');
```

You can also take a screenshot of a specific element using the `screenshotElement()` method:

```php
$page->screenshotElement('#my-element');
```

You can also use the `tinker()` method to open a Tinker session in the context of the current page. This allows you to interact with the page using PHP code:

```php
$page->tinker();
```

After you can run your tests with the `--headed` option to open the browser window:

```bash
./vendor/bin/pest --headed
```

If you wish to run the tests in a headed mode by default, you can set it in your `Pest.php` configuration file:

```php
pest()->browser()->headed();
```

## Continuous Integration

You may refer to Pest's [Continuous Integration](https://pestphp.com/docs/continuous-integration) documentation for more information on how to run your browser tests in a CI environment.

However, if you are using GitHub Actions, you need to add the following steps to your workflow file:

```yaml
    - uses: actions/setup-node@v4
      with:
        node-version: lts/*

    - name: Install dependencies
      run: npm ci

    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
```

---

Now, let's dive into architectural testing and how it can benefit your development process. By performing architectural testing, you can evaluate the overall design of your application and identify potential flaws before they become significant issues: [Arch Testing](/docs/arch-testing)

---

## Browser Testing wait
Browser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/browser-testing.md

### wait

The `wait` method pauses for the given number of seconds:

```php
$page->wait(2); // Wait for 2 seconds
```

---

## Http Tests Validation Assertions
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

## Validation Assertions

Laravel provides two primary validation related assertions that you may use to ensure the data provided in your request was either valid or invalid.

---

## Http Tests Available Assertions Authentication Assertions
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

### Authentication Assertions

Laravel also provides a variety of authentication related assertions that you may utilize within your application's feature tests. Note that these methods are invoked on the test class itself and not the `Illuminate\Testing\TestResponse` instance returned by methods such as `get` and `post`.

---

## Pennant Adding Custom Pennant Drivers Defining Features Externally
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

### Defining Features Externally

If your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\Pennant\Contracts\DefinesFeaturesExternally` interface:

```php
<?php

namespace App\Extensions;

use Laravel\Pennant\Contracts\Driver;
use Laravel\Pennant\Contracts\DefinesFeaturesExternally;

class FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally
{
    /**
     * Get the features defined for the given scope.
     */
    public function definedFeaturesForScope(mixed $scope): array {}

    /* ... */
}
```

The `definedFeaturesForScope` method should return a list of feature names defined for the provided scope.

_[Truncated]_


Output:
Wall time: 3.4040 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Http Tests Making Requests Session / Authentication\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n### Session / Authentication\n\nLaravel provides several helpers for interacting with the session during HTTP testing. First, you may set the session data to a given array using the `withSession` method. This is useful for loading the session with data before issuing a request to your application:\n\n```php tab=Pest\n<?php\n\ntest('interacting with the session', function () {\n    $response = $this->withSession(['banned' => false])->get('/');\n\n    //\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    public function test_interacting_with_the_session(): void\n    {\n        $response = $this->withSession(['banned' => false])->get('/');\n\n        //\n    }\n}\n```\n\nLaravel's session is typically used to maintain state for the currently authenticated user. Therefore, the `actingAs` helper method provides a simple way to authenticate a given user as the current user. For example, we may use a [model factory](/docs/{{version}}/eloquent-factories) to generate and authenticate a user:\n\n```php tab=Pest\n<?php\n\nuse App\\Models\\User;\n\ntest('an action that requires authentication', function () {\n    $user = User::factory()->create();\n\n    $response = $this->actingAs($user)\n        ->withSession(['banned' => false])\n        ->get('/');\n\n    //\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse App\\Models\\User;\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    public function test_an_action_that_requires_authentication(): void\n    {\n        $user = User::factory()->create();\n\n        $response = $this->actingAs($user)\n            ->withSession(['banned' => false])\n            ->get('/');\n\n        //\n    }\n}\n```\n\nYou may also specify which guard should be used to authenticate the given user by passing the guard name as the second argument to the `actingAs` method. The guard that is provided to the `actingAs` method will also become the default guard for the duration of the test:\n\n```php\n$this->actingAs($user, 'web');\n```\n\nIf you would like to ensure the request is unauthenticated, you may use the `actingAsGuest` method:\n\n```php\n$this->actingAsGuest();\n```\n\n---\n\n## Http Tests Available Assertions Response Assertions assertRedirectBack\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertRedirectBack\n\nAssert whether the response is redirecting back to the previous page:\n\n```php\n$response->assertRedirectBack();\n```\n\n---\n\n## Browser Testing\nBrowser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/browser-testing.md\n\n---\ntitle: Browser Testing\ndescription:\n---\n\n# Browser Testing\n\nBrowser testing is an essential part of modern web development, allowing you to ensure that your application works correctly across different browsers and devices. Pest provides a simple and elegant way to write browser tests. Here is an example of how to write a browser test using Pest:\n\n```php\nit('may welcome the user', function () {\n    $page = visit('/');\n\n    $page->assertSee('Welcome');\n});\n```\n\nThis is a basic example of a browser test that checks if the homepage contains the text \"Welcome\". However, Pest's browser testing capabilities go beyond this simple example. You can use various methods to interact with the page, such as clicking buttons, filling out forms, and navigating between pages.\n\nHere is an example of a more complex browser test, on Laravel, that checks if a user can sign in:\n\n```php\nit('may sign in the user', function () {\n    Event::fake();\n\n    User::factory()->create([ // assumes RefreshDatabase trait is used on Pest.php...\n        'email' => 'nuno@laravel.com',\n        'password' => 'password',\n    ]);\n\n    $page = visit('/')->on()->mobile()->firefox();\n\n    $page->click('Sign In')\n         ->assertUrlIs('/login')\n         ->assertSee('Sign In to Your Account')\n         ->fill('email', 'nuno@laravel.com')\n         ->fill('password', 'password')\n         ->click('Submit')\n         ->assertSee('Dashboard');\n\n    $this->assertAuthenticated();\n\n    Event::assertDispatched(UserLoggedIn::class);\n});\n```\n\nNote that, you are leveraging the full power of Laravel's testing capabilities, such as refresh database, event faking, and authentication assertions, while also actually doing browser testing.\n\n## Getting Started\n\nTo get started with browser testing in Pest, you need to install the Pest Browser plugin. You can do this by running the following command:\n\n```bash\ncomposer require pestphp/pest-plugin-browser --dev\n\nnpm install playwright@latest\nnpx playwright install\n```\n\nFinally, add `tests/Browser/Screenshots` to your `.gitignore` file to avoid committing screenshots taken during browser tests.\n\n### Running Browser Tests\n\nRunning browser tests is similar to running regular Pest tests:\n\n```bash\n./vendor/bin/pest\n```\n\nWe recommend running tests in parallel using the `--parallel` option to speed up the execution:\n\n```bash\n./vendor/bin/pest --parallel\n```\n\nFor debugging purposes, you can run the tests in a headed mode and pause the execution at the end of the failed test run:\n\n```bash\n./vendor/bin/pest --debug\n```\n\n### Navigation\n\nThe `visit()` method is used to navigate to a specific URL in your browser test. It provides various methods to interact with the page:\n\n```php\ntest('example', function () {\n    $page = visit('/');\n\n    $page->assertSee('Welcome');\n});\n```\n\n### Using Other Browsers\n\nBy default, the `visit()` method uses Chrome as the browser. However, if you want to use a different browser, you can specify it using the `--browser` option when running the tests:\n\n```bash\n./vendor/bin/pest --browser firefox\n./vendor/bin/pest --browser safari\n```\n\nIf you wish to use a different browser by default without specifying it in the command line, you can set it in your `Pest.php` configuration file:\n\n```php\npest()->browser()->inFirefox();\npest()->browser()->inSafari();\n```\n\n### Using Other Devices\n\nThe `visit()` method uses a desktop viewport. However, you can specify a mobile viewport using the `onMobile()` method. For example:\n\n```php\n$page = visit('/')->on()->mobile();\n```\n\nIf you wish to use a specific device, you can use the `on()` method and chain it with the `macbook14`, `iPhone14Pro`, etc:\n\n```php\n$page = visit('/')->on()->iPhone14Pro();\n```\n\n### Using Dark Mode\n\nPest enforces a light color scheme by default. However, you can specify a dark color scheme using the `inDarkMode()` method:\n\n```php\n$page = visit('/')->inDarkMode();\n```\n\n### Visiting Multiple Pages\n\nYou can visit multiple pages simultaneously by passing an array of URLs to the `visit()` method. This is useful for testing scenarios where you need to interact with multiple pages at once:\n\n```php\n$pages = visit(['/', '/about']);\n\n$pages->assertNoSmoke()\n    ->assertNoAccessibilityIssues()\n    ->assertNoConsoleLogs()\n    ->assertNoJavaScriptErrors();\n\n[$homePage, $aboutPage] = $pages;\n\n$homePage->assertSee('Welcome to our website');\n$aboutPage->assertSee('About Us');\n```\n\n### Navigation\n\nAfter visiting a page, you can navigate to other pages using the `navigate()` method. This method allows you to navigate to a different URL while keeping the current browser context:\n\n```php\n$page = visit('/');\n\n$page->navigate('/about')\n     ->assertSee('About Us');\n```\n\n### Locating Elements\n\nYou can locate elements in the DOM using text or CSS selectors. Pest provides a simple syntax for locating elements:\n\n```php\n// Clicks the first link with the text \"Login\"\n$page->click('Login');\n\n// Clicks the first element with the class \"btn-primary\"\n$page->click('.btn-primary');\n\n// Clicks the element with the data-test attribute \"login\"\n$page->click('@login');\n\n// Clicks the element with the ID \"submit-button\"\n$page->click('#submit-button');\n\n// etc...\n```\n\n### Configuring Timeouts\n\nSometimes, elements may take time to appear on the page. By default, Pest waits for `5` seconds before timing out. You can configure the default timeout for browser tests in your `Pest.php` configuration file:\n\n```php\npest()->browser()->timeout(10000);\n```\n\n### Configuring User Agent\n\nBy default, the User Agent will default to the Browser you're running for tests such as: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/133.0.6943.16 Safari/537.36` \n\nYou may wish to override the User Agent of the browser for all of your tests, you can configure this in the `Pest.php` configuration file:\n\n```php\npest()->browser()->userAgent('CustomUserAgent');\n```\n\n### Configuring Host\n\nBy default, the server will bind to `127.0.0.1` for all browser tests.\n\nYou may wish to override the host for subdomain applications. You can configure this in the `Pest.php` configuration file:\n\n```php\npest()->browser()->withHost('some-subdomain.localhost');\n```\n\n### Geolocation\n\nSometimes, you need to define where the browser believes it is physically on the earth.  This method takes a latitude and longitude and will set the `geolocation` permission in the browser and then make the coordinates available via Javascript's getCurrentPosition API:\n\n```php\n$page = visit('/')\n     ->geolocation(39.399872, -8.224454);\n```\n\n### Configuring Locale\n\nYou can set the locale for your test requests using the `withLocale` method. This is particularly useful for testing multilingual applications.\n\n```php\n$page = visit('/')->withLocale('fr-FR');\n\n$page->assertSee('Bienvenue');\n```\n### Configuring Timezone\n\nYou can set the timezone for your test requests using the `withTimezone` method. This is useful for testing date and time displays in different time zones.\n\n```php\n$page = visit('/')->withTimezone('America/New_York');\n\n$page->assertSee('EST');\n```\n\n### Configuring UserAgent\n\nYou can set the User-Agent header for your test requests using the `withUserAgent` method. This is useful for testing how your application responds to different types of clients, such as mobile browsers or bots.\n\n```php\n$page = visit('/')->withUserAgent('Googlebot');\n\n$page->assertSee('Welcome, bot!');\n```\n\n### Configuring Host\n\nYou can set the host for your test server using the `withHost` method. This is useful for testing subdomains or where different hosts serve different content.\n\n```php\n$page = visit('/dashboard')->withHost('some-subdomain.localhost');\n\n$page->assertSee('Welcome to Some Subdomain');\n```\n\n## Table of Contents\n\n### Available Assertions\n\n
\n\n[assertTitle](#assert-title)\n[assertTitleContains](#assert-title-contains)\n[assertSee](#assert-see)\n[assertDontSee](#assert-dont-see)\n[assertSeeIn](#assert-see-in)\n[assertDontSeeIn](#assert-dont-see-in)\n[assertSeeAnythingIn](#assert-see-anything-in)\n[assertSeeNothingIn](#assert-see-nothing-in)\n[assertCount](#assert-count)\n[assertScript](#assert-script)\n[assertSourceHas](#assert-source-has)\n[assertSourceMissing](#assert-source-missing)\n[assertSeeLink](#assert-see-link)\n[assertDontSeeLink](#assert-dont-see-link)\n[assertChecked](#assert-checked)\n[assertNotChecked](#assert-not-checked)\n[assertIndeterminate](#assert-indeterminate)\n[assertRadioSelected](#assert-radio-selected)\n[assertRadioNotSelected](#assert-radio-not-selected)\n[assertSelected](#assert-selected)\n[assertNotSelected](#assert-not-selected)\n[assertValue](#assert-value)\n[assertValueIsNot](#assert-value-is-not)\n[assertAttribute](#assert-attribute)\n[assertAttributeMissing](#assert-attribute-missing)\n[assertAttributeContains](#assert-attribute-contains)\n[assertAttributeDoesntContain](#assert-attribute-doesnt-contain)\n[assertAriaAttribute](#assert-aria-attribute)\n[assertDataAttribute](#assert-data-attribute)\n[assertVisible](#assert-visible)\n[assertPresent](#assert-present)\n[assertNotPresent](#assert-not-present)\n[assertMissing](#assert-missing)\n[assertEnabled](#assert-enabled)\n[assertDisabled](#assert-disabled)\n[assertButtonEnabled](#assert-button-enabled)\n[assertButtonDisabled](#assert-button-disabled)\n[assertUrlIs](#assert-url-is)\n[assertSchemeIs](#assert-scheme-is)\n[assertSchemeIsNot](#assert-scheme-is-not)\n[assertHostIs](#assert-host-is)\n[assertHostIsNot](#assert-host-is-not)\n[assertPortIs](#assert-port-is)\n[assertPortIsNot](#assert-port-is-not)\n[assertPathBeginsWith](#assert-path-begins-with)\n[assertPathEndsWith](#assert-path-ends-with)\n[assertPathContains](#assert-path-contains)\n[assertPathIs](#assert-path-is)\n[assertPathIsNot](#assert-path-is-not)\n[assertQueryStringHas](#assert-query-string-has)\n[assertQueryStringMissing](#assert-query-string-missing)\n[assertFragmentIs](#assert-fragment-is)\n[assertFragmentBeginsWith](#assert-fragment-begins-with)\n[assertFragmentIsNot](#assert-fragment-is-not)\n[assertNoSmoke](#assert-no-smoke)\n[assertNoConsoleLogs](#assert-no-console-logs)\n[assertNoJavaScriptErrors](#assert-no-javascript-errors)\n[assertNoAccessibilityIssues](#assert-no-accessibility-issues)\n[assertScreenshotMatches](#assert-screenshot-matches)\n\n
\n\n### Element Interactions\n\n
\n\n[click](#click)\n[text](#text)\n[attribute](#attribute)\n[keys](#keys)\n[withKeyDown](#withKeyDown)\n[type](#type)\n[typeSlowly](#type-slowly)\n[select](#select)\n[append](#append)\n[clear](#clear)\n[radio](#radio)\n[check](#check)\n[uncheck](#uncheck)\n[attach](#attach)\n[press](#press)\n[pressAndWaitFor](#press-and-wait-for)\n[drag](#drag)\n[hover](#hover)\n[submit](#submit)\n[value](#value)\n[withinIframe](#within-iframe)\n[resize](#resize)\n[script](#script)\n[content](#content)\n[url](#url)\n[wait](#wait)\n[waitForKey](#wait-for-key)\n\n
\n\n### Debugging tests\n\n
\n\n[debug](#debug)\n[screenshot](#screenshot)\n[screenshotElement](#screenshot-element)\n[tinker](#tinker)\n[headed](#headed)\n\n
\n\n## Element Assertions\n\n---\n\n## Http Tests Available Assertions Response Assertions\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n### Response Assertions\n\nLaravel's `Illuminate\\Testing\\TestResponse` class provides a variety of custom assertion methods that you may utilize when testing your application. These assertions may be accessed on the response that is returned by the `json`, `get`, `post`, `put`, and `delete` test methods:\n\n\n\n
\n\n[assertAccepted](#assert-accepted)\n[assertBadRequest](#assert-bad-request)\n[assertClientError](#assert-client-error)\n[assertConflict](#assert-conflict)\n[assertCookie](#assert-cookie)\n[assertCookieExpired](#assert-cookie-expired)\n[assertCookieNotExpired](#assert-cookie-not-expired)\n[assertCookieMissing](#assert-cookie-missing)\n[assertCreated](#assert-created)\n[assertDontSee](#assert-dont-see)\n[assertDontSeeText](#assert-dont-see-text)\n[assertDownload](#assert-download)\n[assertExactJson](#assert-exact-json)\n[assertExactJsonStructure](#assert-exact-json-structure)\n[assertFailedDependency](#assert-failed-dependency)\n[assertForbidden](#assert-forbidden)\n[assertFound](#assert-found)\n[assertGone](#assert-gone)\n[assertHeader](#assert-header)\n[assertHeaderContains](#assert-header-contains)\n[assertHeaderMissing](#assert-header-missing)\n[assertInternalServerError](#assert-internal-server-error)\n[assertJson](#assert-json)\n[assertJsonCount](#assert-json-count)\n[assertJsonFragment](#assert-json-fragment)\n[assertJsonIsArray](#assert-json-is-array)\n[assertJsonIsObject](#assert-json-is-object)\n[assertJsonMissing](#assert-json-missing)\n[assertJsonMissingExact](#assert-json-missing-exact)\n[assertJsonMissingValidationErrors](#assert-json-missing-validation-errors)\n[assertJsonPath](#assert-json-path)\n[assertJsonMissingPath](#assert-json-missing-path)\n[assertJsonStructure](#assert-json-structure)\n[assertJsonValidationErrors](#assert-json-validation-errors)\n[assertJsonValidationErrorFor](#assert-json-validation-error-for)\n[assertLocation](#assert-location)\n[assertMethodNotAllowed](#assert-method-not-allowed)\n[assertMovedPermanently](#assert-moved-permanently)\n[assertContent](#assert-content)\n[assertNoContent](#assert-no-content)\n[assertStreamed](#assert-streamed)\n[assertStreamedContent](#assert-streamed-content)\n[assertNotFound](#assert-not-found)\n[assertOk](#assert-ok)\n[assertPaymentRequired](#assert-payment-required)\n[assertPlainCookie](#assert-plain-cookie)\n[assertRedirect](#assert-redirect)\n[assertRedirectBack](#assert-redirect-back)\n[assertRedirectBackWithErrors](#assert-redirect-back-with-errors)\n[assertRedirectBackWithoutErrors](#assert-redirect-back-without-errors)\n[assertRedirectContains](#assert-redirect-contains)\n[assertRedirectToRoute](#assert-redirect-to-route)\n[assertRedirectToSignedRoute](#assert-redirect-to-signed-route)\n[assertRequestTimeout](#assert-request-timeout)\n[assertSee](#assert-see)\n[assertSeeInOrder](#assert-see-in-order)\n[assertSeeText](#assert-see-text)\n[assertSeeTextInOrder](#assert-see-text-in-order)\n[assertServerError](#assert-server-error)\n[assertServiceUnavailable](#assert-service-unavailable)\n[assertSessionHas](#assert-session-has)\n[assertSessionHasInput](#assert-session-has-input)\n[assertSessionHasAll](#assert-session-has-all)\n[assertSessionHasErrors](#assert-session-has-errors)\n[assertSessionHasErrorsIn](#assert-session-has-errors-in)\n[assertSessionHasNoErrors](#assert-session-has-no-errors)\n[assertSessionDoesntHaveErrors](#assert-session-doesnt-have-errors)\n[assertSessionMissing](#assert-session-missing)\n[assertStatus](#assert-status)\n[assertSuccessful](#assert-successful)\n[assertTooManyRequests](#assert-too-many-requests)\n[assertUnauthorized](#assert-unauthorized)\n[assertUnprocessable](#assert-unprocessable)\n[assertUnsupportedMediaType](#assert-unsupported-media-type)\n[assertValid](#assert-valid)\n[assertInvalid](#assert-invalid)\n[assertViewHas](#assert-view-has)\n[assertViewHasAll](#assert-view-has-all)\n[assertViewIs](#assert-view-is)\n[assertViewMissing](#assert-view-missing)\n\n
\n\n---\n\n## Http Tests Available Assertions Response Assertions assertRedirect\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertRedirect\n\nAssert that the response is a redirect to the given URI:\n\n```php\n$response->assertRedirect($uri = null);\n```\n\n---\n\n## Processes Testing\nProcesses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/processes.md\n\n\n## Testing\n\nMany Laravel services provide functionality to help you easily and expressively write tests, and Laravel's process service is no exception. The `Process` facade's `fake` method allows you to instruct Laravel to return stubbed / dummy results when processes are invoked.\n\n---\n\n## Http Client Testing\nHttp Client | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-client.md\n\n\n## Testing\n\nMany Laravel services provide functionality to help you easily and expressively write tests, and Laravel's HTTP client is no exception. The `Http` facade's `fake` method allows you to instruct the HTTP client to return stubbed / dummy responses when requests are made.\n\n---\n\n## Http Tests Available Assertions Response Assertions assertRedirectToRoute\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertRedirectToRoute\n\nAssert that the response is a redirect to the given [named route](/docs/{{version}}/routing#named-routes):\n\n```php\n$response->assertRedirectToRoute($name, $parameters = []);\n```\n\n---\n\n## Processes Testing Available Assertions\nProcesses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/processes.md\n\n\n### Available Assertions\n\nAs [previously discussed](#faking-processes), Laravel provides several process assertions for your feature tests. We'll discuss each of these assertions below.\n\n---\n\n## Http Tests Available Assertions Response Assertions assertRedirectBackWithoutErrors\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertRedirectBackWithoutErrors\n\nAssert whether the response is redirecting back to the previous page and the session does not contain any error messages:\n\n```php\n$response->assertRedirectBackWithoutErrors();\n```\n\n---\n\n## Prompts Testing\nPrompts | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/prompts.md\n\n\n## Testing\n\nLaravel provides a variety of methods for testing that your command displays the expected Prompt messages:\n\n```php tab=Pest\ntest('report generation', function () {\n    $this->artisan('report:generate')\n        ->expectsPromptsInfo('Welcome to the application!')\n        ->expectsPromptsWarning('This action cannot be undone')\n        ->expectsPromptsError('Something went wrong')\n        ->expectsPromptsAlert('Important notice!')\n        ->expectsPromptsIntro('Starting process...')\n        ->expectsPromptsOutro('Process completed!')\n        ->expectsPromptsTable(\n            headers: ['Name', 'Email'],\n            rows: [\n                ['Taylor Otwell', 'taylor@example.com'],\n                ['Jason Beggs', 'jason@example.com'],\n            ]\n        )\n        ->assertExitCode(0);\n});\n```\n\n```php tab=PHPUnit\npublic function test_report_generation(): void\n{\n    $this->artisan('report:generate')\n        ->expectsPromptsInfo('Welcome to the application!')\n        ->expectsPromptsWarning('This action cannot be undone')\n        ->expectsPromptsError('Something went wrong')\n        ->expectsPromptsAlert('Important notice!')\n        ->expectsPromptsIntro('Starting process...')\n        ->expectsPromptsOutro('Process completed!')\n        ->expectsPromptsTable(\n            headers: ['Name', 'Email'],\n            rows: [\n                ['Taylor Otwell', 'taylor@example.com'],\n                ['Jason Beggs', 'jason@example.com'],\n            ]\n        )\n        ->assertExitCode(0);\n}\n```\n\n---\n\n## Database Testing Introduction\nDatabase Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md\n\n\n## Introduction\n\nLaravel provides a variety of helpful tools and assertions to make it easier to test your database driven applications. In addition, Laravel model factories and seeders make it painless to create test database records using your application's Eloquent models and relationships. We'll discuss all of these powerful features in the following documentation.\n\n---\n\n## Http Tests Testing Views\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n## Testing Views\n\nLaravel also allows you to render a view without making a simulated HTTP request to the application. To accomplish this, you may call the `view` method within your test. The `view` method accepts the view name and an optional array of data. The method returns an instance of `Illuminate\\Testing\\TestView`, which offers several methods to conveniently make assertions about the view's contents:\n\n```php tab=Pest\n<?php\n\ntest('a welcome view can be rendered', function () {\n    $view = $this->view('welcome', ['name' => 'Taylor']);\n\n    $view->assertSee('Taylor');\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    public function test_a_welcome_view_can_be_rendered(): void\n    {\n        $view = $this->view('welcome', ['name' => 'Taylor']);\n\n        $view->assertSee('Taylor');\n    }\n}\n```\n\nThe `TestView` class provides the following assertion methods: `assertSee`, `assertSeeInOrder`, `assertSeeText`, `assertSeeTextInOrder`, `assertDontSee`, and `assertDontSeeText`.\n\nIf needed, you may get the raw, rendered view contents by casting the `TestView` instance to a string:\n\n```php\n$contents = (string) $this->view('welcome');\n```\n\n---\n\n## Database Testing Available Assertions\nDatabase Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md\n\n\n## Available Assertions\n\nLaravel provides several database assertions for your [Pest](https://pestphp.com) or [PHPUnit](https://phpunit.de) feature tests. We'll discuss each of these assertions below.\n\n---\n\n## Http Tests Available Assertions Response Assertions assertRedirectToSignedRoute\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertRedirectToSignedRoute\n\nAssert that the response is a redirect to the given [signed route](/docs/{{version}}/urls#signed-urls):\n\n```php\n$response->assertRedirectToSignedRoute($name = null, $parameters = []);\n```\n\n---\n\n## Pest V4 Is Here Now With Browser Testing\nPest V4 Is Here Now With Browser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/pest-v4-is-here-now-with-browser-testing.md\n\n---\ntitle: Pest v4 Is Here — Now with Browser Testing\ndescription: Today, we're thrilled to announce Pest v4 — our biggest release yet, featuring powerful new browser testing with parallel support and full Laravel integration.\n---\n\n> To get started with Pest v4's new features including browser testing, please refer to the upgrade guide: [Upgrade Guide →](/docs/upgrade-guide).\n\n- [Browser Testing](#content-pest-v4-is-here-now-with-browser-testing)\n- [Smoke Testing](#content-smoke-testing)\n- [Visual Regression Testing](#content-visual-regression-testing)\n- [Test Sharding](#content-test-sharding)\n- [Type Coverage Is Much Faster](#content-type-coverage-is-much-faster)\n- [Profanity Checking](#content-profanity-checking)\n- [On Top of PHPUnit 12](#content-on-top-of-phpunit-12)\n\n# Pest v4 Is Here — Now with Browser Testing\n\nToday, we're thrilled to announce the release of **Pest v4**, bringing the biggest testing upgrade yet: powerful **[Browser Testing](/docs/browser-testing)**. Pest's new browser testing features let you run elegant, maintainable browser tests — with first-class support for Laravel's testing API and the ability to run tests in parallel. For the first time, this is browser testing that feels as good as writing unit tests.\n\nHere is the creator of Pest, [Nuno Maduro](https://twitter.com/enunomaduro), demoing the new browser testing features in Pest v4 at Laracon US:\n\n[![Pest v4](/assets/pest4_play.png)](https://youtu.be/f5gAgwwwwOI?si=LtPpySZe3tf8qMjz&t=52)\n\nHere is an example of Browser Testing using [Laravel](https://laravel.com):\n\n```php\nit('may reset the password', function () {\n    // access any laravel testing helpers...\n    Notification::fake();\n\n    // access to the database — using the RefreshDatabase trait (even sqlite in memory...)\n    $this->actingAs(User::factory()->create());\n\n    $page = visit('/sign-in') // visit on a real browser...\n        ->on()->mobile() // or ->desktop(), ->tablet(), etc...\n        ->inDarkMode(); // or ->inLightMode()\n\n    $page->assertSee('Sign In')\n         ->click('Forgot Password?')\n         ->type('email', 'nuno@laravel.com')\n         ->press('Send Reset Link')\n         ->assertSee('We have emailed your password reset link!')\n         ->assertNoJavascriptErrors(); // or ->assertNoConsoleLogs()\n\n    Notification::assertSent(ResetPassword::class);\n});\n```\n\nWith Pest v4's browser testing, you can:\n- Seamlessly use **Laravel features** like `Event::fake()`, `assertAuthenticated()`, and model factories\n- Use `RefreshDatabase`, even with SQLite in-memory databases, to ensure a clean state for each test\n- Test on **multiple browsers** (Chrome, Firefox, Safari)\n- Test on **different devices** and viewports (like iPhone 14 Pro, tablets, or custom breakpoints)\n- Switch **color schemes** (light/dark mode)\n- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.)\n- Run **parallel browser tests** for dramatically faster suites\n- Take **screenshots** or pause tests for debugging\n- …all with the elegance of Pest syntax\n- **Playwright-based** — modern, fast, and reliable\n\nTo get started with browser testing in Pest, you need to install the Pest Browser Plugin:\n\n```bash\ncomposer require pestphp/pest-plugin-browser --dev\n\nnpm install playwright@latest\nnpx playwright install\n```\n\nAfter, you may use the `visit()` function anywhere. Finally, running this test is as simple as executing `./vendor/bin/pest` in your terminal. Pest will handle the rest, launching a browser, navigating to the page, and performing the actions you specified.\n\n## Smoke Testing\n\nSmoke testing your application in real browsers has never been easier. With Pest v4, you can literally visit all your application pages, and ensure they don't throw any JavaScript errors, and they don't log any console errors.\n\n```php\n$routes = ['/', '/about', '/contact'];\n\nvisit($routes)->assertNoSmoke();\n\n// assertNoSmoke() is a shorthand for:\n// - assertNoJavascriptErrors()\n// - assertNoConsoleLogs()\n```\n\n## Visual Regression Testing\n\nWant to ensure your pages look exactly as expected over time? Pest v4 introduces visual regression testing with the `assertScreenshotMatches()` assertion. This allows you to take screenshots of your pages and compare them against baseline images, ensuring that your UI remains consistent across changes.\n\n```php\n$pages = visit(['/', '/about', '/contact']);\n\n$pages->assertScreenshotMatches();\n```\n\nThis is just a glimpse of what Browser Testing in Pest v4 can do. Find out more about the new features below, and check out the [Browser Testing documentation](/docs/browser-testing) for a complete guide on how to get started.\n\n## Test Sharding\n\nPest v4 introduces **Test Sharding**, allowing you to split your test suite into smaller, manageable chunks. This is particularly useful for large applications (or when running browser tests) where running all tests at once can be time-consuming.\n\nThis feature is particularly useful on CI platforms, where on things like GitHub actions you can no longer scale vertical, but rather horizontally. This means you can run your tests in parallel across multiple machines, significantly speeding up your test suite execution.\n\nTo get started with Test Sharding, you can use the `--shard` option when running Pest:\n\n```bash\n# GitHub Workflow One\n./vendor/bin/pest --shard=1/4\n\n# GitHub Workflow Two\n./vendor/bin/pest --shard=2/4\n\n# GitHub Workflow Three\n./vendor/bin/pest --shard=3/4\n\n# GitHub Workflow Four\n./vendor/bin/pest --shard=4/4\n```\n\nYou may combine this with the `--parallel` option to run your tests in parallel, and this way trully maximize your test suite execution speed:\n\n```bash\n./vendor/bin/pest --shard=1/4 --parallel\n```\n\nFinally, to easily set up sharding your CI configuration, you just have to ensure each job in your CI configuration runs a different shard of your test suite. For example, in GitHub Actions, you can use the `matrix` strategy to define multiple jobs that run different shards:\n\n```yaml\nstrategy:\n  matrix:\n    shard: [1, 2, 3, 4]\n\nname: Tests (Shard ${{ matrix.shard }}/4)\n\nsteps:\n  - name: Run tests\n    run: pest --parallel --shard ${{ matrix.shard }}/4\n```\n\n## Type Coverage Is Much Faster\n\nRemember the days when you had to wait for your type coverage to run? Not anymore! Pest v4 introduces a new type coverage engine that is significantly faster than previous versions.\n\nType coverage is 2x faster on the first run and instant on subsequent runs. This means you can quickly check your type coverage without waiting for long periods, making your development workflow much more efficient.\n\nIn addition, Type Coverage now supports **Sharding**. This means you can run type coverage with the `--shard` option, just like you do with your tests.\n\n## Profanity Checking\n\nPest v4 introduces a new feature that allows you to check for profanity in your test code. This is particularly useful for maintaining a clean and professional codebase, especially in collaborative environments.\n\nYou can enable profanity checking by adding the `--profanity` option when running Pest:\n\n```bash\n./vendor/bin/pest --profanity\n```\n\nTo start using Pest's Profanity plugin, you need to require the plugin via Composer.\n\n```bash\ncomposer require pestphp/pest-plugin-profanity --dev\n```\n\nAfter requiring the plugin, you may utilize the `--profanity` option to generate a report of your profanity.\n\n```bash\n./vendor/bin/pest --profanity\n```\n\n\n\nIf any of your files contain profanity, they will be highlighted in red and displayed using their respective line\nnumbers and the profane word(s) that have been found.\n\nAs an example, `pr31(f*ck)` means that the word \"fuck\" was found on line 31.\n\nTo learn more about the Profanity plugin and how to configure it, check out the [Profanity documentation](/docs/profanity).\n\n### Skip Locally or On CI\n\nPest v4 introduces the ability to conditionally skip tests based on the environment. You can use `skipLocally()` to skip tests when running locally, or `skipOnCi` to skip tests when running on a CI server.\n\n```php\nit('does not run locally', function () {\n    // This test will be skipped when running locally\n})->skipLocally();\n\nit('does not run on CI', function () {\n    // This test will be skipped when running on a CI server\n})->skipOnCi();\n```\n\n### Miscellaneous Improvements\n\n- You may now use `skipLocally()` or `skipOnCi` to conditionally skip tests based on the environment.\n- The `not->toHaveSuspiciousCharacters()` arch expectation has been added to help you identify potential suspicious characters in your code. This arch expectation is now enabled by default on the `php` arch preset. This expectation requires the `intl` PHP extension.\n- The expectation `toBeSlug` has been added to help you validate that a string is a valid slug.\n\n### On Top of PHPUnit 12\n\nPest v4 is built on top of PHPUnit 12, which means you get all the latest features and improvements from PHPUnit. As such, be sure to check out the [PHPUnit 12 release announcement](https://phpunit.de/announcements/phpunit-12.html).\n\n### Thanks To You, Pest v4 Is Here!\n\nThere's never been a better time to dive into testing and start using Pest. If you're ready to get started with Pest v4 right away, check out our [installation guide](/docs/installation) for step-by-step instructions. And if you're currently using an earlier version of Pest, we've got you covered with detailed upgrade instructions in our [upgrade guide](/docs/upgrade-guide).\n\nThank you for your continued support and feedback. We can't wait to see what you build with Pest v4!\n\n---\n\nThank you for reading about Pest v4's new features! Want to get started with Pest? You can find the installation guide in the next section of the documentation: [Installation →](/docs/installation)\n\n\n\n---\n\n## Browser Testing waitForKey\nBrowser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/browser-testing.md\n\n\n### waitForKey\n\nThe `waitForKey` method opens the current page URL in the default web browser and waits for a key press:\n\n```php\n$page->waitForKey(); // Useful for debugging\n```\n\n## Debugging tests\n\n\nSometimes you may want to debug your browser tests. Pest provides a convenient way to do this by using the `--debug` option, which makes pest to open the browser window and pause the execution of the test when it fails. You can then inspect the page and see what went wrong.\n\n```bash\n./vendor/bin/pest --debug\n```\n\nOptionally, you can also use the `debug()` method in your test. It will limit execution to this test (like using [`only()`](filtering-tests.md#only)), pause the execution and open the browser window:\n\n```php\n$page->debug();\n```\n\n\n\nYou can also take a screenshot of the current page using the `screenshot()` method. This is useful for visual debugging:\n\nNOTE: If you don't pass the filename, it will use the test name as the filename.\n\n```php\n$page->screenshot();\n$page->screenshot(fullPage: true);\n$page->screenshot(filename: 'custom-name');\n```\n\n\nYou can also take a screenshot of a specific element using the `screenshotElement()` method:\n\n```php\n$page->screenshotElement('#my-element');\n```\n\n\nYou can also use the `tinker()` method to open a Tinker session in the context of the current page. This allows you to interact with the page using PHP code:\n\n```php\n$page->tinker();\n```\n\n\nAfter you can run your tests with the `--headed` option to open the browser window:\n\n```bash\n./vendor/bin/pest --headed\n```\n\nIf you wish to run the tests in a headed mode by default, you can set it in your `Pest.php` configuration file:\n\n```php\npest()->browser()->headed();\n```\n\n## Continuous Integration\n\nYou may refer to Pest's [Continuous Integration](https://pestphp.com/docs/continuous-integration) documentation for more information on how to run your browser tests in a CI environment.\n\nHowever, if you are using GitHub Actions, you need to add the following steps to your workflow file:\n\n```yaml\n    - uses: actions/setup-node@v4\n      with:\n        node-version: lts/*\n\n    - name: Install dependencies\n      run: npm ci\n\n    - name: Install Playwright Browsers\n      run: npx playwright install --with-deps\n```\n\n---\n\nNow, let's dive into architectural testing and how it can benefit your development process. By performing architectural testing, you can evaluate the overall design of your application and identify potential flaws before they become significant issues: [Arch Testing](/docs/arch-testing)\n\n---\n\n## Browser Testing wait\nBrowser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/browser-testing.md\n\n\n### wait\n\nThe `wait` method pauses for the given number of seconds:\n\n```php\n$page->wait(2); // Wait for 2 seconds\n```\n\n---\n\n## Http Tests Validation Assertions\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n## Validation Assertions\n\nLaravel provides two primary validation related assertions that you may use to ensure the data provided in your request was either valid or invalid.\n\n---\n\n## Http Tests Available Assertions Authentication Assertions\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n### Authentication Assertions\n\nLaravel also provides a variety of authentication related assertions that you may utilize within your application's feature tests. Note that these methods are invoked on the test class itself and not the `Illuminate\\Testing\\TestResponse` instance returned by methods such as `get` and `post`.\n\n---\n\n## Pennant Adding Custom Pennant Drivers Defining Features Externally\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n### Defining Features Externally\n\nIf your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\\Pennant\\Contracts\\DefinesFeaturesExternally` interface:\n\n```php\n<?php\n\nnamespace App\\Extensions;\n\nuse Laravel\\Pennant\\Contracts\\Driver;\nuse Laravel\\Pennant\\Contracts\\DefinesFeaturesExternally;\n\nclass FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally\n{\n    /**\n     * Get the features defined for the given scope.\n     */\n    public function definedFeaturesForScope(mixed $scope): array {}\n\n    /* ... */\n}\n```\n\nThe `definedFeaturesForScope` method should return a list of feature names defined for the provided scope.\n\n_[Truncated]_"}]


php artisan make:test --help | sed -n '1,180p'


Output:
Chunk ID: ca2d31
Wall time: 0.0306 seconds
Process exited with code 0
Original token count: 245
Output:
Description:
  Create a new test class

Usage:
  make:test [options] [--] <name>

Arguments:
  name                  The name of the test

Options:
  -f, --force           Create the test even if the test already exists
  -u, --unit            Create a unit test
      --pest            Create a Pest test
      --phpunit         Create a PHPUnit test
  -h, --help            Display help for the given command. When no command is given display help for the list command
      --silent          Do not output any message
  -q, --quiet           Only errors are displayed. All other output is suppressed
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug


php artisan make:test --help | sed -n '1,180p'

Description:
  Create a new test class

Usage:
  make:test [options] [--] <name>

Arguments:
  name                  The name of the test

Options:
  -f, --force           Create the test even if the test already exists
  -u, --unit            Create a unit test
      --pest            Create a Pest test
      --phpunit         Create a PHPUnit test
  -h, --help            Display help for the given command. When no command is given display help for the list command
      --silent          Do not output any message
  -q, --quiet           Only errors are displayed. All other output is suppressed
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug


php artisan make:test Browser/Admin/AuthenticationTest --pest --no-interaction


Output:
Chunk ID: 3cb578
Wall time: 0.0310 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Test [tests/Feature/Browser/Admin/AuthenticationTest.php] created successfully.


php artisan make:test Browser/Admin/AuthenticationTest --pest --no-interaction

INFO  Test [tests/Feature/Browser/Admin/AuthenticationTest.php] created successfully.


mkdir -p tests/Browser/Admin && mv tests/Feature/Browser/Admin/AuthenticationTest.php tests/Browser/Admin/AuthenticationTest.php && rmdir tests/Feature/Browser/Admin tests/Feature/Browser 2>/dev/null || true


Output:
Chunk ID: f311ff
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


sed -n '1,220p' app/Livewire/Admin/Auth/Login.php && sed -n '1,140p' resources/views/livewire/admin/auth/login.blade.php


sed -n '1,180p' resources/views/layouts/app/sidebar.blade.php && sed -n '1,100p' tests/Browser/Admin/AuthenticationTest.php


sed -n '312,505p' specs/08-PLAYWRIGHT-E2E-PLAN.md


sed -n '312,505p' specs/08-PLAYWRIGHT-E2E-PLAN.md

## Suite 2: Admin Authentication

**File:** `tests/Browser/Admin/AuthenticationTest.php`

**Purpose:** Verify the admin login flow, credential validation, session management, logout, and sidebar navigation after successful login.

### Authentication Flow

```mermaid
stateDiagram-v2
    [*] --> LoginPage: visit /admin/login
    LoginPage --> Dashboard: valid credentials + click "Sign in"
    LoginPage --> LoginPage: invalid credentials (show error)
    LoginPage --> LoginPage: empty fields (show validation)
    Dashboard --> Products: click "Products"
    Dashboard --> Orders: click "Orders"
    Dashboard --> Customers: click "Customers"
    Dashboard --> Discounts: click "Discounts"
    Dashboard --> Settings: click "Settings"
    Dashboard --> Analytics: click "Analytics"
    Dashboard --> Themes: click "Themes"
    Dashboard --> LoginPage: click profile dropdown then "Logout"
```

---

### Test 2.1: Can log in as admin

**Preconditions:** Seeded admin user

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test`
3. Enter password: `password`
4. Click the "Sign in" button

**Expected Results:**
- Page displays "Dashboard"
- No JavaScript errors

---

### Test 2.2: Shows error for invalid credentials

**Preconditions:** Seeded admin user

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test`
3. Enter password: `wrongpassword`
4. Click the "Sign in" button

**Expected Results:**
- Page displays "Invalid credentials"
- No JavaScript errors

---

### Test 2.3: Shows error for empty email

**Preconditions:** None

**Steps:**
1. Navigate to `/admin/login`
2. Enter password: `password` (leave email empty)
3. Click the "Sign in" button

**Expected Results:**
- Validation error referencing "email" is visible
- No JavaScript errors

---

### Test 2.4: Shows error for empty password

**Preconditions:** None

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test` (leave password empty)
3. Click the "Sign in" button

**Expected Results:**
- Validation error referencing "password" is visible
- No JavaScript errors

---

### Test 2.5: Redirects unauthenticated users to login from dashboard

**Preconditions:** Not logged in

**Steps:**
1. Navigate to `/admin` (without logging in)

**Expected Results:**
- Page displays "Sign in" (redirected to login form)
- No JavaScript errors

---

### Test 2.6: Redirects unauthenticated users to login from products

**Preconditions:** Not logged in

**Steps:**
1. Navigate to `/admin/products` (without logging in)

**Expected Results:**
- Page displays "Sign in" (redirected to login form)
- No JavaScript errors

---

### Test 2.7: Can log out

**Preconditions:** Seeded admin user

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test`
3. Enter password: `password`
4. Click the "Sign in" button
5. Verify "Dashboard" is visible
6. Click on "Admin User" (profile/user menu)
7. Click "Logout"

**Expected Results:**
- Page displays "Sign in" (returned to login form)

---

### Test 2.8: Can navigate through admin sidebar sections

**Preconditions:** Seeded admin user

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test`
3. Enter password: `password`
4. Click the "Sign in" button
5. Click "Products" in the sidebar
6. Verify "Products" heading is visible
7. Click "Orders" in the sidebar
8. Verify "Orders" heading is visible
9. Click "Customers" in the sidebar
10. Verify "Customers" heading is visible
11. Click "Discounts" in the sidebar
12. Verify "Discounts" heading is visible
13. Click "Settings" in the sidebar
14. Verify "Store Settings" heading is visible

**Expected Results:**
- Each section loads without JavaScript errors
- Each section heading is visible after clicking its sidebar link

---

### Test 2.9: Can navigate to analytics from sidebar

**Preconditions:** Seeded admin user

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test`
3. Enter password: `password`
4. Click the "Sign in" button
5. Click "Analytics" in the sidebar

**Expected Results:**
- Page displays "Analytics"
- No JavaScript errors

---

### Test 2.10: Can navigate to themes from sidebar

**Preconditions:** Seeded admin user

**Steps:**
1. Navigate to `/admin/login`
2. Enter email: `admin@acme.test`
3. Enter password: `password`
4. Click the "Sign in" button
5. Click "Themes" in the sidebar

**Expected Results:**
- Page displays "Themes"
- No JavaScript errors

**Login Helper Pattern:** Tests 2.7-2.10 require the admin to be logged in first. Each test should perform the full login sequence (visit login page, fill credentials, click sign in, assert dashboard) before proceeding. This ensures test independence.

**Total: 10 tests**


sed -n '1,220p' app/Livewire/Admin/Auth/Login.php && sed -n '1,140p' resources/views/livewire/admin/auth/login.blade.php

login.blade.php

<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Livewire\Component;

class Login extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $this->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
            'remember' => ['boolean'],
        ]);

        $key = 'admin-login:'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            throw ValidationException::withMessages([
                'email' => __('Too many attempts. Try again in :seconds seconds.', [
                    'seconds' => RateLimiter::availableIn($key),
                ]),
            ]);
        }

        if (! Auth::guard('web')->attempt([
            'email' => $this->email,
            'password' => $this->password,
            'status' => 'active',
        ], $this->remember)) {
            RateLimiter::hit($key, 60);

            throw ValidationException::withMessages([
                'email' => __('Invalid credentials'),
            ]);
        }

        RateLimiter::clear($key);

        if (request()->hasSession()) {
            request()->session()->regenerate();
        }

        Auth::user()?->forceFill(['last_login_at' => now()])->save();

        $this->redirectRoute('admin.dashboard', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.login')
            ->layout('layouts.auth');
    }
}
<div class="flex flex-col gap-6">
    <x-auth-header :title="__('Sign in')" :description="__('Use your staff account to manage this store')" />

    <form wire:submit="login" class="flex flex-col gap-6">
        <flux:input
            wire:model="email"
            :label="__('Email address')"
            type="email"
            required
            autofocus
            autocomplete="email"
            placeholder="admin@acme.test"
        />

        <flux:input
            wire:model="password"
            :label="__('Password')"
            type="password"
            required
            autocomplete="current-password"
            :placeholder="__('Password')"
            viewable
        />

        <flux:checkbox wire:model="remember" :label="__('Remember me')" />

        <flux:button variant="primary" type="submit" class="w-full" data-test="admin-login-button">
            {{ __('Sign in') }}
        </flux:button>
    </form>
</div>


Output:
Chunk ID: e5097d
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 659
Output:
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Livewire\Component;

class Login extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $this->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
            'remember' => ['boolean'],
        ]);

        $key = 'admin-login:'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            throw ValidationException::withMessages([
                'email' => __('Too many attempts. Try again in :seconds seconds.', [
                    'seconds' => RateLimiter::availableIn($key),
                ]),
            ]);
        }

        if (! Auth::guard('web')->attempt([
            'email' => $this->email,
            'password' => $this->password,
            'status' => 'active',
        ], $this->remember)) {
            RateLimiter::hit($key, 60);

            throw ValidationException::withMessages([
                'email' => __('Invalid credentials'),
            ]);
        }

        RateLimiter::clear($key);

        if (request()->hasSession()) {
            request()->session()->regenerate();
        }

        Auth::user()?->forceFill(['last_login_at' => now()])->save();

        $this->redirectRoute('admin.dashboard', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.login')
            ->layout('layouts.auth');
    }
}
<div class="flex flex-col gap-6">
    <x-auth-header :title="__('Sign in')" :description="__('Use your staff account to manage this store')" />

    <form wire:submit="login" class="flex flex-col gap-6">
        <flux:input
            wire:model="email"
            :label="__('Email address')"
            type="email"
            required
            autofocus
            autocomplete="email"
            placeholder="admin@acme.test"
        />

        <flux:input
            wire:model="password"
            :label="__('Password')"
            type="password"
            required
            autocomplete="current-password"
            :placeholder="__('Password')"
            viewable
        />

        <flux:checkbox wire:model="remember" :label="__('Remember me')" />

        <flux:button variant="primary" type="submit" class="w-full" data-test="admin-login-button">
            {{ __('Sign in') }}
        </flux:button>
    </form>
</div>