browser()->timeout(60_000); uses(RefreshDatabase::class); beforeEach(function (): void { Storage::fake('exports'); }); it('Spec372 smokes customer and auditor review output surfaces with screenshots', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); $tenant->forceFill(['name' => 'Spec372 Browser Environment'])->save(); [ 'review' => $review, 'snapshot' => $snapshot, 'pack' => $pack, 'report' => $report, ] = spec372BrowserFixture($tenant, $user); $workspacePath = spec372BrowserPath(CustomerReviewWorkspace::environmentFilterUrl($tenant)); $reviewPath = spec372BrowserPath(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant)); $packPath = spec372BrowserPath(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'admin')); $reportPath = spec372BrowserPath(StoredReportResource::getUrl('view', ['record' => $report], tenant: $tenant, panel: 'admin')); $evidencePath = spec372BrowserPath(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin')); $page = visit(spec372BrowserLoginUrl($user, $tenant, $workspacePath)) ->resize(1440, 1100) ->waitForText('Customer Review Workspace') ->assertSee('Evidence path') ->assertSee('Review pack state') ->assertDontSee('Operation proof') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, '001-customer-review-workspace-after'); spec372BrowserCopyScreenshot('001-customer-review-workspace-after'); $page = visit($reviewPath) ->resize(1440, 1100) ->waitForText('Outcome summary') ->assertSee('Output guidance') ->assertSee('Executive posture') ->assertSee('Evidence basis') ->assertSee('Technical details') ->assertScript("document.body.textContent.indexOf('Output guidance') < document.body.textContent.indexOf('Technical details')", true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, '002-environment-review-view-after'); spec372BrowserCopyScreenshot('002-environment-review-view-after'); $page = visit($packPath) ->resize(1440, 1100) ->waitForText('Outcome summary') ->assertSee('Output guidance') ->assertSee('Pack readiness and contents') ->assertSee('Technical pack details') ->assertScript("document.body.textContent.indexOf('Pack readiness and contents') < document.body.textContent.indexOf('Technical pack details')", true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, '003-review-pack-view-after'); spec372BrowserCopyScreenshot('003-review-pack-view-after'); $page = visit($reportPath) ->resize(1440, 1100) ->waitForText('Outcome summary') ->assertSee('Report scope and readiness') ->assertSee('Permission posture summary') ->assertSee('Technical report details') ->assertScript("document.body.textContent.indexOf('Permission posture summary') < document.body.textContent.indexOf('Technical report details')", true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, '004-stored-report-view-after'); spec372BrowserCopyScreenshot('004-stored-report-view-after'); $page = visit($evidencePath) ->resize(1440, 1100) ->waitForText('Outcome summary') ->assertSee('Evidence basis and readiness') ->assertSee('Related review and report context') ->assertSee('Technical evidence details') ->assertSee('Evidence dimensions') ->assertScript("document.body.textContent.indexOf('Related review and report context') < document.body.textContent.indexOf('Technical evidence details')", true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, '005-evidence-snapshot-view-after-or-blocked'); spec372BrowserCopyScreenshot('005-evidence-snapshot-view-after-or-blocked'); $page ->resize(430, 900) ->assertScript('document.documentElement.scrollWidth <= window.innerWidth', true) ->assertNoJavaScriptErrors() ->screenshot(true, '006-evidence-snapshot-view-mobile'); spec372BrowserCopyScreenshot('006-evidence-snapshot-view-mobile'); }); /** * @return array{review: EnvironmentReview, snapshot: EvidenceSnapshot, pack: ReviewPack, report: StoredReport, run: OperationRun} */ function spec372BrowserFixture(ManagedEnvironment $tenant, User $user): array { $snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 1, driftCount: 0); $run = OperationRun::factory() ->forTenant($tenant) ->withUser($user) ->create([ 'type' => OperationRunType::ReviewPackGenerate->value, 'status' => OperationRunStatus::Completed->value, 'initiator_name' => 'Spec372 Browser Operator', ]); $review = composeEnvironmentReviewForTest($tenant, $user, $snapshot); $review->forceFill([ 'status' => EnvironmentReviewStatus::Published->value, 'generated_at' => now()->subHours(2), 'published_at' => now()->subHour(), 'published_by_user_id' => (int) $user->getKey(), 'operation_run_id' => (int) $run->getKey(), ])->save(); $review = markEnvironmentReviewCustomerSafeReady($review); Storage::disk('exports')->put('review-packs/spec372-browser-review-pack.zip', 'PK-spec372-browser'); $pack = ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'operation_run_id' => (int) $run->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'file_path' => 'review-packs/spec372-browser-review-pack.zip', 'file_disk' => 'exports', 'summary' => [ 'finding_count' => 1, 'report_count' => 2, 'operation_count' => 1, 'evidence_resolution' => ['outcome' => 'complete'], ], 'options' => [ 'include_pii' => false, 'include_operations' => true, ], ]); $review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save(); $report = StoredReport::factory() ->permissionPosture([ 'posture_score' => 78, 'required_count' => 4, 'granted_count' => 3, 'permissions' => [ ['key' => 'DeviceManagementConfiguration.Read.All', 'status' => 'granted'], ['key' => 'DeviceManagementApps.ReadWrite.All', 'status' => 'missing'], ], ]) ->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, ]); $snapshot->forceFill([ 'operation_run_id' => (int) $run->getKey(), 'summary' => array_replace(is_array($snapshot->summary) ? $snapshot->summary : [], [ 'finding_count' => 1, 'report_count' => 2, 'operation_count' => 1, 'missing_dimensions' => 0, 'stale_dimensions' => 0, ]), ])->save(); EvidenceSnapshotItem::query()->updateOrCreate( [ 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'dimension_key' => 'permission_posture', ], [ 'workspace_id' => (int) $snapshot->workspace_id, 'managed_environment_id' => (int) $snapshot->managed_environment_id, 'state' => EvidenceCompletenessState::Complete->value, 'required' => true, 'source_kind' => 'stored_report', 'source_record_type' => 'stored_report', 'source_record_id' => (string) $report->getKey(), 'measured_at' => now(), 'freshness_at' => now(), 'summary_payload' => [ 'required_count' => 4, 'granted_count' => 3, 'posture_score' => 78, 'payload' => [ 'missing_permissions' => ['DeviceManagementApps.ReadWrite.All'], ], ], 'sort_order' => 10, ], ); return [ 'review' => $review->refresh(), 'snapshot' => $snapshot->refresh(), 'pack' => $pack->refresh(), 'report' => $report->refresh(), 'run' => $run, ]; } function spec372BrowserLoginUrl(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 spec372BrowserPath(string $url): string { $path = parse_url($url, PHP_URL_PATH) ?: '/admin'; $query = parse_url($url, PHP_URL_QUERY); return is_string($query) && $query !== '' ? $path.'?'.$query : $path; } function spec372BrowserCopyScreenshot(string $name): void { $filename = $name.'.png'; $source = base_path('tests/Browser/Screenshots/'.$filename); $targetDirectory = repo_path('specs/372-customer-auditor-surface-safety-pass/artifacts/screenshots'); if (! is_dir($targetDirectory)) { @mkdir($targetDirectory, 0755, true); } if (! is_file($source)) { $source = \Pest\Browser\Support\Screenshot::path($filename); } for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) { usleep(100_000); clearstatcache(true, $source); } if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) { @copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$filename); } }