Find Untested Sources
dotnet/skills
Statically pairs source files with test files to list code that no test references, using Roslyn for C# or tree-sitter for many languages, with no build.
Agent for diagnosing and fixing flaky terminal UI tests in the Hex1b test suite.
$ npx skills add mitchdenny/hex1b --skill test-fixer -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install mitchdenny/hex1b test-fixer --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/mitchdenny/hex1b.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/test-fixer .claude/skills/test-fixer && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "test-fixer" agent skill from https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixer into .claude/skills/test-fixer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "test-fixer", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixerType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add mitchdenny/hex1b --skill test-fixer -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install mitchdenny/hex1b test-fixer --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mitchdenny/hex1b.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.github/skills/test-fixer .agents/skills/test-fixer && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "test-fixer" agent skill from https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixer into .agents/skills/test-fixer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "test-fixer", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add mitchdenny/hex1b --skill test-fixer -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install mitchdenny/hex1b test-fixer --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mitchdenny/hex1b.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.github/skills/test-fixer .cursor/skills/test-fixer && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "test-fixer" agent skill from https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixer into .cursor/skills/test-fixer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "test-fixer", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/mitchdenny/hex1b.git --path .github/skills/test-fixer--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add mitchdenny/hex1b --skill test-fixer -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install mitchdenny/hex1b test-fixer --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mitchdenny/hex1b.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.github/skills/test-fixer .gemini/skills/test-fixer && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "test-fixer" agent skill from https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixer into .gemini/skills/test-fixer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "test-fixer", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install mitchdenny/hex1b test-fixerInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add mitchdenny/hex1b --skill test-fixer -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/mitchdenny/hex1b.git skills-src && mkdir -p .github/skills && cp -r skills-src/.github/skills/test-fixer .github/skills/test-fixer && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "test-fixer" agent skill from https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixer into .github/skills/test-fixer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "test-fixer", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add mitchdenny/hex1b --skill test-fixer -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install mitchdenny/hex1b test-fixer --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mitchdenny/hex1b.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.github/skills/test-fixer .opencode/skills/test-fixer && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "test-fixer" agent skill from https://github.com/mitchdenny/hex1b/tree/main/.github/skills/test-fixer into .opencode/skills/test-fixer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "test-fixer", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
test-fixerAgent for diagnosing and fixing flaky terminal UI tests in the Hex1b test suite.
Test Fixer is an agent skill from mitchdenny/hex1b. Agent for diagnosing and fixing flaky terminal UI tests in the Hex1b test suite. Use when tests pass locally but fail in CI, or when tests exhibit timing-sensitive behavior.
Its SKILL.md is about 6.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Testing & QA, covering Test generation. It works with .NET and Linux. The repository describes itself as: The .NET Terminal Application Stack. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 98d8766. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
dotnetghFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use gh, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Test Fixer loads about 6.5k tokens when it runs. Until then it costs about 46 tokens; SKILL.md has 1,351 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from mitchdenny/hex1b at commit 98d8766, republished under its MIT licence (© mitchdenny). 1,351 words, ~6,503 tokens.
.claude/skills/test-fixer/SKILL.md (or your agent's skills folder).This skill provides guidelines for AI agents to diagnose and fix flaky tests in the Hex1b TUI library test suite. These tests use MSTest 4 (MSTest.Sdk/4.2.3, OutputType=Exe) with Microsoft.Testing.Platform and Hex1bTerminalInputSequenceBuilder to simulate user interactions with terminal applications. global.json configures dotnet test to use MTP, and test projects can also be run as executables with dotnet run --project tests/SomeProject/.
| Pattern | Symptom | Fix |
|---|---|---|
| Snapshot After Exit | Test passes locally, fails on Linux CI | Move WaitUntil before Capture, ensure Capture is before exit |
| Missing WaitUntil | Intermittent assertion failures | Add WaitUntil for expected state before Capture |
| Race with Ctrl+C | Snapshot missing expected content | Add WaitUntil between last action and Capture |
| Task.WhenAny Race | Test sometimes times out | Replace with proper WaitUntil or increase timeout |
| Test Interference | Pass isolated, fail in suite | Check for shared state, file locks, or parallel execution issues |
| Platform-Specific | Fails consistently on Windows/Linux | Add TestCategory/Ignore or fix platform-specific code |
| Task.Delay for Async Events | Flaky on slower CI runners | Replace Task.Delay with TaskCompletionSource signal |
| Helper Partial Wait | Tests using multi-line helpers fail intermittently | Wait for all/last content, not just first line |
Assert.IsTrue failed or An item should be selected with indicatorThe ApplyWithCaptureAsync method returns terminal.CreateSnapshot() after all steps complete, not at the point where .Capture() is called. When the sequence includes Ctrl+C to exit the app, the terminal buffer may be cleared before the final snapshot is taken.
Platform difference: Windows terminal buffers persist longer after app exit; Linux clears them more aggressively.
// ❌ BROKEN: Snapshot is taken AFTER Ctrl+C exits the app
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("Counter: 3"), TimeSpan.FromSeconds(2))
.Capture("final") // This saves SVG but doesn't capture for return!
.Ctrl().Key(Hex1bKey.C) // App exits, terminal buffer may be cleared
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
// snapshot is from AFTER Ctrl+C, not from Capture step!
Assert.IsTrue(snapshot.ContainsText("Counter: 3")); // May fail on Linux!Hex1bTerminalInputSequenceBuilder builds a sequence of TestStep objectsApplyWithCaptureAsync executes all steps, then calls terminal.CreateSnapshot() at the endCaptureStep only saves SVG/HTML files—it does NOT store the snapshot for returnCtrl+C triggers app exit, the terminal may clear its buffer before the final snapshotOption A: Ensure WaitUntil is immediately before Capture (Recommended)
// ✅ FIXED: WaitUntil confirms state, then Capture, then exit
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.Key(Hex1bKey.A)
.Key(Hex1bKey.B)
.WaitUntil(s => s.ContainsText("Counter: 3"), TimeSpan.FromSeconds(2))
.Capture("final")
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
await runTask;
// If the WaitUntil passed, we know the content was there at Capture time
// The assertion is now checking what WaitUntil already verified
Assert.IsTrue(snapshot.ContainsText("Counter: 3"));Option B: Don't use snapshot for content assertions
// ✅ ALTERNATIVE: Use render counters or other non-snapshot assertions
var runTask = app.RunAsync(TestContext.Current.CancellationToken);
await new Hex1bTerminalInputSequenceBuilder()
.Key(Hex1bKey.A)
.Key(Hex1bKey.B)
.Capture("final")
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
await runTask;
// Assert on counters/state captured during execution, not snapshot content
Assert.AreEqual(1, staticRenderCount);Option C: Use WaitUntil as the assertion itself
// ✅ ALTERNATIVE: WaitUntil IS the assertion - if it passes, test passes
await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("> Item 15"), TimeSpan.FromSeconds(2))
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyAsync(terminal, TestContext.Current.CancellationToken);
await runTask;
// No additional assertions needed - WaitUntil already verified the contentUser input (key press, mouse click) triggers async rendering. Without WaitUntil, the snapshot may be captured before the render completes.
// ❌ BROKEN: No wait after Down() for selection to update
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("First"), TimeSpan.FromSeconds(2))
.Down() // Triggers async render
.Capture("final") // May capture before render completes!
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
Assert.IsTrue(snapshot.ContainsText("> Second")); // Flaky!// ✅ FIXED: Wait for expected state after action
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("First"), TimeSpan.FromSeconds(2))
.Down()
.WaitUntil(s => s.ContainsText("> Second"), TimeSpan.FromSeconds(2)) // Wait for render!
.Capture("final")
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
Assert.IsTrue(snapshot.ContainsText("> Second")); // ReliableCtrl+C can be processed before the previous action's render completes, especially if the action triggers complex layout recalculation.
// ❌ BROKEN: Scroll may not complete before Ctrl+C
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("Item 01"), TimeSpan.FromSeconds(2))
.ScrollDown(5)
.Capture("after_scroll")
.Ctrl().Key(Hex1bKey.C) // May interrupt scroll processing!
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
Assert.IsTrue(snapshot.ContainsText("> Item 06")); // Flaky!// ✅ FIXED: Verify scroll completed before exiting
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("Item 01"), TimeSpan.FromSeconds(2))
.ScrollDown(5)
.WaitUntil(s => s.ContainsText("> Item 06"), TimeSpan.FromSeconds(2)) // Verify scroll!
.Capture("after_scroll")
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
Assert.IsTrue(snapshot.ContainsText("> Item 06")); // Reliable| Component | Location | Purpose |
|---|---|---|
Hex1bTerminalInputSequenceBuilder | src/Hex1b/Automation/ | Fluent builder for test sequences |
Hex1bTerminalInputSequence | src/Hex1b/Automation/ | Executes steps and returns final snapshot |
CaptureStep | tests/Hex1b.Tests/CaptureStep.cs | Saves SVG/HTML at capture point |
WaitUntilStep | src/Hex1b/Automation/WaitUntilStep.cs | Polls until condition met or timeout |
TestSequenceExtensions | tests/Hex1b.Tests/TestSequenceExtensions.cs | Adds .Capture() extension method |
// From Hex1bTerminalInputSequence.cs
public async Task<Hex1bTerminalSnapshot> ApplyAsync(Hex1bTerminal terminal, CancellationToken ct = default)
{
foreach (var step in _steps)
{
ct.ThrowIfCancellationRequested();
await step.ExecuteAsync(terminal, _options, ct);
}
return terminal.CreateSnapshot(); // ⚠️ Snapshot is ALWAYS at the end!
}The CaptureStep only saves files—it does NOT affect what ApplyAsync returns:
// From CaptureStep.cs
internal override Task ExecuteAsync(...)
{
TestCaptureHelper.Capture(terminal, Name); // Saves SVG/HTML only
return Task.CompletedTask;
}# Get CI logs
gh run view <run-id> --log-failed
# Look for test names and error messages
# Common patterns:
# - Assert.IsTrue failed
# - Expected:<True> or equivalent / Actual:<False>
# - "An item should be selected with indicator"# Run specific test locally
dotnet test tests/Hex1b.Tests --filter "FullyQualifiedName~<TestName>"
# Run multiple times to check for flakiness
for ($i = 1; $i -le 10; $i++) {
dotnet test tests/Hex1b.Tests --filter "FullyQualifiedName~<TestName>" --no-build 2>&1 |
Select-String -Pattern "(Passed|Failed)"
}Look for these anti-patterns:
// Anti-pattern 1: Snapshot assertion after Ctrl+C
.Capture("final")
.Ctrl().Key(Hex1bKey.C)
// ... later ...
Assert.IsTrue(snapshot.ContainsText("expected")); // ❌
// Anti-pattern 2: Action without WaitUntil before Capture
.Down()
.Capture("final") // ❌ No WaitUntil after Down()
// Anti-pattern 3: Complex action right before Ctrl+C
.ScrollDown(10)
.Capture("final")
.Ctrl().Key(Hex1bKey.C) // ❌ No WaitUntil to verify scroll completedWaitUntil after any action that changes stateWaitUntil checks for the exact condition being assertedWaitUntil immediately before Capture[TestMethod]
public async Task Navigation_DownArrow_SelectsNextItem()
{
using var workload = new Hex1bAppWorkloadAdapter();
using var terminal = Hex1bTerminal.CreateBuilder()
.WithWorkload(workload)
.WithHeadless()
.WithDimensions(40, 10)
.Build();
using var app = new Hex1bApp(
ctx => Task.FromResult<Hex1bWidget>(ctx.List(["First", "Second", "Third"])),
new Hex1bAppOptions { WorkloadAdapter = workload }
);
var runTask = app.RunAsync(TestContext.Current.CancellationToken);
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("> First"), TimeSpan.FromSeconds(2)) // Initial state
.Down()
.WaitUntil(s => s.ContainsText("> Second"), TimeSpan.FromSeconds(2)) // After action
.Capture("after_down")
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
await runTask;
// This assertion is technically redundant since WaitUntil verified it
Assert.IsTrue(snapshot.ContainsText("> Second"));
}[TestMethod]
public async Task StaticWidget_OnlyRendersOnce()
{
using var workload = new Hex1bAppWorkloadAdapter();
using var terminal = Hex1bTerminal.CreateBuilder()
.WithWorkload(workload)
.WithHeadless()
.Build();
var renderCount = 0;
var staticWidget = new TestWidget().OnRender(_ => renderCount++);
using var app = new Hex1bApp(
ctx => Task.FromResult<Hex1bWidget>(staticWidget),
new Hex1bAppOptions { WorkloadAdapter = workload }
);
var runTask = app.RunAsync(TestContext.Current.CancellationToken);
await new Hex1bTerminalInputSequenceBuilder()
.Key(Hex1bKey.A)
.Key(Hex1bKey.B)
.Capture("final")
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
await runTask;
// Assert on counter, not snapshot content
Assert.AreEqual(1, renderCount);
}[TestMethod]
public async Task Render_AllItems_AreVisible()
{
using var workload = new Hex1bAppWorkloadAdapter();
using var terminal = Hex1bTerminal.CreateBuilder()
.WithWorkload(workload)
.WithHeadless()
.Build();
var context = CreateContext(workload);
var node = new ListNode { Items = ["Item 1", "Item 2", "Item 3"] };
node.Arrange(new Rect(0, 0, 20, 5));
node.Render(context);
// No app, no Ctrl+C needed
var snapshot = await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s =>
s.ContainsText("Item 1") &&
s.ContainsText("Item 2") &&
s.ContainsText("Item 3"),
TimeSpan.FromSeconds(2))
.Capture("final")
.Build()
.ApplyWithCaptureAsync(terminal, TestContext.Current.CancellationToken);
Assert.IsTrue(snapshot.ContainsText("Item 1"));
Assert.IsTrue(snapshot.ContainsText("Item 2"));
Assert.IsTrue(snapshot.ContainsText("Item 3"));
}Task.WhenAny to check if app exitedUsing Task.WhenAny(runTask, Task.Delay(...)) creates a race condition. The delay may win even when the app is about to exit, or the exit may happen but not be detected in time.
// ❌ BROKEN: Race condition with Task.WhenAny
var runTask = app.RunAsync(TestContext.Current.CancellationToken);
await renderTest.Task.WaitAsync(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken);
await new Hex1bTerminalInputSequenceBuilder()
.Key(Hex1bKey.C, Hex1bModifiers.Control)
.Build()
.ApplyAsync(terminal);
var completed = await Task.WhenAny(runTask, Task.Delay(2000));
Assert.IsTrue(completed == runTask, "Expected CTRL-C to exit"); // Flaky!// ✅ FIXED: Use WaitAsync with timeout instead of Task.WhenAny
var runTask = app.RunAsync(TestContext.Current.CancellationToken);
await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText("expected content"), TimeSpan.FromSeconds(2))
.Ctrl().Key(Hex1bKey.C)
.Build()
.ApplyAsync(terminal, TestContext.Current.CancellationToken);
// Use WaitAsync - throws TimeoutException if it takes too long
await runTask.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);dotnet test --filter "FullyQualifiedName~TestName"Tests may interfere with each other through:
# Run test in isolation
dotnet test --filter "FullyQualifiedName~TestName" --no-build
# Run test with suspected interfering tests
dotnet test --filter "FullyQualifiedName~TestName|FullyQualifiedName~OtherTest" --no-buildusing statements on terminals/workloads[DoNotParallelize] on the test class - tests parallelize by default, and this forces sequential execution for conflicting tests// Use unique temp file names
var tempPath = Path.Combine(Path.GetTempPath(), $"hex1b_test_{Guid.NewGuid()}.cast");
// Or include test name
var tempPath = Path.Combine(Path.GetTempPath(), $"hex1b_{nameof(MyTestMethod)}.cast");| Feature | Windows | Linux |
|---|---|---|
| PTY support | Limited (ConPTY) | Native |
| Terminal buffer cleanup | Delayed | Immediate |
| File locking | Strict | Flexible |
| Path separators | \ | / |
// Skip on Windows - PTY tests require Unix
[TestMethod]
[TestCategory("LinuxOnly")]
public async Task WithPtyProcess_ExecutesProcess()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Skip on Windows - PTY not fully supported
return;
}
// ... test code
}
// Or use Ignore attribute
[TestMethod]
[Ignore("Requires Unix PTY support")]
public async Task PtyTest() { }
// Or conditional skip with MSTest
public sealed class LinuxOnlyTestMethodAttribute : TestMethodAttribute
{
public override async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return [new TestResult
{
Outcome = UnitTestOutcome.Inconclusive,
TestFailureException = new AssertInconclusiveException("This test requires Linux")
}];
}
return await base.ExecuteAsync(testMethod);
}
}Hex1bTerminalBuilderTests.WithPtyProcess_* - Require PTYNanoExploratoryTests.* - Require nano and PTYWithPtyProcess() builder methodTask.Delay(100)) for an async event like input binding firingUsing Task.Delay to wait for async events like input bindings firing is unreliable. The fixed delay may not be long enough on slower CI runners, or may be unnecessarily long on fast machines.
Example: Input binding tests that send a key and wait for the binding callback to fire.
// ❌ BROKEN: Fixed delay may not be long enough on slow CI runners
var bindingFired = false;
using var app = new Hex1bApp(
ctx =>
{
var vstack = new VStackWidget([ctx.Test()])
.InputBindings(bindings =>
{
bindings.Shift().Key(key).Action(_ =>
{
bindingFired = true; // Sets flag when binding fires
return Task.CompletedTask;
}, $"Test Shift+{key}");
});
return Task.FromResult<Hex1bWidget>(vstack);
},
new Hex1bAppOptions { WorkloadAdapter = workload }
);
// ... wait for render ...
await new Hex1bTerminalInputSequenceBuilder()
.Shift().Key(key)
.Build()
.ApplyAsync(terminal, TestContext.Current.CancellationToken);
await Task.Delay(100); // ❌ Fixed delay - may not be long enough!
Assert.IsTrue(bindingFired); // Flaky!Replace the boolean flag and Task.Delay with a TaskCompletionSource that signals when the event occurs:
// ✅ FIXED: Use TaskCompletionSource to wait for the async event
var bindingFired = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
using var app = new Hex1bApp(
ctx =>
{
var vstack = new VStackWidget([ctx.Test()])
.InputBindings(bindings =>
{
bindings.Shift().Key(key).Action(_ =>
{
bindingFired.TrySetResult(); // Signal completion
return Task.CompletedTask;
}, $"Test Shift+{key}");
});
return Task.FromResult<Hex1bWidget>(vstack);
},
new Hex1bAppOptions { WorkloadAdapter = workload }
);
// ... wait for render ...
await new Hex1bTerminalInputSequenceBuilder()
.Shift().Key(key)
.Build()
.ApplyAsync(terminal, TestContext.Current.CancellationToken);
// Wait for the event with a timeout - will fail fast if binding doesn't fire
await bindingFired.Task.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken);
// If we got here, the binding fired (the wait would have timed out otherwise)
Assert.IsTrue(bindingFired.Task.IsCompleted); // ReliableTaskCompletionSource instead of a boolean flagTrySetResult() in the event handler to signal completionWaitAsync with timeout instead of Task.DelayTaskCreationOptions.RunContinuationsAsynchronously to avoid potential deadlocksTest helper methods that write multiple lines of content may only wait for the first line to appear before taking a snapshot. On faster CI systems, the snapshot may be captured before all lines are processed by the output pump.
Example: A helper writes lines ["A", "B"] but only waits for "A" to appear. Tests that expect "B" on a separate line may fail because the snapshot is taken before "B" is processed.
// ❌ BROKEN: Only waits for first line
private static async Task<Hex1bTerminalSnapshot> CreateSnapshotAsync(string[] lines)
{
using var workload = new Hex1bAppWorkloadAdapter();
using var terminal = Hex1bTerminal.CreateBuilder().WithWorkload(workload).Build();
foreach (var line in lines)
{
workload.Write(line + "\r\n");
}
// BUG: Only waits for first line!
var firstLine = lines.Length > 0 ? lines[0] : "";
await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => s.ContainsText(firstLine), TimeSpan.FromSeconds(1))
.Build()
.ApplyAsync(terminal);
return terminal.CreateSnapshot(); // May miss content after first line!
}// ✅ FIXED: Wait for last line to ensure all content is processed
private static async Task<Hex1bTerminalSnapshot> CreateSnapshotAsync(string[] lines)
{
using var workload = new Hex1bAppWorkloadAdapter();
using var terminal = Hex1bTerminal.CreateBuilder().WithWorkload(workload).Build();
foreach (var line in lines)
{
workload.Write(line + "\r\n");
}
// Wait for the LAST line to ensure all lines are written
var lastLine = lines.Length > 0 ? lines[^1] : "";
await new Hex1bTerminalInputSequenceBuilder()
.WaitUntil(s => string.IsNullOrEmpty(lastLine) || s.ContainsText(lastLine),
TimeSpan.FromSeconds(1), "last line content")
.Build()
.ApplyAsync(terminal);
return terminal.CreateSnapshot(); // Now contains all lines
}WaitUntil condition only checks for initial/first contentBefore committing test changes, verify:
WaitUntilWaitUntil condition matches what will be assertedCapture is placed after WaitUntil, before Ctrl+CWaitUntil, not Wait)Task.WhenAny race conditions - use WaitAsync insteadTask.Delay for async events - use TaskCompletionSource insteadTestCategory, Ignore, or conditional TestMethodAttribute handling© mitchdenny, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .github/skills/test-fixer of mitchdenny/hex1b.
Open the folder on GitHubat commit 98d8766
Test Fixer next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Test Fixer this skillmitchdenny/hex1b | 178 | — | ~6.5k | Automated safety check: Pass | MIT | |
| Find Untested Sourcesdotnet/skills | 5.6k | 1 repos | ~3.3k | Automated safety check: Pass | MIT | |
| ScottPlot Test RunnerScottPlot/ScottPlot | 6.8k | — | ~308 | Automated safety check: Pass | MIT | |
| Raven Test Triagemarinasundstrom/raven | 108 | — | ~1.4k | Automated safety check: Pass | MIT | |
| Run Testsrunceel/ReactiveProperty | 944 | — | ~3.6k | Automated safety check: Pass | MIT | |
| MAUI UI Test Writerdotnet/maui | 23k | — | ~3k | Automated safety check: Pass | MIT |
dotnet/skills
Statically pairs source files with test files to list code that no test references, using Roslyn for C# or tree-sitter for many languages, with no build.
ScottPlot/ScottPlot
Run or add ScottPlot 5 tests. Use for unit-test and cookbook-test work; unless explicitly asked otherwise, restrict manual test execution to the Unit Tests…
marinasundstrom/raven
Testing and stabilization workflow for the Raven compiler test suite.
runceel/ReactiveProperty
Runs .NET tests with dotnet test. An agent skill from runceel/ReactiveProperty.
dotnet/maui
Writes UI tests that reproduce a GitHub issue in .NET MAUI and keeps iterating until the tests actually fail, proving they catch the bug.
dotnet/maui
Writes a paired .xaml and .xaml.cs unit test for a .NET MAUI issue that is about XAML behavior itself, such as parsing, XamlC output or generated code.
mitchdenny/hex1b
Guidelines for reviewing API design in the Hex1b codebase. An agent skill from mitchdenny/hex1b.
mitchdenny/hex1b
Guidelines for running and interpreting Surface API performance benchmarks.
mitchdenny/hex1b
Agent for validating Hex1b documentation against actual library behavior.
mitchdenny/hex1b
Guidelines for producing accurate and maintainable documentation for the Hex1b TUI library.
mitchdenny/hex1b
Step-by-step guide for creating new widgets in the Hex1b TUI library.
mitchdenny/hex1b
Guidelines for writing unit tests in the Hex1b TUI library. An agent skill from mitchdenny/hex1b.
Categories
Agent for diagnosing and fixing flaky terminal UI tests in the Hex1b test suite. Test Fixer is an agent skill from mitchdenny/hex1b. Agent for diagnosing and fixing flaky terminal UI tests in the Hex1b test suite.
Test Fixer fits situations like: tests pass locally but fail in CI; tests exhibit timing-sensitive behavior.
Run `npx skills add mitchdenny/hex1b --skill test-fixer -a claude-code`. Or copy the skill folder (.github/skills/test-fixer in mitchdenny/hex1b) into .claude/skills/test-fixer in your project. Claude Code loads it when a task matches its description.
Run `npx skills add mitchdenny/hex1b --skill test-fixer -a codex`. Or copy the skill folder (.github/skills/test-fixer in mitchdenny/hex1b) into .agents/skills/test-fixer in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mitchdenny/hex1b --skill test-fixer -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/test-fixer, .gemini/skills/test-fixer, .github/skills/test-fixer and .opencode/skills/test-fixer in your project.
Going by SKILL.md and its folder, Test Fixer needs the command-line tools its instructions call (dotnet and gh).
SKILL.md contains no URLs. Its commands use gh, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Test Fixer is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.5k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Test Fixer: Find Untested Sources (dotnet/skills, 5.6k stars), ScottPlot Test Runner (ScottPlot/ScottPlot, 6.8k stars), Raven Test Triage (marinasundstrom/raven, 108 stars) and Run Tests (runceel/ReactiveProperty, 944 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
mitchdenny (a GitHub user) maintains it in mitchdenny/hex1b, which has 178 GitHub stars. The repository holds 7 skills in this directory. The repository was last updated on October 2, 2026.
Source: mitchdenny/hex1b on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.