browser()->timeout(60_000); beforeEach(function (): void { Storage::fake('exports'); }); it('Spec349 smokes output resolution guidance states and collapsed disclosures', function (): void { [$user, $readyEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); $readyEnvironment->forceFill(['name' => 'Spec349 Browser Ready'])->save(); $blockedEnvironment = spec349BrowserEnvironmentFor($user, $readyEnvironment, 'Spec349 Browser Blocked'); $internalEnvironment = spec349BrowserEnvironmentFor($user, $readyEnvironment, 'Spec349 Browser Internal'); spec349BrowserCreatePublishedReviewWithPack( $readyEnvironment, $user, seedEnvironmentReviewEvidence($readyEnvironment, findingCount: 0, driftCount: 0), [], [ 'include_pii' => false, 'include_operations' => true, ], 'review-packs/spec349-browser-ready.zip', markReady: true, ); spec349BrowserCreatePublishedReviewWithPack( $blockedEnvironment, $user, seedPartialEnvironmentReviewEvidence($blockedEnvironment, findingCount: 0, driftCount: 0), [ 'governance_package' => [ 'decision_summary' => [ 'status' => 'incomplete', 'evidence_state' => EnvironmentReviewCompletenessState::Partial->value, 'decision_data_state' => 'incomplete', 'total_count' => 1, 'summary' => 'Decision evidence is incomplete for this released review.', 'next_action' => 'Review the evidence basis before relying on the decision summary.', 'entries' => [], ], ], ], [ 'include_pii' => false, 'include_operations' => true, ], 'review-packs/spec349-browser-blocked.zip', markReady: false, ); spec349BrowserCreatePublishedReviewWithPack( $internalEnvironment, $user, seedEnvironmentReviewEvidence($internalEnvironment, findingCount: 0, driftCount: 0), [], [ 'include_pii' => true, 'include_operations' => true, ], 'review-packs/spec349-browser-internal.zip', markReady: true, ); spec349AuthenticateBrowser($this, $user, $readyEnvironment); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($blockedEnvironment)) ->resize(1236, 900) ->waitForText('Output not customer-ready') ->assertSee('Inspect review blockers') ->assertSee('Download review pack with limitations') ->assertSee('The primary action opens the review detail with blockers, evidence status, and next steps.') ->assertScript('Array.from(document.querySelectorAll("[data-testid=\"customer-review-decision-card\"] [data-testid=\"customer-review-secondary-action\"]")).some((element) => element.innerText.includes("Open review")) === false', true) ->assertSee('Requires review') ->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true) ->assertScript('document.querySelector("[data-testid=\"customer-review-technical-details\"]")?.open === false', true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec349BrowserScreenshotName('01-output-blocked')); spec349CopyBrowserScreenshot('01-output-blocked'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($internalEnvironment)) ->waitForText('Internal review package available') ->assertSee('Review PII/redaction state') ->assertSee('Download internal review pack') ->assertSee('Internal only') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec349BrowserScreenshotName('02-internal-only')); spec349CopyBrowserScreenshot('02-internal-only'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment)) ->waitForText('Customer-safe review pack ready') ->assertSee('Download customer-safe review pack') ->assertDontSee('Ready to share') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec349BrowserScreenshotName('03-customer-safe-ready')); spec349CopyBrowserScreenshot('03-customer-safe-ready'); }); function spec349BrowserScreenshotName(string $name): string { return 'spec349-output-resolution-guidance-'.$name; } function spec349CopyBrowserScreenshot(string $name): void { $filename = spec349BrowserScreenshotName($name).'.png'; $source = base_path('tests/Browser/Screenshots/'.$filename); $targetDirectory = repo_path('specs/349-customer-review-workspace-output-resolution-guidance/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.$name.'.png'); } } function spec349AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void { $workspaceId = (int) $environment->workspace_id; $test->actingAs($user)->withSession([ WorkspaceContext::SESSION_KEY => $workspaceId, WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [ (string) $workspaceId => (int) $environment->getKey(), ], ]); session()->put(WorkspaceContext::SESSION_KEY, $workspaceId); session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [ (string) $workspaceId => (int) $environment->getKey(), ]); setAdminPanelContext($environment); } function spec349BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnvironment, string $name): ManagedEnvironment { $environment = ManagedEnvironment::factory()->active()->create([ 'workspace_id' => (int) $baseEnvironment->workspace_id, 'name' => $name, ]); createUserWithTenant(tenant: $environment, user: $user, role: 'owner', workspaceRole: 'manager'); return $environment; } /** * @param array $summaryOverrides * @param array $packOptions * @return array{0: EnvironmentReview, 1: ReviewPack} */ function spec349BrowserCreatePublishedReviewWithPack( ManagedEnvironment $environment, User $user, EvidenceSnapshot $snapshot, array $summaryOverrides = [], array $packOptions = [], string $filePath = 'review-packs/spec349-browser-review-pack.zip', bool $markReady = true, ): array { $review = composeEnvironmentReviewForTest($environment, $user, $snapshot); $summary = array_replace_recursive( is_array($review->summary) ? $review->summary : [], [ 'control_interpretation' => [ 'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY, 'controls' => [ [ 'control_key' => 'customer-output', 'title' => 'Customer output', 'readiness_bucket' => $markReady ? 'evidence_on_record' : 'review_recommended', 'readiness_label' => $markReady ? 'Evidence on record' : 'Review recommended', 'primary_reason' => $markReady ? 'Evidence path is complete.' : 'Evidence basis needs review.', 'recommended_next_action' => $markReady ? 'Open the current customer review pack.' : 'Review the evidence basis before sharing.', ], ], ], 'governance_package' => [ 'decision_summary' => [ 'status' => $markReady ? 'none' : 'incomplete', 'evidence_state' => $markReady ? EnvironmentReviewCompletenessState::Complete->value : EnvironmentReviewCompletenessState::Partial->value, 'decision_data_state' => $markReady ? 'complete' : 'incomplete', 'total_count' => $markReady ? 0 : 1, 'summary' => $markReady ? 'No governance decisions require customer awareness.' : 'Decision evidence is incomplete for this released review.', 'next_action' => $markReady ? 'Open the current customer review pack.' : 'Review the evidence basis before relying on the decision summary.', 'entries' => [], ], ], ], $summaryOverrides, ); Storage::disk('exports')->put($filePath, 'PK-spec349-browser-test'); $review->forceFill([ 'status' => EnvironmentReviewStatus::Published->value, 'completeness_state' => $markReady ? EnvironmentReviewCompletenessState::Complete->value : (string) $review->completeness_state, 'summary' => $summary, 'generated_at' => now()->subMinutes(5), 'published_at' => now()->subMinutes(3), 'published_by_user_id' => (int) $user->getKey(), ])->save(); if ($markReady) { $review = markEnvironmentReviewCustomerSafeReady($review); } $pack = ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $environment->getKey(), 'workspace_id' => (int) $environment->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'options' => array_replace([ 'include_pii' => false, 'include_operations' => true, ], $packOptions), 'file_path' => $filePath, 'file_disk' => 'exports', 'generated_at' => now()->subMinutes(4), ]); $review->forceFill([ 'current_export_review_pack_id' => (int) $pack->getKey(), ])->save(); return [$review->refresh(), $pack->refresh()]; }