Use runner Node executable and platform quoting in terminal E2E tests (#5890)

Ensure terminal E2E probes run reliably on Windows CI, where the shell
does not always inherit setup-node's PATH.

- Add `nodeTerminalCommand` helper to resolve and quote `process.execPath`
  for either POSIX shells or PowerShell depending on the platform.
- Update terminal tests and probes to use the new helper instead of
  hardcoded `node` invocations.
This commit is contained in:
Jinjing
2026-06-20 01:27:41 -07:00
committed by GitHub
parent e2c8a66ee0
commit 55055b42af
6 changed files with 37 additions and 8 deletions
+5 -1
View File
@@ -7,6 +7,7 @@ import {
waitForPtyPaneMounted,
waitForPtyShellEcho
} from './terminal-pty-readiness'
import { nodeTerminalCommand } from './terminal-node-command'
type TerminalColumnProbeWindow = Window & {
__store?: {
@@ -87,7 +88,10 @@ export async function waitForPtyColumnsAtMost(
await sendToTerminal(
page,
ptyId,
`node -e ${JSON.stringify(`console.log('${marker}:' + (process.stdout.columns || 0))`)}\r`
`${nodeTerminalCommand([
'-e',
`console.log('${marker}:' + (process.stdout.columns || 0))`
])}\r`
)
const probeDeadline = Date.now() + Math.min(5_000, Math.max(0, deadline - Date.now()))
while (Date.now() < probeDeadline) {
@@ -21,6 +21,7 @@ import {
waitForPtyColumnsAtMost,
waitForRenderedTerminalColumnsAtMost
} from './terminal-column-probes'
import { nodeTerminalCommand } from './terminal-node-command'
import { waitForPtyShellEcho } from './terminal-pty-readiness'
type TerminalRenderDiagnostics = {
@@ -620,7 +621,7 @@ test.describe('Terminal long table scroll restore repro', () => {
writeFileSync(scriptPath, longMarkdownTableScript(runId))
try {
await sendToTerminal(orcaPage, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
await sendToTerminal(orcaPage, ptyId, `${nodeTerminalCommand([scriptPath])}\r`)
await orcaPage.waitForTimeout(80)
await switchToWorktree(orcaPage, secondWorktreeId)
await waitForActiveTerminalManager(orcaPage, 30_000)
@@ -689,7 +690,7 @@ test.describe('Terminal long table scroll restore repro', () => {
writeFileSync(scriptPath, narrowSignerMarkdownTableScript(runId))
try {
await sendToTerminal(orcaPage, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
await sendToTerminal(orcaPage, ptyId, `${nodeTerminalCommand([scriptPath])}\r`)
await orcaPage.waitForTimeout(80)
await switchToWorktree(orcaPage, secondWorktreeId)
await waitForActiveTerminalManager(orcaPage, 30_000)
@@ -770,7 +771,7 @@ test.describe('Terminal long table scroll restore repro', () => {
writeFileSync(scriptPath, emojiFixtureMarkdownTableScript(EMOJI_TABLE_FIXTURE, runId))
try {
await sendToTerminal(orcaPage, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
await sendToTerminal(orcaPage, ptyId, `${nodeTerminalCommand([scriptPath])}\r`)
await orcaPage.waitForTimeout(80)
await switchToWorktree(orcaPage, secondWorktreeId)
await waitForActiveTerminalManager(orcaPage, 30_000)
+20
View File
@@ -0,0 +1,20 @@
function quotePosixShellArg(value: string): string {
return `'${value.replaceAll("'", "'\\''")}'`
}
function quotePowerShellArg(value: string): string {
return `'${value.replaceAll("'", "''")}'`
}
function quoteTerminalArg(value: string): string {
return process.platform === 'win32' ? quotePowerShellArg(value) : quotePosixShellArg(value)
}
export function nodeTerminalCommand(args: readonly string[]): string {
const nodeExecutable = quoteTerminalArg(process.execPath)
const executable = process.platform === 'win32' ? `& ${nodeExecutable}` : nodeExecutable
// Why: Windows CI shells do not always inherit setup-node's PATH, so E2E
// terminal probes must invoke the runner's Node executable directly.
return [executable, ...args.map(quoteTerminalArg)].join(' ')
}
@@ -15,6 +15,7 @@ import {
analyzeRasterCursorCells,
type TerminalRasterProbeTarget
} from './terminal-cursor-raster-probe'
import { nodeTerminalCommand } from './terminal-node-command'
type TerminalRenderState = {
coreCursorHidden: boolean | null
@@ -270,7 +271,7 @@ test.describe('OpenCode emoji table terminal rendering', () => {
const scriptPath = path.join(testRepoPath, `.orca-opencode-emoji-table-${runId}.mjs`)
writeFileSync(scriptPath, emojiTableScript(marker))
try {
await sendToTerminal(orcaPage, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
await sendToTerminal(orcaPage, ptyId, `${nodeTerminalCommand([scriptPath])}\r`)
await waitForTerminalOutput(orcaPage, marker, 10_000)
await orcaPage.waitForTimeout(250)
await forceCursorProbeTheme(orcaPage)
+4 -2
View File
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto'
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
import { sendToTerminal } from './helpers/terminal'
import { nodeTerminalCommand } from './terminal-node-command'
type TerminalPtyReadinessWindow = Window & {
__paneManagers?: Map<
@@ -67,9 +68,10 @@ export async function waitForPtyPaneMounted(
function encodedMarkerCommand(marker: string): string {
const encoded = Buffer.from(marker, 'utf8').toString('base64')
return `node -e ${JSON.stringify(
return `${nodeTerminalCommand([
'-e',
`console.log(Buffer.from('${encoded}', 'base64').toString('utf8'))`
)}\r`
])}\r`
}
export async function waitForPtyShellEcho(
@@ -18,6 +18,7 @@ import {
waitForTerminalOutput
} from './helpers/terminal'
import { scrollActiveTerminalToText } from './artificial-opencode-active-terminal-scroll'
import { nodeTerminalCommand } from './terminal-node-command'
type BrowserTerminalPane = {
terminal: {
@@ -522,7 +523,7 @@ test.describe('Terminal raw emoji table scroll restore repro', () => {
const frameTailMarker = rawEmojiFixtureFrameTailMarker(runId)
// Why: the fixture marker is the shell-readiness signal here; an extra
// Ctrl+C/Ctrl+U preflight can race Windows ConPTY startup and eat input.
await sendToTerminal(orcaPage, ptyId, `node ${JSON.stringify(scriptPath)}\r`)
await sendToTerminal(orcaPage, ptyId, `${nodeTerminalCommand([scriptPath])}\r`)
// Why: Windows ConPTY can return the PowerShell prompt while xterm is
// still flushing synchronized output if the pane is hidden immediately.
// This golden is about restored table geometry, not shell-flush timing.