Files
orca/tests/e2e/helpers/seeded-ssh-workspaces.ts
T
Jinwoo HongandClaude 0c3807e73b test(e2e): reveal specs create workspaces through main so they pass with the sidebar switch on (#30642)
* test(e2e): create reveal-spec workspaces through main so they pass with the sidebar switch on

The agent-send picker and host-section reveal specs injected fake repos and
workspaces into the window store. With ORCA_SIDEBAR_INPUTS=main the sidebar
lists main's rows, so those workspaces never appeared. The SSH host is now an
unreachable target seeded into the stopped profile, and the paired-server host
is a real headless orca serve host; pins, lineage and notes go through main.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): type the seeded hosts as execution host ids

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): poll the reveal specs' page setup instead of awaiting it across CDP

An evaluate that awaits a preload promise sometimes fails with "Resulting
promise was garbage collected" in switch-on runs. evaluatePolled runs the work
in the page, polls its outcome, and still fails on a rejected or failed write.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 05:10:40 -04:00

109 lines
3.8 KiB
TypeScript

import type { ElectronApplication, Page, TestInfo } from '@stablyai/playwright-test'
import type { Repo } from '../../../src/shared/repo-types'
import { toSshExecutionHostId } from '../../../src/shared/execution-host'
import { expect } from './orca-app'
import { seedRelayEraTarget } from './orcad-upgrade-profile'
import { attachRepoAndOpenTerminal, createRestartSession } from './orca-restart'
import { mutateStoppedProfileState } from './persisted-profile-state'
import { waitForSessionReady, waitForStartupWorktreeRefresh } from './store'
export type SeededSshWorkspaces = {
targetId: string
hostId: `ssh:${string}`
repoId: string
/** Each seeded workspace's id, by its name. */
worktreeIds: Record<string, string>
}
/**
* An SSH host with one project and the workspaces main stores for it, seeded into a stopped profile.
* Nothing listens on its port, so the next launch lists exactly these stored rows.
*/
function seedUnreachableSshWorkspaces(
userDataDir: string,
input: { label: string; workspaces: { name: string; displayName: string }[] }
): SeededSshWorkspaces {
const targetId = seedRelayEraTarget(userDataDir, {
label: input.label,
host: '127.0.0.1',
port: 1,
username: 'nobody'
})
const hostId = toSshExecutionHostId(targetId)
const repoPath = '/srv/seeded-ssh-repo'
const repo: Repo = {
id: `repo-${targetId}`,
path: repoPath,
displayName: 'Remote project',
badgeColor: '#737373',
addedAt: Date.now(),
connectionId: targetId
}
const worktreeIds = Object.fromEntries(
input.workspaces.map(({ name }) => [name, `${repo.id}::${repoPath}/${name}`])
)
mutateStoppedProfileState(userDataDir, (state) => {
state.repos = [...(Array.isArray(state.repos) ? state.repos : []), repo]
state.worktreeMeta = {
...(state.worktreeMeta && typeof state.worktreeMeta === 'object' ? state.worktreeMeta : {}),
...Object.fromEntries(
input.workspaces.map(({ name, displayName }) => [
worktreeIds[name],
{ hostId, instanceId: `${name}-instance`, displayName }
])
)
}
})
return { targetId, hostId, repoId: repo.id, worktreeIds }
}
/**
* A desktop with the local test repo and an unreachable SSH host's seeded workspaces, both held by
* main as a user's would be. `dispose` closes the app and removes the profile.
*/
export async function launchWithUnreachableSshWorkspaces(
testInfo: TestInfo,
localRepoPath: string,
input: Parameters<typeof seedUnreachableSshWorkspaces>[1]
): Promise<{
page: Page
ssh: SeededSshWorkspaces
/** The local test repo's primary workspace. */
localWorktreeId: string
dispose: () => Promise<void>
}> {
const session = createRestartSession(testInfo)
let app: ElectronApplication | undefined
try {
const first = await session.launch()
app = first.app
await waitForSessionReady(first.page)
const localWorktreeId = await attachRepoAndOpenTerminal(first.page, localRepoPath)
await session.close(app)
app = undefined
const ssh = seedUnreachableSshWorkspaces(session.userDataDir, input)
const launched = await session.launch()
app = launched.app
await waitForSessionReady(launched.page)
await waitForStartupWorktreeRefresh(launched.page)
const seededIds = Object.values(ssh.worktreeIds)
await expect
.poll(() =>
launched.page.evaluate((ids) => {
const listed = new Set(
Object.values(window.__store!.getState().worktreesByRepo)
.flat()
.map((worktree) => worktree.id)
)
return ids.every((id) => listed.has(id))
}, seededIds)
)
.toBe(true)
const running = app
return { page: launched.page, ssh, localWorktreeId, dispose: () => session.dispose(running) }
} catch (error) {
await session.dispose(app)
throw error
}
}