Files
orca/tests/e2e/windows-project-runtime-smoke.spec.ts
T
Brennan Benson 8d908764dc Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)" (#34662)
* Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)"

This reverts commit 29a5f89b8a.

On a machine with several clones of one repo, #32272 moved the oldest clone
into the shared cross-host project. Shared Windows QA machines have several
orca clones, so everyone's sidebar "orca" project suddenly gained that
machine's main checkout and its worktrees. Revert until the intended
grouping is decided.

Conflicts resolved against later main: Settings files moved into
settings/repository (imports adjusted), and two test files later deleted on
main stay deleted.

* Use checked types in restored test fixtures

* Combine restored fixture type imports
2026-10-27 20:38:35 -07:00

194 lines
7.7 KiB
TypeScript

import { execFileSync } from 'node:child_process'
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { mkdtemp } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import type { Page } from '@stablyai/playwright-test'
import { test, expect } from './helpers/orca-app'
import { waitForSessionReady } from './helpers/store'
const tempRoots: string[] = []
const CLEANUP_RETRY_COUNT = 6
async function removeTempRoot(root: string): Promise<void> {
for (let attempt = 0; attempt < CLEANUP_RETRY_COUNT; attempt += 1) {
try {
rmSync(root, { recursive: true, force: true })
return
} catch (error) {
if (attempt === CLEANUP_RETRY_COUNT - 1) {
throw error
}
// Why: Windows and WSL-backed git probes can release repo handles shortly
// after the Electron fixture closes; retry to keep the smoke idempotent.
await new Promise((resolve) => setTimeout(resolve, 250 * (attempt + 1)))
}
}
}
async function createGitRepo(prefix: string, repoName: string): Promise<string> {
const rootPath = await mkdtemp(path.join(os.tmpdir(), prefix))
tempRoots.push(rootPath)
const repoPath = path.join(rootPath, repoName)
mkdirSync(repoPath, { recursive: true })
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' })
execFileSync('git', ['config', 'user.email', 'e2e@test.local'], {
cwd: repoPath,
stdio: 'pipe'
})
execFileSync('git', ['config', 'user.name', 'E2E Test'], { cwd: repoPath, stdio: 'pipe' })
writeFileSync(path.join(repoPath, 'README.md'), `# ${repoName}\n`)
execFileSync('git', ['add', 'README.md'], { cwd: repoPath, stdio: 'pipe' })
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: repoPath, stdio: 'pipe' })
execFileSync('git', ['branch', '-M', 'main'], { cwd: repoPath, stdio: 'pipe' })
return repoPath
}
async function openRepoSettings(page: Page, repoId: string): Promise<void> {
await page.evaluate((nextRepoId) => {
const state = window.__store!.getState()
state.setSettingsSearchQuery('')
state.openSettingsTarget({ pane: 'repo', repoId: nextRepoId })
state.openSettingsPage()
}, repoId)
await expect(page.getByPlaceholder('Search settings')).toBeVisible({ timeout: 10_000 })
const maybeLaterButton = page.getByRole('button', { name: 'Maybe Later' })
if (await maybeLaterButton.isVisible({ timeout: 1_000 }).catch(() => false)) {
await maybeLaterButton.click()
}
}
async function chooseProjectRuntime(
page: Page,
repoId: string,
runtimeLabel: 'Windows' | 'WSL'
): Promise<void> {
const section = page.locator(`[data-settings-section="repo-${repoId}"]`)
await section.getByRole('radio', { name: runtimeLabel, exact: true }).click()
const applyButton = section.getByRole('button', { name: 'Apply runtime change' })
if (await applyButton.isVisible({ timeout: 1_000 }).catch(() => false)) {
await applyButton.click()
}
}
test.afterAll(async () => {
for (const root of tempRoots.splice(0)) {
await removeTempRoot(root)
}
})
test.describe('Windows project runtime smoke', () => {
test('keeps a Windows-host project and WSL project available side by side', async ({
orcaPage,
testRepoPath
}) => {
test.skip(process.platform !== 'win32', 'Windows project runtime smoke requires Windows')
await waitForSessionReady(orcaPage)
const wsl = await orcaPage.evaluate(async () => ({
available: await window.api.wsl.isAvailable(),
distros: await window.api.wsl.listDistros()
}))
test.skip(!wsl.available || wsl.distros.length === 0, 'WSL distro is required for smoke')
const wslDistro = wsl.distros[0]!
const wslRepoPath = await createGitRepo('orca-e2e-project-runtime-', 'wsl-runtime-project')
const smoke = await orcaPage.evaluate(
async ({ hostRepoPath, wslRepoPath, wslDistro }) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
await window.api.repos.add({ path: wslRepoPath })
await store.getState().fetchRepos({ runtimeEnvironmentId: null, throwOnError: true })
const state = store.getState()
const hostRepo = state.repos.find((repo) => repo.path === hostRepoPath)
const wslRepo = state.repos.find((repo) => repo.path === wslRepoPath)
if (!hostRepo || !wslRepo) {
throw new Error('Expected host and WSL smoke repos to be loaded')
}
const hostProject = state.projects.find((project) =>
project.sourceRepoIds.includes(hostRepo.id)
)
const wslProject = state.projects.find((project) =>
project.sourceRepoIds.includes(wslRepo.id)
)
if (!hostProject || !wslProject) {
throw new Error('Expected host and WSL smoke projects to be loaded')
}
await state.updateProject(
hostProject.id,
{
localWindowsRuntimePreference: { kind: 'windows-host' }
},
'local'
)
await state.updateProject(
wslProject.id,
{
localWindowsRuntimePreference: { kind: 'wsl', distro: wslDistro }
},
'local'
)
const hostWorktrees = await window.api.worktrees.listDetected({ repoId: hostRepo.id })
const wslWorktrees = await window.api.worktrees.listDetected({ repoId: wslRepo.id })
return {
hostRepoId: hostRepo.id,
wslRepoId: wslRepo.id,
wslDistro,
hostWorktreeCount: hostWorktrees.worktrees.length,
wslWorktreeCount: wslWorktrees.worktrees.length
}
},
{ hostRepoPath: testRepoPath, wslRepoPath, wslDistro }
)
expect(smoke.hostWorktreeCount).toBeGreaterThan(0)
expect(smoke.wslWorktreeCount).toBeGreaterThan(0)
await openRepoSettings(orcaPage, smoke.hostRepoId)
const hostSection = orcaPage.locator(`[data-settings-section="repo-${smoke.hostRepoId}"]`)
await expect(hostSection.getByText('Project Runtime')).toBeVisible()
await expect(hostSection.getByText('This project runs on Windows.')).toBeVisible()
await chooseProjectRuntime(orcaPage, smoke.hostRepoId, 'WSL')
await expect(
hostSection.getByText(`This project runs in ${smoke.wslDistro} via WSL.`)
).toBeVisible()
const hostAfterWslUiSwitch = await orcaPage.evaluate((hostRepoId) => {
const state = window.__store!.getState()
const hostProject = state.projects.find((project) =>
project.sourceRepoIds.includes(hostRepoId)
)
return hostProject?.localWindowsRuntimePreference
}, smoke.hostRepoId)
expect(hostAfterWslUiSwitch).toEqual({ kind: 'wsl', distro: smoke.wslDistro })
await chooseProjectRuntime(orcaPage, smoke.hostRepoId, 'Windows')
await expect(hostSection.getByText('This project runs on Windows.')).toBeVisible()
const hostAfterWindowsUiSwitch = await orcaPage.evaluate((hostRepoId) => {
const state = window.__store!.getState()
const hostProject = state.projects.find((project) =>
project.sourceRepoIds.includes(hostRepoId)
)
return hostProject?.localWindowsRuntimePreference
}, smoke.hostRepoId)
expect(hostAfterWindowsUiSwitch).toEqual({ kind: 'windows-host' })
await openRepoSettings(orcaPage, smoke.wslRepoId)
const wslSection = orcaPage.locator(`[data-settings-section="repo-${smoke.wslRepoId}"]`)
await expect(wslSection.getByText('Project Runtime')).toBeVisible()
await expect(
wslSection.getByText(`This project runs in ${smoke.wslDistro} via WSL.`)
).toBeVisible()
await orcaPage.evaluate(async (repoId) => {
const repo = window.__store!.getState().repos.find((entry) => entry.id === repoId)
await window.api.repos.removeForHost({ repoId, hostId: repo?.executionHostId ?? 'local' })
}, smoke.wslRepoId)
})
})