From f8578262e702880a607bd403ec91c849fbd67258 Mon Sep 17 00:00:00 2001 From: Tony Lea Date: Wed, 12 Jun 2024 15:32:43 -0400 Subject: [PATCH] bobby@bobbyiliev.com * Adding dusk tests --------- Co-authored-by: tnylea Co-authored-by: Bobby Iliev Co-authored-by: bobbyiliev --- .env.dusk.ci | 60 ++++++++++ .github/workflows/tests.yml | 42 ++++++- README.md | 2 +- composer.json | 5 +- ..._update_passwords_field_to_be_nullable.php | 8 +- .../elements/input-placeholder.blade.php | 2 +- .../views/components/layouts/app.blade.php | 5 +- .../components/setup/sidebar-links.blade.php | 2 +- resources/views/pages/auth/login.blade.php | 12 +- .../views/pages/auth/password/reset.blade.php | 2 +- resources/views/pages/auth/register.blade.php | 8 +- resources/views/pages/auth/verify.blade.php | 2 +- src/AuthServiceProvider.php | 5 + src/Providers/DuskServiceProvider.php | 71 +++++++++++ tests/Browser/LoginTest.php | 110 ++++++++++++++++++ tests/Browser/Pages/Login.php | 39 +++++++ tests/Browser/Pages/Register.php | 46 ++++++++ tests/Browser/Pages/VerifyEmail.php | 16 +++ tests/Browser/RegisterTest.php | 67 +++++++++++ tests/Browser/Traits.php | 13 +++ tests/Browser/VerifyEmailTest.php | 55 +++++++++ tests/Browser/console/.gitignore | 2 + tests/Browser/screenshots/.gitignore | 2 + tests/Browser/source/.gitignore | 2 + tests/DuskTestCase.php | 68 +++++++++++ tests/Pest.php | 5 + 26 files changed, 632 insertions(+), 19 deletions(-) create mode 100644 .env.dusk.ci create mode 100644 src/Providers/DuskServiceProvider.php create mode 100644 tests/Browser/LoginTest.php create mode 100644 tests/Browser/Pages/Login.php create mode 100644 tests/Browser/Pages/Register.php create mode 100644 tests/Browser/Pages/VerifyEmail.php create mode 100644 tests/Browser/RegisterTest.php create mode 100644 tests/Browser/Traits.php create mode 100644 tests/Browser/VerifyEmailTest.php create mode 100644 tests/Browser/console/.gitignore create mode 100644 tests/Browser/screenshots/.gitignore create mode 100644 tests/Browser/source/.gitignore create mode 100644 tests/DuskTestCase.php diff --git a/.env.dusk.ci b/.env.dusk.ci new file mode 100644 index 0000000..6e20d81 --- /dev/null +++ b/.env.dusk.ci @@ -0,0 +1,60 @@ +APP_NAME='DevDojo Auth' +APP_ENV=testing +APP_KEY=base64:La7oMA4FgO9U3dEgYzu7UwlpYjFvGKSgP2uwnkbaPK8= +APP_DEBUG=true +APP_TIMEZONE=UTC +APP_URL=http://127.0.0.1:8000 + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +APP_MAINTENANCE_STORE=database + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +DB_DATABASE=/home/runner/work/auth/auth/laravel_app/database/dusk.sqlite + +SESSION_DRIVER=file +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=file +CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a2897a1..d75b02f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -108,20 +108,60 @@ jobs: composer install working-directory: ./laravel_app - - name: Install PestPHP and PHPStan + - name: Install PestPHP, PHPStan, Dusk, and Dusk API Conf run: | composer require pestphp/pest --dev --with-all-dependencies composer require larastan/larastan:^2.0 --dev --with-all-dependencies + composer require laravel/dusk --dev --with-all-dependencies + composer require alebatistella/duskapiconf --dev --with-all-dependencies + composer require protonemedia/laravel-dusk-fakes:^1.6 --dev --with-all-dependencies working-directory: ./laravel_app + - name: Upgrade Chrome Driver + run: php artisan dusk:chrome-driver --detect + working-directory: ./laravel_app + + - name: Start Chrome Driver + run: ./vendor/laravel/dusk/bin/chromedriver-linux & + working-directory: ./laravel_app + + - name: Check Chrome & ChromeDriver Versions + run: | + google-chrome --version + chromedriver --version + - name: Clear all view caches run: php artisan view:clear working-directory: ./laravel_app + - name: Run Artisan Serve + run: php artisan serve --no-reload & + working-directory: ./laravel_app + - name: Run Tests run: ./vendor/bin/pest working-directory: ./laravel_app + - name: Run Dusk Tests + env: + APP_URL: http://127.0.0.1:8000 + APP_ENV: testing + run: php artisan dusk -vvv + working-directory: ./laravel_app + + - name: Upload Screenshots + if: failure() + uses: actions/upload-artifact@v2 + with: + name: screenshots + path: tests/Browser/screenshots + - name: Upload Console Logs + if: failure() + uses: actions/upload-artifact@v2 + with: + name: console + path: tests/Browser/console + - name: Move the PHP config file to the root directory run: cp vendor/devdojo/auth/phpstan.neon phpstan.neon working-directory: ./laravel_app diff --git a/README.md b/README.md index 7e84660..90d4a98 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Be sure to visit the official documentation at Laravel Starter Kits. ``` composer require devdojo/auth diff --git a/composer.json b/composer.json index 5297061..fba0a85 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,10 @@ "pestphp/pest": "^2.34", "pestphp/pest-plugin-laravel": "^2.4", "larastan/larastan": "^2.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.11", + "laravel/dusk": "^8.2", + "protonemedia/laravel-dusk-fakes": "^1.6", + "alebatistella/duskapiconf": "^1.2" }, "autoload": { "psr-4": { diff --git a/database/migrations/2024_04_24_000002_update_passwords_field_to_be_nullable.php b/database/migrations/2024_04_24_000002_update_passwords_field_to_be_nullable.php index ece8fc8..1c97524 100644 --- a/database/migrations/2024_04_24_000002_update_passwords_field_to_be_nullable.php +++ b/database/migrations/2024_04_24_000002_update_passwords_field_to_be_nullable.php @@ -2,6 +2,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -22,9 +23,14 @@ public function up() */ public function down() { + // Update records with NULL values to avoid constraint violations + DB::table('users')->whereNull('name')->update(['name' => '']); + DB::table('users')->whereNull('password')->update(['password' => '']); + + // Change the table structure Schema::table('users', function (Blueprint $table) { - $table->string('password')->nullable(false)->change(); $table->string('name')->nullable(false)->change(); + $table->string('password')->nullable(false)->change(); }); } }; diff --git a/resources/views/components/elements/input-placeholder.blade.php b/resources/views/components/elements/input-placeholder.blade.php index a9fcb9f..39c5f59 100644 --- a/resources/views/components/elements/input-placeholder.blade.php +++ b/resources/views/components/elements/input-placeholder.blade.php @@ -2,7 +2,7 @@ 'value' => '' ]) -
merge(['class' => 'px-3.5 bg-gray-50 py-2.5 text-sm flex items-center justify-between border rounded-md border-gray-300']) }}> +
merge(['class' => 'px-3.5 bg-gray-50 py-2.5 text-sm flex items-center justify-between border rounded-md border-gray-300']) }}> {{ $value }} {{ $slot }}
\ No newline at end of file diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index 9a04e75..a2dbce2 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -4,7 +4,10 @@ @include('auth::includes.head') -
+ @php + $dyanicPageId = str_replace('/', '-', str_replace('.', '', Request::path())); + @endphp +
@if(config('devdojo.auth.appearance.background.image'))
diff --git a/resources/views/components/setup/sidebar-links.blade.php b/resources/views/components/setup/sidebar-links.blade.php index a6dff49..6537535 100644 --- a/resources/views/components/setup/sidebar-links.blade.php +++ b/resources/views/components/setup/sidebar-links.blade.php @@ -43,7 +43,7 @@ > diff --git a/resources/views/pages/auth/login.blade.php b/resources/views/pages/auth/login.blade.php index e474984..120e041 100644 --- a/resources/views/pages/auth/login.blade.php +++ b/resources/views/pages/auth/login.blade.php @@ -135,11 +135,11 @@ public function authenticate() @if($showPasswordField) - + @else @if($showIdentifierInput) - + @endif @endif @@ -158,19 +158,19 @@ public function authenticate() @endif @if($showPasswordField) - +
- Forgot your password? + Forgot your password?
@endif - Continue + Continue
Don't have an account? - Sign up + Sign up
@if(config('devdojo.auth.settings.login_show_social_providers') && config('devdojo.auth.settings.social_providers_location') != 'top') diff --git a/resources/views/pages/auth/password/reset.blade.php b/resources/views/pages/auth/password/reset.blade.php index 5878d62..37647f3 100644 --- a/resources/views/pages/auth/password/reset.blade.php +++ b/resources/views/pages/auth/password/reset.blade.php @@ -51,7 +51,7 @@ public function sendResetPasswordLink() /> @if ($emailSentMessage) -
+
diff --git a/resources/views/pages/auth/register.blade.php b/resources/views/pages/auth/register.blade.php index 32db621..7c7c0af 100644 --- a/resources/views/pages/auth/register.blade.php +++ b/resources/views/pages/auth/register.blade.php @@ -128,19 +128,19 @@ public function register() @php $autofocusEmail = ($showNameField) ? false : true; @endphp - + @endif @if($showPasswordField) - + @endif - Continue + Continue
Already have an account? - Sign in + Sign in
@if(config('devdojo.auth.settings.social_providers_location') != 'top') diff --git a/resources/views/pages/auth/verify.blade.php b/resources/views/pages/auth/verify.blade.php index 0dba519..507f57a 100644 --- a/resources/views/pages/auth/verify.blade.php +++ b/resources/views/pages/auth/verify.blade.php @@ -57,7 +57,7 @@ public function resend() @endif
-

Before proceeding, please check your email for a verification link. If you did not receive the email, click here to request another.

+

Before proceeding, please check your email for a verification link. If you did not receive the email, click here to request another.

diff --git a/src/AuthServiceProvider.php b/src/AuthServiceProvider.php index 59826ae..fe8d988 100644 --- a/src/AuthServiceProvider.php +++ b/src/AuthServiceProvider.php @@ -144,5 +144,10 @@ public function register() $this->app->singleton(Google2FA::class, function ($app) { return new Google2FA(); }); + + // Register the DuskServiceProvider + if (($this->app->environment('local') || $this->app->environment('testing')) && class_exists(\Laravel\Dusk\DuskServiceProvider::class)) { + $this->app->register(\Devdojo\Auth\Providers\DuskServiceProvider::class); + } } } diff --git a/src/Providers/DuskServiceProvider.php b/src/Providers/DuskServiceProvider.php new file mode 100644 index 0000000..367ab45 --- /dev/null +++ b/src/Providers/DuskServiceProvider.php @@ -0,0 +1,71 @@ +script("document.querySelector('$selector').setAttribute('$attribute', '$value');"); + + return $this; + }); + + Browser::macro('authAttributeRemove', function (?string $selector, string $attribute) { + $this->script("document.querySelector('$selector').removeAttribute('$attribute');"); + + return $this; + }); + + Browser::macro('testValidationErrorOnSubmit', function (string $message = '') { + $this + ->click('@submit-button') + ->waitForText($message) + ->assertSee($message); + + return $this; + }); + + Browser::macro('createJohnDoe', function () { + $user = \App\Models\User::factory()->create([ + 'email' => 'johndoe@gmail.com', + 'password' => \Hash::make('password'), + ]); + + return $this; + }); + + Browser::macro('assertRedirectAfterAuthUrlIsCorrect', function () { + $redirectExpectedToBe = '/'; + if (class_exists(\Devdojo\Genesis\Genesis::class)) { + $redirectExpectedToBe = '/dashboard'; + } + $this->assertPathIs($redirectExpectedToBe); + + return $this; + }); + + Browser::macro('clearLogFile', function () { + file_put_contents(storage_path('logs/laravel.log'), ''); + + return $this; + }); + + Browser::macro('getLogFile', function ($callback) { + $content = file_get_contents(storage_path('logs/laravel.log')); + $callback($content); + + return $this; + }); + } +} diff --git a/tests/Browser/LoginTest.php b/tests/Browser/LoginTest.php new file mode 100644 index 0000000..0d2ac63 --- /dev/null +++ b/tests/Browser/LoginTest.php @@ -0,0 +1,110 @@ +browse(function (Browser $browser) { + $browser->visit(new Login) + ->createJohnDoe() + ->loginAsJohnDoe(); + }); +}); + +test('Validation Error for Empty Fields', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Login) + ->authAttributeRemove('#email', 'required') + ->testValidationErrorOnSubmit('The email field is required') + ->typeAndSubmit('@email-input', 'johndoe@gmail.com') + ->waitFor('@password-input') + ->authAttributeRemove('#password', 'required') + ->testValidationErrorOnSubmit('The password field is required'); + + }); +}); + +test('Invalid Email', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Login) + ->authAttributeChange('#email', 'type', 'text') + ->type('@email-input', 'johndoe') + ->testValidationErrorOnSubmit('The email field must be a valid email address'); + }); +}); + +test('Incorrect Password', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Login) + ->createJohnDoe() + ->typeAndSubmit('@email-input', 'johndoe@gmail.com') + ->waitFor('@password-input') + ->type('@password-input', 'password123') + ->testValidationErrorOnSubmit('These credentials do not match our records'); + }); +}); + +test('Can Edit Email Address', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Login) + ->typeAndSubmit('@email-input', 'canichange@myemail.com') + ->waitFor('@edit-email-button') + ->click('@edit-email-button') + ->waitFor('@email-input') + ->typeAndSubmit('@email-input', 'yesicanchange@myemail.com') + ->waitFor('@email-read-only-placeholder') + ->assertSeeIn('@email-read-only-placeholder', 'yesicanchange@myemail.com'); + }); +}); + +test('Link to Register Page', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Login) + ->click('@register-link') + ->waitFor('@auth-register') + ->assertPathIs('/auth/register'); + + }); +}); + +test('Link to Forgot Password Page', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Login) + ->typeAndSubmit('@email-input', 'testingForgotPassword@gmail.com') + ->waitFor('@forgot-password-link') + ->click('@forgot-password-link') + ->waitFor('@auth-password-reset') + ->assertPathIs('/auth/password/reset'); + + }); +}); + +/* --------------------------------------------------------- +* Here are a few more that we'll need to add once +* we have reveal password functionality and remember me +------------------------------------------------------------ */ + +test('Verify Password Visibility Toggle', function () { + $this->browse(function (Browser $browser) { + // Test here + }); +})->todo(); + +test('Verify Remember Me Functionality', function () { + $this->browse(function (Browser $browser) { + // Test here + }); +})->todo(); + +/* --------------------------------------------------------- +* Investigate further how we can do an accessiblity check +------------------------------------------------------------ */ + +test('Verify Page Accessibility', function () { + $this->browse(function (Browser $browser) { + // Test here + }); +})->todo(); diff --git a/tests/Browser/Pages/Login.php b/tests/Browser/Pages/Login.php new file mode 100644 index 0000000..d3ad4a9 --- /dev/null +++ b/tests/Browser/Pages/Login.php @@ -0,0 +1,39 @@ +visit('/auth/login') + ->type('@email-input', 'johndoe@gmail.com') + ->click('@submit-button') + ->waitFor('@password-input') + ->type('@password-input', 'password') + ->clickAndWaitForReload('@submit-button') + ->assertRedirectAfterAuthUrlIsCorrect(); + + return $this; + } + + public function typeAndSubmit(Browser $browser, $selector, $value) + { + $browser->type($selector, $value) + ->click('@submit-button'); + + return $this; + } +} diff --git a/tests/Browser/Pages/Register.php b/tests/Browser/Pages/Register.php new file mode 100644 index 0000000..3c4a306 --- /dev/null +++ b/tests/Browser/Pages/Register.php @@ -0,0 +1,46 @@ +type('@email-input', 'johndoe@gmail.com') + ->type('@password-input', 'password') + ->clickAndWaitForReload('@submit-button'); + + return $browser; + + } + + public function assertUserReceivedEmail() + { + // Mail::fake(); + $user = \App\Models\User::where('email', 'johndoe@gmail.com')->first(); + Mail::assertSent(MailMessage::class, function ($mail) use ($user) { + return $mail->hasTo($user->email); + }); + } +} diff --git a/tests/Browser/Pages/VerifyEmail.php b/tests/Browser/Pages/VerifyEmail.php new file mode 100644 index 0000000..8da865d --- /dev/null +++ b/tests/Browser/Pages/VerifyEmail.php @@ -0,0 +1,16 @@ +browse(function (Browser $browser) { + $browser->visit(new Register) + ->registerAsJohnDoe() + ->assertRedirectAfterAuthUrlIsCorrect(); + }); +}); + +test('Validation Error for Empty Fields', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Register) + ->authAttributeRemove('#email', 'required') + ->authAttributeRemove('#password', 'required') + ->testValidationErrorOnSubmit('The email field is required') + ->assertSee('The password field is required'); + }); +}); + +test('Invalid Email Address', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Register) + ->authAttributeChange('#email', 'type', 'text') + ->authAttributeRemove('#password', 'required') + ->type('@email-input', 'johndoe') + ->testValidationErrorOnSubmit('The email field must be a valid email address'); + }); +}); + +test('Invalid Password', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Register) + ->type('@email-input', 'johndoe@gmail.com') + ->type('@password-input', 'pass') + ->testValidationErrorOnSubmit('The password field must be at least 8 characters'); + }); +}); + +test('Email already taken', function () { + $this->browse(function (Browser $browser) { + $browser + ->visit(new Register) + ->createJohnDoe() + ->type('@email-input', 'johndoe@gmail.com') + ->type('@password-input', 'password') + ->testValidationErrorOnSubmit('The email has already been taken'); + }); +}); + +test('Return to Login', function () { + $this->browse(function (Browser $browser) { + $browser->visit(new Register) + ->click('@login-link') + ->waitFor('@auth-login') + ->assertPathIs('/auth/login'); + }); +}); + +// Add more tests to test when the Name field is shown on the register page, or if the user keeps the password on a separate screen diff --git a/tests/Browser/Traits.php b/tests/Browser/Traits.php new file mode 100644 index 0000000..f1dc961 --- /dev/null +++ b/tests/Browser/Traits.php @@ -0,0 +1,13 @@ +setConfig('devdojo.auth.settings.registration_require_email_verification', true); + $browser = $this->browse(function (Browser $browser) { + $browser + ->visit(new Register) + ->disableFitOnFailure() + ->clearLogFile() + ->registerAsJohnDoe() + ->assertPathIs('/auth/verify') + ->getLogFile(function ($content) use ($browser) { + + $foundLine = $this->findLineContainingSubstring($content, 'Verify Email Address:'); + $url = str_replace('Verify Email Address: ', '', $foundLine); + $browser + ->visit($url) + ->assertRedirectAfterAuthUrlIsCorrect(); + }); + + }); + $this->resetConfig(); +}); + +test('Resend Email Verification Link', function () { + // Turn on Require Email Validation before running tests + $this->setConfig('devdojo.auth.settings.registration_require_email_verification', true); + $browser = $this->browse(function (Browser $browser) { + $browser + ->visit(new Register) + ->disableFitOnFailure() + ->registerAsJohnDoe() + ->assertPathIs('/auth/verify') + ->clearLogFile() + ->click('@verify-email-resend-link') + ->waitForText('A new link has been sent to your email address') + ->getLogFile(function ($content) use ($browser) { + $foundLine = $this->findLineContainingSubstring($content, 'Verify Email Address:'); + $url = str_replace('Verify Email Address: ', '', $foundLine); + $browser + ->visit($url) + ->assertRedirectAfterAuthUrlIsCorrect(); + }); + + }); + $this->resetConfig(); + +}); diff --git a/tests/Browser/console/.gitignore b/tests/Browser/console/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/Browser/console/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Browser/screenshots/.gitignore b/tests/Browser/screenshots/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/Browser/screenshots/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Browser/source/.gitignore b/tests/Browser/source/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/Browser/source/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php new file mode 100644 index 0000000..e2e8590 --- /dev/null +++ b/tests/DuskTestCase.php @@ -0,0 +1,68 @@ +resetConfig(); + // parent::setUp(); + // } + + /** + * Prepare for Dusk test execution. + */ + #[BeforeClass] + public static function prepare(): void + { + if (! static::runningInSail()) { + static::startChromeDriver(); + } + } + + public function findLineContainingSubstring($content, $substring) + { + $lines = explode("\n", $content); + $foundLine = current(array_filter($lines, fn ($line) => strpos($line, $substring) === 0)); + + return $foundLine ?: null; + } + + /** + * Create the RemoteWebDriver instance. + */ + protected function driver(): RemoteWebDriver + { + $chromeOptions = [ + '--disable-gpu', + '--window-size=1920,1080', + '--no-sandbox', + '--disable-dev-shm-usage', + '--disable-software-rasterizer', + ]; + + if (env('APP_ENV') != 'local') { + $chromeOptions[] = '--headless'; + } + + $options = (new ChromeOptions)->addArguments($chromeOptions); + + $options->setExperimentalOption('mobileEmulation', ['userAgent' => 'laravel/dusk']); + + return RemoteWebDriver::create( + 'http://localhost:9515', DesiredCapabilities::chrome()->setCapability( + ChromeOptions::CAPABILITY, $options + ) + ); + } +} diff --git a/tests/Pest.php b/tests/Pest.php index dba58d7..50e4fe7 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,10 @@ in('Browser'); + use Illuminate\Foundation\Testing\RefreshDatabase; /*