browser()->timeout(60_000); uses(RefreshDatabase::class); it('Spec371 smokes backup set list and detail decision hierarchy with screenshots', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); $healthyBackupSet = spec371BrowserHealthyBackupSet($tenant, [ 'name' => 'Spec 371 Browser Healthy Backup', ]); BackupSet::factory() ->for($tenant) ->degradedCompleted() ->create([ 'name' => 'Spec 371 Browser Degraded Backup', ]); spec371BrowserSourceOperation($tenant, $user, $healthyBackupSet); $listPath = spec371BrowserListPath($tenant); $detailPath = spec371BrowserViewPath($tenant, $healthyBackupSet); $page = visit(spec371BrowserLoginUrl($user, $tenant, $listPath)) ->resize(1440, 1100) ->waitForText('Backup Sets') ->waitForText('Restore-point decision') ->assertSee('Spec 371 Browser Healthy Backup') ->assertSee('Usable for restore review') ->assertSee('Spec 371 Browser Degraded Backup') ->assertSee('Action needed before restore review') ->assertSee('Items captured') ->assertDontSee('No degradations detected across') ->assertScript("document.querySelector('a[href$=\"{$detailPath}\"]') !== null", true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, spec371BrowserScreenshotName('01-backup-sets-list')); spec371BrowserCopyScreenshot('01-backup-sets-list'); $page = visit($detailPath) ->resize(1440, 1100) ->waitForText('Restore-point decision') ->assertSee('Spec 371 Browser Healthy Backup') ->assertSee('Usable for restore review') ->assertSee('Captured item inventory is available for operator review.') ->assertSee('Review included items below before starting any separate restore workflow.') ->assertSee('Included backup items') ->assertSee('Related operation and evidence') ->assertSee('Open operation') ->assertSee('Technical and lifecycle detail') ->assertDontSee('Item review state') ->assertDontSee('Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.') ->assertDontSee('This backup set has captured items ready for operator review before any separate restore workflow starts.') ->assertDontSee('Usable for review') ->assertScript("(() => { const firstSection = document.querySelector('section.fi-section'); return firstSection !== null && ! firstSection.textContent.includes('Usable for review') && ! firstSection.textContent.includes('Usability') && ! firstSection.textContent.includes('Active'); })()", true) ->assertScript("document.body.textContent.indexOf('Restore-point decision') < document.body.textContent.indexOf('Technical and lifecycle detail')", true) ->assertScript("document.body.textContent.indexOf('Technical and lifecycle detail') < document.body.textContent.indexOf('Included backup items')", true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, spec371BrowserScreenshotName('02-backup-set-detail')); spec371BrowserCopyScreenshot('02-backup-set-detail'); $page ->resize(430, 900) ->assertScript('document.documentElement.scrollWidth <= window.innerWidth', true) ->assertNoJavaScriptErrors() ->screenshot(true, spec371BrowserScreenshotName('03-backup-set-detail-mobile')); spec371BrowserCopyScreenshot('03-backup-set-detail-mobile'); }); function spec371BrowserLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect): string { return route('admin.local.smoke-login', [ 'email' => $user->email, 'tenant' => $tenant->external_id, 'workspace' => $tenant->workspace->slug, 'redirect' => $redirect, ]); } function spec371BrowserListPath(ManagedEnvironment $tenant): string { $url = BackupSetResource::getUrl('index', panel: 'admin', tenant: $tenant); return parse_url($url, PHP_URL_PATH) ?: '/admin'; } function spec371BrowserViewPath(ManagedEnvironment $tenant, BackupSet $backupSet): string { $url = BackupSetResource::getUrl('view', ['record' => $backupSet], panel: 'admin', tenant: $tenant); return parse_url($url, PHP_URL_PATH) ?: '/admin'; } /** * @param array $attributes */ function spec371BrowserHealthyBackupSet(ManagedEnvironment $tenant, array $attributes = []): BackupSet { $backupSet = BackupSet::factory() ->for($tenant) ->create(array_merge([ 'name' => 'Spec 371 Browser Healthy Backup', 'status' => 'completed', 'item_count' => 1, 'completed_at' => now()->subMinutes(30), 'metadata' => [], ], $attributes)); BackupItem::factory() ->for($tenant) ->for($backupSet) ->create([ 'payload' => ['id' => 'spec-371-browser-policy'], 'metadata' => [], 'assignments' => [], ]); return $backupSet; } function spec371BrowserSourceOperation(ManagedEnvironment $tenant, User $user, BackupSet $backupSet): OperationRun { return OperationRun::factory() ->forTenant($tenant) ->withUser($user) ->create([ 'type' => OperationRunType::BackupScheduleExecute->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'completed_at' => now()->subMinutes(20), 'context' => [ 'backup_set_id' => (int) $backupSet->getKey(), ], ]); } function spec371BrowserScreenshotName(string $name): string { return 'spec371-backup-set-productization-'.$name; } function spec371BrowserCopyScreenshot(string $name): void { $filename = spec371BrowserScreenshotName($name).'.png'; $source = base_path('tests/Browser/Screenshots/'.$filename); $targetDirectory = repo_path('specs/371-core-operator-view-surfaces-productization/artifacts/screenshots'); if (! is_dir($targetDirectory)) { @mkdir($targetDirectory, 0755, true); } if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) { return; } if (! is_file($source)) { $source = \Pest\Browser\Support\Screenshot::path($filename); } if (is_file($source)) { @copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$filename); } }