orca/tests/e2e/terminal-column-probes.ts

132 lines
4.2 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
import { sendToTerminal } from './helpers/terminal'
import {
getTerminalContentForPtyId,
waitForPtyPaneMounted,
waitForPtyShellEcho
} from './terminal-pty-readiness'
import { nodeTerminalCommand } from './terminal-node-command'
import { buildFreshShellProbeInputSequence } from './terminal-probe-input-sequence'
type TerminalColumnProbeWindow = Window & {
__store?: {
getState: () => {
activeTabId?: string | null
activeTabIdByWorktree?: Record<string, string | undefined>
activeTabType?: string | null
activeWorktreeId?: string | null
}
}
__paneManagers?: Map<
string,
{
getActivePane?: () => { terminal?: { cols?: number } } | null
getPanes?: () => { terminal?: { cols?: number } }[]
}
>
}
export async function waitForRenderedTerminalColumnsAtMost(
page: Page,
maxCols: number,
timeoutMs = 10_000
): Promise<number> {
let observedCols = 0
await expect
.poll(
async () => {
observedCols = await page.evaluate(() => {
const { __paneManagers: paneManagers, __store: store } =
window as TerminalColumnProbeWindow
const state = store?.getState()
const worktreeId = state?.activeWorktreeId
const tabId =
state?.activeTabType === 'terminal'
? state.activeTabId
: worktreeId
? (state?.activeTabIdByWorktree?.[worktreeId] ?? null)
: null
const manager = tabId ? paneManagers?.get(tabId) : null
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
return pane?.terminal?.cols ?? 0
})
return observedCols > 0 ? observedCols : maxCols + 1
},
{
timeout: timeoutMs,
message: `rendered terminal columns did not settle at or below ${maxCols}`
}
)
.toBeLessThanOrEqual(maxCols)
return observedCols
}
export async function waitForPtyColumnsAtMost(
page: Page,
ptyId: string,
maxCols: number,
timeoutMs = 30_000
): Promise<number> {
const deadline = Date.now() + timeoutMs
await waitForPtyPaneMounted(page, ptyId, Math.min(10_000, timeoutMs))
await waitForPtyShellEcho(page, ptyId, Math.min(15_000, Math.max(0, deadline - Date.now())))
let markerObserved = false
let lastObservedCols: number | null = null
let lastMarker = ''
let lastTerminalTail = ''
while (Date.now() < deadline) {
const marker = `ORCA_PTY_COLUMNS_${randomUUID()}`
lastMarker = marker
for (const input of buildFreshShellProbeInputSequence(
`${nodeTerminalCommand([
'-e',
`console.log('${marker}:' + (process.stdout.columns || 0))`
])}\r`
)) {
await sendToTerminal(page, ptyId, input)
}
const probeDeadline = Date.now() + Math.min(5_000, Math.max(0, deadline - Date.now()))
while (Date.now() < probeDeadline) {
const content = await getTerminalContentForPtyId(page, ptyId, 30_000)
lastTerminalTail = content
const match = content.match(new RegExp(`${marker}:(\\d+)`))
const observedCols = Number(match?.[1] ?? 0)
if (observedCols > 0) {
markerObserved = true
lastObservedCols = observedCols
break
}
await page.waitForTimeout(100)
}
if (lastObservedCols !== null && lastObservedCols <= maxCols) {
return lastObservedCols
}
const retryDelayMs = Math.min(250, Math.max(0, deadline - Date.now()))
if (retryDelayMs > 0) {
await page.waitForTimeout(retryDelayMs)
}
}
lastTerminalTail = await getTerminalContentForPtyId(page, ptyId, 30_000)
const finalState = {
lastMarker,
markerObserved,
lastObservedCols,
maxCols,
terminalTail: lastTerminalTail.slice(-4_000)
}
if (!markerObserved) {
throw new Error(
`PTY column probe never observed a marker within ${timeoutMs}ms; final state ${JSON.stringify(
finalState
)}`
)
}
throw new Error(
`PTY columns stayed above ${maxCols}; last observed ${lastObservedCols}; final state ${JSON.stringify(
finalState
)}`
)
}