Saltar al contenido

Tabla de contenidos Laravel Dusk Una de las grandes herramientas de Laravel es Laravel Dusk, disponible desde Laravel 5.4, una potente herramienta para realizar pruebas en el navegador con Laravel la cual facilitará la vida a todos los desarrolladores proporcionando una API común, algo completamente necesario para un framework como Laravel.

Mi recomendación personal es que inviertas tanto tiempo como puedas en tus proyectos con Laravel Dusk, todo lo que puedas hacer con Laravel Dusk será beneficioso para tu proyecto.

Por defecto Laravel Dusk utiliza ChromeDriver, una herramienta de código abierto para realizar pruebas automatizadas de aplicaciones web, aunque podemos utilizar otros navegadores para realizar las pruebas unitarias, no será lo normal. Instalar Laravel Dusk Para instalar Laravel Dusk simplemente ejecuta el siguiente comando.

composer require laravel/dusk

Para registrar el provider en nuestra aplicación, abre el arhivo AppServiceProvider y modifica el código por el siguiente.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\DuskServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        if ($this->app->environment('local', 'testing')) {
            $this->app->register(DuskServiceProvider::class);
        }
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

Finalmente ejecuta el siguiente comando para instalar todo lo necesario para empezar con las pruebas.

php artisan dusk:install

El comando dusk:install habrá creado el directorio tests/Browser y todo lo necesario. Primer test con Laravel Dusk Para crear nuestra primera prueba de navegador con Laravel Dusk vamos a ejecutar los siguientes comandos, primero para instalar el sistema de autenticación de Laravel, a continuación para crear un test para las zonas de autenticación.

# auth laravel
composer require laravel/ui
php artisan ui vue --auth
npm install && npm run dev

# make login test with dusk
php artisan dusk:make LoginTest

Antes de crear tu primer test, asegúrate que el APP_URL de tu archivo .env es el correcto ya que de otra forma Laravel Dusk fallará.

Ahora vamos a abrir el archivo tests/Browser/LoginTest.php y vamos a modificar el código por el siguiente para comprobar si realmente estamos en la página de login.

<?php

namespace Tests\Browser;

use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class LoginTest extends DuskTestCase
{
    public function testIsLoginPage()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                ->assertTitle('Laravel')
                ->assertSee('Forgot Your Password?');
        });
    }
}

Para que Laravel Dusk sepa que un método es un test éste debe empezar por test.

Lo único que hacemos es decirle a Dusk que visite la página de Login, que compruebe que el título es Laravel y que revise si existe el texto Forgot Your Password?. Para ejecutar la prueba simplemente ejecuta el siguiente comando y verás que todo ha salido bien.

php artisan dusk

laravel-dusk-test-login-1

Si queremos navegar entre páginas con Laravel Dusk también podemos hacerlo de forma muy sencilla, vamos a modificar la prueba para navegar a la página password/reset y hacer algunas pruebas en ella.

<?php
public function testIsLoginPage()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/login')
            ->assertTitle('Laravel')
            ->assertSee('Forgot Your Password?')
            ->clickLink("Forgot Your Password?")
            ->assertPathIs('/password/reset')
            ->assertSee('Reset Password');
    });
}

Interactuar con bases de datos con Laravel Dusk

Ahora vamos a crear un archivo de entorno llamado .env.dusk.local y vamos a añadir el siguiente código para trabajar con SQLite y así poder realizar tests sin alterar nuestra base de datos.

APP_ENV=local
APP_KEY=base64:358E4UyIq+Zbg+H+Pm03dYA2B/vz3ViPJ+U48ikg6qE=
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost:8000

DB_CONNECTION=dusk

Para configurar la conexión dusk vamos a modificar el archivo config/database.php añadiendo la siguiente clave al array de configuración.

<?php

'dusk' => [
    'driver' => 'sqlite',
    'database' => database_path('dusk.sqlite'),
    'prefix' => '',
    'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],

Para finalizar la configuración y así poder trabajar con SQLite debemos crear un archivo dusk.sqlite en el directorio databases de nuestro proyecto.

Para ejecutar nuestra aplicación con el entorno que acabamos de crear deberás ejecutar el siguiente comando.

php artisan serve --env=dusk.local

A partir de ahora, todas las pruebas que hagamos serán utilizando la base de datos SQLite en lugar de nuestra base de datos MySQL. Probando el sistema de Login con Laravel Dusk Vamos a abrir de nuevo el archivo LoginTest y vamos a modificar el código para intentar hacer login en la aplicación.

<?php

namespace Tests\Browser;

use App\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class LoginTest extends DuskTestCase
{
    use DatabaseMigrations;

    protected $user;

    public function setUp(): void {
        parent::setUp();
        $this->user = factory(User::class)->create([
            'email' => '[email protected]'
        ]);
    }

    public function testIsLoginPage() {
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                ->assertTitle('Laravel')
                ->assertSee('Forgot Your Password?')
                ->clickLink("Forgot Your Password?")
                ->assertPathIs('/password/reset')
                ->assertSee('Reset Password');
        });
    }


    public function testLogin() {
        $this->browse(function(Browser $browser) {
            $browser->visit('/login')
                ->type('email', $this->user->email)
                ->type('password', 'password')
                ->press('Login')
                ->assertPathIs('/home');
        });
    }
}

Gracias al trait DatabaseMigrations, una vez ejecutemos nuestros tests, se ejecutará el comando php artisan migrate y, una vez hayan finalizado, se ejecutará el comando php artisan migrate:rollback, así no nos tenemos que preocupar de tener datos para realizar las pruebas.

Cómo puedes ver, con el método type podemos introducir información en los campos con nombre email y password del formulario de login.

A continuación, utilizamos el método press para ejecutar dicho formulario, este método buscará un botón con el texto Login y, si todo ha ido bien, el test assertPathIs devolverá true porque estaremos en la página home que es donde Laravel te deja por defecto al iniciar sesión.

php artisan dusk

laravel-dusk-test-login-2

LoginAs en Laravel Dusk

Aunque el método anterior es completamente funcional, Laravel Dusk nos ofrece un atajo para iniciar sesión, el método loginAs, al cual simplemente le debemos pasar el id de un usuario.

<?php
public function testLogin() {
    $this->browse(function(Browser $browser) {
        $browser->loginAs($this->user->id)
            ->visit('/home')
            ->assertSee('You are logged in!');
    });
}
php artisan dusk --debug

login dusk

Utilizando el flag --debug podemos ver más información de lo que está sucediendo en nuestros Tests.

Probando tablas html con Laravel Dusk

Otro test que podemos hacer es sobre tablas, para ello vamos a crear una ruta /users.

<?php

Route::group(['prefix' => 'users', 'middleware' => 'auth'], function()
{
	Route::get('/:id', function($id) {
		echo $id;
	})->name('users.detail');

	Route::get('/', function() {
		$users = \App\User::all();
		return view('users.index', compact('users'));
	})->name('users.index');

});

Fíjate que estamos utilizando el middleware auth para controlar el acceso. Ahora crea el archivo users/index.blade.php y añade el siguiente código.

@extends('layouts.app')

@section('title', 'Listado Usuarios')

@section('content')
    <div class="container">
        <div class="row">
            <table class="table table-bordered">
                <thead>
                <tr>
                    <th>Id</th>
                    <th>Name</th>
                    <th>Email</th>
                </tr>
                </thead>
                <tbody>
                @forelse($users as $user)
                    <tr>
                        <td>{{ $user->id }}</td>
                        <td>{{ $user->name }}</td>
                        <td>{{ $user->email }}</td>
                        <td>
                            <a class="edit" href="/users/{{$user->id}}">
                                Detail
                            </a>
                        </td>
                    </tr>
                @empty
                    <tr>
                        <td colspan="3">No hay usuarios</td>
                    </tr>
                @endforelse
                </tbody>
            </table>
        </div>
    </div>
@endsection

Antes de continuar, hágamos una pequeña modificación en el archivo layouts/app.blade.php.

<title>
    @yield('title') - {{ config('app.name', 'Laravel') }}
</title>

Para poder probar nuestro código, vamos a crear un nuevo test con el siguiente comando.

php artisan dusk:make UsersTableTest

Abre el archivo de test que se acaba de crear en tests/Browser/UsersTableTest.php y añade el siguiente código que pasamos a comentar.

<?php

namespace Tests\Browser;

use App\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class UsersTableTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * @group users
     */
    public function testRouteProtected()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/users')
                ->assertPathIs('/login');
        });
    }

    /**
     * @group users
     */
    public function testFindUserInTable()
    {
        $user = factory(User::class)->create([
            'email' => '[email protected]'
        ]);

        $this->browse(function (Browser $browser) use($user) {
            $browser->loginAs($user->id)
                ->visit('/users')
                ->with('.table', function (Browser $table) use ($user) {
                    $table->assertSee('[email protected]')
                        ->clickLink('Detail')
                        ->assertPathIs('/users/'.$user->id);
                });
        });
    }
}

Lo primero en lo que debemos fijarnos es en cómo utilizamos la anotación @group. Piensa que prácticamente todo lo que puedas hacer con PHPUnit lo puedes hacer con Laravel Dusk.

Gracias a la anotación @group podemos ejecutar únicamente los tests del archivo UsersTableTest con el siguiente comando.

php artisan dusk --group=users --debug

Con el método with en Laravel Dusk podemos acceder a un elemento del DOM, y acceder a él utilizando una closure cómo podemos ver en el ejemplo. Ajax en Laravel Dusk Prácticamente todas las aplicaciones hoy en día trabajan con ajax, así que Laravel Dusk nos ofrece varios métodos para solucionar este problema.

Nosotros vamos a trabajar con Yajra Datatables y vamos a desarrollar las siguientes pruebas de funcionalidad.

  • Comprobar si la tabla ha cargado correctamente.

  • Comprobar si un usuario existe en la página actual.

  • Eliminar usuarios y comprobar que ha sido eliminado correctamente.

  • Comprobar la paginación.

  • Realizar búsquedas con ajax.

Primero instala Laravel DataTables.

composer require yajra/laravel-datatables-oracle:"~9.0"

Modifica el archivo app.blade.php para que DataTables funcione correctamente.

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>
        @yield('title') - {{ config('app.name', 'Laravel') }}
    </title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">

    @stack('styles')
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                            </li>
                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}"></script>
    @stack('scripts')
</body>
</html>

Añade algunas rutas dentro del grupo protegido por el middleware auth con el prefijo users para tener nuestra tabla disponible y así poder testearla.

<?php

use Illuminate\Support\Facades\Route;
use Yajra\DataTables\Facades\DataTables;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Route::group(['prefix' => 'users', 'middleware' => 'auth'], function()
{
    Route::get('/:id', function($id)
    {
        echo $id;
    })->name('users.detail');

    Route::get('/', function() {
        $users = \App\User::all();
        return view('users.index', compact('users'));
    })->name('users.index');

    Route::get('/datatables', function() {
        return view('users.datatables');
    })->name('users.datatables');

    Route::get('/json_data', function() {
        $model = \App\User::query();
        return Datatables::eloquent($model)
            ->addColumn('actions', function ($user) {
                return '
                <a href="' . route('users.destroy') . '" data-id="' . $user['id'] . '" data-remove="true" class="btn btn-danger"><i class="ion ion-trash-a"></i> Destroy </a>
                ';
            })
            ->rawColumns(['actions'])
            ->make(true);
    })->name('users.json_data');

    Route::delete('/', function(\Illuminate\Http\Request $request)
    {
        if($request->ajax())
        {
            $id = $request->input('id');
            \Illuminate\Support\Facades\Log::info($id);
            \App\User::destroy($id);
            return response()->json(['response' => 'user removed']);
        }
        abort(401);
    })->name('users.destroy');
});

Ahora crea el archivo users/datatables.blade.php para pintar la tabla con Datatables.

@extends('layouts.app')

@section('title', 'Listado Usuarios')

@push('styles')
    <link href="https://cdn.datatables.net/1.10.21/css/dataTables.bootstrap4.min.css" rel="stylesheet" />
    <style>
        #users-table th, #users-table td
        {
            text-align:center;
            vertical-align:middle;
        }
    </style>
@endpush

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-12">
                <table class="table table-bordered" id="users-table">
                    <thead>
                    <tr>
                        <th>Id</th>
                        <th>Name</th>
                        <th>Email</th>
                        <th>Acciones</th>
                    </tr>
                    </thead>
                </table>
            </div>
        </div>
    </div>
@endsection

@push('scripts')

    <script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
    <script src="https://cdn.datatables.net/1.10.21/js/dataTables.bootstrap4.min.js"></script>

    <script>
        var dt;
        jQuery(function()
        {
            dt = jQuery('#users-table').DataTable({
                "language": {
                    "url": "//cdn.datatables.net/plug-ins/1.10.21/i18n/Spanish.json"
                },
                pageLength: 5,
                processing: true,
                serverSide: true,
                ajax: '{!! route('users.json_data') !!}',
                columns: [
                    {data: 'id', name: 'id' },
                    { data: 'name', name: 'name' },
                    { data: 'email', name: 'email' },
                    { data: 'actions', name: 'actions', searchable: false },
                ]
            });

            jQuery(document).on('click', '#users-table a', function(e)
            {
                if(jQuery(this).data('remove'))
                {
                    e.preventDefault();
                    var id = $(this).data('id');
                    jQuery.ajax({
                        url: jQuery(this).attr('href'),
                        method: 'DELETE',
                        headers: {
                            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'),
                        },
                        data: {
                            id: id
                        },
                        success: function (data) {
                            dt.ajax.reload();
                        }
                    });
                }
            })
        });
    </script>
@endpush

Probando tablas con Datatables en Laravel Dusk Vamos a crear un nuevo test llamado DatatablesTest para realizar una primera prueba y asegurarnos que la tabla ha cargado correctamente.

php artisan dusk:make DatatablesTest

Y vamos a actualizar el código para poder interactuar con Ajax en Dusk por primera vez.

<?php

namespace Tests\Browser;

use App\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

/**
 * Class DatatablesTest
 * @package Tests\Browser
 * @group datatables
 */
class DatatablesTest extends DuskTestCase
{
    use DatabaseMigrations;

    public function testDatatablesCheckLoad()
    {
        factory(User::class, 10)->create();
        $user = User::find(2);
        $this->browse(function ($browser) use($user)
        {
            $browser->loginAs($user)
                ->visit('/users/datatables')
                //->pause(500) //peticiones ajax
                ->waitForText('Siguiente')
                ->with('.table', function (Browser $table) use ($user)
                {
                    $table->assertSee($user->email);
                });
        });
    }
}

Fíjate que ahora estamos utilizando @group datatables para toda la clase, así que todos los métodos que empiecen por test se ejecutarán cuando ejecutemos el comando php artisan dusk --group=datatables.

Lo primero que hacemos es crear 10 usuarios con factory, después obtenemos el usuario con id 2 y lo utilizamos para iniciar sesión, visitamos la ruta /users/datatables y utilizamos el método waitForText hasta que el texto Siguiente sea visible, este texto es el que por defecto muestra la paginación de Datatables en Español, si lo vemos, accedemos a la tabla y nos aseguramos que podemos ver el email del usuario con el que nos hemos identificado.

php artisan dusk --group=datatables --debug

Eliminar datos con Ajax y Laravel Dusk

Otra funcionalidad necesaria en la mayoría de los casos es eliminar datos con Ajax, para probar esta funcionalidad vamos a añadir el siguiente método a la clase DatatablesTest.

<?php

public function testDatatablesRemoveUser()
{
    factory(User::class, 10)->create();
    $userToLogin = User::find(2);
    $userToRemove = User::find(1);
    $this->browse(function (Browser $browser) use($userToLogin, $userToRemove)
    {
        $browser->loginAs($userToLogin)
            ->visit('/users/datatables')
            ->pause(500) //peticiones ajax
            ->with('.table', function (Browser $table) use ($userToLogin, $userToRemove)
            {
                $table
                    ->assertSee($userToLogin->email)
                    ->clickLink('Destroy')
                    ->pause(500)
                    ->assertDontSee($userToRemove->email);
            });
    });
}

Ahora, en lugar de utilizar el método waitForText, utilizamos el método pause de Laravel Dusk, gracias a este método podemos decirle la cantidad de milisegundos que queremos esperar hasta que el las pruebas continúen.

Con el método assertDontSee nos aseguramos que el usuario eliminado ya no existe en la tabla, así que si todo ha ido bien tendremos un ok en la terminal.

php artisan dusk --group=datatables --debug

Paginación con Laravel Dusk y Ajax

Otra funcionalidad muy interesante son las paginaciones con Ajax, nosotros vamos a hacer el siguiente test, iremos a la primera página, nos aseguraremos que estamos en ella comprobando que existe un usuario que debe ser mostrado, a continuación pulsaremos en el botón siguiente, haremos un wait y con assertSee comprobaremos que podemos ver el usuario que debe aparecer en la segunda página.

<?php

public function testDatatablesPagination()
{
    factory(User::class, 10)->create();
    $userToLogin = User::find(2);
    $userFirstOnNextPage = User::find(8);
    $this->browse(function (Browser $browser) use($userToLogin, $userFirstOnNextPage)
    {
        $browser->loginAs($userToLogin)
            ->visit('/users/datatables')
            ->pause(1000) //peticiones ajax
            ->with('.table', function (Browser $table) use ($userToLogin)
            {
                $table->assertSee($userToLogin->email);
            })
            ->assertSee('Siguiente')
            ->clickLink('Siguiente')
            ->pause(1000)
            ->assertSee($userFirstOnNextPage->email);
    });
}

php artisan dusk --group=datatables --debug

Búsquedas con Datatables y Laravel Dusk

Finalmente vamos a realizar una búsqueda utilizando el buscador de datatables y vamos a comprobar que el usuario se ha encontrado.

<?php


public function testDatatablesSearchable()
{
    factory(User::class, 10)->create();
    $userToLogin = User::find(2);
    $userToSearch = User::find(8);
    $this->browse(function (Browser $browser) use($userToLogin, $userToSearch)
    {
        $browser->loginAs($userToLogin)
            ->visit('/users/datatables')
            ->pause(1000) //peticiones ajax
            ->with('.table', function ($table) use ($userToSearch)
            {
                $table->assertDontSee($userToSearch->email);
            })
            ->type('input[type=search]', $userToSearch->name)
            ->pause(500)
            ->with('.table', function (Browser $table) use ($userToSearch)
            {
                $table->assertSee($userToSearch->email);
            });
    });
}

Llegados a este punto debes tener una idea muy clara del funcionamiento de Laravel Dusk, pero aún queda lo mejor y el trabajo con LaravelDuskPage, una clase para realizar tests ya semi-definidos donde podemos hacer cosas que no son posibles en los tests que hemos visto hasta ahora. Laravel Dusk Pages, tests avanzados con Laravel Todo test que herede de LaravelDuskPage podrá agrupar toda la lógica de una página asociada a una ruta en un mismo archivo con los beneficios que esto puede suponer.

Para llevar a cabo el trabajo con los tests a páginas necesitamos crear primero un modelo y una migración.

php artisan make:model Framework -m

La migración va a ser muy sencilla, únicamente tendremos un campo name.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateFrameworksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('frameworks', function (Blueprint $table) {
            $table->id();
            $table->string("name")->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('frameworks');
    }
}

Ahora vamos a crear unas nuevas rutas para poder procesar un sencillo formulario para dar de alta registros con Laravel Dusk.

<?php

Route::group(['prefix' => 'frameworks', 'middleware' => 'auth'], function()
{
    Route::get('/create', function()
    {
        return view('frameworks.form');
    });

    Route::post('/store', function(\App\Http\Requests\FrameworksRequest $frameworksRequest)
    {
        $framework = new \App\Framework();
        $framework->fill($frameworksRequest->all());
        $framework->save();
        return redirect('frameworks/create')->with('message', 'Framework guardado correctamente');
    });
});

También debemos crear un sencillo form request con Laravel para las validaciones.

php artisan make:request FrameworksRequest
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class FrameworksRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|unique:frameworks|max:30',
        ];
    }
}

Ahora crea el archivo frameworks/form.blade.php y añade el siguiente código para pintar nuestro formulario.

@extends('layouts.app')

@section('title', 'Nuevo Framework')

@section('content')
    <div class="container">
        @if(session('message'))
            <div class="alert alert-success">
                {{ session('message') }}
            </div>
        @endif
        <form action="{{ route('frameworks.store') }}" method="POST" novalidate>
            @csrf
            <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                <label for="name" class="col-md-4 control-label">Framework</label>

                <div class="col-md-6">
                    <input id="name" type="text" class="form-control" name="name" required>
                    @if ($errors->has('name'))
                        <span class="help-block">
                                <strong>{{ $errors->first('name') }}</strong>
                            </span>
                    @endif
                </div>
            </div>

            <div class="form-group{{ $errors->has('language_framework_id') ? ' has-error' : '' }}">
                <label for="language_framework_id" class="col-md-4 control-label">Language Framework</label>

                <div class="col-md-6">
                    <select name="language_framework_id">
                        @foreach(\App\Framework::all() as $framework)
                            <option value="{{ $framework->id }}">{{ $framework->name }}</option>
                        @endforeach
                    </select>
                    @if ($errors->has('language_framework_id'))
                        <span class="help-block">
                                <strong>{{ $errors->first('language_framework_id') }}</strong>
                            </span>
                    @endif
                </div>
            </div>

            <div class="form-group">
                <div class="col-md-8 col-md-offset-4">
                    <button type="submit" class="btn btn-primary">
                        Save Framework
                    </button>
                </div>
            </div>
        </form>
    </div>
@endsection

Crear nuestra primera página con Laravel Dusk Para crear un test de tipo página simplemente ejecuta el siguiente comando.

php artisan dusk:page Framework

Ahora abre el archivo que se acaba de generar en tests/Browser/Pages/Framework.php y vamos a añadir el siguiente código que pasamos a explicar.

<?php

namespace Tests\Browser\Pages;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Page as BasePage;

class Framework extends BasePage
{
    /**
     * Get the URL for the page.
     *
     * @return string
     */
    public function url()
    {
        return '/frameworks/create';
    }

    /**
     * Assert that the browser is on the page.
     *
     * @return void
     */
    public function assert(Browser $browser)
    {
        $browser->assertPathIs($this->url());
    }

    /**
     * Get the element shortcuts for the page.
     *
     * @return array
     */
    public function elements()
    {
        return [
            '@framework-name' => '#name',
        ];
    }

	/**
	 * Create a new framework.
	 *
	 * @param  \Laravel\Dusk\Browser  $browser
	 * @param  string  $name
	 * @return void
	 */
	public function createFramework(Browser $browser, $name)
	{
		$browser->type('@framework-name', $name)
		        ->press('Save Framework');
	}
}

El método url le dice a nuestra página donde debe ir una vez la llamemos, el método elements nos permite crear atajos para acceder a los elementos de la página a visitar, y el método createFramework nos va a ayudar a ejecutar el formulario anterior para crear nuevos registros en base de datos utilizando el atajo @framework-name, que cómo puedes ver es el segundo parámetro del método.

Para utilizar esta página, vamos a crear el test y añadimos primero el siguiente método que nos dirá si haciendo uso de nuestra página hemos podido visitar la ruta asignada.

php artisan dusk:make FrameworkTest
<?php

namespace Tests\Browser;

use App\LanguageFramework;
use App\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

/**
 *
 * testing with pages
 *
 * Class FrameworkTest
 * @package Tests\Browser
 */
class FrameworkTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * @group framework-page
     */
    public function testVisitFrameworkPage()
    {
        factory(User::class, 1)->create();
        $this->browse(function (Browser $browser) {
            $browser->loginAs(User::find(1))
                ->visit(new \Tests\Browser\Pages\Framework)
                ->assertSee('Framework');
        });
    }
}

Cómo puedes ver, ahora al método visit le pasamos una instancia de Framework, eso es suficiente para que Laravel Dusk sepa qué tiene que hacer.

php artisan dusk --group=framework-page --debug

Añade el siguiente método para asegurarnos que el campo name del formulario es requerido y la validación funciona perfectamente.

<?php

/**
 * @group framework-page
 */
public function testCheckErrorFormFrameworkPage()
{
    factory(User::class, 1)->create();
    $this->browse(function (Browser $browser) {
        $browser->loginAs(User::find(1))
            ->visit(new \Tests\Browser\Pages\Framework)
            ->press('Save Framework')
            ->assertSee('The name field is required.');
    });
}

Aquí lo único que hacemos es visitar la ruta y pulsar el botón submit, al ser name un campo requerido podemos ver el típico mensaje error de Laravel. Esto es posible por haber añadido novalidate al tag form de nuestro formulario.

php artisan dusk --group=framework-page --debug

Para finalizar este tutorial de Laravel Dusk vamos a crear un nuevo framework y de paso vamos a ver que una vez el registro existe en base de datos podemos asegurarnos que no se puede volver a insertar ya que estamos utilizando unique en nuestro form request.

<?php

/**
 * @group framework-page
 */
public function testCheckCanSubmitFormFrameworkPage()
{
    factory(User::class, 1)->create();
    Framework::create(['name' => 'Php']);
    $this->browse(function (Browser $browser)
    {
        $browser->loginAs(User::find(1))
            ->visit(new \Tests\Browser\Pages\Framework)
            ->press('Save Framework')
            ->assertSee('The name field is required.')
            ->createFramework('Laravel')
            ->assertSee('Framework guardado correctamente')
            ->createFramework('Laravel')
            ->assertSee('The name has already been taken.');
    });
}

Aquí vemos algo muy interesante, y es cómo podemos ejecutar el método createFramework de nuestra página, una vez visitamos la ruta con la instancia de la clase Framework ese método pasa a estar disponible en la cadena de métodos, una vez se ejecuta el método createFramework se ejecutará el formulario y si todo ha ido bien tendrás un ok en tu consola.

php artisan dusk --group=framework-page --debug

Eso es todo lo que necesitas saber de saque en lo que respecta a Laravel Dusk, si te ha parecido interesante comparte el post en las redes sociales para que llegue a más usuarios.

No te pierdas todo lo que tenemos para Laravel

school Curso completo

Curso Laravel 12
Completo 2026

El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje