mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Gate releases on terminal rendering golden
This commit is contained in:
@@ -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'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user