Summary
Tests tagged [Trait("Category", "SlowLayout")] are excluded from the fast test suite with the comment "can hang in headless environments." Investigation shows the hangs are caused by specific fixable bugs, not fundamental headless limitations. ~60–70% of SlowLayout tests don't touch the visual tree at all and were swept into the category by bulk class-level tagging. The category and its filter should be eliminated.
Root Cause 1 — ForceRenderTimerTick() Self-Deadlock
The primary hang is in MainWindowIntegrationTests.cs:
window.Show();
for (var i = 0; i < 10; i++)
{
Dispatcher.UIThread.RunJobs();
AvaloniaHeadlessPlatform.ForceRenderTimerTick(); // ← deadlock
}
[AvaloniaFact] tests run on the UIThread. ForceRenderTimerTick() posts a synthetic render tick and synchronously waits for the UIThread to process it — but the UIThread is already blocked inside that call. Classic re-entrant deadlock with no nested message pump to escape it.
Fix: Write a WaitForLayoutAsync(Window) helper (alongside the existing WaitForWorkspaceTabAsync / WaitForAgentReadyAsync helpers) and replace every ForceRenderTimerTick loop with a call to it:
internal static Task WaitForLayoutAsync(Window window, TimeSpan? timeout = null)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
EventHandler? handler = null;
handler = (_, _) => { window.LayoutUpdated -= handler; tcs.TrySetResult(); };
window.LayoutUpdated += handler;
var wait = tcs.Task;
if (timeout is { } t) wait = wait.WaitAsync(t);
return wait;
}
Root Cause 2 — Over-Broad Class-Level Tagging
A single bulk commit tagged entire test classes with [Trait("Category", "SlowLayout")], sweeping in ViewModel-only tests that have no visual-tree dependency and run fine without Avalonia at all. Examples:
CloneEntityShortcutHandlerTests — no visual tree
WebViewModelTests — no visual tree
ReviewWorktreeShortcutHandlerTests — no visual tree
WorkspaceGuiContextProviderTests — no visual tree
AgentChatOutputControlTests — uses HeadlessControllableBrowser stub, no real window
These tests use [AvaloniaFact] only because the class was bulk-tagged, not because they need it. Downgrading them to [Fact] removes them from the PerAssembly Avalonia domain and cuts startup pressure.
Three Categories of Tests Currently in SlowLayout
| Class |
Examples |
Genuinely needs visual tree? |
| Genuine layout tests |
MainWindowIntegrationTests, AgentGuiMainWindowIntegrationTests |
✅ window.Show() |
| ViewModel-only (swept in by bulk tagging) |
CloneEntityShortcutHandlerTests, WebViewModelTests |
❌ |
| Control tests with stub browser |
AgentChatOutputControlTests |
❌ |
Required Fixes
Fix A — Replace ForceRenderTimerTick() with WaitForLayoutAsync
Write the WaitForLayoutAsync(Window) helper and replace all ForceRenderTimerTick loop call sites with it.
Fix B — Downgrade ViewModel-only tests from [AvaloniaFact] to [Fact]
For any SlowLayout-tagged test that does not call window.Show() or manipulate the visual tree, remove [Trait("Category", "SlowLayout")] and change [AvaloniaFact] → [Fact] / [AvaloniaTheory] → [Theory].
End State
After fixes A and B, the genuine layout tests no longer hang and run correctly in the headless environment. The Category!=SlowLayout filter in run-tests.ps1 fast mode should be deleted — no separate mode is needed. The SlowLayout category is removed from all tests.
Affected Files
Phantom.Workspaces.Tests\MainWindowIntegrationTests.cs — add WaitForLayoutAsync helper, replace ForceRenderTimerTick loop
Phantom.Workspaces.Tests\AgentGuiMainWindowIntegrationTests.cs — review for same pattern
- All test files with bulk class-level
[Trait("Category", "SlowLayout")] on ViewModel-only tests — Fix B
scripts\run-tests.ps1 — delete Category!=SlowLayout filter from fast mode
Summary
Tests tagged
[Trait("Category", "SlowLayout")]are excluded from the fast test suite with the comment "can hang in headless environments." Investigation shows the hangs are caused by specific fixable bugs, not fundamental headless limitations. ~60–70% of SlowLayout tests don't touch the visual tree at all and were swept into the category by bulk class-level tagging. The category and its filter should be eliminated.Root Cause 1 —
ForceRenderTimerTick()Self-DeadlockThe primary hang is in
MainWindowIntegrationTests.cs:[AvaloniaFact]tests run on the UIThread.ForceRenderTimerTick()posts a synthetic render tick and synchronously waits for the UIThread to process it — but the UIThread is already blocked inside that call. Classic re-entrant deadlock with no nested message pump to escape it.Fix: Write a
WaitForLayoutAsync(Window)helper (alongside the existingWaitForWorkspaceTabAsync/WaitForAgentReadyAsynchelpers) and replace everyForceRenderTimerTickloop with a call to it:Root Cause 2 — Over-Broad Class-Level Tagging
A single bulk commit tagged entire test classes with
[Trait("Category", "SlowLayout")], sweeping in ViewModel-only tests that have no visual-tree dependency and run fine without Avalonia at all. Examples:CloneEntityShortcutHandlerTests— no visual treeWebViewModelTests— no visual treeReviewWorktreeShortcutHandlerTests— no visual treeWorkspaceGuiContextProviderTests— no visual treeAgentChatOutputControlTests— usesHeadlessControllableBrowserstub, no real windowThese tests use
[AvaloniaFact]only because the class was bulk-tagged, not because they need it. Downgrading them to[Fact]removes them from thePerAssemblyAvalonia domain and cuts startup pressure.Three Categories of Tests Currently in SlowLayout
MainWindowIntegrationTests,AgentGuiMainWindowIntegrationTestswindow.Show()CloneEntityShortcutHandlerTests,WebViewModelTestsAgentChatOutputControlTestsRequired Fixes
Fix A — Replace
ForceRenderTimerTick()withWaitForLayoutAsyncWrite the
WaitForLayoutAsync(Window)helper and replace allForceRenderTimerTickloop call sites with it.Fix B — Downgrade ViewModel-only tests from
[AvaloniaFact]to[Fact]For any SlowLayout-tagged test that does not call
window.Show()or manipulate the visual tree, remove[Trait("Category", "SlowLayout")]and change[AvaloniaFact]→[Fact]/[AvaloniaTheory]→[Theory].End State
After fixes A and B, the genuine layout tests no longer hang and run correctly in the headless environment. The
Category!=SlowLayoutfilter inrun-tests.ps1fastmode should be deleted — no separate mode is needed. TheSlowLayoutcategory is removed from all tests.Affected Files
Phantom.Workspaces.Tests\MainWindowIntegrationTests.cs— addWaitForLayoutAsynchelper, replaceForceRenderTimerTickloopPhantom.Workspaces.Tests\AgentGuiMainWindowIntegrationTests.cs— review for same pattern[Trait("Category", "SlowLayout")]on ViewModel-only tests — Fix Bscripts\run-tests.ps1— deleteCategory!=SlowLayoutfilter fromfastmode