browser()->timeout(20_000); uses(RefreshDatabase::class); function operationActivityFeedbackSmokeLoginUrl(User $user, Tenant $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))); } it('keeps findings row actions reachable while the activity hint collapses and reopens within the browser session', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); InventoryItem::factory()->count(3)->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'display_name' => 'Browser Inventory Item', 'policy_type' => 'deviceConfiguration', 'platform' => 'windows', 'last_seen_at' => now()->subMinute(), ]); Finding::factory()->count(5)->for($tenant)->create([ 'workspace_id' => (int) $tenant->workspace_id, 'status' => Finding::STATUS_NEW, ]); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'type' => 'inventory_sync', 'status' => 'running', 'outcome' => 'pending', 'started_at' => now()->subMinutes(2), ]); visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant)) ->waitForText('Dashboard') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $inventoryPage = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant)) ->resize(1440, 1200) ->assertScript('window.innerWidth >= 1400', true) ->waitForText('Inventory Items') ->waitForText('Browser Inventory Item') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $shellGeometry = $inventoryPage->script(<<<'JS' (() => { const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); const topbar = document.querySelector('.fi-topbar'); const table = document.querySelector('.fi-ta'); const tableContainer = table?.closest('.overflow-x-auto') ?? table?.parentElement ?? null; const contentShell = document.querySelector('.fi-page') ?? document.querySelector('.fi-main') ?? tableContainer?.parentElement ?? null; const main = document.querySelector('.fi-main'); const header = banner?.querySelector('.tp-ops-activity-header') ?? null; const actionGroup = document.querySelector('[data-testid="ops-ux-activity-feedback-actions"]'); const layout = banner?.querySelector('.tp-ops-activity-layout') ?? null; const headerCopy = banner?.querySelector('.tp-ops-activity-header-copy') ?? null; const title = banner?.querySelector('[data-testid="ops-ux-activity-feedback-title"]') ?? null; const middleColumn = banner?.querySelector('.tp-ops-activity-summary') ?? null; const track = banner?.querySelector('[data-testid="ops-ux-activity-feedback-track"]') ?? null; if (!banner || !topbar || !tableContainer || !contentShell || !actionGroup || !layout || !header || !headerCopy || !title || !middleColumn || !track) { return null; } const bannerRect = banner.getBoundingClientRect(); const topbarRect = topbar.getBoundingClientRect(); const tableRect = tableContainer.getBoundingClientRect(); const contentShellRect = contentShell.getBoundingClientRect(); const headerRect = header.getBoundingClientRect(); const actionGroupRect = actionGroup.getBoundingClientRect(); const headerCopyRect = headerCopy.getBoundingClientRect(); const titleRect = title.getBoundingClientRect(); const middleRect = middleColumn.getBoundingClientRect(); const trackRect = track.getBoundingClientRect(); const layoutStyle = window.getComputedStyle(layout); const headerStyle = window.getComputedStyle(header); const titleStyle = window.getComputedStyle(title); const titleLineHeight = Number.parseFloat(titleStyle.lineHeight || '0'); return { viewportWidth: window.innerWidth, itemCount: banner.querySelectorAll('[data-testid="ops-ux-activity-feedback-item"]').length, stylesheetCount: document.styleSheets.length, hasThemeStylesheet: Array.from(document.styleSheets).some((sheet) => (sheet.href || '').includes('/build/assets/theme-')), mainClassName: main?.className ?? '', mainWidth: main?.getBoundingClientRect().width ?? 0, layoutClassName: layout.className, layoutDisplay: layoutStyle.display, layoutFlexDirection: layoutStyle.flexDirection, headerFlexDirection: headerStyle.flexDirection, bannerTop: bannerRect.top, topbarBottom: topbarRect.bottom, bannerTopGap: bannerRect.top - topbarRect.bottom, bannerHeight: bannerRect.height, bannerWidth: bannerRect.width, tableWidth: tableRect.width, contentWidth: contentShellRect.width, leftDelta: Math.abs(bannerRect.left - contentShellRect.left), rightDelta: Math.abs(bannerRect.right - contentShellRect.right), actionGroupRightDelta: Math.abs(bannerRect.right - actionGroupRect.right), summaryTopDelta: middleRect.top - headerRect.bottom, trackSpanRatio: middleRect.width > 0 ? trackRect.width / middleRect.width : 0, titleOverflows: title.scrollWidth > (title.clientWidth + 1), titleLineCount: titleLineHeight > 0 ? titleRect.height / titleLineHeight : 0, headerCopyWidth: headerCopyRect.width, middleHeight: middleRect.height, actionHeight: actionGroupRect.height, }; })() JS); expect($shellGeometry)->not->toBeNull() ->and($shellGeometry['viewportWidth'] ?? 0)->toBeGreaterThanOrEqual(1400) ->and($shellGeometry['itemCount'] ?? 0)->toBe(1) ->and($shellGeometry['stylesheetCount'] ?? 0)->toBeGreaterThan(0) ->and($shellGeometry['hasThemeStylesheet'] ?? false)->toBeTrue() ->and($shellGeometry['mainClassName'] ?? '')->toContain('fi-width-full') ->and($shellGeometry['mainWidth'] ?? 0)->toBeGreaterThanOrEqual(900) ->and($shellGeometry['layoutClassName'] ?? '')->toContain('tp-ops-activity-layout') ->and($shellGeometry['layoutClassName'] ?? '')->toContain('flex') ->and($shellGeometry['layoutDisplay'] ?? '')->toBe('flex') ->and($shellGeometry['layoutFlexDirection'] ?? '')->toBe('column') ->and($shellGeometry['headerFlexDirection'] ?? '')->toBe('row') ->and($shellGeometry['bannerTop'] ?? null)->toBeGreaterThanOrEqual($shellGeometry['topbarBottom'] ?? PHP_INT_MAX) ->and($shellGeometry['bannerTopGap'] ?? PHP_INT_MIN)->toBeGreaterThanOrEqual(12) ->and($shellGeometry['bannerWidth'] ?? 0)->toBeGreaterThan(0) ->and($shellGeometry['tableWidth'] ?? 0)->toBeGreaterThan(0) ->and($shellGeometry['contentWidth'] ?? 0)->toBeGreaterThan(0) ->and($shellGeometry['summaryTopDelta'] ?? PHP_INT_MIN)->toBeGreaterThanOrEqual(8) ->and($shellGeometry['trackSpanRatio'] ?? 0)->toBeGreaterThanOrEqual(0.72) ->and($shellGeometry['titleOverflows'] ?? true)->toBeFalse() ->and($shellGeometry['titleLineCount'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(1.2) ->and($shellGeometry['headerCopyWidth'] ?? 0)->toBeGreaterThanOrEqual(240) ->and($shellGeometry['leftDelta'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(32) ->and($shellGeometry['rightDelta'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(32) ->and($shellGeometry['actionGroupRightDelta'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(32) ->and($shellGeometry['bannerHeight'] ?? 0)->toBeGreaterThanOrEqual(96) ->and($shellGeometry['bannerHeight'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(145); $inventoryPage ->assertScript('document.documentElement.scrollWidth <= window.innerWidth', true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page = visit(FindingResource::getUrl('index', panel: 'tenant', tenant: $tenant)) ->resize(1440, 1200); $page ->waitForText('Triage all matching') ->waitForText('View operation') ->waitForText('Show all operations') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->click('tbody tr.fi-ta-row:last-of-type [aria-label="More"]') ->waitForText('Triage') ->assertSee('Triage') ->click('[data-testid="ops-ux-activity-feedback-toggle"]') ->waitForText('Show activity') ->refresh() ->waitForText('Show activity'); $page->script(<<<'JS' window.dispatchEvent(new CustomEvent('ops-ux:run-enqueued', { detail: { tenantId: Number(document.querySelector('[data-testid="ops-ux-activity-feedback-root"]')?.dataset.tenantId || 0), }, })); JS); $page ->waitForText('View operation') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->assertDontSee('Show activity'); }); it('keeps terminal follow-up acknowledge local to the browser session and reopens for new work', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $failedRun = OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'type' => 'inventory_sync', 'status' => 'completed', 'outcome' => 'failed', 'started_at' => now()->subMinutes(3), 'completed_at' => now()->subSeconds(8), ]); visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant)) ->waitForText('Dashboard') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant)) ->resize(1440, 1200) ->waitForText('Inventory Items') ->waitForText('Acknowledge') ->assertSee('Review needed') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page ->click('[data-testid="ops-ux-activity-feedback-toggle"]') ->wait(1) ->assertScript(<<<'JS' (() => { const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); return banner !== null && window.getComputedStyle(banner).display === 'none'; })() JS, true) ->refresh() ->waitForText('Inventory Items') ->assertScript(<<<'JS' (() => { const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); return banner !== null && window.getComputedStyle(banner).display === 'none'; })() JS, true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); expect($failedRun->refresh()->status)->toBe('completed') ->and($failedRun->outcome)->toBe('failed'); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'type' => 'inventory_sync', 'status' => 'running', 'outcome' => 'pending', 'started_at' => now()->subSeconds(10), ]); $page->script(<<<'JS' window.dispatchEvent(new CustomEvent('ops-ux:run-enqueued', { detail: { tenantId: Number(document.querySelector('[data-testid="ops-ux-activity-feedback-root"]')?.dataset.tenantId || 0), }, })); JS); $page ->waitForText('Review operations') ->waitForText('Acknowledge') ->assertScript(<<<'JS' (() => { const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); return banner !== null && window.getComputedStyle(banner).display !== 'none'; })() JS, true) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); });