- Fix arrow selection line count calculation (overcounted by 1 due to trailing newline, causing progressive line truncation on each redraw) - Replace sync Loreline bridge with queue-based async pattern to avoid "Cannot wait on monitors" WASM deadlock - Add bounded input buffer (8 keys, DropOldest) to prevent held-key accumulation - Set Spectre.Console Profile.Height on all AnsiConsole.Create calls to prevent PlatformNotSupportedException on Console.WindowHeight - Add explicit Loreline.dll reference + TrimmerRootAssembly for WASM - Use MSBuild CopyGameContent target instead of Content/Link for static file serving in Blazor WASM - Add WASM guards for file I/O in ContentRegistry, LocalizationManager, AdventureEngine - Enforce min 120x30 terminal dimensions in xterm.js - Add Playwright E2E tests (6 tests: page load, language selection, full flow, multi-box progression, extended play, adventure)
107 lines
3.7 KiB
C#
107 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");
|
|
}
|
|
}
|