Files
orca/tests/e2e/artificial-opencode-terminal-load.spec.ts
T
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00

789 lines
28 KiB
TypeScript

import { presentTerminalPerfWindow } from './terminal-perf-presentation'
import type { Page, TestInfo } from '@stablyai/playwright-test'
import { randomUUID } from 'node:crypto'
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { test, expect } from './helpers/orca-app'
import {
ensureTerminalVisible,
getActiveWorktreeId,
getAllWorktreeIds,
switchToWorktree,
waitForActiveWorktree,
waitForSessionReady
} from './helpers/store'
import {
sendToTerminal,
waitForActivePanePtyId,
waitForActiveTerminalManager
} from './helpers/terminal'
import {
ensureActiveWorktreePaneLoad,
focusActiveTerminalInput,
focusPane,
waitForMarkerLatency,
waitForTerminalOutputForPtyId
} from './artificial-opencode-pane-interactions'
import { runHiddenRealPtyPressureScenario } from './artificial-opencode-hidden-pressure-scenario'
import type { HiddenPressureOutputMode } from './artificial-opencode-hidden-pressure-script'
import { runMainPressureScenario } from './artificial-opencode-main-pressure-scenario'
import { runRendererBackpressureRevisitScenario } from './artificial-opencode-revisit-pressure-scenario'
import { startSyntheticOpenCodeInjection } from './artificial-opencode-synthetic-injection'
test.beforeEach(async ({ electronApp, orcaPage }, testInfo) => {
await orcaPage.waitForLoadState('domcontentloaded')
await presentTerminalPerfWindow(electronApp, testInfo)
})
type TypingMeasurement = {
latencies: number[]
medianLatencyMs: number
worstLatencyMs: number
maxTimerDriftMs: number
frameCount: number
}
type SyntheticOpenCodeWindow = Window & {
__terminalPtyDataInjection?: {
inject: (paneKey: string, data: string) => boolean
}
__terminalPtyAckGate?: {
hold: (ptyIds: string[]) => void
release: () => void
snapshot: () => TerminalPtyAckGateSnapshot
}
__terminalPtyOutputDebug?: {
reset: () => void
snapshot: () => TerminalPtyOutputDebugSnapshot
}
__terminalOutputSchedulerDebug?: {
reset: () => void
snapshot: () => TerminalOutputSchedulerDebugSnapshot
}
}
type TerminalPtyOutputDebugSnapshot = {
hiddenRendererSkipCount: number
}
type TerminalOutputSchedulerDebugSnapshot = {
backgroundEnqueueCount: number
deferredForegroundEnqueueCount: number
foregroundWriteCount: number
backgroundWriteCount: number
deferredForegroundWriteCount: number
flushWriteCount: number
scheduledDrainCount: number
queuedTerminalCount: number
queuedChars: number
peakQueuedTerminalCount: number
peakQueuedChars: number
peakQueuedCharsByTerminal: number
droppedBacklogCount: number
drainWrites: number[]
}
type TerminalPtyAckGateSnapshot = {
gatedPtyCount: number
heldAckCount: number
heldAckChars: number
}
type MainPtyPressureDebugSnapshot = {
pendingPtyCount: number
pendingChars: number
maxPendingCharsByPty: number
rendererInFlightPtyCount: number
rendererInFlightChars: number
maxRendererInFlightCharsByPty: number
activeRendererPtyCount: number
flushScheduled: boolean
peakPendingChars: number
peakMaxPendingCharsByPty: number
peakRendererInFlightChars: number
peakMaxRendererInFlightCharsByPty: number
ackGatedFlushSkipCount: number
// Phase-4 hidden-delivery gate: bytes dropped in main after model ingestion.
hiddenDeliveryGatedPtyCount: number
deliveryInterestPtyCount: number
hiddenDeliveryDroppedChars: number
hiddenDeliveryDroppedChunks: number
pendingDroppedChars: number
}
const KEY_LATENCY_SAMPLES = 'abcdefghijklmnop'
const DEFAULT_SAME_WORKSPACE_PANES = 5
const DEFAULT_CROSS_WORKSPACE_PANES_PER_WORKTREE = 3
const DEFAULT_PRESSURE_BACKGROUND_PANES = 17
const DEFAULT_PRESSURE_OUTPUT_CHARS = 768 * 1024
const DEFAULT_HIDDEN_PRESSURE_PANES = 17
const HIDDEN_PRESSURE_START_DELAY_MS = 1200
const DEFAULT_FRAME_COUNT = 180
const DEFAULT_FRAME_INTERVAL_MS = 6
const MAIN_RENDERER_PRESSURE_TARGET_CHARS = 2 * 1024 * 1024
// Gate visible typing regressions with CI headroom above repeated local sub-50ms echo measurements.
const MAX_MEDIAN_KEY_LATENCY_MS = 75
const MAX_WORST_KEY_LATENCY_MS = 300
// Keep median latency strict; worst loaded echo can hit a ~3.1s runner stall and serves only as a hang detector.
const MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS = 3_500
// Revisit printf runs under background pressure plus whole-buffer polling, so it needs the loaded budget.
const MAX_REVISIT_LATENCY_UNDER_LOAD_MS = 3_000
// Why: bounds the longest renderer task while typing and the scroll probe's interval drift; a
// smoke gate for main-thread stalls, not a CPU lottery.
const MAX_TIMER_DRIFT_MS = 250
// Loaded timer drift may share ~3.1s runner starvation with worst echo; keep the strict unloaded baseline.
const MAX_TIMER_DRIFT_UNDER_LOAD_MS = 3_500
const MAX_SCROLL_LATENCY_MS = 150
// Why: byte-level peaks vary by drain quantum; the coarse guard matches the main-pressure scenario.
const MAX_RENDERER_SCHEDULER_QUEUED_CHARS = 5 * 1024 * 1024
function readPositiveInt(name: string, fallback: number): number {
const raw = process.env[name]
if (!raw) {
return fallback
}
const value = Number(raw)
return Number.isInteger(value) && value > 0 ? value : fallback
}
function readPositiveIntList(name: string): number[] {
const raw = process.env[name]
if (!raw) {
return []
}
return raw
.split(',')
.map((part) => Number(part.trim()))
.filter((value, index, values) => {
return Number.isInteger(value) && value > 1 && values.indexOf(value) === index
})
}
const SAME_WORKSPACE_PANES = readPositiveInt(
'ORCA_E2E_OPENCODE_SAME_WORKSPACE_PANES',
DEFAULT_SAME_WORKSPACE_PANES
)
const CROSS_WORKSPACE_PANES_PER_WORKTREE = readPositiveInt(
'ORCA_E2E_OPENCODE_CROSS_WORKSPACE_PANES',
DEFAULT_CROSS_WORKSPACE_PANES_PER_WORKTREE
)
const PRESSURE_BACKGROUND_PANES = readPositiveInt(
'ORCA_E2E_OPENCODE_PRESSURE_BACKGROUND_PANES',
DEFAULT_PRESSURE_BACKGROUND_PANES
)
const PRESSURE_OUTPUT_CHARS = readPositiveInt(
'ORCA_E2E_OPENCODE_PRESSURE_OUTPUT_CHARS',
DEFAULT_PRESSURE_OUTPUT_CHARS
)
const HIDDEN_PRESSURE_PANES = readPositiveInt(
'ORCA_E2E_OPENCODE_HIDDEN_PRESSURE_PANES',
DEFAULT_HIDDEN_PRESSURE_PANES
)
const FRAME_COUNT = readPositiveInt('ORCA_E2E_OPENCODE_FRAME_COUNT', DEFAULT_FRAME_COUNT)
const FRAME_INTERVAL_MS = readPositiveInt(
'ORCA_E2E_OPENCODE_FRAME_INTERVAL_MS',
DEFAULT_FRAME_INTERVAL_MS
)
const SCALE_SAME_WORKSPACE_PANES = readPositiveIntList('ORCA_E2E_OPENCODE_SCALE_PANES')
const SCALE_CROSS_WORKSPACE_PANES = readPositiveIntList(
'ORCA_E2E_OPENCODE_SCALE_CROSS_WORKSPACE_PANES'
)
const SCALE_PRESSURE_PANES = readPositiveIntList('ORCA_E2E_OPENCODE_SCALE_PRESSURE_PANES')
const SCALE_HIDDEN_PRESSURE_PANES = readPositiveIntList(
'ORCA_E2E_OPENCODE_SCALE_HIDDEN_PRESSURE_PANES'
)
function interactivePromptScript(runId: string): string {
return `
process.stdin.setEncoding('utf8')
if (process.stdin.isTTY) process.stdin.setRawMode(true)
process.stdin.resume()
let seq = 0
const interrupt = String.fromCharCode(3)
process.stdout.write('\\x1b]0;OpenCode load typing benchmark\\x07')
process.stdout.write('OPENCODE_TYPING_READY_${runId}\\n')
process.stdin.on('data', (chunk) => {
if (chunk.includes(interrupt)) {
process.exit(0)
}
for (const char of chunk) {
if (char === '\\r' || char === '\\n') continue
seq += 1
process.stdout.write('\\r\\x1b[2KOpenCode load prompt ' + seq + ': ' + char + ' OPENCODE_TYPING_KEY_${runId}_' + seq + '\\n')
}
})
`
}
function writeInteractivePromptScript(scriptPath: string, runId: string): void {
// Use any writable directory: scale-run prompt scripts need no git state and can outlive fixture-repo cleanup.
mkdirSync(path.dirname(scriptPath), { recursive: true })
writeFileSync(scriptPath, interactivePromptScript(runId))
}
function median(values: number[]): number {
const sorted = [...values].sort((a, b) => a - b)
return sorted[Math.floor(sorted.length / 2)] ?? 0
}
async function measureTypingDuringLoad(
page: Page,
scriptPath: string,
ptyId: string,
runId: string
): Promise<TypingMeasurement> {
await sendToTerminal(page, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
await waitForTerminalOutputForPtyId(page, ptyId, `OPENCODE_TYPING_READY_${runId}`, 10_000)
await focusActiveTerminalInput(page)
// Hidden-renderer intervals can drift while echo stays fast; use long tasks to measure actual main-thread blocking.
const eventLoop = await page.evaluateHandle(() => {
if (!PerformanceObserver.supportedEntryTypes.includes('longtask')) {
throw new Error('Long-task timing is unavailable in this renderer')
}
let longestTaskMs = 0
const record = (entries: PerformanceEntryList): void => {
for (const entry of entries) {
longestTaskMs = Math.max(longestTaskMs, entry.duration)
}
}
const observer = new PerformanceObserver((list) => record(list.getEntries()))
observer.observe({ type: 'longtask' })
return {
stop: () => {
record(observer.takeRecords())
observer.disconnect()
return longestTaskMs
}
}
})
const latencies: number[] = []
for (const [index, char] of [...KEY_LATENCY_SAMPLES].entries()) {
const marker = `OPENCODE_TYPING_KEY_${runId}_${index + 1}`
const start = performance.now()
await page.keyboard.type(char)
// Allow the loaded echo budget so slow input becomes a latency failure rather than a missing-output error.
await waitForMarkerLatency(page, marker, MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS)
latencies.push(performance.now() - start)
}
const maxTimerDriftMs = await eventLoop.evaluate((watcher) => watcher.stop())
await eventLoop.dispose()
return {
latencies,
medianLatencyMs: median(latencies),
worstLatencyMs: Math.max(...latencies),
maxTimerDriftMs,
frameCount: FRAME_COUNT
}
}
async function resetTerminalPtyOutputDebug(page: Page): Promise<void> {
await page.evaluate(async () => {
;(window as SyntheticOpenCodeWindow).__terminalPtyOutputDebug?.reset()
;(window as SyntheticOpenCodeWindow).__terminalOutputSchedulerDebug?.reset()
await window.api.pty.resetRendererDeliveryDebug()
})
}
async function readTerminalPtyOutputDebug(
page: Page
): Promise<TerminalPtyOutputDebugSnapshot | null> {
return page.evaluate(() => {
return (window as SyntheticOpenCodeWindow).__terminalPtyOutputDebug?.snapshot() ?? null
})
}
async function readTerminalOutputSchedulerDebug(
page: Page
): Promise<TerminalOutputSchedulerDebugSnapshot | null> {
return page.evaluate(() => {
return (window as SyntheticOpenCodeWindow).__terminalOutputSchedulerDebug?.snapshot() ?? null
})
}
async function readMainPtyPressureDebug(page: Page): Promise<MainPtyPressureDebugSnapshot | null> {
return page.evaluate(async () => {
return window.api.pty.getRendererDeliveryDebugSnapshot()
})
}
async function holdTerminalAckGate(page: Page, ptyIds: string[]): Promise<void> {
await page.evaluate((ids) => {
const gate = (window as SyntheticOpenCodeWindow).__terminalPtyAckGate
if (!gate) {
throw new Error('terminal PTY ACK gate is unavailable')
}
gate.hold(ids)
}, ptyIds)
}
async function releaseTerminalAckGate(page: Page): Promise<void> {
await page.evaluate(() => {
;(window as SyntheticOpenCodeWindow).__terminalPtyAckGate?.release()
})
}
async function readTerminalAckGateDebug(page: Page): Promise<TerminalPtyAckGateSnapshot | null> {
return page.evaluate(() => {
return (window as SyntheticOpenCodeWindow).__terminalPtyAckGate?.snapshot() ?? null
})
}
async function waitForMainPtyPressureBacklog(page: Page): Promise<MainPtyPressureDebugSnapshot> {
let lastSnapshot: MainPtyPressureDebugSnapshot | null = null
await expect
.poll(
async () => {
lastSnapshot = await readMainPtyPressureDebug(page)
return (
(lastSnapshot?.peakRendererInFlightChars ?? 0) >= MAIN_RENDERER_PRESSURE_TARGET_CHARS &&
(lastSnapshot?.peakPendingChars ?? 0) > 0 &&
(lastSnapshot?.ackGatedFlushSkipCount ?? 0) > 0
)
},
{
timeout: 20_000,
message: 'Main PTY renderer delivery pressure did not build up'
}
)
.toBe(true)
if (!lastSnapshot) {
throw new Error('Main PTY pressure snapshot unavailable')
}
return lastSnapshot
}
function annotateTypingMeasurement(
testInfo: TestInfo,
type: string,
paneCount: number,
measurement: TypingMeasurement,
debug: TerminalPtyOutputDebugSnapshot | null = null,
scheduler: TerminalOutputSchedulerDebugSnapshot | null = null,
mainPressure: MainPtyPressureDebugSnapshot | null = null,
ackGate: TerminalPtyAckGateSnapshot | null = null
): void {
const hiddenSkipSummary = debug ? ` hiddenRendererSkips=${debug.hiddenRendererSkipCount}` : ''
const schedulerSummary = scheduler
? ` deferredForegroundEnqueue=${scheduler.deferredForegroundEnqueueCount} deferredForegroundWrite=${scheduler.deferredForegroundWriteCount} scheduledDrains=${scheduler.scheduledDrainCount} rendererQueuedTerminals=${scheduler.queuedTerminalCount} rendererQueuedChars=${scheduler.queuedChars} rendererPeakQueuedTerminals=${scheduler.peakQueuedTerminalCount} rendererPeakQueuedChars=${scheduler.peakQueuedChars} rendererPeakQueuedCharsByTerminal=${scheduler.peakQueuedCharsByTerminal} rendererDroppedBacklogs=${scheduler.droppedBacklogCount}`
: ''
const mainPressureSummary = mainPressure
? ` mainPendingPtys=${mainPressure.pendingPtyCount} mainPendingChars=${mainPressure.pendingChars} mainMaxPendingChars=${mainPressure.maxPendingCharsByPty} mainInFlightPtys=${mainPressure.rendererInFlightPtyCount} mainInFlightChars=${mainPressure.rendererInFlightChars} mainMaxInFlightChars=${mainPressure.maxRendererInFlightCharsByPty} mainActivePtys=${mainPressure.activeRendererPtyCount} mainFlushScheduled=${mainPressure.flushScheduled} mainPeakPendingChars=${mainPressure.peakPendingChars} mainPeakMaxPendingChars=${mainPressure.peakMaxPendingCharsByPty} mainPeakInFlightChars=${mainPressure.peakRendererInFlightChars} mainPeakMaxInFlightChars=${mainPressure.peakMaxRendererInFlightCharsByPty} mainAckGatedFlushSkips=${mainPressure.ackGatedFlushSkipCount} mainHiddenGatedPtys=${mainPressure.hiddenDeliveryGatedPtyCount} mainHiddenDroppedChars=${mainPressure.hiddenDeliveryDroppedChars} mainPendingDroppedChars=${mainPressure.pendingDroppedChars}`
: ''
const ackGateSummary = ackGate
? ` heldAckPtys=${ackGate.heldAckCount} heldAckChars=${ackGate.heldAckChars} gatedAckPtys=${ackGate.gatedPtyCount}`
: ''
testInfo.annotations.push({
type,
description: `panes=${paneCount} frames=${measurement.frameCount} median=${measurement.medianLatencyMs.toFixed(
1
)}ms worst=${measurement.worstLatencyMs.toFixed(
1
)}ms maxTimerDrift=${measurement.maxTimerDriftMs.toFixed(1)}ms samples=${measurement.latencies
.map((value) => value.toFixed(1))
.join(',')}${hiddenSkipSummary}${schedulerSummary}${mainPressureSummary}${ackGateSummary}`
})
}
async function measureCrossWorkspaceTypingDuringHiddenLoad({
orcaPage,
testRepoPath,
hiddenPaneCount,
annotationType,
testInfo
}: {
orcaPage: Page
testRepoPath: string
hiddenPaneCount: number
annotationType: string
testInfo: TestInfo
}): Promise<void> {
await waitForSessionReady(orcaPage)
const firstWorktreeId = await waitForActiveWorktree(orcaPage)
const allWorktreeIds = await getAllWorktreeIds(orcaPage)
const secondWorktreeId = allWorktreeIds.find((id) => id !== firstWorktreeId)
test.skip(!secondWorktreeId, 'OpenCode cross-workspace load needs the seeded secondary worktree')
if (!secondWorktreeId) {
return
}
await switchToWorktree(orcaPage, secondWorktreeId)
const hiddenPanes = await ensureActiveWorktreePaneLoad(orcaPage, hiddenPaneCount)
await switchToWorktree(orcaPage, firstWorktreeId)
await expect.poll(() => getActiveWorktreeId(orcaPage), { timeout: 10_000 }).toBe(firstWorktreeId)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
const typingPtyId = await waitForActivePanePtyId(orcaPage)
const runId = randomUUID()
const scriptPath = path.join(testRepoPath, `.orca-opencode-cross-${hiddenPaneCount}-${runId}.mjs`)
writeInteractivePromptScript(scriptPath, runId)
await resetTerminalPtyOutputDebug(orcaPage)
const load = await startSyntheticOpenCodeInjection({
frameCount: FRAME_COUNT,
intervalMs: FRAME_INTERVAL_MS,
page: orcaPage,
paneKeys: hiddenPanes.map((pane) => pane.paneKey)
})
try {
const measurement = await measureTypingDuringLoad(orcaPage, scriptPath, typingPtyId, runId)
const debug = await readTerminalPtyOutputDebug(orcaPage)
const scheduler = await readTerminalOutputSchedulerDebug(orcaPage)
const mainPressure = await readMainPtyPressureDebug(orcaPage)
annotateTypingMeasurement(
testInfo,
annotationType,
hiddenPanes.length + 1,
measurement,
debug,
scheduler,
mainPressure
)
expect(scheduler?.droppedBacklogCount ?? Number.POSITIVE_INFINITY).toBe(0)
expect(measurement.medianLatencyMs).toBeLessThan(MAX_MEDIAN_KEY_LATENCY_MS)
expect(measurement.worstLatencyMs).toBeLessThan(MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS)
expect(measurement.maxTimerDriftMs).toBeLessThan(MAX_TIMER_DRIFT_UNDER_LOAD_MS)
} finally {
await load.stop()
await sendToTerminal(orcaPage, typingPtyId, '\x03').catch(() => undefined)
rmSync(scriptPath, { force: true })
}
}
async function runConfiguredMainPressureScenario({
annotationSuffix,
backgroundPaneCount,
orcaPage,
testInfo,
testRepoPath
}: {
annotationSuffix: string
backgroundPaneCount: number
orcaPage: Page
testInfo: TestInfo
testRepoPath: string
}): Promise<void> {
await runMainPressureScenario({
annotationSuffix,
backgroundPaneCount,
orcaPage,
pressureOutputChars: PRESSURE_OUTPUT_CHARS,
testInfo,
testRepoPath,
maxMedianKeyLatencyMs: MAX_MEDIAN_KEY_LATENCY_MS,
maxScrollLatencyMs: MAX_SCROLL_LATENCY_MS,
// Background pressure makes drift runner-dependent; the 75ms median remains the responsiveness gate.
maxTimerDriftMs: MAX_TIMER_DRIFT_UNDER_LOAD_MS,
maxWorstKeyLatencyMs: MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS,
deps: terminalLoadScenarioDeps
})
}
const terminalLoadScenarioDeps = {
annotateTypingMeasurement,
ensureActiveWorktreePaneLoad,
focusPane,
holdTerminalAckGate,
measureTypingDuringLoad,
readMainPtyPressureDebug,
readTerminalAckGateDebug,
readTerminalOutputSchedulerDebug,
readTerminalPtyOutputDebug,
releaseTerminalAckGate,
resetTerminalPtyOutputDebug,
waitForActiveWorktree,
waitForMainPtyPressureBacklog,
waitForSessionReady,
writeInteractivePromptScript
}
test.describe('Artificial OpenCode terminal load', () => {
test.describe.configure({ mode: 'serial' })
test('measures baseline typing responsiveness with one active terminal', async ({
orcaPage,
testRepoPath
}, testInfo) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage, 30_000)
const typingPtyId = await waitForActivePanePtyId(orcaPage)
const runId = randomUUID()
const scriptPath = path.join(testRepoPath, `.orca-opencode-baseline-typing-${runId}.mjs`)
writeInteractivePromptScript(scriptPath, runId)
await resetTerminalPtyOutputDebug(orcaPage)
try {
const measurement = await measureTypingDuringLoad(orcaPage, scriptPath, typingPtyId, runId)
const debug = await readTerminalPtyOutputDebug(orcaPage)
const scheduler = await readTerminalOutputSchedulerDebug(orcaPage)
const mainPressure = await readMainPtyPressureDebug(orcaPage)
annotateTypingMeasurement(
testInfo,
'opencode-baseline-typing',
1,
measurement,
debug,
scheduler,
mainPressure
)
expect(measurement.medianLatencyMs).toBeLessThan(MAX_MEDIAN_KEY_LATENCY_MS)
expect(measurement.worstLatencyMs).toBeLessThan(MAX_WORST_KEY_LATENCY_MS)
expect(measurement.maxTimerDriftMs).toBeLessThan(MAX_TIMER_DRIFT_MS)
} finally {
await sendToTerminal(orcaPage, typingPtyId, '\x03').catch(() => undefined)
rmSync(scriptPath, { force: true })
}
})
test('keeps typing responsive while same-workspace panes redraw simultaneously', async ({
orcaPage,
testRepoPath
}, testInfo) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
const panes = await ensureActiveWorktreePaneLoad(orcaPage, SAME_WORKSPACE_PANES)
const [typingPane, ...loadPanes] = panes
await focusPane(orcaPage, typingPane.paneKey)
const runId = randomUUID()
const scriptPath = path.join(testRepoPath, `.orca-opencode-typing-${runId}.mjs`)
writeInteractivePromptScript(scriptPath, runId)
await resetTerminalPtyOutputDebug(orcaPage)
const load = await startSyntheticOpenCodeInjection({
frameCount: FRAME_COUNT,
intervalMs: FRAME_INTERVAL_MS,
page: orcaPage,
paneKeys: loadPanes.map((pane) => pane.paneKey)
})
try {
const measurement = await measureTypingDuringLoad(
orcaPage,
scriptPath,
typingPane.ptyId,
runId
)
annotateTypingMeasurement(
testInfo,
'opencode-same-workspace-typing',
panes.length,
measurement,
await readTerminalPtyOutputDebug(orcaPage),
await readTerminalOutputSchedulerDebug(orcaPage),
await readMainPtyPressureDebug(orcaPage)
)
expect(measurement.medianLatencyMs).toBeLessThan(MAX_MEDIAN_KEY_LATENCY_MS)
expect(measurement.worstLatencyMs).toBeLessThan(MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS)
expect(measurement.maxTimerDriftMs).toBeLessThan(MAX_TIMER_DRIFT_UNDER_LOAD_MS)
} finally {
await load.stop()
await sendToTerminal(orcaPage, typingPane.ptyId, '\x03').catch(() => undefined)
rmSync(scriptPath, { force: true })
}
})
test('keeps active typing responsive while background PTYs are ACK-backpressured', async ({
orcaPage,
testRepoPath
}, testInfo) => {
await runConfiguredMainPressureScenario({
orcaPage,
testRepoPath,
backgroundPaneCount: PRESSURE_BACKGROUND_PANES,
annotationSuffix: '',
testInfo
})
})
test('keeps renderer backpressure bounded across worktree revisit', async ({
orcaPage,
testRepoPath
}, testInfo) => {
await runRendererBackpressureRevisitScenario({
backgroundPaneCount: PRESSURE_BACKGROUND_PANES,
deps: terminalLoadScenarioDeps,
mainRendererPressureTargetChars: MAIN_RENDERER_PRESSURE_TARGET_CHARS,
maxMedianKeyLatencyMs: MAX_MEDIAN_KEY_LATENCY_MS,
maxRendererSchedulerQueuedChars: MAX_RENDERER_SCHEDULER_QUEUED_CHARS,
maxRevisitLatencyMs: MAX_REVISIT_LATENCY_UNDER_LOAD_MS,
// Use loaded worst-echo/drift budgets under background pressure; strict budgets belong to the unloaded baseline.
maxTimerDriftMs: MAX_TIMER_DRIFT_UNDER_LOAD_MS,
maxWorstKeyLatencyMs: MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS,
orcaPage,
pressureOutputChars: PRESSURE_OUTPUT_CHARS,
testInfo,
testRepoPath
})
})
for (const paneCount of SCALE_PRESSURE_PANES) {
test(`keeps active interactions responsive at ${paneCount} ACK-backpressured OpenCode PTYs`, async ({
orcaPage,
testRepoPath
}, testInfo) => {
await runConfiguredMainPressureScenario({
orcaPage,
testRepoPath,
backgroundPaneCount: paneCount,
annotationSuffix: `-${paneCount}`,
testInfo
})
})
}
for (const paneCount of SCALE_SAME_WORKSPACE_PANES) {
test(`keeps typing responsive at ${paneCount} same-workspace OpenCode panes`, async ({
orcaPage,
testRepoPath
}, testInfo) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
const panes = await ensureActiveWorktreePaneLoad(orcaPage, paneCount)
const [typingPane, ...loadPanes] = panes
await focusPane(orcaPage, typingPane.paneKey)
const runId = randomUUID()
const scriptPath = path.join(testRepoPath, `.orca-opencode-scale-${paneCount}-${runId}.mjs`)
writeInteractivePromptScript(scriptPath, runId)
await resetTerminalPtyOutputDebug(orcaPage)
const load = await startSyntheticOpenCodeInjection({
frameCount: FRAME_COUNT,
intervalMs: FRAME_INTERVAL_MS,
page: orcaPage,
paneKeys: loadPanes.map((pane) => pane.paneKey)
})
try {
const measurement = await measureTypingDuringLoad(
orcaPage,
scriptPath,
typingPane.ptyId,
runId
)
annotateTypingMeasurement(
testInfo,
`opencode-scale-same-workspace-${paneCount}`,
panes.length,
measurement,
await readTerminalPtyOutputDebug(orcaPage),
await readTerminalOutputSchedulerDebug(orcaPage),
await readMainPtyPressureDebug(orcaPage)
)
expect(measurement.medianLatencyMs).toBeLessThan(MAX_MEDIAN_KEY_LATENCY_MS)
expect(measurement.worstLatencyMs).toBeLessThan(MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS)
expect(measurement.maxTimerDriftMs).toBeLessThan(MAX_TIMER_DRIFT_UNDER_LOAD_MS)
} finally {
await load.stop()
await sendToTerminal(orcaPage, typingPane.ptyId, '\x03').catch(() => undefined)
rmSync(scriptPath, { force: true })
}
})
}
test('keeps typing responsive while another workspace streams OpenCode-style output', async ({
orcaPage,
testRepoPath
}, testInfo) => {
await measureCrossWorkspaceTypingDuringHiddenLoad({
orcaPage,
testRepoPath,
hiddenPaneCount: CROSS_WORKSPACE_PANES_PER_WORKTREE,
annotationType: 'opencode-cross-workspace-typing',
testInfo
})
})
async function runConfiguredHiddenRealPtyPressureScenario(
orcaPage: Page,
testRepoPath: string,
testInfo: TestInfo,
hiddenPaneCount: number,
annotationSuffix?: string,
pressureOutputMode?: HiddenPressureOutputMode
): Promise<void> {
await runHiddenRealPtyPressureScenario({
orcaPage,
testRepoPath,
annotationSuffix,
hiddenPaneCount,
pressureOutputChars: PRESSURE_OUTPUT_CHARS,
pressureOutputMode,
pressureStartDelayMs: HIDDEN_PRESSURE_START_DELAY_MS,
testInfo,
deps: terminalLoadScenarioDeps
})
}
const hiddenPressureCases: {
title: string
suffix?: string
mode?: HiddenPressureOutputMode
}[] = [
{ title: 'keeps typing responsive while hidden real PTYs are ACK-backpressured' },
// Assert hidden delivery is withheld in main; renderer skip-scanning was removed.
{
title: 'withholds renderer delivery for plain hidden PTY output while preserving restore',
suffix: '-plain',
mode: 'plain'
},
{
title: 'withholds renderer delivery for Latin hidden PTY output while preserving restore',
suffix: '-latin',
mode: 'latin'
},
{
title:
'withholds renderer delivery for title-only hidden PTY output while preserving restore',
suffix: '-title',
mode: 'title'
},
{
title: 'restores rich hidden model output under ACK-backpressured PTY output',
suffix: '-rich-model',
mode: 'rich-model'
}
]
for (const hiddenPressureCase of hiddenPressureCases) {
test(hiddenPressureCase.title, async ({ orcaPage, testRepoPath }, testInfo) => {
await runConfiguredHiddenRealPtyPressureScenario(
orcaPage,
testRepoPath,
testInfo,
HIDDEN_PRESSURE_PANES,
hiddenPressureCase.suffix,
hiddenPressureCase.mode
)
})
}
for (const paneCount of SCALE_HIDDEN_PRESSURE_PANES) {
test(`keeps hidden restore responsive with ${paneCount} ACK-backpressured real PTYs`, async ({
orcaPage,
testRepoPath
}, testInfo) => {
await runConfiguredHiddenRealPtyPressureScenario(
orcaPage,
testRepoPath,
testInfo,
paneCount,
`-${paneCount}`
)
})
}
for (const paneCount of SCALE_CROSS_WORKSPACE_PANES) {
test(`keeps typing responsive with ${paneCount} hidden cross-workspace OpenCode panes`, async ({
orcaPage,
testRepoPath
}, testInfo) => {
await measureCrossWorkspaceTypingDuringHiddenLoad({
orcaPage,
testRepoPath,
hiddenPaneCount: paneCount,
annotationType: `opencode-scale-cross-workspace-${paneCount}`,
testInfo
})
})
}
})