mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
The scheduled E2E suite has been red for 3+ weeks with ~19 deterministic failures across 9/10 shards. All are test-side issues (stale assertions, CI-timing races, over-strict perf thresholds, and fixture gaps); no product regressions were found. Two small app changes are test-support only: a stable data-testid on the GitHub item detail surface, and honoring prefers-reduced-motion in the sidebar reveal scroll (also an a11y win). Fixes: - github-cli-stall / pr-comments / onboarding: update stale assertions to current UI (inline GitHub detail, removed 'Open' badge #7338, error-state recovery #6473, Host-selector Add Project UI). - source-control / workspace-space-git-status: poll worktrees.list past the 5s detection-scan cache; match git-reported store paths (not realpath'd). - terminal-column-desync / combined-diff: poll to convergence instead of a fixed wait; ignore virtualizer remeasurement in the scroll-jump metric. - terminal-tui-wheel-reports/-drain: space notches past the burst window; reduce dense CDP stream + test.slow to fit the 120s budget. - settings-display-name-ime: commit the IME composition (persist-on-commit since #6238). onboarding: broaden step predicate for auto-skipped steps. - terminal-shortcuts: guard the split before Cmd/Ctrl+W and confirm the 'Stop and Close' dialog. tab-close: drain late startup terminals. - artificial-opencode: tolerate a single scheduler spike in the drift gate. - worktree: resolve create base to the local HEAD branch; assert URL-resolve reuse via the lookup count. Co-authored-by: Orca <help@stably.ai>
136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
import { execSync } from 'node:child_process'
|
|
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { test as base, expect } from './helpers/orca-app'
|
|
import { waitForSessionReady } from './helpers/store'
|
|
|
|
const fakeGhDir = mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-fake-gh-'))
|
|
const fakeGhBody = `#!/usr/bin/env node
|
|
const args = process.argv.slice(2)
|
|
const joined = args.join(' ')
|
|
if (args[0] === 'auth' && args[1] === 'status') {
|
|
console.error('github.com\\n ✓ Logged in to github.com account e2e (GITHUB_TOKEN)')
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && args[1] === 'user') {
|
|
console.log(JSON.stringify({ login: 'e2e' }))
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && args.includes('rate_limit')) {
|
|
console.log(JSON.stringify({ resources: { core: { limit: 5000, remaining: 5000, reset: 0 }, graphql: { limit: 5000, remaining: 5000, reset: 0 }, search: { limit: 30, remaining: 30, reset: 0 } } }))
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'issue' && args[1] === 'list') {
|
|
console.log('[]')
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'pr' && args[1] === 'list') {
|
|
console.log('[]')
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && (args[1] === 'graphql' || joined.includes('repos/acme/repo/issues/5388') || joined.includes('repos/acme/repo/pulls/5388'))) {
|
|
setTimeout(() => {}, 60_000)
|
|
return
|
|
}
|
|
console.error('fake gh: unhandled ' + joined)
|
|
process.exit(1)
|
|
`
|
|
|
|
const fakeGhPath = path.join(fakeGhDir, process.platform === 'win32' ? 'gh.cmd' : 'gh')
|
|
if (process.platform === 'win32') {
|
|
writeFileSync(fakeGhPath, '@echo off\nnode "%~dp0\\fake-gh.js" %*\n')
|
|
writeFileSync(path.join(fakeGhDir, 'fake-gh.js'), fakeGhBody)
|
|
} else {
|
|
writeFileSync(fakeGhPath, fakeGhBody)
|
|
chmodSync(fakeGhPath, 0o755)
|
|
}
|
|
|
|
const test = base.extend({
|
|
launchEnv: [
|
|
{
|
|
PATH: `${fakeGhDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
ORCA_GH_EXEC_TIMEOUT_MS: '1000'
|
|
},
|
|
{ option: true }
|
|
]
|
|
})
|
|
|
|
test.afterAll(() => {
|
|
rmSync(fakeGhDir, { recursive: true, force: true })
|
|
})
|
|
|
|
function configureGitHubRemote(repoPath: string): void {
|
|
try {
|
|
execSync('git remote remove origin', { cwd: repoPath, stdio: 'ignore' })
|
|
} catch {
|
|
// Missing origin is fine for the disposable E2E repo.
|
|
}
|
|
try {
|
|
execSync('git remote remove upstream', { cwd: repoPath, stdio: 'ignore' })
|
|
} catch {
|
|
// Missing upstream is fine for the disposable E2E repo.
|
|
}
|
|
execSync('git remote add origin https://github.com/acme/repo.git', {
|
|
cwd: repoPath,
|
|
stdio: 'pipe'
|
|
})
|
|
execSync('git remote add upstream https://github.com/acme/repo.git', {
|
|
cwd: repoPath,
|
|
stdio: 'pipe'
|
|
})
|
|
}
|
|
|
|
test('GitHub Tasks drawer recovers when gh stalls on issue details', async ({
|
|
orcaPage,
|
|
testRepoPath
|
|
}) => {
|
|
configureGitHubRemote(testRepoPath)
|
|
await waitForSessionReady(orcaPage)
|
|
|
|
const { repoId } = await orcaPage.evaluate((repoPath) => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
const repo = store.getState().repos.find((candidate) => candidate.path === repoPath)
|
|
if (!repo) {
|
|
throw new Error(`Expected repo to be loaded: ${repoPath}`)
|
|
}
|
|
const item = {
|
|
id: 'issue-5388',
|
|
type: 'issue',
|
|
number: 5388,
|
|
title: 'Issue detail fetch that hangs in gh',
|
|
state: 'open',
|
|
url: 'https://github.com/acme/repo/issues/5388',
|
|
labels: [],
|
|
updatedAt: '2026-06-15T20:00:00.000Z',
|
|
author: 'octocat',
|
|
repoId: repo.id
|
|
}
|
|
store.getState().openTaskPage({ taskSource: 'github', openGitHubWorkItem: item })
|
|
return { repoId: repo.id }
|
|
}, testRepoPath)
|
|
|
|
// Why: GitHub item details render inline (a plain container, not a Radix
|
|
// dialog), so scope to the detail surface via its stable test id.
|
|
const drawer = orcaPage
|
|
.getByTestId('github-item-detail')
|
|
.filter({ hasText: 'Issue detail fetch that hangs in gh' })
|
|
await expect(drawer).toBeVisible()
|
|
|
|
// Why: this is the user-visible regression signal. Before ghExecFileAsync had
|
|
// a default timeout, the drawer's pending details promise never settled and
|
|
// the conversation pane stayed stuck in its loading shell. Since #6473 a
|
|
// failed detail fetch degrades to an explicit "unable to load" error rather
|
|
// than an empty shell, so the stable proof of recovery is that the drawer
|
|
// settles on that message and stops spinning instead of hanging.
|
|
await expect(drawer.getByText('Unable to load details for this GitHub item.')).toBeVisible({
|
|
timeout: 5_000
|
|
})
|
|
await expect(drawer.locator('.animate-spin')).toHaveCount(0)
|
|
|
|
expect(repoId).toBeTruthy()
|
|
})
|