browser()->timeout(15_000); beforeEach(function () { // The session pages sit behind session.database, and the users row menu // hides its first item without it. Set once here so every capture agrees. config(['session.driver' => 'database']); $this->artisan('db:seed'); $this->artisan('db:seed', ['--class' => AnnouncementsDatabaseSeeder::class]); $this->setSettings([ 'reg_enabled' => true, 'registration.captcha.enabled' => false, 'forgot_password' => true, 'tos' => true, '2fa.enabled' => true, ]); seedGalleryUsers(); }); afterAll(function () { Gallery::collect(); }); /** * A fixed cast, so tables and the dashboard look populated without the names, * emails or dates changing between runs. */ function seedGalleryUsers(): void { $people = [ ['Ada', 'Lovelace', 'ada@vanguard.test', 'ada'], ['Grace', 'Hopper', 'grace@vanguard.test', 'grace'], ['Alan', 'Turing', 'alan@vanguard.test', 'alan'], ['Katherine', 'Johnson', 'katherine@vanguard.test', 'katherine'], ]; foreach ($people as $index => [$first, $last, $email, $username]) { User::factory()->create([ 'first_name' => $first, 'last_name' => $last, 'email' => $email, 'username' => $username, 'phone' => '+1 555 0100', 'address' => '1 Analytical Engine Way', 'birthday' => '1950-01-01', 'password' => 'password', 'country_id' => null, 'created_at' => Carbon::parse('2026-01-0'.($index + 1).' 09:00:00'), ]); } } it('captures the login page', function (string $device, string $viewport) { visit('/login')->on()->{$device}() ->assertSee('Login') ->screenshot(true, Gallery::filename('login', $viewport)); })->with('viewports'); it('captures the registration page', function (string $device, string $viewport) { visit('/register')->on()->{$device}() ->assertSee('Register') ->screenshot(true, Gallery::filename('register', $viewport)); })->with('viewports'); it('captures the forgot password page', function (string $device, string $viewport) { visit('/password/reset')->on()->{$device}() ->assertSee('Forgot Your Password?') ->screenshot(true, Gallery::filename('password-forgot', $viewport)); })->with('viewports'); it('captures the reset password page', function (string $device, string $viewport) { $token = Password::broker()->createToken( User::where('username', 'ada')->firstOrFail() ); visit("/password/reset/{$token}?email=ada@vanguard.test")->on()->{$device}() ->assertSee('Reset Your Password') ->screenshot(true, Gallery::filename('password-reset', $viewport)); })->with('viewports'); it('captures the terms of service dialog', function (string $device, string $viewport) { visit('/register')->on()->{$device}() ->assertSee('I accept') ->click('Terms of Service') ->assertSee('Close') ->screenshot(true, Gallery::filename('dialog-terms', $viewport)); })->with('viewports'); it('captures registration validation errors', function (string $device, string $viewport) { // A malformed address would be rejected by the browser's own type="email" // check and never reach Laravel, so this submits a well formed one that is // already taken. visit('/register')->on()->{$device}() ->assertSee('Register') ->fill('#email', 'ada@vanguard.test') ->fill('#username', 'ada') ->click('button[type="submit"]') ->assertSee('The email has already been taken.') ->screenshot(true, Gallery::filename('state-validation-errors', $viewport)); })->with('viewports'); it('captures the dashboard', function (string $device, string $viewport) { visualLogin(); visit('/')->on()->{$device}() ->assertSee('Total Users') ->screenshot(true, Gallery::filename('dashboard', $viewport)); })->with('viewports'); it('captures the users index', function (string $device, string $viewport) { visualLogin(); visit('/users')->on()->{$device}() ->assertSee('Registration Date') ->assertSee('ada@vanguard.test') ->screenshot(true, Gallery::filename('users-index', $viewport)); })->with('viewports'); it('captures the open row menu', function (string $device, string $viewport) { visualLogin(); visit('/users')->on()->{$device}() ->assertSee('ada@vanguard.test') ->click('tr:has-text("ada@vanguard.test") [aria-label="Action"]') ->assertSee('View User') ->screenshot(true, Gallery::filename('state-row-menu', $viewport)); })->with('viewports'); it('captures the delete confirmation dialog', function (string $device, string $viewport) { visualLogin(); visit('/users')->on()->{$device}() ->assertSee('ada@vanguard.test') ->click('tr:has-text("ada@vanguard.test") [aria-label="Delete User"]') ->assertSee('Are you sure that you want to delete this user?') ->screenshot(true, Gallery::filename('dialog-delete', $viewport)); })->with('viewports'); it('captures the create user page', function (string $device, string $viewport) { visualLogin(); visit('/users/create')->on()->{$device}() ->assertSee('Create New User') ->screenshot(true, Gallery::filename('users-create', $viewport)); })->with('viewports'); it('captures the user detail page', function (string $device, string $viewport) { visualLogin(); $user = User::where('username', 'ada')->firstOrFail(); visit("/users/{$user->id}")->on()->{$device}() ->assertSee('ada@vanguard.test') ->screenshot(true, Gallery::filename('users-show', $viewport)); })->with('viewports'); it('captures the edit user page', function (string $device, string $viewport) { visualLogin(); $user = User::where('username', 'ada')->firstOrFail(); visit("/users/{$user->id}/edit")->on()->{$device}() ->assertSee('User Details') ->screenshot(true, Gallery::filename('users-edit', $viewport)); })->with('viewports'); it('captures the user sessions page', function (string $device, string $viewport) { visualLogin(); $user = User::where('username', 'ada')->firstOrFail(); DB::table('sessions')->insert([ 'id' => Str::random(), 'user_id' => $user->id, 'ip_address' => '203.0.113.7', 'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15', 'payload' => 'foo', 'last_activity' => Carbon::now()->subMinutes(12)->timestamp, ]); visit("/users/{$user->id}/sessions")->on()->{$device}() ->assertSee('203.0.113.7') ->screenshot(true, Gallery::filename('users-sessions', $viewport)); })->with('viewports'); it('captures the profile details tab', function (string $device, string $viewport) { visualLogin(); visit('/profile')->on()->{$device}() ->assertSee('My Profile') ->screenshot(true, Gallery::filename('profile-details', $viewport)); })->with('viewports'); it('captures the profile login details tab', function (string $device, string $viewport) { visualLogin(); visit('/profile')->on()->{$device}() ->assertSee('My Profile') ->click('Login Details') ->assertSee('New Password') ->screenshot(true, Gallery::filename('profile-login', $viewport)); })->with('viewports'); it('captures the roles index', function (string $device, string $viewport) { visualLogin(); visit('/roles')->on()->{$device}() ->assertSee('Add Role') ->screenshot(true, Gallery::filename('roles-index', $viewport)); })->with('viewports'); it('captures the role form', function (string $device, string $viewport) { visualLogin(); visit('/roles/create')->on()->{$device}() ->assertSee('Create New Role') ->screenshot(true, Gallery::filename('roles-create', $viewport)); })->with('viewports'); it('captures the permission matrix', function (string $device, string $viewport) { visualLogin(); visit('/permissions')->on()->{$device}() ->assertSee('Save Permissions') ->screenshot(true, Gallery::filename('permissions-index', $viewport)); })->with('viewports'); it('captures the permission form', function (string $device, string $viewport) { visualLogin(); visit('/permissions/create')->on()->{$device}() ->assertSee('Create New Permission') ->screenshot(true, Gallery::filename('permissions-create', $viewport)); })->with('viewports'); it('captures the general settings page', function (string $device, string $viewport) { visualLogin(); visit('/settings')->on()->{$device}() ->assertSee('General Settings') ->screenshot(true, Gallery::filename('settings-general', $viewport)); })->with('viewports'); it('captures a success flash', function (string $device, string $viewport) { visualLogin(); visit('/settings')->on()->{$device}() ->assertSee('General Settings') ->click('Update') ->assertSee('Settings updated successfully.') ->screenshot(true, Gallery::filename('state-success-flash', $viewport)); })->with('viewports'); it('captures the authentication settings tab', function (string $device, string $viewport) { visualLogin(); visit('/settings/auth')->on()->{$device}() ->assertSee('Authentication Throttling') ->screenshot(true, Gallery::filename('settings-auth', $viewport)); })->with('viewports'); it('captures the registration settings tab', function (string $device, string $viewport) { visualLogin(); visit('/settings/auth')->on()->{$device}() ->assertSee('Authentication Throttling') ->click('Registration') ->assertSee('Allow Registration') ->screenshot(true, Gallery::filename('settings-registration', $viewport)); })->with('viewports'); it('captures the notification settings page', function (string $device, string $viewport) { visualLogin(); visit('/settings/notifications')->on()->{$device}() ->assertSee('Sign-Up Notification') ->screenshot(true, Gallery::filename('settings-notifications', $viewport)); })->with('viewports'); it('captures the announcements page', function (string $device, string $viewport) { visualLogin(); visit('/announcements')->on()->{$device}() ->assertSee('Create Announcement') ->screenshot(true, Gallery::filename('plugin-announcements', $viewport)); })->with('viewports'); it('captures the announcement form', function (string $device, string $viewport) { visualLogin(); visit('/announcements/create')->on()->{$device}() ->assertSee('New Announcement') ->screenshot(true, Gallery::filename('plugin-announcement-form', $viewport)); })->with('viewports'); it('captures the activity log', function (string $device, string $viewport) { visualLogin(); visit('/activity')->on()->{$device}() ->assertSee('Search for Action') ->screenshot(true, Gallery::filename('plugin-activity', $viewport)); })->with('viewports'); it('captures the approval notice', function (string $device, string $viewport) { visualLogin(); visit('/approval-notice')->on()->{$device}() ->assertSee('Waiting Approval') ->screenshot(true, Gallery::filename('gate-approval', $viewport)); })->with('viewports'); it('captures the verify email notice', function (string $device, string $viewport) { $user = User::where('username', 'ada')->firstOrFail(); $user->forceFill(['email_verified_at' => null])->save(); visit('/login') ->fill('#username', 'ada') ->fill('#password', 'password') ->click('Log In') ->assertSee('Verify Your Email Address'); visit('/email/verify')->on()->{$device}() ->assertSee('Verify Your Email Address') ->screenshot(true, Gallery::filename('gate-verify-email', $viewport)); })->with('viewports');