108 lines
3.7 KiB
C#
108 lines
3.7 KiB
C#
|
|
using Microsoft.Playwright;
|
||
|
|
|
||
|
|
namespace OpenTheBox.Web.Tests;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Helpers for interacting with the xterm.js terminal in Playwright tests.
|
||
|
|
/// </summary>
|
||
|
|
public static class TerminalHelper
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Reads the visible text content from the xterm.js terminal buffer.
|
||
|
|
/// </summary>
|
||
|
|
public static async Task<string> ReadTerminalAsync(IPage page)
|
||
|
|
{
|
||
|
|
return await page.EvaluateAsync<string>(@"() => {
|
||
|
|
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;
|
||
|
|
}");
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Waits until the terminal contains the specified text, with timeout.
|
||
|
|
/// </summary>
|
||
|
|
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}");
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Types text into the terminal (sends keystrokes).
|
||
|
|
/// </summary>
|
||
|
|
public static async Task TypeAsync(IPage page, string text)
|
||
|
|
{
|
||
|
|
await page.Keyboard.TypeAsync(text, new KeyboardTypeOptions { Delay = 50 });
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Presses Enter in the terminal.
|
||
|
|
/// </summary>
|
||
|
|
public static async Task PressEnterAsync(IPage page)
|
||
|
|
{
|
||
|
|
await page.Keyboard.PressAsync("Enter");
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Presses a specific key (Arrow keys, Escape, etc.).
|
||
|
|
/// </summary>
|
||
|
|
public static async Task PressKeyAsync(IPage page, string key)
|
||
|
|
{
|
||
|
|
await page.Keyboard.PressAsync(key);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Sends a digit key press (1-9) for numbered menu selection.
|
||
|
|
/// </summary>
|
||
|
|
public static async Task PressDigitAsync(IPage page, int digit)
|
||
|
|
{
|
||
|
|
await page.Keyboard.PressAsync($"Digit{digit}");
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Waits for the terminal to be initialized (xterm.js loaded and terminal visible).
|
||
|
|
/// </summary>
|
||
|
|
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<bool>(@"() => {
|
||
|
|
return window.terminalInterop?.term != null;
|
||
|
|
}");
|
||
|
|
if (ready)
|
||
|
|
{
|
||
|
|
// Verify terminal has proper dimensions (should be 120x30 after init)
|
||
|
|
var cols = await page.EvaluateAsync<int>("() => 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");
|
||
|
|
}
|
||
|
|
}
|