Files
orca/tests/e2e/global-setup.ts
T
Younghwan ChaeandNeil 46c0a1f453 test(e2e): skip contributor git hooks and signing when seeding fixture repos (#31591)
Global setup and the worker-scoped fallback now share one seedTestRepository()
that runs its commit and worktree add with per-command
`-c core.hooksPath=/dev/null -c commit.gpgsign=false`, so a contributor's
global hooks or always-on signing no longer abort e2e before any spec runs,
and the test repo's own hook config stays untouched for specs that install hooks.

Fixes #31587

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 10:10:18 -07:00

137 lines
5.8 KiB
TypeScript

// Build current E2E output and create an isolated fixture repo whose path is shared with workers.
import { execSync } from 'node:child_process'
import { randomUUID } from 'node:crypto'
import { existsSync, mkdirSync, mkdtempSync, realpathSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import { prepareDockerSshHostImage } from './helpers/docker-ssh-host-image'
export const E2E_TEST_REPO_PATH_FILE_ENV = 'ORCA_E2E_TEST_REPO_PATH_FILE'
const RUN_TEST_REPO_PATH_FILE =
process.env[E2E_TEST_REPO_PATH_FILE_ENV] ??
path.join(os.tmpdir(), `orca-e2e-test-repo-path-${randomUUID()}.txt`)
export function workerTestRepositoryPathFile(runPathFile: string, workerIndex?: string): string {
if (workerIndex === undefined) {
return runPathFile
}
if (!/^\d+$/.test(workerIndex)) {
throw new Error('Invalid Playwright worker index')
}
return `${runPathFile}.worker-${workerIndex}`
}
// Restart helpers and the app fixture must read the same worker-owned repository.
export const TEST_REPO_PATH_FILE = workerTestRepositoryPathFile(
RUN_TEST_REPO_PATH_FILE,
process.env.TEST_WORKER_INDEX
)
const ELECTRON_E2E_BUILD_TIMEOUT_MS = 300_000
const CLI_E2E_BUILD_TIMEOUT_MS = 120_000
const WEB_E2E_BUILD_TIMEOUT_MS = 300_000
export default function globalSetup(): void {
// Why: workers and teardown need this run's path, never another concurrent run's.
process.env[E2E_TEST_REPO_PATH_FILE_ENV] = TEST_REPO_PATH_FILE
const root = process.cwd()
const outMain = path.join(root, 'out', 'main', 'index.js')
const outCli = path.join(root, 'out', 'cli', 'index.js')
const outWeb = path.join(root, 'out', 'web', 'web-index.html')
if (process.env.SKIP_BUILD && existsSync(outMain)) {
console.error('[e2e] SKIP_BUILD set and out/main/index.js exists — skipping build')
} else {
// Use E2E mode to expose window.__store; retain the env flag for older local overrides.
console.error('[e2e] Building Electron app with electron-vite build --mode e2e...')
execSync('npx electron-vite build --mode e2e', {
env: { ...process.env, VITE_EXPOSE_STORE: 'true' },
cwd: root,
stdio: 'inherit',
// Allow healthy Windows renderer builds to outlast 120s before specs start.
timeout: ELECTRON_E2E_BUILD_TIMEOUT_MS
})
console.error('[e2e] Build complete.')
}
if (process.env.SKIP_BUILD && existsSync(outCli)) {
console.error('[e2e] SKIP_BUILD set and out/cli/index.js exists — skipping CLI build')
// Artifact downloads lose executable bits; each runner still needs its local dev launcher.
execSync('pnpm run prepare:cli-output', {
cwd: root,
stdio: 'inherit',
timeout: CLI_E2E_BUILD_TIMEOUT_MS
})
} else {
console.error('[e2e] Building bundled CLI...')
execSync('pnpm run build:cli', {
cwd: root,
stdio: 'inherit',
timeout: CLI_E2E_BUILD_TIMEOUT_MS
})
console.error('[e2e] CLI build complete.')
}
if (process.env.ORCA_E2E_WEB_CLIENT === '1') {
if (process.env.SKIP_BUILD && existsSync(outWeb)) {
console.error('[e2e] SKIP_BUILD set and web client exists — skipping web build')
} else {
// Why: paired-browser specs need the web bundle served by the runtime; ordinary Electron E2E does not.
console.error('[e2e] Building paired runtime web client...')
execSync('pnpm run build:web', {
env: { ...process.env, VITE_EXPOSE_STORE: 'true' },
cwd: root,
stdio: 'inherit',
timeout: WEB_E2E_BUILD_TIMEOUT_MS
})
console.error('[e2e] Web client build complete.')
}
}
if (process.env.ORCA_E2E_SSH_DOCKER === '1') {
console.error('[e2e] Preparing Docker OpenSSH fixture image...')
prepareDockerSshHostImage(root)
}
const testRepoDir = seedTestRepository()
writeFileSync(TEST_REPO_PATH_FILE, testRepoDir)
console.error(`[e2e] Test repo created at ${testRepoDir}`)
}
// Shared by global setup and worker fallbacks so both seed paths stay identical.
export function seedTestRepository(): string {
// Resolve macOS /var symlinks like git does when registering repo.path.
const testRepoDir = realpathSync(mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-repo-')))
execSync('git init', { cwd: testRepoDir, stdio: 'pipe' })
execSync('git config user.email "e2e@test.local"', { cwd: testRepoDir, stdio: 'pipe' })
execSync('git config user.name "E2E Test"', { cwd: testRepoDir, stdio: 'pipe' })
writeFileSync(
path.join(testRepoDir, 'README.md'),
'# Orca E2E Test Repo\n\nThis repo was created automatically for Playwright tests.\n'
)
writeFileSync(path.join(testRepoDir, 'CLAUDE.md'), '# CLAUDE.md\n\nTest instructions for E2E.\n')
writeFileSync(
path.join(testRepoDir, 'package.json'),
`${JSON.stringify({ name: 'orca-e2e-test', version: '0.0.0', private: true }, null, 2)}\n`
)
writeFileSync(path.join(testRepoDir, '.gitignore'), 'node_modules/\n')
mkdirSync(path.join(testRepoDir, 'src'), { recursive: true })
writeFileSync(path.join(testRepoDir, 'src', 'index.ts'), 'export const hello = "world"\n')
writeFileSync(path.join(testRepoDir, 'src', 'diff-note-layout.ts'), 'export const seed = true\n')
execSync('git add -A', { cwd: testRepoDir, stdio: 'pipe' })
// Why: per-command -c so contributor global hooks/signing can't fail the seed without persisting into repo config.
const isolatedGit = 'git -c core.hooksPath=/dev/null -c commit.gpgsign=false'
execSync(`${isolatedGit} commit -m "Initial commit for E2E tests"`, {
cwd: testRepoDir,
stdio: 'pipe'
})
// Seed a second worktree so retention/switching specs run; UUIDs keep concurrent workers apart.
const worktreeDir = path.join(testRepoDir, '..', `orca-e2e-worktree-${randomUUID()}`)
execSync(`${isolatedGit} worktree add "${worktreeDir}" -b e2e-secondary`, {
cwd: testRepoDir,
stdio: 'pipe'
})
return testRepoDir
}