diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/System/IConsole.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/System/IConsole.cs
index 6951af5697..3f4de402fd 100644
--- a/src/Platform/Microsoft.Testing.Platform/Helpers/System/IConsole.cs
+++ b/src/Platform/Microsoft.Testing.Platform/Helpers/System/IConsole.cs
@@ -26,6 +26,18 @@ internal interface IConsole
[UnsupportedOSPlatform("tvos")]
int BufferWidth { get; }
+ [UnsupportedOSPlatform("android")]
+ [UnsupportedOSPlatform("browser")]
+ [UnsupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("tvos")]
+ int WindowHeight { get; }
+
+ [UnsupportedOSPlatform("android")]
+ [UnsupportedOSPlatform("browser")]
+ [UnsupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("tvos")]
+ int WindowWidth { get; }
+
bool IsOutputRedirected { get; }
[UnsupportedOSPlatform("android")]
diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemConsole.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemConsole.cs
index 39500ecafa..f26bf08ef1 100644
--- a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemConsole.cs
+++ b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemConsole.cs
@@ -26,6 +26,24 @@ internal sealed class SystemConsole : IConsole
[UnsupportedOSPlatform("tvos")]
public int BufferWidth => Console.BufferWidth;
+ ///
+ /// Gets the height of the console window area.
+ ///
+ [UnsupportedOSPlatform("android")]
+ [UnsupportedOSPlatform("browser")]
+ [UnsupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("tvos")]
+ public int WindowHeight => Console.WindowHeight;
+
+ ///
+ /// Gets the width of the console window area.
+ ///
+ [UnsupportedOSPlatform("android")]
+ [UnsupportedOSPlatform("browser")]
+ [UnsupportedOSPlatform("ios")]
+ [UnsupportedOSPlatform("tvos")]
+ public int WindowWidth => Console.WindowWidth;
+
///
/// Gets a value indicating whether output has been redirected from the standard output stream.
///
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiTerminal.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiTerminal.cs
index 7460235e79..ee9587e09d 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiTerminal.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/AnsiTerminal.cs
@@ -56,12 +56,12 @@ public AnsiTerminal(IConsole console)
public int Width
=> _console.IsOutputRedirected || OperatingSystem.IsBrowser() || OperatingSystem.IsAndroid() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS()
? int.MaxValue
- : _console.BufferWidth;
+ : _console.WindowWidth;
public int Height
=> _console.IsOutputRedirected || OperatingSystem.IsBrowser() || OperatingSystem.IsAndroid() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS()
? int.MaxValue
- : _console.BufferHeight;
+ : _console.WindowHeight;
public void Append(char value)
{
diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SimpleTerminalBase.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SimpleTerminalBase.cs
index 5cf26f9dbb..a3a1214e8e 100644
--- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SimpleTerminalBase.cs
+++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/SimpleTerminalBase.cs
@@ -17,12 +17,12 @@ public SimpleTerminal(IConsole console)
public int Width
=> Console.IsOutputRedirected || OperatingSystem.IsBrowser() || OperatingSystem.IsAndroid() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS()
? int.MaxValue
- : Console.BufferWidth;
+ : Console.WindowWidth;
public int Height
=> Console.IsOutputRedirected || OperatingSystem.IsBrowser() || OperatingSystem.IsAndroid() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS()
? int.MaxValue
- : Console.BufferHeight;
+ : Console.WindowHeight;
protected IConsole Console { get; }
diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs
index d8dc222a9c..f3081c81dd 100644
--- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs
+++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/OutputDevice/Terminal/TerminalTestReporterTests.cs
@@ -458,11 +458,11 @@ public void AnsiTerminal_OutputProgressFrameIsCorrect()
Error output
Oh no!
␛[m
- [␛[32m✓1␛[m/␛[31mx0␛[m/␛[33m↓0␛[m] assembly.dll (net8.0|x64)␛[2147483640G(1m 31s)
- SkippedTest1␛[2147483640G(1m 31s)
- InProgressTest1␛[2147483640G(1m 31s)
- InProgressTest2␛[2147483643G(31s)
- InProgressTest3␛[2147483644G(1s)
+ [␛[32m✓1␛[m/␛[31mx0␛[m/␛[33m↓0␛[m] assembly.dll (net8.0|x64)␛[242G(1m 31s)
+ SkippedTest1␛[242G(1m 31s)
+ InProgressTest1␛[242G(1m 31s)
+ InProgressTest2␛[245G(31s)
+ InProgressTest3␛[246G(1s)
␛[7F
␛[J␛[33mskipped␛[m SkippedTest1 ␛[90m(10s 000ms)␛[m
␛[90m Standard output
@@ -470,10 +470,10 @@ Oh no!
Error output
Oh no!
␛[m
- [␛[32m✓1␛[m/␛[31mx0␛[m/␛[33m↓1␛[m] assembly.dll (net8.0|x64)␛[2147483640G(1m 31s)
- InProgressTest1␛[2147483640G(1m 31s)
- InProgressTest2␛[2147483643G(31s)
- InProgressTest3␛[2147483644G(1s)
+ [␛[32m✓1␛[m/␛[31mx0␛[m/␛[33m↓1␛[m] assembly.dll (net8.0|x64)␛[242G(1m 31s)
+ InProgressTest1␛[242G(1m 31s)
+ InProgressTest2␛[245G(31s)
+ InProgressTest3␛[246G(1s)
""";
@@ -556,7 +556,11 @@ internal class StringBuilderConsole : IConsole
public int BufferHeight => int.MaxValue;
- public int BufferWidth => int.MinValue;
+ public int BufferWidth => int.MaxValue;
+
+ public int WindowHeight => int.MaxValue;
+
+ public int WindowWidth => int.MaxValue;
public bool IsOutputRedirected => false;
@@ -903,4 +907,178 @@ public void TerminalTestReporter_WhenInDiscoveryMode_ShouldIncrementDiscoveredTe
// Assert - should contain information about 2 tests discovered
Assert.IsTrue(output.Contains('2') || output.Contains("TestMethod1"), "Output should contain information about discovered tests");
}
+
+ [TestMethod]
+ public void SimpleTerminal_UsesWindowWidthNotBufferWidth()
+ {
+ // Arrange - Create a console where BufferWidth and WindowWidth are different
+ var console = new TestConsoleWithDifferentBufferAndWindowWidth
+ {
+ BufferWidth = 4096,
+ WindowWidth = 120,
+ };
+
+ var terminal = new NonAnsiTerminal(console);
+
+ // Assert - Width should use WindowWidth, not BufferWidth
+ Assert.AreEqual(120, terminal.Width);
+ }
+
+ [TestMethod]
+ public void AnsiTerminal_UsesWindowWidthNotBufferWidth()
+ {
+ // Arrange - Create a console where BufferWidth and WindowWidth are different
+ var console = new TestConsoleWithDifferentBufferAndWindowWidth
+ {
+ BufferWidth = 4096,
+ WindowWidth = 120,
+ };
+
+ var terminal = new AnsiTerminal(console);
+
+ // Assert - Width should use WindowWidth, not BufferWidth
+ Assert.AreEqual(120, terminal.Width);
+ }
+
+ ///
+ /// Reproduces the bug from issue #7240: when Console.BufferWidth > Console.WindowWidth,
+ /// the ANSI cursor positioning places timings off-screen because it was using BufferWidth
+ /// (capped to 250) instead of WindowWidth.
+ ///
+ /// Before fix: cursor goes to column 242 (= MaxColumn 250 - 8), off-screen for 120-col window.
+ /// After fix: cursor goes to column 112 (= WindowWidth 120 - 8), visible in window.
+ ///
+ [TestMethod]
+ public void AnsiTerminal_ProgressFrame_UseWindowWidthForCursorPositioning_WhenBufferWidthIsLarger()
+ {
+ string targetFramework = "net8.0";
+ string architecture = "x64";
+ string assembly = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"C:\work\assembly.dll" : "/mnt/work/assembly.dll";
+
+ // Console with BufferWidth=4096 but WindowWidth=120, mimicking the bug scenario.
+ var stringBuilderConsole = new StringBuilderConsoleWithCustomWidths(bufferWidth: 4096, windowWidth: 120);
+ var stopwatchFactory = new StopwatchFactory();
+ var terminalReporter = new TerminalTestReporter(assembly, targetFramework, architecture, stringBuilderConsole, new CTRLPlusCCancellationTokenSource(), new TerminalTestReporterOptions
+ {
+ ShowPassedTests = () => true,
+ AnsiMode = AnsiMode.ForceAnsi,
+ ShowActiveTests = true,
+ ShowProgress = () => true,
+ })
+ {
+ CreateStopwatch = stopwatchFactory.CreateStopwatch,
+ };
+
+ var startHandle = new AutoResetEvent(initialState: false);
+ var stopHandle = new AutoResetEvent(initialState: false);
+
+ terminalReporter.OnProgressStartUpdate += (sender, args) => startHandle.WaitOne();
+ terminalReporter.OnProgressStopUpdate += (sender, args) => stopHandle.Set();
+
+ DateTimeOffset startTime = DateTimeOffset.MinValue;
+ terminalReporter.TestExecutionStarted(startTime, 1, isDiscovery: false);
+ terminalReporter.AssemblyRunStarted();
+
+ terminalReporter.TestInProgress(testNodeUid: "Test1", displayName: "Test1");
+ stopwatchFactory.AddTime(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(31));
+
+ terminalReporter.TestCompleted(testNodeUid: "Test1", "Test1", TestOutcome.Passed, TimeSpan.FromSeconds(10),
+ informativeMessage: null, errorMessage: null, exception: null, expected: null, actual: null, standardOutput: null, errorOutput: null);
+
+ string output = stringBuilderConsole.Output;
+ startHandle.Set();
+ stopHandle.WaitOne();
+
+ string escapedOutput = ShowEscape(output)!;
+
+ // With WindowWidth=120, cursor for "(1m 31s)" (8 chars) should be at column 120-8=112.
+ // Before the fix, BufferWidth=4096 was capped to MaxColumn=250, giving column 250-8=242.
+ Assert.Contains("␛[112G(1m 31s)", escapedOutput,
+ "Cursor should be positioned at column 112 (WindowWidth=120 minus duration length), not at 242 (MaxColumn=250 minus duration length)");
+ Assert.DoesNotContain("␛[242G", escapedOutput,
+ "Cursor must NOT be positioned at column 242 which would happen if BufferWidth (4096, capped to 250) was used instead of WindowWidth");
+ }
+
+ internal class TestConsoleWithDifferentBufferAndWindowWidth : IConsole
+ {
+ public int BufferHeight { get; set; } = 300;
+
+ public int BufferWidth { get; set; } = 4096;
+
+ public int WindowHeight { get; set; } = 30;
+
+ public int WindowWidth { get; set; } = 120;
+
+ public bool IsOutputRedirected => false;
+
+ public event ConsoleCancelEventHandler? CancelKeyPress = (sender, e) => { };
+
+ public void Clear() => throw new NotImplementedException();
+
+ public ConsoleColor GetForegroundColor() => ConsoleColor.White;
+
+ public void SetForegroundColor(ConsoleColor color)
+ {
+ }
+
+ public void Write(string? value)
+ {
+ }
+
+ public void Write(char value)
+ {
+ }
+
+ public void WriteLine()
+ {
+ }
+
+ public void WriteLine(string? value)
+ {
+ }
+ }
+
+ ///
+ /// A StringBuilderConsole variant that captures output and allows custom Buffer/Window dimensions.
+ ///
+ internal sealed class StringBuilderConsoleWithCustomWidths : IConsole
+ {
+ private readonly StringBuilder _output = new();
+
+ public StringBuilderConsoleWithCustomWidths(int bufferWidth, int windowWidth)
+ {
+ BufferWidth = bufferWidth;
+ WindowWidth = windowWidth;
+ }
+
+ public int BufferHeight => int.MaxValue;
+
+ public int BufferWidth { get; }
+
+ public int WindowHeight => int.MaxValue;
+
+ public int WindowWidth { get; }
+
+ public bool IsOutputRedirected => false;
+
+ public string Output => _output.ToString();
+
+ public event ConsoleCancelEventHandler? CancelKeyPress = (sender, e) => { };
+
+ public void Clear() => throw new NotImplementedException();
+
+ public ConsoleColor GetForegroundColor() => ConsoleColor.White;
+
+ public void SetForegroundColor(ConsoleColor color)
+ {
+ }
+
+ public void Write(string? value) => _output.Append(value);
+
+ public void Write(char value) => _output.Append(value);
+
+ public void WriteLine() => _output.AppendLine();
+
+ public void WriteLine(string? value) => _output.AppendLine(value);
+ }
}