browser()->timeout(60_000); uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); function spec332RestoreWizardScreenshot(string $name): string { return 'spec332-restore-create-'.$name; } function spec332CopyBrowserScreenshot(string $name, ?string $targetFilename = null): void { $filename = spec332RestoreWizardScreenshot($name).'.png'; $source = base_path('tests/Browser/Screenshots/'.$filename); $targetDirectory = repo_path('specs/332-product-process-flow-system-v1/artifacts/screenshots'); $targetFilename ??= $filename; if (is_dir($targetDirectory) && ! is_writable($targetDirectory)) { return; } 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.$targetFilename); } } function spec332RestoreWizardScreenshotsLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect = ''): string { return route('admin.local.smoke-login', array_filter([ 'email' => $user->email, 'tenant' => $tenant->external_id, 'workspace' => $tenant->workspace->slug, 'redirect' => $redirect, ], static fn (?string $value): bool => filled($value))); } function spec332ScreenshotsTenant(): array { $tenant = ManagedEnvironment::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ]); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); ensureDefaultProviderConnection($tenant, 'microsoft'); bindFailHardGraphClient(); return [$user, $tenant]; } function spec332ScreenshotsRedirect(ManagedEnvironment $tenant): string { $redirectBase = RestoreRunResource::getUrl('create', panel: 'admin', tenant: $tenant); return parse_url($redirectBase, PHP_URL_PATH) ?: '/admin'; } function spec332ScreenshotsSelectBackupSet($page, BackupSet $backupSet): void { $selected = $page->script(<< { const select = document.getElementById('form.backup_set_id'); if (! select) { return false; } select.value = '{$backupSet->getKey()}'; select.dispatchEvent(new Event('input', { bubbles: true })); select.dispatchEvent(new Event('change', { bubbles: true })); return true; })() JS); expect($selected)->toBeTrue(); } function spec332ScreenshotsWizardNext($page): void { $clicked = $page->script(<<<'JS' (() => { const footer = document.querySelector('.fi-sc-wizard-footer'); if (! footer) { return false; } const nextTrigger = footer.querySelector('div[x-on\\:click*="requestNextStep"]'); if (! nextTrigger) { return false; } if (nextTrigger.classList.contains('fi-hidden')) { return false; } nextTrigger.scrollIntoView({ block: 'center' }); nextTrigger.click(); return true; })() JS); expect($clicked)->toBeTrue(); } function spec332ScreenshotsUsableBackupFixture(ManagedEnvironment $tenant): BackupSet { $policy = Policy::create([ 'managed_environment_id' => (int) $tenant->getKey(), 'external_id' => 'spec332-screenshots-policy-usable', 'policy_type' => 'deviceConfiguration', 'display_name' => 'Spec332 Screenshots Policy', 'platform' => 'windows', ]); PolicyVersion::create([ 'managed_environment_id' => (int) $tenant->getKey(), 'policy_id' => (int) $policy->getKey(), 'version_number' => 1, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'captured_at' => now(), 'snapshot' => [ 'foo' => 'current', ], 'metadata' => [], 'assignments' => [], 'scope_tags' => [], ]); $backupSet = BackupSet::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'name' => 'Spec332 Screenshots Usable Backup', 'status' => 'completed', 'item_count' => 1, ]); BackupItem::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'backup_set_id' => (int) $backupSet->getKey(), 'policy_id' => (int) $policy->getKey(), 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'captured_at' => now(), 'payload' => [ 'foo' => 'backup', 'displayName' => 'Spec332 Screenshots Policy', ], 'assignments' => [], 'metadata' => [ 'displayName' => 'Spec332 Screenshots Policy', ], ]); return $backupSet; } function spec332ScreenshotsUnresolvedGroupBackupFixture(ManagedEnvironment $tenant): BackupSet { $policy = Policy::create([ 'managed_environment_id' => (int) $tenant->getKey(), 'external_id' => 'spec332-screenshots-policy-group', 'policy_type' => 'deviceConfiguration', 'display_name' => 'Spec332 Screenshots Group Mapping Policy', 'platform' => 'windows', ]); $backupSet = BackupSet::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'name' => 'Spec332 Screenshots Group Backup', 'status' => 'completed', 'item_count' => 1, ]); BackupItem::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'backup_set_id' => (int) $backupSet->getKey(), 'policy_id' => (int) $policy->getKey(), 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'captured_at' => now(), 'payload' => [ 'foo' => 'backup', 'displayName' => 'Spec332 Screenshots Group Mapping Policy', ], 'assignments' => [[ 'target' => [ '@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => '11111111-1111-1111-1111-111111111111', 'group_display_name' => 'Spec332 Missing Group', ], ]], 'metadata' => [ 'displayName' => 'Spec332 Screenshots Group Mapping Policy', ], ]); return $backupSet; } function spec332ScreenshotsMetadataOnlyFixture(ManagedEnvironment $tenant): BackupSet { $policy = Policy::create([ 'managed_environment_id' => (int) $tenant->getKey(), 'external_id' => 'spec332-screenshots-policy-metadata-only', 'policy_type' => 'deviceConfiguration', 'display_name' => 'Spec332 Screenshots Metadata Only Policy', 'platform' => 'windows', ]); $backupSet = BackupSet::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'name' => 'Spec332 Screenshots Metadata-only Backup', 'status' => 'completed', 'item_count' => 1, ]); BackupItem::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'backup_set_id' => (int) $backupSet->getKey(), 'policy_id' => (int) $policy->getKey(), 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'captured_at' => now(), 'payload' => [], 'assignments' => [], 'metadata' => [ 'displayName' => 'Spec332 Screenshots Metadata Only Policy', 'snapshot_source' => 'metadata_only', 'warnings' => ['metadata only fallback'], ], ]); return $backupSet; } it('captures step 1 screenshot with a degraded backup selected', function (): void { [$user, $tenant] = spec332ScreenshotsTenant(); $backupSet = spec332ScreenshotsMetadataOnlyFixture($tenant); $page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant))); $page->resize(1920, 1200) ->waitForText('Select Backup Set'); spec332ScreenshotsSelectBackupSet($page, $backupSet); $page->waitForText('The selected backup does not contain a usable captured item yet.'); $page->script('window.scrollTo(0, 0);'); $page->screenshot(true, spec332RestoreWizardScreenshot('step-1-backup-selected')); spec332CopyBrowserScreenshot('step-1-backup-selected', 'step-1-backup-selected.png'); }); it('captures step 2 screenshots for mapping summary + resolver expansion', function (): void { [$user, $tenant] = spec332ScreenshotsTenant(); $backupSet = spec332ScreenshotsUnresolvedGroupBackupFixture($tenant); EntraGroup::factory()->for($tenant)->create([ 'entra_id' => 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'display_name' => 'Spec332 Cached Target Group', 'last_seen_at' => now(), ]); $page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant))); $page->resize(1920, 1200) ->waitForText('Select Backup Set'); spec332ScreenshotsSelectBackupSet($page, $backupSet); $page->waitForText('Resolve 1 remaining group mapping before validation can prove the current draft.'); spec332ScreenshotsWizardNext($page); $page->waitForText('Resolve target mappings'); $page->script('window.scrollTo(0, 0);'); $page->screenshot(true, spec332RestoreWizardScreenshot('step-2-scope-default')); spec332CopyBrowserScreenshot('step-2-scope-default', 'step-2-scope-default.png'); $page->click('[data-testid="restore-run-open-mapping-resolver"]') ->waitForText('Resolve mappings'); $page->script('window.scrollTo(0, 0);'); $page->screenshot(true, spec332RestoreWizardScreenshot('step-2-resolver-expanded')); spec332CopyBrowserScreenshot('step-2-resolver-expanded', 'step-2-resolver-expanded.png'); }); it('captures step 3 screenshot when validation is blocked', function (): void { [$user, $tenant] = spec332ScreenshotsTenant(); $backupSet = spec332ScreenshotsMetadataOnlyFixture($tenant); $page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant))); $page->resize(1920, 1200) ->waitForText('Select Backup Set'); spec332ScreenshotsSelectBackupSet($page, $backupSet); $page->waitForText('The selected backup does not contain a usable captured item yet.'); spec332ScreenshotsWizardNext($page); $page->waitForText('Define Restore Scope'); spec332ScreenshotsWizardNext($page); $page->waitForText('Safety & Conflict Checks'); $page->waitForText('Run checks') ->click('Run checks') ->waitForText('Snapshot completeness'); spec332ScreenshotsWizardNext($page); $page->waitForText('Validation blocked'); $page->script('window.scrollTo(0, 0);'); $page->screenshot(true, spec332RestoreWizardScreenshot('step-3-validation-blocked')); spec332CopyBrowserScreenshot('step-3-validation-blocked', 'step-3-validation-blocked.png'); }); it('captures step 4 and 5 screenshots after preview generation', function (): void { [$user, $tenant] = spec332ScreenshotsTenant(); $backupSet = spec332ScreenshotsUsableBackupFixture($tenant); $page = visit(spec332RestoreWizardScreenshotsLoginUrl($user, $tenant, spec332ScreenshotsRedirect($tenant))); $page->resize(1920, 1200) ->waitForText('Select Backup Set'); spec332ScreenshotsSelectBackupSet($page, $backupSet); $page->waitForText('A usable source backup is selected for this restore draft.'); spec332ScreenshotsWizardNext($page); $page->waitForText('Define Restore Scope'); spec332ScreenshotsWizardNext($page); $page->waitForText('Safety & Conflict Checks'); $page->waitForText('Run checks') ->click('Run checks') ->waitForText('No group-based assignments detected.'); spec332ScreenshotsWizardNext($page); $page->waitForText('Generate preview') ->click('Generate preview') ->waitForText('Policy change preview'); $page->script('window.scrollTo(0, 0);'); $page->screenshot(true, spec332RestoreWizardScreenshot('step-4-preview-generated')); spec332CopyBrowserScreenshot('step-4-preview-generated', 'step-4-preview-generated.png'); spec332ScreenshotsWizardNext($page); $page->waitForText('Confirm & Execute'); $page->script('window.scrollTo(0, 0);'); $page->screenshot(true, spec332RestoreWizardScreenshot('step-5-confirm-ready')); spec332CopyBrowserScreenshot('step-5-confirm-ready', 'step-5-confirm-ready.png'); });