using Microsoft.Playwright; namespace OpenTheBox.Web.Tests; /// /// Helpers for interacting with the xterm.js terminal in Playwright tests. /// public static class TerminalHelper { /// /// Reads the visible text content from the xterm.js terminal buffer. /// public static async Task ReadTerminalAsync(IPage page) { return await page.EvaluateAsync(@"() => { const term = window.terminalInterop?.term; if (!term) return ''; const buffer = term.buffer.active; let text = ''; for (let i = 0; i < buffer.length; i++) { const line = buffer.getLine(i); if (line) text += line.translateToString(true) + '\n'; } return text; }"); } /// /// Waits until the terminal contains the specified text, with timeout. /// public static async Task WaitForTerminalTextAsync(IPage page, string text, int timeoutMs = 30000) { var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs); while (DateTime.UtcNow < deadline) { var content = await ReadTerminalAsync(page); if (content.Contains(text, StringComparison.OrdinalIgnoreCase)) return; await Task.Delay(500); } // Read one final time for the error message var finalContent = await ReadTerminalAsync(page); throw new TimeoutException( $"Terminal did not contain \"{text}\" within {timeoutMs}ms.\n" + $"Terminal content:\n{finalContent}"); } /// /// Types text into the terminal (sends keystrokes). /// public static async Task TypeAsync(IPage page, string text) { await page.Keyboard.TypeAsync(text, new KeyboardTypeOptions { Delay = 50 }); } /// /// Presses Enter in the terminal. /// public static async Task PressEnterAsync(IPage page) { await page.Keyboard.PressAsync("Enter"); } /// /// Presses a specific key (Arrow keys, Escape, etc.). /// public static async Task PressKeyAsync(IPage page, string key) { await page.Keyboard.PressAsync(key); } /// /// Sends a digit key press (1-9) for numbered menu selection. /// public static async Task PressDigitAsync(IPage page, int digit) { await page.Keyboard.PressAsync($"Digit{digit}"); } /// /// Waits for the terminal to be initialized (xterm.js loaded and terminal visible). /// public static async Task WaitForTerminalReadyAsync(IPage page, int timeoutMs = 60000) { var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs); while (DateTime.UtcNow < deadline) { var ready = await page.EvaluateAsync(@"() => { return window.terminalInterop?.term != null; }"); if (ready) { // Verify terminal has proper dimensions (should be 120x30 after init) var cols = await page.EvaluateAsync("() => window.terminalInterop.term.cols"); if (cols < 120) { // Force resize if fit addon miscalculated (e.g., headless browser) await page.EvaluateAsync("() => window.terminalInterop.term.resize(120, 30)"); await Task.Delay(200); } return; } await Task.Delay(500); } throw new TimeoutException($"Terminal was not initialized within {timeoutMs}ms"); } }