mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
* fix(e2e): repair seven specs whose assertions drifted from shipped behavior The E2E suite could not collect at all until #13758, so these seven had been failing unobserved. Each is a stale test, not a product defect — verified individually against src/ rather than by making the assertion pass. - worktree-jump-palette-filter: the palette placeholder gained chats and terminals. Hoisted to one SEARCH_PLACEHOLDER const. - tab-create-entry-file-paths: matched the omnibox by its translated aria-label. Switched to aria-controls, which is structural and unlocalized. - browser-local-https-certificate-trust: once a load settles, the toolbar reload button relabels itself "Retry" alongside the failure overlay's own Retry, so the slot-wide locator hit two elements and failed strict mode. The test only ever passed inside the window where the toolbar still read "Stop". Narrowed by visible text; the icon button has none. - repro-7732-gitlab-checks-job-details: the activity-bar label carries a failure suffix ("Checks — Error"), which an exact-name match stops seeing precisely when the checks under test fail. Anchored regex, and bounded the click so the poll retries instead of hanging on a label that flips mid-action. - floating-tab-rename: the panel's open flag is persisted, so after the restart the helper's blind toggle closed the panel it was about to assert on. Made it idempotent. - github-created-issue-start-prefill: starting an issue now routes through the quick-create composer, so the launch command only forms once that is submitted. The spec now drives it. - ssh-config-host-import: P6 waited on "All hosts already in Orca", a string that exists nowhere in src/ and never could have matched. P7 required the tombstoned host to be absent, but listing it behind a "Removed from Orca" badge is deliberate — see the rationale at ssh-config-host-picker.ts:54 and the unit test already pinning it. Both retargeted; P7 still proves the host is excluded from bulk re-adoption, which is what it was for. Verified locally: all seven pass. github-created-issue-start-prefill cannot be verified on a machine whose gh resolves to an Orca terminal-attribution shim, since hydrateShellPath puts that ahead of the fixture's fake gh; CI has no shim. Three further failures are NOT addressed here. Adversarial review found the obvious test-side fix for each would have masked a real product bug, so they are being fixed in the product instead. * fix(e2e): configure the issue spec's git remote before Electron launches The spec added origin inside the test body, after the orcaPage fixture had already launched the app and added the repo. "New GitHub issue" is disabled when no repo is task-eligible, and eligibility is decided by the git remote probe alone: repos.add runs detectRepoIconAndUpstream, and a settled "no remote" writes gitRemoteIdentity = null, which is the ineligible marker. Background enrichment then suppresses re-probing that location for five minutes, so a remote added afterwards can never recover the button. It passed only when the shared seeded repo happened to already carry an origin from an earlier spec in the same worker — github-cli-stall-repro adds one, source-control-create-pr-intent-switch removes one — which is why it passed in the sharded lane and failed in the changed-specs lane. Moved to test.beforeAll, whose worker-scoped testRepoPath runs before the test-scoped Electron fixtures. Reproduced the failure against a fresh origin-less repo, then confirmed the fix on the same, including --repeat-each=2. Note the earlier attribution was wrong: the local failure at this line was never the gh attribution shim. The button's enabled state reads `git remote -v` and never consults gh, so a shim can only bite later, at issue creation.
205 lines
7.1 KiB
TypeScript
205 lines
7.1 KiB
TypeScript
import { execFileSync } 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 { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
|
import { getTerminalContent } from './helpers/terminal'
|
|
|
|
const ISSUE_NUMBER = 6613
|
|
const ISSUE_TITLE = 'Start a newly created issue without losing its context'
|
|
const ISSUE_URL = `https://github.com/acme/repo/issues/${ISSUE_NUMBER}`
|
|
const fakeCliDir = mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-created-issue-prefill-'))
|
|
|
|
const fakeGhSource = `
|
|
const args = process.argv.slice(2)
|
|
const joined = args.join(' ')
|
|
const issue = {
|
|
number: ${ISSUE_NUMBER},
|
|
title: ${JSON.stringify(ISSUE_TITLE)},
|
|
state: 'open',
|
|
html_url: ${JSON.stringify(ISSUE_URL)},
|
|
labels: [],
|
|
assignees: [],
|
|
user: { login: 'e2e' },
|
|
updated_at: '2026-07-22T12:00:00.000Z'
|
|
}
|
|
|
|
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] === 'api' && args.includes('-X') && args.includes('POST') && joined.includes('repos/acme/repo/issues')) {
|
|
console.log(JSON.stringify(issue))
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && joined.includes('/labels')) {
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && joined.includes('/assignees')) {
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && joined.includes('repos/acme/repo/issues/${ISSUE_NUMBER}')) {
|
|
console.log(JSON.stringify(issue))
|
|
process.exit(0)
|
|
}
|
|
if (args[0] === 'api' && joined.includes('search/issues')) {
|
|
console.log(JSON.stringify({ total_count: 0, incomplete_results: false, items: [] }))
|
|
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') {
|
|
console.log(JSON.stringify({ data: { search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] } } }))
|
|
process.exit(0)
|
|
}
|
|
console.error('fake gh: unhandled ' + joined)
|
|
process.exit(1)
|
|
`
|
|
|
|
const fakeClaudeSource = `
|
|
const args = process.argv.slice(2)
|
|
process.stdout.write('E2E_CLAUDE_ARGV ' + JSON.stringify(args) + '\\n')
|
|
setInterval(() => {}, 60_000)
|
|
`
|
|
|
|
function installFakeCli(name: 'gh' | 'claude', source: string): void {
|
|
if (process.platform === 'win32') {
|
|
writeFileSync(path.join(fakeCliDir, `fake-${name}.js`), source)
|
|
writeFileSync(
|
|
path.join(fakeCliDir, `${name}.cmd`),
|
|
`@echo off\r\nnode "%~dp0\\fake-${name}.js" %*\r\n`
|
|
)
|
|
return
|
|
}
|
|
const executable = path.join(fakeCliDir, name)
|
|
writeFileSync(executable, `#!/usr/bin/env node\n${source}`)
|
|
chmodSync(executable, 0o755)
|
|
}
|
|
|
|
installFakeCli('gh', fakeGhSource)
|
|
installFakeCli('claude', fakeClaudeSource)
|
|
|
|
const test = base.extend({
|
|
launchEnv: [
|
|
{
|
|
PATH: `${fakeCliDir}${path.delimiter}${process.env.PATH ?? ''}`
|
|
},
|
|
{ option: true }
|
|
]
|
|
})
|
|
|
|
test.afterAll(() => {
|
|
rmSync(fakeCliDir, { recursive: true, force: true })
|
|
})
|
|
|
|
function configureGitHubRemote(repoPath: string): void {
|
|
try {
|
|
execFileSync('git', ['remote', 'remove', 'origin'], { cwd: repoPath, stdio: 'ignore' })
|
|
} catch {
|
|
// The disposable E2E repo does not have an origin on its first run.
|
|
}
|
|
execFileSync('git', ['remote', 'add', 'origin', 'https://github.com/acme/repo.git'], {
|
|
cwd: repoPath,
|
|
stdio: 'pipe'
|
|
})
|
|
}
|
|
|
|
// Why: the remote must exist before Electron launches. `repos.add` probes the
|
|
// remote once, and a settled "no remote" both makes the repo ineligible for the
|
|
// tasks page (disabling "New GitHub issue") and suppresses re-probes for five
|
|
// minutes — so adding origin inside the test body can never recover.
|
|
test.beforeAll(({ testRepoPath }) => {
|
|
configureGitHubRemote(testRepoPath)
|
|
})
|
|
|
|
test('starting a just-created GitHub issue launches Claude with its URL prefilled', async ({
|
|
orcaPage
|
|
}) => {
|
|
await waitForSessionReady(orcaPage)
|
|
await waitForActiveWorktree(orcaPage)
|
|
|
|
await orcaPage.evaluate(async () => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
const state = store.getState()
|
|
const preparedWorkspace = state
|
|
.allWorktrees()
|
|
.find((worktree) => worktree.branch?.endsWith('e2e-secondary'))
|
|
if (!preparedWorkspace) {
|
|
throw new Error('Seeded secondary E2E worktree is not available')
|
|
}
|
|
// Why: this regression owns renderer-to-PTY command propagation, while the shared fixture already covers Git worktree creation.
|
|
store.setState({
|
|
createWorktree: async () => ({ worktree: preparedWorkspace })
|
|
})
|
|
await state.updateSettings({
|
|
defaultTuiAgent: 'claude',
|
|
disabledTuiAgents: [],
|
|
...(navigator.userAgent.includes('Windows') ? { terminalWindowsShell: 'git-bash' } : {})
|
|
})
|
|
store.getState().openTaskPage({ taskSource: 'github' })
|
|
})
|
|
|
|
const newIssueButton = orcaPage.getByRole('button', { name: 'New GitHub issue' })
|
|
await expect(newIssueButton).toBeEnabled({ timeout: 15_000 })
|
|
await newIssueButton.click()
|
|
|
|
const createDialog = orcaPage.getByRole('dialog', { name: 'New GitHub issue' })
|
|
await expect(createDialog).toBeVisible()
|
|
await createDialog.getByPlaceholder('Short summary').fill(ISSUE_TITLE)
|
|
await createDialog.getByRole('button', { name: 'Create issue' }).click()
|
|
|
|
await expect(createDialog).toBeHidden({ timeout: 10_000 })
|
|
await expect(orcaPage.getByRole('heading', { name: ISSUE_TITLE })).toBeVisible({
|
|
timeout: 10_000
|
|
})
|
|
|
|
await orcaPage.getByRole('button', { name: 'Start workspace from issue' }).click()
|
|
|
|
// Why: starting a GitHub issue now routes through the quick-create composer,
|
|
// which carries the linked issue URL into the agent launch command on submit.
|
|
const composer = orcaPage.getByRole('dialog', { name: /Create (workspace|worktree)/i })
|
|
await expect(composer).toBeVisible({ timeout: 15_000 })
|
|
const createWorkspaceButton = composer.getByRole('button', {
|
|
name: /Create (workspace|worktree)/i
|
|
})
|
|
await expect(createWorkspaceButton).toBeEnabled({ timeout: 15_000 })
|
|
await createWorkspaceButton.click()
|
|
await expect(composer).toBeHidden({ timeout: 20_000 })
|
|
|
|
let terminalText = ''
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
terminalText = await getTerminalContent(orcaPage, 12_000)
|
|
return terminalText
|
|
},
|
|
{
|
|
timeout: 30_000,
|
|
message: 'Claude prefill command did not reach the active terminal buffer'
|
|
}
|
|
)
|
|
.toContain('--prefill')
|
|
expect(terminalText).toContain('--dangerously-skip-permissions')
|
|
expect(terminalText).toContain('--prefill')
|
|
expect(terminalText).toContain(ISSUE_URL)
|
|
expect(terminalText).not.toMatch(/(?:^|\n)claude '--dangerously-skip-permissions'(?:\r?\n|$)/)
|
|
})
|