Gate releases on terminal rendering golden

This commit is contained in:
Neil
2026-06-08 20:49:17 -07:00
committed by GitHub
parent 85a5dc0a06
commit 4660a25f09
4 changed files with 220 additions and 16 deletions
+160
View File
@@ -526,6 +526,165 @@ jobs:
with:
ref: refs/tags/${{ needs.cut.outputs.tag }}
terminal-rendering-golden:
needs: cut
if: needs.cut.outputs.should_release == 'true'
name: terminal rendering golden ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
env:
NODE_OPTIONS: --max-old-space-size=4096
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
- os: macos-15
platform: mac
- os: windows-latest
platform: windows
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: refs/tags/${{ needs.cut.outputs.tag }}
- name: Install native build tools
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y build-essential python3 xvfb
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
run_install: false
# Why: Linux terminal golden E2E uses the same native install path as
# release CI, which needs pnpm to bypass its non-executable gyp_main.py.
- name: Use external node-gyp to avoid pnpm's bundled copy (Linux only)
if: runner.os == 'Linux'
run: |
npm install -g node-gyp@11.5.0
echo "npm_config_node_gyp=$(npm root -g)/node-gyp/bin/node-gyp.js" >> "$GITHUB_ENV"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Electron app for terminal rendering golden
run: npx electron-vite build --mode e2e
- name: Run terminal rendering golden on Linux
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-golden
- name: Run terminal rendering golden on macOS
if: runner.os == 'macOS'
run: env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-golden
- name: Run terminal rendering golden on Windows
if: runner.os == 'Windows'
shell: pwsh
run: |
$env:SKIP_BUILD = '1'
$env:ORCA_E2E_FORWARD_APP_LOGS = '1'
pnpm run test:e2e:terminal-rendering-golden
- name: Upload Playwright traces
if: failure()
uses: actions/upload-artifact@v7
with:
name: terminal-rendering-golden-${{ matrix.platform }}-playwright-traces
path: test-results/
retention-days: 7
if-no-files-found: ignore
# Why: these broader terminal rendering repros are useful release evidence,
# but they include heavier app-like flows and must not block publishing.
terminal-rendering-release-evidence:
needs: cut
if: needs.cut.outputs.should_release == 'true'
continue-on-error: true
name: terminal rendering release evidence ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
timeout-minutes: 35
env:
NODE_OPTIONS: --max-old-space-size=4096
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
- os: macos-15
platform: mac
- os: windows-latest
platform: windows
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: refs/tags/${{ needs.cut.outputs.tag }}
- name: Install native build tools
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y build-essential python3 xvfb
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
run_install: false
# Why: keep the non-blocking evidence lane on the same Linux native
# install path as the blocking golden and release build jobs.
- name: Use external node-gyp to avoid pnpm's bundled copy (Linux only)
if: runner.os == 'Linux'
run: |
npm install -g node-gyp@11.5.0
echo "npm_config_node_gyp=$(npm root -g)/node-gyp/bin/node-gyp.js" >> "$GITHUB_ENV"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Electron app for terminal rendering evidence
run: npx electron-vite build --mode e2e
- name: Run terminal rendering evidence on Linux
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-release-evidence
- name: Run terminal rendering evidence on macOS
if: runner.os == 'macOS'
run: env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-release-evidence
- name: Run terminal rendering evidence on Windows
if: runner.os == 'Windows'
shell: pwsh
run: |
$env:SKIP_BUILD = '1'
$env:ORCA_E2E_FORWARD_APP_LOGS = '1'
pnpm run test:e2e:terminal-rendering-release-evidence
- name: Upload Playwright traces
if: failure()
uses: actions/upload-artifact@v7
with:
name: terminal-rendering-release-evidence-${{ matrix.platform }}-playwright-traces
path: test-results/
retention-days: 7
if-no-files-found: ignore
build:
needs:
- cut
@@ -771,6 +930,7 @@ jobs:
needs:
- cut
- build
- terminal-rendering-golden
runs-on: ubuntu-latest
permissions:
contents: write
@@ -25,6 +25,8 @@ describe('Electron runtime package contract', () => {
'build:mac:release',
'build:linux',
'test:e2e',
'test:e2e:terminal-rendering-golden',
'test:e2e:terminal-rendering-release-evidence',
'test:e2e:headful'
]
@@ -217,11 +219,18 @@ describe('Electron runtime package contract', () => {
const goldenWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/golden-e2e-experiment.yml'), 'utf8')
)
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
)
const steps = goldenWorkflow.jobs['golden-e2e'].steps
const linuxRunStep = steps.find((step) => step.name === 'Run golden E2E tests on Linux')
const macRunStep = steps.find((step) => step.name === 'Run golden E2E tests on macOS')
const windowsRunStep = steps.find((step) => step.name === 'Run golden E2E tests on Windows')
const pullRequestPaths = goldenWorkflow.on.pull_request.paths
const releaseGoldenJob = releaseWorkflow.jobs['terminal-rendering-golden']
const releaseEvidenceJob = releaseWorkflow.jobs['terminal-rendering-release-evidence']
const releaseBuildNeeds = releaseWorkflow.jobs.build.needs
const publishReleaseNeeds = releaseWorkflow.jobs['publish-release'].needs
expect(packageScripts['test:e2e:terminal-rendering-golden']).toContain(
'@terminal-rendering-golden'
@@ -232,11 +241,36 @@ describe('Electron runtime package contract', () => {
expect(packageScripts['test:e2e:terminal-rendering-golden']).not.toContain(
'terminal-long-table-scroll-restore.spec.ts'
)
expect(packageScripts['test:e2e:terminal-rendering-release-evidence']).toContain(
'terminal-opencode-emoji-table-rendering.spec.ts'
)
expect(packageScripts['test:e2e:terminal-rendering-release-evidence']).toContain(
'terminal-long-table-scroll-restore.spec.ts'
)
for (const runStep of [linuxRunStep, macRunStep, windowsRunStep]) {
expect(runStep.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
}
expect(pullRequestPaths).toContain('tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts')
expect(pullRequestPaths).toContain('tests/e2e/fixtures/terminal-emoji-table.md')
expect(pullRequestPaths).toContain('src/renderer/src/lib/pane-manager/**')
expect(releaseBuildNeeds).not.toContain('terminal-rendering-golden')
expect(releaseBuildNeeds).not.toContain('terminal-rendering-release-evidence')
expect(publishReleaseNeeds).toContain('terminal-rendering-golden')
expect(publishReleaseNeeds).toContain('build')
expect(publishReleaseNeeds).not.toContain('terminal-rendering-release-evidence')
expect(releaseGoldenJob['continue-on-error']).toBeUndefined()
expect(releaseGoldenJob.strategy.matrix.include.map(({ platform }) => platform).sort()).toEqual(
['linux', 'mac', 'windows']
)
expect(releaseGoldenJob.steps.map((step) => step.run ?? '')).toContain(
'xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-golden'
)
expect(releaseEvidenceJob['continue-on-error']).toBe(true)
expect(
releaseEvidenceJob.strategy.matrix.include.map(({ platform }) => platform).sort()
).toEqual(['linux', 'mac', 'windows'])
expect(releaseEvidenceJob.steps.map((step) => step.run ?? '')).toContain(
'xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-release-evidence'
)
})
})
+1
View File
@@ -61,6 +61,7 @@
"build:linux": "pnpm run build:desktop && pnpm run ensure:electron-runtime && electron-builder --config config/electron-builder.config.cjs --linux",
"test:e2e": "pnpm run ensure:electron-runtime && npx playwright test --config tests/playwright.config.ts --project electron-headless",
"test:e2e:terminal-rendering-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts --grep @terminal-rendering-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:terminal-rendering-release-evidence": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-opencode-emoji-table-rendering.spec.ts tests/e2e/terminal-long-table-scroll-restore.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=2",
"test:e2e:terminal-perf": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-typing-latency.spec.ts tests/e2e/terminal-foreground-redraw-freeze.spec.ts tests/e2e/terminal-output-scheduler.spec.ts tests/e2e/terminal-hidden-tui-visual-restore.spec.ts tests/e2e/artificial-opencode-terminal-load.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=2",
"test:e2e:terminal-perf:scale": "pnpm run ensure:electron-runtime && node config/scripts/run-terminal-scale-perf-e2e.mjs",
"test:e2e:terminal-perf:scale:report": "pnpm run ensure:electron-runtime && node config/scripts/run-terminal-scale-perf-report-gate.mjs",
@@ -269,28 +269,37 @@ async function readVisibleSingerRowGeometry(page: Page): Promise<{
throw new Error('Active terminal DOM unavailable')
}
const screenRect = screen.getBoundingClientRect()
if (!rows) {
const buffer = pane.terminal.buffer.active
const line = Array.from(
{ length: pane.terminal.rows },
(_, row) => buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? ''
const buffer = pane.terminal.buffer.active
const visibleLine = Array.from(
{ length: pane.terminal.rows },
(_, row) => buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? ''
).find((text) => text.includes('Singer'))
const scrollbackLine =
visibleLine ??
Array.from(
{ length: buffer.baseY + buffer.length },
(_, index) => buffer.getLine(index)?.translateToString(true) ?? ''
).find((text) => text.includes('Singer'))
if (!line) {
throw new Error('Singer row buffer line unavailable')
}
const cellWidth = pane.terminal._core?._renderService?.dimensions?.css?.cell?.width ?? 0
return {
cols: pane.terminal.cols,
screenRight: screenRect.right,
rowRight: screenRect.left + pane.terminal.cols * cellWidth,
rowText: line
}
if (!scrollbackLine) {
throw new Error('Singer row buffer line unavailable')
}
const cellWidth = pane.terminal._core?._renderService?.dimensions?.css?.cell?.width ?? 0
const bufferGeometry = {
cols: pane.terminal.cols,
screenRight: screenRect.right,
rowRight: screenRect.left + pane.terminal.cols * cellWidth,
rowText: scrollbackLine
}
if (!rows) {
return bufferGeometry
}
const row = Array.from(rows.children).find((element) =>
(element.textContent ?? '').includes('Singer')
) as HTMLElement | undefined
if (!row) {
throw new Error('Singer row DOM unavailable')
// Why: xterm can repaint DOM rows between scroll and measurement; the
// terminal buffer still gives a stable right-edge bound for the golden.
return bufferGeometry
}
const rowRect = row.getBoundingClientRect()
return {